Skip to content

Commit 40c7936

Browse files
committed
[ECO-5076] Moved ObjectsState to separate file
1. Added ObjectsEvents and emitter for the same 2. Implemented stateChange mechanism for objectsState changes 3. updated getRootAsync to ensure objects are synced
1 parent 111e15a commit 40c7936

3 files changed

Lines changed: 84 additions & 28 deletions

File tree

live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,6 @@ import kotlinx.coroutines.*
88
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
99
import kotlinx.coroutines.flow.MutableSharedFlow
1010

11-
/**
12-
* @spec RTO2 - enum representing objects state
13-
*/
14-
internal enum class ObjectsState {
15-
INITIALIZED,
16-
SYNCING,
17-
SYNCED
18-
}
19-
2011
/**
2112
* Default implementation of LiveObjects interface.
2213
* Provides the core functionality for managing live objects on a channel.
@@ -99,7 +90,7 @@ internal class DefaultLiveObjects(private val channelName: String, internal val
9990
private suspend fun getRootAsync(): LiveMap {
10091
return sequentialScope.async {
10192
adapter.throwIfInvalidAccessApiConfiguration(channelName)
102-
// TODO - wait for state in synced state
93+
objectsManager.ensureSynced()
10394
objectsPool.get(ROOT_OBJECT_ID) as LiveMap
10495
}.await()
10596
}
@@ -186,22 +177,6 @@ internal class DefaultLiveObjects(private val channelName: String, internal val
186177
}
187178
}
188179

189-
/**
190-
* Changes the state and emits events.
191-
*
192-
* @spec RTO2 - Emits state change events for syncing and synced states
193-
*/
194-
internal fun stateChange(newState: ObjectsState, deferEvent: Boolean) {
195-
if (state == newState) {
196-
return
197-
}
198-
199-
state = newState
200-
Log.v(tag, "Objects state changed to: $newState")
201-
202-
// TODO: Emit state change events
203-
}
204-
205180
// Dispose of any resources associated with this LiveObjects instance
206181
fun dispose() {
207182
incomingObjectsHandler.cancel() // objectsEventBus automatically garbage collected when collector is cancelled

live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsManager.kt

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import io.ably.lib.objects.type.BaseLiveObject
44
import io.ably.lib.objects.type.livecounter.DefaultLiveCounter
55
import io.ably.lib.objects.type.livemap.DefaultLiveMap
66
import io.ably.lib.util.Log
7+
import kotlinx.coroutines.*
78

89
/**
910
* @spec RTO5 - Processes OBJECT and OBJECT_SYNC messages during sync sequences
@@ -21,6 +22,13 @@ internal class ObjectsManager(private val liveObjects: DefaultLiveObjects) {
2122
*/
2223
private val bufferedObjectOperations = mutableListOf<ObjectMessage>() // RTO7a
2324

25+
// composition over inheritance, used to handle object state changes internally
26+
private val internalObjectStateEmitter = ObjectsStateEmitter()
27+
// related to RTC10, should have a separate EventEmitter for users of the library
28+
private val publicObjectStateEmitter = ObjectsStateEmitter()
29+
// Coroutine scope for running sequential operations on a single thread, used to avoid concurrency issues.
30+
private val emitterScope = CoroutineScope(Dispatchers.Default.limitedParallelism(1) + SupervisorJob())
31+
2432
/**
2533
* Handles object messages (non-sync messages).
2634
*
@@ -77,7 +85,7 @@ internal class ObjectsManager(private val liveObjects: DefaultLiveObjects) {
7785
bufferedObjectOperations.clear() // RTO5a2b
7886
syncObjectsDataPool.clear() // RTO5a2a
7987
currentSyncId = syncId
80-
liveObjects.stateChange(ObjectsState.SYNCING, false)
88+
stateChange(ObjectsState.SYNCING, false)
8189
}
8290

8391
/**
@@ -95,7 +103,7 @@ internal class ObjectsManager(private val liveObjects: DefaultLiveObjects) {
95103
bufferedObjectOperations.clear() // RTO5c5
96104
syncObjectsDataPool.clear() // RTO5c4
97105
currentSyncId = null // RTO5c3
98-
liveObjects.stateChange(ObjectsState.SYNCED, deferStateEvent)
106+
stateChange(ObjectsState.SYNCED, deferStateEvent)
99107
}
100108

101109
/**
@@ -215,8 +223,48 @@ internal class ObjectsManager(private val liveObjects: DefaultLiveObjects) {
215223
}
216224
}
217225

226+
/**
227+
* Suspends the current coroutine until objects are synchronized.
228+
* Returns immediately if state is already SYNCED, otherwise waits for the SYNCED event.
229+
*/
230+
internal suspend fun ensureSynced() {
231+
if (liveObjects.state != ObjectsState.SYNCED) {
232+
val deferred = CompletableDeferred<Unit>()
233+
internalObjectStateEmitter.once(ObjectsEvent.SYNCED) {
234+
Log.v(tag, "Objects state changed to SYNCED, resuming ensureSynced")
235+
deferred.complete(Unit)
236+
}
237+
deferred.await()
238+
}
239+
}
240+
241+
/**
242+
* Changes the state and emits events.
243+
*
244+
* @spec RTO2 - Emits state change events for syncing and synced states
245+
*/
246+
private fun stateChange(newState: ObjectsState, deferEvent: Boolean) {
247+
if (liveObjects.state == newState) {
248+
return
249+
}
250+
251+
liveObjects.state = newState
252+
Log.v(tag, "Objects state changed to: $newState")
253+
254+
val event = objectsStateToEventMap[newState]
255+
event?.let {
256+
// Use of deferEvent not needed, since emit method is synchronized amongst different threads
257+
// emitterScope makes sure, next launch can only start when previous launch finishes processing of all events
258+
emitterScope.launch {
259+
internalObjectStateEmitter.emit(it)
260+
publicObjectStateEmitter.emit(it)
261+
}
262+
}
263+
}
264+
218265
internal fun dispose() {
219266
syncObjectsDataPool.clear()
220267
bufferedObjectOperations.clear()
268+
emitterScope.cancel("ObjectsManager disposed")
221269
}
222270
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.ably.lib.objects
2+
3+
import io.ably.lib.util.EventEmitter
4+
5+
/**
6+
* @spec RTO2 - enum representing objects state
7+
*/
8+
internal enum class ObjectsState {
9+
INITIALIZED,
10+
SYNCING,
11+
SYNCED
12+
}
13+
14+
public enum class ObjectsEvent {
15+
SYNCING,
16+
SYNCED
17+
}
18+
19+
internal val objectsStateToEventMap = mapOf(
20+
ObjectsState.INITIALIZED to null,
21+
ObjectsState.SYNCING to ObjectsEvent.SYNCING,
22+
ObjectsState.SYNCED to ObjectsEvent.SYNCED
23+
)
24+
25+
public fun interface ObjectsStateListener {
26+
public fun onStateChanged(state: ObjectsEvent)
27+
}
28+
29+
internal class ObjectsStateEmitter : EventEmitter<ObjectsEvent, ObjectsStateListener>() {
30+
override fun apply(listener: ObjectsStateListener?, event: ObjectsEvent?, vararg args: Any?) {
31+
listener?.onStateChanged(event!!)
32+
}
33+
}

0 commit comments

Comments
 (0)