Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimization of Recomposition #97

Merged
merged 1 commit into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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