com.xrlab.labframe_brainbit 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  using UnityEngine;
2
2
  using System.Collections;
3
3
  using System.Collections.Generic;
4
+ using System.Collections.Concurrent;
4
5
  using LabFrame2023;
5
6
  using UnityEngine.Events;
6
7
  using NeuroSDK;
@@ -91,6 +92,9 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
91
92
  private string _currentEEGTag = "eeg";
92
93
  // 用於記錄目前阻抗寫入資料的標籤
93
94
  private string _currentImpedanceTag = "impedance";
95
+
96
+ // 主執行緒派發佇列,用於將背景執行緒的回呼安全地轉移到主執行緒執行
97
+ private readonly ConcurrentQueue<Action> _mainThreadActions = new ConcurrentQueue<Action>();
94
98
  #endregion
95
99
 
96
100
  #region IManager Implementation
@@ -119,6 +123,24 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
119
123
  LabTools.Log($"[BrainBit] Config - AutoSelectBestSignal: {_config.AutoSelectBestSignal}, ConnectDelay: {_config.ConnectDelaySeconds}s");
120
124
  }
121
125
 
126
+ /// <summary>
127
+ /// 每幀執行主執行緒佇列中的 Action(處理從背景執行緒派發過來的回呼)
128
+ /// </summary>
129
+ private void Update()
130
+ {
131
+ while (_mainThreadActions.TryDequeue(out var action))
132
+ {
133
+ try
134
+ {
135
+ action?.Invoke();
136
+ }
137
+ catch (Exception e)
138
+ {
139
+ LabTools.LogError($"[BrainBit] Error executing main thread action: {e.Message}");
140
+ }
141
+ }
142
+ }
143
+
122
144
  public IEnumerator ManagerDispose()
