Skip to content

Commit e7529de

Browse files
authored
Simplify internal lifecycle of Peripheral (#491)
1 parent ce8951f commit e7529de

3 files changed

Lines changed: 24 additions & 27 deletions

File tree

core/src/appleMain/kotlin/CentralManager.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.juul.kable
22

33
import com.benasher44.uuid.Uuid
44
import com.juul.kable.logs.Logging
5+
import kotlinx.coroutines.CoroutineScope
56
import kotlinx.coroutines.flow.MutableSharedFlow
67
import kotlinx.coroutines.withContext
78
import platform.CoreBluetooth.CBCentralManager
@@ -49,20 +50,20 @@ public class CentralManager internal constructor() {
4950
.firstOrNull() as? CBPeripheral
5051

5152
internal suspend fun connectPeripheral(
53+
scope: CoroutineScope,
5254
peripheral: CBPeripheralCoreBluetoothPeripheral,
5355
characteristicChanges: MutableSharedFlow<ObservationEvent<NSData>>,
5456
logging: Logging,
5557
options: Map<Any?, *>? = null,
5658
): Connection {
57-
val parentCoroutineContext = peripheral.connectionScope.coroutineContext
5859
val cbPeripheral = peripheral.cbPeripheral
5960
val identifier = cbPeripheral.identifier.UUIDString
6061
val delegate = PeripheralDelegate(peripheral.canSendWriteWithoutResponse, characteristicChanges, logging, identifier)
6162
withContext(dispatcher) {
6263
cbPeripheral.delegate = delegate
6364
cbCentralManager.connectPeripheral(cbPeripheral, options)
6465
}
65-
return Connection(parentCoroutineContext, delegate, logging, identifier)
66+
return Connection(scope, delegate, logging, identifier)
6667
}
6768

6869
internal suspend fun cancelPeripheralConnection(

core/src/appleMain/kotlin/Connection.kt

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,22 @@ import kotlinx.coroutines.CoroutineScope
77
import kotlinx.coroutines.Deferred
88
import kotlinx.coroutines.Job
99
import kotlinx.coroutines.async
10-
import kotlinx.coroutines.cancel
1110
import kotlinx.coroutines.sync.Mutex
1211
import kotlinx.coroutines.sync.withLock
13-
import kotlin.coroutines.CoroutineContext
1412

1513
internal class Connection(
16-
parentCoroutineContext: CoroutineContext,
14+
scope: CoroutineScope,
1715
val delegate: PeripheralDelegate,
1816
logging: Logging,
1917
identifier: String,
2018
) {
2119

22-
private val scope = CoroutineScope(parentCoroutineContext + Job(parentCoroutineContext[Job]) + CoroutineName("Kable/Connection@$identifier"))
20+
private val job = Job(scope.coroutineContext[Job]).apply {
21+
invokeOnCompletion {
22+
delegate.close()
23+
}
24+
}
25+
val scope = CoroutineScope(scope.coroutineContext + job + CoroutineName("Kable/Connection/$identifier"))
2326

2427
private val logger = Logger(logging, tag = "Kable/Connection", identifier = identifier)
2528

@@ -59,9 +62,4 @@ internal class Connection(
5962
if (error != null) throw IOException(error.description, cause = null)
6063
response as T
6164
}
62-
63-
fun close() {
64-
scope.cancel()
65-
delegate.close()
66-
}
6765
}

core/src/appleMain/kotlin/Peripheral.kt

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import kotlinx.atomicfu.atomic
2828
import kotlinx.atomicfu.updateAndGet
2929
import kotlinx.coroutines.CoroutineName
3030
import kotlinx.coroutines.CoroutineScope
31-
import kotlinx.coroutines.Job
3231
import kotlinx.coroutines.NonCancellable
3332
import kotlinx.coroutines.SupervisorJob
3433
import kotlinx.coroutines.flow.Flow
@@ -42,6 +41,7 @@ import kotlinx.coroutines.flow.map
4241
import kotlinx.coroutines.flow.onEach
4342
import kotlinx.coroutines.flow.onSubscription
4443
import kotlinx.coroutines.flow.updateAndGet
44+
import kotlinx.coroutines.isActive
4545
import kotlinx.coroutines.job
4646
import kotlinx.coroutines.sync.withLock
4747
import kotlinx.coroutines.withContext
@@ -109,9 +109,11 @@ internal class CBPeripheralCoreBluetoothPeripheral(
109109
private val logging: Logging,
110110
) : CoreBluetoothPeripheral {
111111

112-
private val job = SupervisorJob(parentCoroutineContext.job) // todo: Disconnect/dispose CBPeripheral on completion?
113-
private val scope = CoroutineScope(parentCoroutineContext + job + CoroutineName("Kable/Peripheral@${cbPeripheral.identifier.UUIDString}"))
114-
internal val connectionScope = CoroutineScope(scope.coroutineContext + Job(scope.coroutineContext[Job]) + CoroutineName("Kable/Connect@${cbPeripheral.identifier.UUIDString}"))
112+
private val scope = CoroutineScope(
113+
parentCoroutineContext +
114+
SupervisorJob(parentCoroutineContext.job) +
115+
CoroutineName("Kable/Peripheral/${cbPeripheral.identifier.UUIDString}"),
116+
)
115117

116118
private val centralManager: CentralManager = CentralManager.Default
117119

@@ -160,18 +162,11 @@ internal class CBPeripheralCoreBluetoothPeripheral(
160162

161163
private val _connection = atomic<Connection?>(null)
162164
private val connection: Connection
163-
inline get() = _connection.value ?: throw NotReadyException(toString())
165+
inline get() = _connection.value?.takeIf { it.scope.isActive } ?: throw NotReadyException(toString())
164166

165167
override val name: String? get() = cbPeripheral.name
166168

167-
private val connectAction = connectionScope.sharedRepeatableAction(::establishConnection)
168-
169-
private fun onDisconnected() {
170-
logger.info { message = "Disconnected" }
171-
connectAction.reset()
172-
_connection.value?.close()
173-
_connection.value = null
174-
}
169+
private val connectAction = scope.sharedRepeatableAction(::establishConnection)
175170

176171
override suspend fun connect() {
177172
connectAction.await()
@@ -185,11 +180,15 @@ internal class CBPeripheralCoreBluetoothPeripheral(
185180
_state.value = State.Connecting.Bluetooth
186181

187182
centralManager.delegate.onDisconnected.onEach { identifier ->
188-
if (identifier == cbPeripheral.identifier) onDisconnected()
183+
if (identifier == cbPeripheral.identifier) {
184+
connectAction.reset()
185+
logger.info { message = "Disconnected" }
186+
}
189187
}.launchIn(scope)
190188

191189
try {
192190
_connection.value = centralManager.connectPeripheral(
191+
scope,
193192
this@CBPeripheralCoreBluetoothPeripheral,
194193
observers.characteristicChanges,
195194
logging,
@@ -208,7 +207,6 @@ internal class CBPeripheralCoreBluetoothPeripheral(
208207
logger.error(e) { message = "Failed to connect" }
209208
withContext(NonCancellable) {
210209
centralManager.cancelPeripheralConnection(cbPeripheral)
211-
_connection.value = null
212210
}
213211
throw e
214212
}
@@ -224,7 +222,7 @@ internal class CBPeripheralCoreBluetoothPeripheral(
224222
withContext(NonCancellable) {
225223
centralManager.cancelPeripheralConnection(cbPeripheral)
226224
}
227-
onDisconnected()
225+
logger.info { message = "Disconnected" }
228226
}
229227
}
230228

0 commit comments

Comments
 (0)