From 05295853a38abec9a5d68c4803bb6c553f3d9bc0 Mon Sep 17 00:00:00 2001 From: teach310 Date: Sat, 28 Oct 2023 22:49:25 +0900 Subject: [PATCH] add CallbackContext MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 背景 NativePluginから呼ばれるコールバックでエラーが発生するとUnityがクラッシュする。 ログに直接的な原因は出力されていないため原因不明 また、swiftのmainthreadでのコールバックはUnityでもmainthreadで実行されていた。 ## 対応 Callbackが指定したSynchronizationContext.Postで実行されるようにする。 ## 影響 - すでにメインスレッドだった場合Delegateが呼ばれるタイミングが1フレーム遅れる。 - swiftのメインスレッド以外でコールバックが呼ばれても大丈夫になる。 --- .../Runtime/CBCentralManager.cs | 63 ++++++--- .../Runtime/CBPeripheral.cs | 124 +++++++++++------- .../Runtime/CBPeripheralManager.cs | 87 ++++++++---- 3 files changed, 180 insertions(+), 94 deletions(-) diff --git a/Packages/com.teach310.core-bluetooth-for-unity/Runtime/CBCentralManager.cs b/Packages/com.teach310.core-bluetooth-for-unity/Runtime/CBCentralManager.cs index 4a5b430..5b3d34c 100644 --- a/Packages/com.teach310.core-bluetooth-for-unity/Runtime/CBCentralManager.cs +++ b/Packages/com.teach310.core-bluetooth-for-unity/Runtime/CBCentralManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace CoreBluetooth { @@ -40,6 +41,11 @@ public ICBCentralManagerDelegate Delegate } } + /// + /// ICBCentralManagerDelegate callbacks will be called in this context. + /// + public SynchronizationContext CallbackContext { get; set; } + NativeCentralManagerProxy _nativeCentralManagerProxy; public CBCentralManager(ICBCentralManagerDelegate centralDelegate = null, CBCentralInitOptions options = null) @@ -55,6 +61,7 @@ public CBCentralManager(ICBCentralManagerDelegate centralDelegate = null, CBCent } Delegate = centralDelegate; _nativeCentralManagerProxy = new NativeCentralManagerProxy(_handle, this); + CallbackContext = SynchronizationContext.Current; } public void Connect(CBPeripheral peripheral) @@ -76,7 +83,7 @@ public CBPeripheral[] RetrievePeripherals(params string[] peripheralIds) var result = new CBPeripheral[peripheralHandles.Length]; for (var i = 0; i < peripheralHandles.Length; i++) { - var peripheral = new CBPeripheral(peripheralHandles[i]); + var peripheral = new CBPeripheral(peripheralHandles[i], CallbackContext); SetPeripheral(peripheral); result[i] = peripheral; } @@ -126,42 +133,56 @@ void SetPeripheral(CBPeripheral peripheral) void INativeCentralManagerDelegate.DidConnect(string peripheralId) { - if (_disposed) return; - var peripheral = GetPeripheral(peripheralId); - if (peripheral == null) return; - Delegate?.DidConnectPeripheral(this, peripheral); + CallbackContext.Post(_ => + { + if (_disposed) return; + var peripheral = GetPeripheral(peripheralId); + if (peripheral == null) return; + Delegate?.DidConnectPeripheral(this, peripheral); + }, null); } void INativeCentralManagerDelegate.DidDisconnectPeripheral(string peripheralId, CBError error) { - if (_disposed) return; - var peripheral = GetPeripheral(peripheralId); - if (peripheral == null) return; - Delegate?.DidDisconnectPeripheral(this, peripheral, error); + CallbackContext.Post(_ => + { + if (_disposed) return; + var peripheral = GetPeripheral(peripheralId); + if (peripheral == null) return; + Delegate?.DidDisconnectPeripheral(this, peripheral, error); + }, null); } void INativeCentralManagerDelegate.DidFailToConnect(string peripheralId, CBError error) { - if (_disposed) return; - var peripheral = GetPeripheral(peripheralId); - if (peripheral == null) return; - Delegate?.DidFailToConnectPeripheral(this, peripheral, error); + CallbackContext.Post(_ => + { + if (_disposed) return; + var peripheral = GetPeripheral(peripheralId); + if (peripheral == null) return; + Delegate?.DidFailToConnectPeripheral(this, peripheral, error); + }, null); } void INativeCentralManagerDelegate.DidDiscoverPeripheral(SafeNativePeripheralHandle peripheralHandle, int rssi) { - if (_disposed) return; - - var peripheral = new CBPeripheral(peripheralHandle); - SetPeripheral(peripheral); - Delegate?.DidDiscoverPeripheral(this, peripheral, rssi); + CallbackContext.Post(_ => + { + if (_disposed) return; + var peripheral = new CBPeripheral(peripheralHandle, CallbackContext); + SetPeripheral(peripheral); + Delegate?.DidDiscoverPeripheral(this, peripheral, rssi); + }, null); } void INativeCentralManagerDelegate.DidUpdateState(CBManagerState state) { - if (_disposed) return; - this.State = state; - Delegate?.DidUpdateState(this); + CallbackContext.Post(_ => + { + if (_disposed) return; + this.State = state; + Delegate?.DidUpdateState(this); + }, null); } public void Dispose() diff --git a/Packages/com.teach310.core-bluetooth-for-unity/Runtime/CBPeripheral.cs b/Packages/com.teach310.core-bluetooth-for-unity/Runtime/CBPeripheral.cs index ba68581..ac85a78 100644 --- a/Packages/com.teach310.core-bluetooth-for-unity/Runtime/CBPeripheral.cs +++ b/Packages/com.teach310.core-bluetooth-for-unity/Runtime/CBPeripheral.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Threading; namespace CoreBluetooth { @@ -61,14 +62,21 @@ public string Name } public ICBPeripheralDelegate Delegate { get; set; } + + /// + /// ICBPeripheralDelegate callbacks will be called in this context. + /// + public SynchronizationContext CallbackContext { get; set; } + List _services = new List(); public ReadOnlyCollection Services { get; } - internal CBPeripheral(SafeNativePeripheralHandle nativePeripheral) + internal CBPeripheral(SafeNativePeripheralHandle nativePeripheral, SynchronizationContext callbackContext) { Handle = nativePeripheral; _nativePeripheral = new NativePeripheralProxy(Handle, this); - this.Services = _services.AsReadOnly(); + Services = _services.AsReadOnly(); + CallbackContext = callbackContext; } /// @@ -189,87 +197,113 @@ CBCharacteristic FindOrInitializeCharacteristic(CBService service, string charac void INativePeripheralDelegate.DidDiscoverServices(string[] serviceUUIDs, CBError error) { - if (_disposed) return; - var services = serviceUUIDs.Select(uuid => + CallbackContext.Post(_ => { - return _services.FirstOrDefault(s => s.UUID == uuid) ?? new CBService(uuid, this); - }).ToArray(); - _services.Clear(); - _services.AddRange(services); + if (_disposed) return; + var services = serviceUUIDs.Select(uuid => + { + return _services.FirstOrDefault(s => s.UUID == uuid) ?? new CBService(uuid, this); + }).ToArray(); + _services.Clear(); + _services.AddRange(services); - Delegate?.DidDiscoverServices(this, error); + Delegate?.DidDiscoverServices(this, error); + }, null); } void INativePeripheralDelegate.DidDiscoverCharacteristics(string serviceUUID, string[] characteristicUUIDs, CBError error) { - if (_disposed) return; - var service = _services.FirstOrDefault(s => s.UUID == serviceUUID); - if (service == null) return; - var characteristics = characteristicUUIDs.Select(uuid => FindOrInitializeCharacteristic(service, uuid)).ToArray(); - service.UpdateCharacteristics(characteristics); - Delegate?.DidDiscoverCharacteristics(this, service, error); + CallbackContext.Post(_ => + { + if (_disposed) return; + var service = _services.FirstOrDefault(s => s.UUID == serviceUUID); + if (service == null) return; + var characteristics = characteristicUUIDs.Select(uuid => FindOrInitializeCharacteristic(service, uuid)).ToArray(); + service.UpdateCharacteristics(characteristics); + Delegate?.DidDiscoverCharacteristics(this, service, error); + }, null); } void INativePeripheralDelegate.DidUpdateValueForCharacteristic(string serviceUUID, string characteristicUUID, byte[] data, CBError error) { - if (_disposed) return; - var characteristic = FindCharacteristic(serviceUUID, characteristicUUID); - if (characteristic == null) return; - characteristic.UpdateValue(data); - Delegate?.DidUpdateValueForCharacteristic(this, characteristic, error); + CallbackContext.Post(_ => + { + if (_disposed) return; + var characteristic = FindCharacteristic(serviceUUID, characteristicUUID); + if (characteristic == null) return; + characteristic.UpdateValue(data); + Delegate?.DidUpdateValueForCharacteristic(this, characteristic, error); + }, null); } void INativePeripheralDelegate.DidWriteValueForCharacteristic(string serviceUUID, string characteristicUUID, CBError error) { - if (_disposed) return; - var characteristic = FindCharacteristic(serviceUUID, characteristicUUID); - if (characteristic == null) return; - - Delegate?.DidWriteValueForCharacteristic(this, characteristic, error); + CallbackContext.Post(_ => + { + if (_disposed) return; + var characteristic = FindCharacteristic(serviceUUID, characteristicUUID); + if (characteristic == null) return; + Delegate?.DidWriteValueForCharacteristic(this, characteristic, error); + }, null); } void INativePeripheralDelegate.IsReadyToSendWriteWithoutResponse() { - if (_disposed) return; - Delegate?.IsReadyToSendWriteWithoutResponse(this); + CallbackContext.Post(_ => + { + if (_disposed) return; + Delegate?.IsReadyToSendWriteWithoutResponse(this); + }, null); } void INativePeripheralDelegate.DidUpdateNotificationStateForCharacteristic(string serviceUUID, string characteristicUUID, bool isNotifying, CBError error) { - if (_disposed) return; - var characteristic = FindCharacteristic(serviceUUID, characteristicUUID); - if (characteristic == null) return; - characteristic.UpdateIsNotifying(isNotifying); - Delegate?.DidUpdateNotificationStateForCharacteristic(this, characteristic, error); + CallbackContext.Post(_ => + { + if (_disposed) return; + var characteristic = FindCharacteristic(serviceUUID, characteristicUUID); + if (characteristic == null) return; + characteristic.UpdateIsNotifying(isNotifying); + Delegate?.DidUpdateNotificationStateForCharacteristic(this, characteristic, error); + }, null); } void INativePeripheralDelegate.DidReadRSSI(int rssi, CBError error) { - if (_disposed) return; - Delegate?.DidReadRSSI(this, rssi, error); + CallbackContext.Post(_ => + { + if (_disposed) return; + Delegate?.DidReadRSSI(this, rssi, error); + }, null); } void INativePeripheralDelegate.DidUpdateName() { - if (_disposed) return; - Delegate?.DidUpdateName(this); + CallbackContext.Post(_ => + { + if (_disposed) return; + Delegate?.DidUpdateName(this); + }, null); } void INativePeripheralDelegate.DidModifyServices(string[] invalidatedServiceUUIDs) { - if (_disposed) return; - List invalidatedServices = new List(); - foreach (var uuid in invalidatedServiceUUIDs) + CallbackContext.Post(_ => { - var service = _services.FirstOrDefault(s => s.UUID == uuid); - if (service != null) + if (_disposed) return; + List invalidatedServices = new List(); + foreach (var uuid in invalidatedServiceUUIDs) { - invalidatedServices.Add(service); - _services.Remove(service); + var service = _services.FirstOrDefault(s => s.UUID == uuid); + if (service != null) + { + invalidatedServices.Add(service); + _services.Remove(service); + } } - } - Delegate?.DidModifyServices(this, invalidatedServices.ToArray()); + Delegate?.DidModifyServices(this, invalidatedServices.ToArray()); + }, null); } public override string ToString() diff --git a/Packages/com.teach310.core-bluetooth-for-unity/Runtime/CBPeripheralManager.cs b/Packages/com.teach310.core-bluetooth-for-unity/Runtime/CBPeripheralManager.cs index be6c805..cfe7269 100644 --- a/Packages/com.teach310.core-bluetooth-for-unity/Runtime/CBPeripheralManager.cs +++ b/Packages/com.teach310.core-bluetooth-for-unity/Runtime/CBPeripheralManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; namespace CoreBluetooth { @@ -39,6 +40,11 @@ public ICBPeripheralManagerDelegate Delegate } } + /// + /// ICBPeripheralManagerDelegate callbacks will be called in this context. + /// + public SynchronizationContext CallbackContext { get; set; } + // key: centralId Dictionary _centrals = new Dictionary(); @@ -53,6 +59,7 @@ public CBPeripheralManager(ICBPeripheralManagerDelegate peripheralDelegate = nul _handle = SafeNativePeripheralManagerHandle.Create(); Delegate = peripheralDelegate; _nativePeripheralManagerProxy = new NativePeripheralManagerProxy(_handle, this); + CallbackContext = SynchronizationContext.Current; } void AddATTRequestDisposable(IDisposable disposable) @@ -176,68 +183,92 @@ CBCharacteristic IPeripheralManagerData.FindCharacteristic(string serviceUUID, s void INativePeripheralManagerDelegate.DidUpdateState(CBManagerState state) { - if (_disposed) return; - State = state; - _delegate?.DidUpdateState(this); + CallbackContext.Post(_ => + { + if (_disposed) return; + State = state; + _delegate?.DidUpdateState(this); + }, null); } void INativePeripheralManagerDelegate.DidAddService(string serviceUUID, CBError error) { - if (_disposed) return; - if (!_addingServiceUUIDs.Remove(serviceUUID)) + CallbackContext.Post(_ => { - return; - } + if (_disposed) return; + if (!_addingServiceUUIDs.Remove(serviceUUID)) + { + return; + } - _delegate?.DidAddService(this, _services[serviceUUID], error); + _delegate?.DidAddService(this, _services[serviceUUID], error); + }, null); } void INativePeripheralManagerDelegate.DidStartAdvertising(CBError error) { - if (_disposed) return; - _delegate?.DidStartAdvertising(this, error); + CallbackContext.Post(_ => + { + if (_disposed) return; + _delegate?.DidStartAdvertising(this, error); + }, null); } void INativePeripheralManagerDelegate.DidSubscribeToCharacteristic(SafeNativeCentralHandle centralHandle, string serviceUUID, string characteristicUUID) { - if (_disposed) return; + CallbackContext.Post(_ => + { + if (_disposed) return; - var central = FindOrCreateCentral(centralHandle); + var central = FindOrCreateCentral(centralHandle); - var characteristic = ((IPeripheralManagerData)this).FindCharacteristic(serviceUUID, characteristicUUID); - _delegate?.DidSubscribeToCharacteristic(this, central, characteristic); + var characteristic = ((IPeripheralManagerData)this).FindCharacteristic(serviceUUID, characteristicUUID); + _delegate?.DidSubscribeToCharacteristic(this, central, characteristic); + }, null); } void INativePeripheralManagerDelegate.DidUnsubscribeFromCharacteristic(SafeNativeCentralHandle centralHandle, string serviceUUID, string characteristicUUID) { - if (_disposed) return; + CallbackContext.Post(_ => + { + if (_disposed) return; - var central = FindOrCreateCentral(centralHandle); + var central = FindOrCreateCentral(centralHandle); - var characteristic = ((IPeripheralManagerData)this).FindCharacteristic(serviceUUID, characteristicUUID); - _delegate?.DidUnsubscribeFromCharacteristic(this, central, characteristic); + var characteristic = ((IPeripheralManagerData)this).FindCharacteristic(serviceUUID, characteristicUUID); + _delegate?.DidUnsubscribeFromCharacteristic(this, central, characteristic); + }, null); } void INativePeripheralManagerDelegate.IsReadyToUpdateSubscribers() { - if (_disposed) return; - _delegate?.IsReadyToUpdateSubscribers(this); + CallbackContext.Post(_ => + { + if (_disposed) return; + _delegate?.IsReadyToUpdateSubscribers(this); + }, null); } void INativePeripheralManagerDelegate.DidReceiveReadRequest(SafeNativeATTRequestHandle requestHandle) { - if (_disposed) return; - var request = new CBATTRequest(requestHandle, new NativeATTRequestProxy(requestHandle, this)); - _delegate?.DidReceiveReadRequest(this, request); - AddATTRequestDisposable(request); + CallbackContext.Post(_ => + { + if (_disposed) return; + var request = new CBATTRequest(requestHandle, new NativeATTRequestProxy(requestHandle, this)); + _delegate?.DidReceiveReadRequest(this, request); + AddATTRequestDisposable(request); + }, null); } void INativePeripheralManagerDelegate.DidReceiveWriteRequests(SafeNativeATTRequestsHandle requestsHandle) { - if (_disposed) return; - var requests = new CBATTRequests(requestsHandle, new NativeATTRequestsProxy(requestsHandle, this)); - _delegate?.DidReceiveWriteRequests(this, requests.Requests); - AddATTRequestDisposable(requests); + CallbackContext.Post(_ => + { + if (_disposed) return; + var requests = new CBATTRequests(requestsHandle, new NativeATTRequestsProxy(requestsHandle, this)); + _delegate?.DidReceiveWriteRequests(this, requests.Requests); + AddATTRequestDisposable(requests); + }, null); } public void Dispose()