123
145
  {
124
146
  LabTools.Log("[BrainBit] Disposing BrainBit Manager...");
@@ -174,8 +196,11 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
174
196
  {
175
197
  LabTools.Log("[BrainBit] Starting device scan...");
176
198
 
177
- // 根據 BrainBit SDK2 文檔,確保使用正確的 SensorFamily
178
- _scanner = new Scanner(SensorFamily.SensorLEBrainBit);
199
+ // 重用 Scanner,避免重複建立造成 native 資源洩漏
200
+ if (_scanner == null)
201
+ {
202
+ _scanner = new Scanner(SensorFamily.SensorLEBrainBit);
203
+ }
179
204
  _scanner.EventSensorsChanged += OnSensorsFound;
180
205
 
181
206
  // 開始掃描
@@ -520,37 +545,46 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
520
545
 
521
546
  private void OnSensorsFound(IScanner scanner, IReadOnlyList<SensorInfo> sensors)
522
547
  {
548
+ // 此回呼由 NeuroSDK 在背景執行緒觸發!
549
+ // 必須將所有 Unity API 呼叫(StartCoroutine、StopCoroutine 等)派發到主執行緒
523
550
  try
524
551
  {
525
- LabTools.Log($"[BrainBit] Found {sensors.Count} device(s)");
526
-
527
- // 觸發設備發現事件
528
- OnDeviceFound?.Invoke(new List<SensorInfo>(sensors));
552
+ LabTools.Log($"[BrainBit] Found {sensors.Count} device(s) (background thread)");
529
553
 
530
554
  if (sensors.Count > 0)
531
555
  {
532
- // 重要:根據 BrainBit SDK 要求,必須先停止掃描才能建立連接
533
- StopScan();
534
-
535
- // 選擇要連接的設備
556
+ // 選擇要連接的設備(純邏輯,不涉及 Unity API,可以在背景執行緒執行)
536
557
  SensorInfo targetSensor = SelectBestDevice(sensors);
537
-
538
558
  LabTools.Log($"[BrainBit] Selected device: {targetSensor.Name} (RSSI: {targetSensor.RSSI})");
539
- if (_scanTimeoutCoroutine != null)
559
+
560
+ // 先在背景緒停止 Scanner(SDK 層級,非 Unity API)
561
+ _scanner?.Stop();
562
+ _scanner.EventSensorsChanged -= OnSensorsFound;
563
+
564
+ // 將剩下的操作排入主執行緒執行
565
+ _mainThreadActions.Enqueue(() =>
540
566
  {
541
- StopCoroutine(_scanTimeoutCoroutine);
542
- _scanTimeoutCoroutine = null;
543
- LabTools.Log("[BrainBit] Scan timeout coroutine stopped");
544
- }
567
+ IsScanning = false;
568
+
569
+ // 觸發設備發現事件
570
+ OnDeviceFound?.Invoke(new List<SensorInfo>(sensors));
545
571
 
546
- // 等待一段時間後建立連接(確保掃描完全停止)
547
- StartCoroutine(DelayedConnect(targetSensor));
572
+ if (_scanTimeoutCoroutine != null)
573
+ {
574
+ StopCoroutine(_scanTimeoutCoroutine);
575
+ _scanTimeoutCoroutine = null;
576
+ LabTools.Log("[BrainBit] Scan timeout coroutine stopped");
577
+ }
578
+
579
+ // 等待一段時間後建立連接(確保掃描完全停止)
580
+ StartCoroutine(DelayedConnect(targetSensor));
581
+ });
548
582
  }
549
583
  }
550
584
  catch (Exception e)
551
585
  {
552
586
  LabTools.LogError($"[BrainBit] Error in OnSensorsFound: {e.Message}");
553
- OnError?.Invoke($"Device discovery error: {e.Message}");
587
+ _mainThreadActions.Enqueue(() => OnError?.Invoke($"Device discovery error: {e.Message}"));
554
588
  }
555
589
  }
556
590
 
@@ -612,6 +646,13 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
612
646
  {
613
647
  LabTools.Log($"[BrainBit] Connecting to device: {sensorInfo.Name} ({sensorInfo.Address})");
614
648
 
649
+ // 清理舊的 Sensor(如果有的話),避免 native 資源洩漏
650
+ if (_currentSensor != null)
651
+ {
652
+ try { _currentSensor.Dispose(); } catch (Exception) { }
653
+ _currentSensor = null;
654
+ }
655
+
615
656
  // 創建 Sensor(此時掃描已停止)
616
657
  _currentSensor = _scanner.CreateSensor(sensorInfo) as BrainBitSensor;
617
658
 
@@ -716,20 +757,26 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
716
757
 
717
758
  private void OnSignalDataReceived(ISensor sensor, BrainBitSignalData[] data)
718
759
  {
760
+ // 此回呼由 NeuroSDK 在背景執行緒觸發!
719
761
  try
720
762
  {
721
763
  foreach (var packet in data)
722
764
  {
723
- _lastEEGData = new BrainBit_EEGData(packet.T3, packet.T4, packet.O1, packet.O2);
724
-
725
- // 觸發事件
726
- OnEEGDataReceived?.Invoke(_lastEEGData);
765
+ var eegData = new BrainBit_EEGData(packet.T3, packet.T4, packet.O1, packet.O2);
766
+ _lastEEGData = eegData;
727
767
 
728
- // 自動保存到 LabDataManager
729
- if (_autoWriteEEGData && LabDataManager.Instance.IsInited)
768
+ // 將事件觸發和資料寫入排入主執行緒
769
+ _mainThreadActions.Enqueue(() =>
730
770
  {
731
- LabDataManager.Instance.WriteData(_lastEEGData, _currentEEGTag);
732
- }
771
+ // 觸發事件(訂閱者可能更新 UI)
772
+ OnEEGDataReceived?.Invoke(eegData);
773
+
774
+ // 自動保存到 LabDataManager
775
+ if (_autoWriteEEGData && LabDataManager.Instance.IsInited)
776
+ {
777
+ LabDataManager.Instance.WriteData(eegData, _currentEEGTag);
778
+ }
779
+ });
733
780
  }
734
781
  }
735
782
  catch (Exception e)
@@ -740,24 +787,30 @@ public class BrainBitManager : LabSingleton<BrainBitManager>, IManager
740
787
 
741
788
  private void OnResistanceDataReceived(ISensor sensor, BrainBitResistData data)
742
789
  {
790
+ // 此回呼由 NeuroSDK 在背景執行緒觸發!
743
791
  try
744
792
  {
745
- _lastImpedanceData = new BrainBit_ImpedanceData(data.T3, data.T4, data.O1, data.O2);
746
-
747
- // 觸發事件
748
- OnImpedanceDataReceived?.Invoke(_lastImpedanceData);
793
+ var impedanceData = new BrainBit_ImpedanceData(data.T3, data.T4, data.O1, data.O2);
794
+ _lastImpedanceData = impedanceData;
749
795
 
750
- // 檢查阻抗警告: 當有任一通道阻抗超過 200,000 時
751
- if (!_lastImpedanceData.IsImpedanceGood)
796
+ // 將事件觸發和資料寫入排入主執行緒
797
+ _mainThreadActions.Enqueue(() =>
752
798
  {
753
- LabTools.LogError($"[BrainBit] High impedance detected: {_lastImpedanceData.GetImpedanceStatus()}");
754
- }
799
+ // 觸發事件(訂閱者可能更新 UI)
800
+ OnImpedanceDataReceived?.Invoke(impedanceData);
755
801
 
756
- // 自動保存到 LabDataManager
757
- if (_autoWriteImpedanceData && LabDataManager.Instance.IsInited)
758
- {
759
- LabDataManager.Instance.WriteData(_lastImpedanceData, _currentImpedanceTag);
760
- }
802
+ // 檢查阻抗警告: 當有任一通道阻抗超過 200,000 時
803
+ if (!impedanceData.IsImpedanceGood)
804
+ {
805
+ LabTools.LogError($"[BrainBit] High impedance detected: {impedanceData.GetImpedanceStatus()}");
806
+ }
807
+
808
+ // 自動保存到 LabDataManager
809
+ if (_autoWriteImpedanceData && LabDataManager.Instance.IsInited)
810
+ {
811
+ LabDataManager.Instance.WriteData(impedanceData, _currentImpedanceTag);
812
+ }
813
+ });
761
814
  }
762
815
  catch (Exception e)
763
816
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.xrlab.labframe_brainbit",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "displayName": "Lab Frame 2023 - BrainBit Plugin",
5
5
  "description": "BrainBit Support for LabFrame2023.\nNote: Currently only supports BrainBit in lab!!",
6
6
  "unity": "2022.3",