Skip to content

Commit

Permalink
Simplify the Internal Lifecycle of Actor
Browse files Browse the repository at this point in the history
This PR simplifies the mechanism for tracking reference counts in the Actor introduced in #44. Previously, the reference
count was managed by invoking the `launchIn(scope)` function via `XxxRef` interface, controlling the increment and decrement
of references based on the lifecycle of the CoroutineScope.

However, this approach proved inconvenient for short-lived scopes, such as those used with the prefetch function.

In this change, a UUID is assigned to each Ref instance, and a simpler attach/detach mechanism is introduced. Naturally,
Ref instances must guarantee these calls. To enforce this, the [AutoCloseable](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-auto-closeable.html) interface is implemented, making it clear
that the close function must be invoked once the instance is no longer needed.

For short-lived use cases like prefetch, the use extension function can be employed to ensure proper cleanup reliably.

```
getQuery(key, marker).use { query -> ... }
```
  • Loading branch information
ogaclejapan committed Nov 30, 2024
1 parent 94d8190 commit dad16b3
Show file tree
Hide file tree
Showing 26 changed files with 383 additions and 358 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
package soil.query.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import soil.query.InfiniteQueryKey
import soil.query.InfiniteQueryRef
import soil.query.QueryChunks
import soil.query.QueryClient
import soil.query.compose.internal.newInfiniteQuery
Expand All @@ -29,6 +31,7 @@ fun <T, S> rememberInfiniteQuery(
): InfiniteQueryObject<QueryChunks<T, S>, S> {
val scope = rememberCoroutineScope()
val query = remember(key.id) { newInfiniteQuery(key, config, client, scope) }
query.Effect()
return with(config.mapper) {
config.strategy.collectAsState(query).toObject(query = query, select = { it })
}
Expand All @@ -54,7 +57,19 @@ fun <T, S, U> rememberInfiniteQuery(
): InfiniteQueryObject<U, S> {
val scope = rememberCoroutineScope()
val query = remember(key.id) { newInfiniteQuery(key, config, client, scope) }
query.Effect()
return with(config.mapper) {
config.strategy.collectAsState(query).toObject(query = query, select = select)
}
}

@Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
@Composable
private inline fun InfiniteQueryRef<*, *>.Effect() {
// TODO: Switch to LifecycleResumeEffect
// Android, it works only with Compose UI 1.7.0-alpha05 or above.
// Therefore, we will postpone adding this code until a future release.
LaunchedEffect(id) {
join()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
package soil.query.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import soil.query.QueryClient
import soil.query.QueryKey
import soil.query.QueryRef
import soil.query.compose.internal.newCombinedQuery
import soil.query.compose.internal.newQuery

Expand All @@ -28,6 +30,7 @@ fun <T> rememberQuery(
): QueryObject<T> {
val scope = rememberCoroutineScope()
val query = remember(key.id) { newQuery(key, config, client, scope) }
query.Effect()
return with(config.mapper) {
config.strategy.collectAsState(query).toObject(query = query, select = { it })
}
Expand All @@ -53,6 +56,7 @@ fun <T, U> rememberQuery(
): QueryObject<U> {
val scope = rememberCoroutineScope()
val query = remember(key.id) { newQuery(key, config, client, scope) }
query.Effect()
return with(config.mapper) {
config.strategy.collectAsState(query).toObject(query = query, select = select)
}
Expand Down Expand Up @@ -83,6 +87,7 @@ fun <T1, T2, R> rememberQuery(
val query = remember(key1.id, key2.id) {
newCombinedQuery(key1, key2, transform, config, client, scope)
}
query.Effect()
return with(config.mapper) {
config.strategy.collectAsState(query).toObject(query = query, select = { it })
}
Expand Down Expand Up @@ -116,6 +121,7 @@ fun <T1, T2, T3, R> rememberQuery(
val query = remember(key1.id, key2.id, key3.id) {
newCombinedQuery(key1, key2, key3, transform, config, client, scope)
}
query.Effect()
return with(config.mapper) {
config.strategy.collectAsState(query).toObject(query = query, select = { it })
}
Expand Down Expand Up @@ -143,7 +149,19 @@ fun <T, R> rememberQuery(
val query = remember(*keys.map { it.id }.toTypedArray()) {
newCombinedQuery(keys, transform, config, client, scope)
}
query.Effect()
return with(config.mapper) {
config.strategy.collectAsState(query).toObject(query = query, select = { it })
}
}

@Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
@Composable
private inline fun QueryRef<*>.Effect() {
// TODO: Switch to LifecycleResumeEffect
// Android, it works only with Compose UI 1.7.0-alpha05 or above.
// Therefore, we will postpone adding this code until a future release.
LaunchedEffect(id) {
join()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import soil.query.QueryClient
import soil.query.QueryId
Expand Down Expand Up @@ -53,6 +54,11 @@ private class CombinedQuery2<T1, T2, R>(
)
override val state: StateFlow<QueryState<R>> = _state

override fun close() {
query1.close()
query2.close()
}

override suspend fun resume() {
coroutineScope {
val deferred1 = async { query1.resume() }
Expand All @@ -69,9 +75,11 @@ private class CombinedQuery2<T1, T2, R>(
}
}

override fun launchIn(scope: CoroutineScope): Job {
return scope.launch {
combine(query1.state, query2.state, ::merge).collect { _state.value = it }
override suspend fun join() {
coroutineScope {
val job1 = launch { query1.join() }
val job2 = launch { query2.join() }
joinAll(job1, job2)
}
}

Expand All @@ -80,26 +88,23 @@ private class CombinedQuery2<T1, T2, R>(
}

// ----- RememberObserver -----//
private var jobs: List<Job>? = null
private var job: Job? = null

override fun onAbandoned() = stop()

override fun onForgotten() = stop()

override fun onRemembered() {
stop()
start()
}
override fun onRemembered() = start()

private fun start() {
val job1 = query1.launchIn(scope)
val job2 = query2.launchIn(scope)
val job3 = launchIn(scope)
jobs = listOf(job1, job2, job3)
job = scope.launch {
combine(query1.state, query2.state, ::merge).collect { _state.value = it }
}
}

private fun stop() {
jobs?.forEach { it.cancel() }
jobs = null
job?.cancel()
job = null
close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import soil.query.QueryClient
import soil.query.QueryId
Expand Down Expand Up @@ -56,6 +57,12 @@ private class CombinedQuery3<T1, T2, T3, R>(
)
override val state: StateFlow<QueryState<R>> = _state

override fun close() {
query1.close()
query2.close()
query3.close()
}

override suspend fun resume() {
coroutineScope {
val deferred1 = async { query1.resume() }
Expand All @@ -74,9 +81,12 @@ private class CombinedQuery3<T1, T2, T3, R>(
}
}

override fun launchIn(scope: CoroutineScope): Job {
return scope.launch {
combine(query1.state, query2.state, query3.state, ::merge).collect { _state.value = it }
override suspend fun join() {
coroutineScope {
val job1 = launch { query1.join() }
val job2 = launch { query2.join() }
val job3 = launch { query3.join() }
joinAll(job1, job2, job3)
}
}

Expand All @@ -85,27 +95,23 @@ private class CombinedQuery3<T1, T2, T3, R>(
}

// ----- RememberObserver -----//
private var jobs: List<Job>? = null
private var job: Job? = null

override fun onAbandoned() = stop()

override fun onForgotten() = stop()

override fun onRemembered() {
stop()
start()
}
override fun onRemembered() = start()

private fun start() {
val job1 = query1.launchIn(scope)
val job2 = query2.launchIn(scope)
val job3 = query3.launchIn(scope)
val job4 = launchIn(scope)
jobs = listOf(job1, job2, job3, job4)
job = scope.launch {
combine(query1.state, query2.state, query3.state, ::merge).collect { _state.value = it }
}
}

private fun stop() {
jobs?.forEach { it.cancel() }
jobs = null
job?.cancel()
job = null
close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import soil.query.QueryClient
import soil.query.QueryId
Expand Down Expand Up @@ -49,6 +50,10 @@ private class CombinedQueryN<T, R>(
)
override val state: StateFlow<QueryState<R>> = _state

override fun close() {
queries.forEach { it.close() }
}

override suspend fun resume() {
coroutineScope {
queries.map { query -> async { query.resume() } }.awaitAll()
Expand All @@ -61,9 +66,9 @@ private class CombinedQueryN<T, R>(
}
}

override fun launchIn(scope: CoroutineScope): Job {
return scope.launch {
combine(queries.map { it.state }, ::merge).collect { _state.value = it }
override suspend fun join() {
coroutineScope {
queries.map { query -> launch { query.join() } }.joinAll()
}
}

Expand All @@ -72,23 +77,23 @@ private class CombinedQueryN<T, R>(
}

// ----- RememberObserver -----//
private var jobs: List<Job>? = null
private var job: Job? = null

override fun onAbandoned() = stop()

override fun onForgotten() = stop()

override fun onRemembered() {
stop()
start()
}
override fun onRemembered() = start()

private fun start() {
jobs = queries.map { it.launchIn(scope) } + launchIn(scope)
job = scope.launch {
combine(queries.map { it.state }, ::merge).collect { _state.value = it }
}
}

private fun stop() {
jobs?.forEach { it.cancel() }
jobs = null
job?.cancel()
job = null
close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ private class InfiniteQuery<T, S>(
)
override val state: StateFlow<QueryState<QueryChunks<T, S>>> = _state

override fun close() = query.close()

override fun nextParam(data: QueryChunks<T, S>): S? = query.nextParam(data)

override suspend fun resume() = query.resume()
Expand All @@ -49,32 +51,26 @@ private class InfiniteQuery<T, S>(

override suspend fun invalidate() = query.invalidate()

override fun launchIn(scope: CoroutineScope): Job {
return scope.launch {
query.state.collect { _state.value = optimize(it) }
}
}
override suspend fun join() = query.join()

// ----- RememberObserver -----//
private var jobs: List<Job>? = null
private var job: Job? = null

override fun onAbandoned() = stop()

override fun onForgotten() = stop()

override fun onRemembered() {
stop()
start()
}
override fun onRemembered() = start()

private fun start() {
val job1 = query.launchIn(scope)
val job2 = launchIn(scope)
jobs = listOf(job1, job2)
job = scope.launch {
query.state.collect { _state.value = optimize(it) }
}
}

private fun stop() {
jobs?.forEach { it.cancel() }
jobs = null
job?.cancel()
job = null
close()
}
}
Loading

0 comments on commit dad16b3

Please sign in to comment.