Skip to content

Commit

Permalink
Merge pull request #97 from soil-kt/omit-state
Browse files Browse the repository at this point in the history
Optimization of Recomposition
  • Loading branch information
ogaclejapan authored Sep 22, 2024
2 parents a372f6e + 7b07346 commit f158a96
Show file tree
Hide file tree
Showing 33 changed files with 2,153 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import soil.query.InfiniteQueryKey
import soil.query.QueryChunks
import soil.query.QueryClient
import soil.query.compose.internal.newInfiniteQuery

/**
* Remember a [InfiniteQueryObject] and subscribes to the query state of [key].
Expand All @@ -27,7 +28,7 @@ fun <T, S> rememberInfiniteQuery(
client: QueryClient = LocalQueryClient.current
): InfiniteQueryObject<QueryChunks<T, S>, S> {
val scope = rememberCoroutineScope()
val query = remember(key.id) { client.getInfiniteQuery(key, config.marker).also { it.launchIn(scope) } }
val query = remember(key.id) { newInfiniteQuery(key, config, client, scope) }
return with(config.mapper) {
config.strategy.collectAsState(query).toObject(query = query, select = { it })
}
Expand All @@ -52,7 +53,7 @@ fun <T, S, U> rememberInfiniteQuery(
client: QueryClient = LocalQueryClient.current
): InfiniteQueryObject<U, S> {
val scope = rememberCoroutineScope()
val query = remember(key.id) { client.getInfiniteQuery(key, config.marker).also { it.launchIn(scope) } }
val query = remember(key.id) { newInfiniteQuery(key, config, client, scope) }
return with(config.mapper) {
config.strategy.collectAsState(query).toObject(query = query, select = select)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,32 +9,43 @@ import soil.query.core.Marker
/**
* Configuration for the infinite query.
*
* @property mapper The mapper for converting query data.
* @property optimizer The optimizer for recomposing the query data.
* @property strategy The strategy for caching query data.
* @property marker The marker with additional information based on the caller of a query.
*/
@Immutable
data class InfiniteQueryConfig internal constructor(
val strategy: InfiniteQueryStrategy,
val mapper: InfiniteQueryObjectMapper,
val optimizer: InfiniteQueryRecompositionOptimizer,
val strategy: InfiniteQueryStrategy,
val marker: Marker
) {

class Builder {
var strategy: InfiniteQueryStrategy = Default.strategy
var mapper: InfiniteQueryObjectMapper = Default.mapper
var marker: Marker = Default.marker
/**
* Creates a new [InfiniteQueryConfig] with the provided [block].
*/
fun builder(block: Builder.() -> Unit) = Builder(this).apply(block).build()

class Builder(config: InfiniteQueryConfig = Default) {
var mapper: InfiniteQueryObjectMapper = config.mapper
var optimizer: InfiniteQueryRecompositionOptimizer = config.optimizer
var strategy: InfiniteQueryStrategy = config.strategy
var marker: Marker = config.marker

fun build() = InfiniteQueryConfig(
strategy = strategy,
optimizer = optimizer,
mapper = mapper,
marker = marker
)
}

companion object {
val Default = InfiniteQueryConfig(
strategy = InfiniteQueryStrategy.Default,
mapper = InfiniteQueryObjectMapper.Default,
optimizer = InfiniteQueryRecompositionOptimizer.Default,
strategy = InfiniteQueryStrategy.Default,
marker = Marker.None
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2024 Soil Contributors
// SPDX-License-Identifier: Apache-2.0

package soil.query.compose

import soil.query.QueryChunks
import soil.query.QueryState
import soil.query.QueryStatus

/**
* A recomposition optimizer for [QueryState] with [QueryChunks].
*/
interface InfiniteQueryRecompositionOptimizer {

/**
* Omit the specified keys from the [QueryState] with [QueryChunks].
*
* @param state The infinite query state.
* @return The optimized infinite query state.
*/
fun <T, S> omit(state: QueryState<QueryChunks<T, S>>): QueryState<QueryChunks<T, S>>

companion object
}

/**
* Optimizer implementation for [InfiniteQueryStrategy.Companion.Default].
*/
val InfiniteQueryRecompositionOptimizer.Companion.Default: InfiniteQueryRecompositionOptimizer
get() = DefaultInfiniteQueryRecompositionOptimizer

private object DefaultInfiniteQueryRecompositionOptimizer : InfiniteQueryRecompositionOptimizer {
override fun <T, S> omit(state: QueryState<QueryChunks<T, S>>): QueryState<QueryChunks<T, S>> {
val keys = buildSet {
add(QueryState.OmitKey.replyUpdatedAt)
add(QueryState.OmitKey.staleAt)
when (state.status) {
QueryStatus.Pending -> {
add(QueryState.OmitKey.errorUpdatedAt)
add(QueryState.OmitKey.fetchStatus)
}

QueryStatus.Success -> {
add(QueryState.OmitKey.errorUpdatedAt)
if (!state.isInvalidated) {
add(QueryState.OmitKey.fetchStatus)
}
}

QueryStatus.Failure -> {
if (!state.isInvalidated) {
add(QueryState.OmitKey.fetchStatus)
}
}
}
}
return state.omit(keys)
}
}

/**
* Option that performs no optimization.
*/
val InfiniteQueryRecompositionOptimizer.Companion.Disabled: InfiniteQueryRecompositionOptimizer
get() = DisabledInfiniteQueryRecompositionOptimizer

private object DisabledInfiniteQueryRecompositionOptimizer : InfiniteQueryRecompositionOptimizer {
override fun <T, S> omit(state: QueryState<QueryChunks<T, S>>): QueryState<QueryChunks<T, S>> {
return state
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import soil.query.MutationClient
import soil.query.MutationKey
import soil.query.compose.internal.newMutation

/**
* Remember a [MutationObject] and subscribes to the mutation state of [key].
Expand All @@ -26,7 +27,7 @@ fun <T, S> rememberMutation(
client: MutationClient = LocalMutationClient.current
): MutationObject<T, S> {
val scope = rememberCoroutineScope()
val mutation = remember(key.id) { client.getMutation(key, config.marker).also { it.launchIn(scope) } }
val mutation = remember(key.id) { newMutation(key, config, client, scope) }
return with(config.mapper) {
config.strategy.collectAsState(mutation).toObject(mutation = mutation)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,43 @@ import soil.query.core.Marker
/**
* Configuration for the mutation.
*
* @property mapper The mapper for converting mutation data.
* @property optimizer The optimizer for recomposing the mutation data.
* @property strategy The strategy for caching mutation data.
* @property marker The marker with additional information based on the caller of a mutation.
*/
@Immutable
data class MutationConfig internal constructor(
val strategy: MutationStrategy,
val mapper: MutationObjectMapper,
val optimizer: MutationRecompositionOptimizer,
val strategy: MutationStrategy,
val marker: Marker
) {

class Builder {
var strategy: MutationStrategy = Default.strategy
var mapper: MutationObjectMapper = Default.mapper
var marker: Marker = Default.marker
/**
* Creates a new [MutationConfig] with the provided [block].
*/
fun builder(block: Builder.() -> Unit) = Builder(this).apply(block).build()

class Builder(config: MutationConfig = Default) {
var mapper: MutationObjectMapper = config.mapper
var optimizer: MutationRecompositionOptimizer = config.optimizer
var strategy: MutationStrategy = config.strategy
var marker: Marker = config.marker

fun build() = MutationConfig(
strategy = strategy,
mapper = mapper,
optimizer = optimizer,
strategy = strategy,
marker = marker
)
}

companion object {
val Default = MutationConfig(
strategy = MutationStrategy.Default,
mapper = MutationObjectMapper.Default,
optimizer = MutationRecompositionOptimizer.Default,
strategy = MutationStrategy.Default,
marker = Marker.None
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2024 Soil Contributors
// SPDX-License-Identifier: Apache-2.0

package soil.query.compose

import soil.query.MutationState
import soil.query.MutationStatus

/**
* A recomposition optimizer for [MutationState].
*/
interface MutationRecompositionOptimizer {

/**
* Omit the specified keys from the [MutationState].
*
* @param state The mutation state.
* @return The optimized mutation state.
*/
fun <T> omit(state: MutationState<T>): MutationState<T>

companion object
}

/**
* Optimizer implementation for [MutationStrategy.Companion.Default].
*/
val MutationRecompositionOptimizer.Companion.Default: MutationRecompositionOptimizer
get() = DefaultMutationRecompositionOptimizer

private object DefaultMutationRecompositionOptimizer : MutationRecompositionOptimizer {
override fun <T> omit(state: MutationState<T>): MutationState<T> {
val keys = buildSet {
add(MutationState.OmitKey.replyUpdatedAt)
add(MutationState.OmitKey.mutatedCount)
when (state.status) {
MutationStatus.Idle -> {
add(MutationState.OmitKey.errorUpdatedAt)
}

MutationStatus.Pending -> {
if (state.error == null) {
add(MutationState.OmitKey.errorUpdatedAt)
}
}

MutationStatus.Success -> {
add(MutationState.OmitKey.errorUpdatedAt)
}

MutationStatus.Failure -> Unit
}
}
return state.omit(keys)
}
}

/**
* Option that performs no optimization.
*/
val MutationRecompositionOptimizer.Companion.Disabled: MutationRecompositionOptimizer
get() = DisabledMutationRecompositionOptimizer

private object DisabledMutationRecompositionOptimizer : MutationRecompositionOptimizer {
override fun <T> omit(state: MutationState<T>): MutationState<T> {
return state
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
package soil.query.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import soil.query.QueryClient
import soil.query.QueryKey
import soil.query.compose.internal.combineQuery
import soil.query.compose.internal.newCombinedQuery
import soil.query.compose.internal.newQuery

/**
* Remember a [QueryObject] and subscribes to the query state of [key].
Expand All @@ -27,7 +27,7 @@ fun <T> rememberQuery(
client: QueryClient = LocalQueryClient.current
): QueryObject<T> {
val scope = rememberCoroutineScope()
val query = remember(key) { client.getQuery(key, config.marker).also { it.launchIn(scope) } }
val query = remember(key.id) { newQuery(key, config, client, scope) }
return with(config.mapper) {
config.strategy.collectAsState(query).toObject(query = query, select = { it })
}
Expand All @@ -52,7 +52,7 @@ fun <T, U> rememberQuery(
client: QueryClient = LocalQueryClient.current
): QueryObject<U> {
val scope = rememberCoroutineScope()
val query = remember(key) { client.getQuery(key, config.marker).also { it.launchIn(scope) } }
val query = remember(key.id) { newQuery(key, config, client, scope) }
return with(config.mapper) {
config.strategy.collectAsState(query).toObject(query = query, select = select)
}
Expand Down Expand Up @@ -80,17 +80,11 @@ fun <T1, T2, R> rememberQuery(
client: QueryClient = LocalQueryClient.current,
): QueryObject<R> {
val scope = rememberCoroutineScope()
val query1 = remember(key1.id) { client.getQuery(key1, config.marker).also { it.launchIn(scope) } }
val query2 = remember(key2.id) { client.getQuery(key2, config.marker).also { it.launchIn(scope) } }
val combinedQuery = remember(query1, query2) {
combineQuery(query1, query2, transform)
}
DisposableEffect(combinedQuery.id) {
val job = combinedQuery.launchIn(scope)
onDispose { job.cancel() }
val query = remember(key1.id, key2.id) {
newCombinedQuery(key1, key2, transform, config, client, scope)
}
return with(config.mapper) {
config.strategy.collectAsState(combinedQuery).toObject(query = combinedQuery, select = { it })
config.strategy.collectAsState(query).toObject(query = query, select = { it })
}
}

Expand Down Expand Up @@ -119,18 +113,11 @@ fun <T1, T2, T3, R> rememberQuery(
client: QueryClient = LocalQueryClient.current,
): QueryObject<R> {
val scope = rememberCoroutineScope()
val query1 = remember(key1.id) { client.getQuery(key1, config.marker).also { it.launchIn(scope) } }
val query2 = remember(key2.id) { client.getQuery(key2, config.marker).also { it.launchIn(scope) } }
val query3 = remember(key3.id) { client.getQuery(key3, config.marker).also { it.launchIn(scope) } }
val combinedQuery = remember(query1, query2, query3) {
combineQuery(query1, query2, query3, transform)
}
DisposableEffect(combinedQuery.id) {
val job = combinedQuery.launchIn(scope)
onDispose { job.cancel() }
val query = remember(key1.id, key2.id, key3.id) {
newCombinedQuery(key1, key2, key3, transform, config, client, scope)
}
return with(config.mapper) {
config.strategy.collectAsState(combinedQuery).toObject(query = combinedQuery, select = { it })
config.strategy.collectAsState(query).toObject(query = query, select = { it })
}
}

Expand All @@ -153,17 +140,10 @@ fun <T, R> rememberQuery(
client: QueryClient = LocalQueryClient.current
): QueryObject<R> {
val scope = rememberCoroutineScope()
val queries = remember(keys) {
keys.map { key -> client.getQuery(key, config.marker).also { it.launchIn(scope) } }
}
val combinedQuery = remember(queries) {
combineQuery(queries.toTypedArray(), transform)
}
DisposableEffect(combinedQuery.id) {
val job = combinedQuery.launchIn(scope)
onDispose { job.cancel() }
val query = remember(*keys.map { it.id }.toTypedArray()) {
newCombinedQuery(keys, transform, config, client, scope)
}
return with(config.mapper) {
config.strategy.collectAsState(combinedQuery).toObject(query = combinedQuery, select = { it })
config.strategy.collectAsState(query).toObject(query = query, select = { it })
}
}
Loading

0 comments on commit f158a96

Please sign in to comment.