Loading packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/BuildScope.kt +24 −11 Original line number Diff line number Diff line Loading @@ -184,7 +184,10 @@ interface BuildScope : HasNetwork, StateScope { * The return value from [block] can be accessed via the returned [DeferredValue]. */ // TODO: return a DisposableHandle instead of Job? fun <A> asyncScope(block: BuildSpec<A>): Pair<DeferredValue<A>, Job> fun <A> asyncScope( coroutineContext: CoroutineContext = EmptyCoroutineContext, block: BuildSpec<A>, ): Pair<DeferredValue<A>, Job> // TODO: once we have context params, these can all become extensions: Loading Loading @@ -689,8 +692,11 @@ interface BuildScope : HasNetwork, StateScope { * emitting) then [block] will be invoked for the first time with the new value; otherwise, it * will be invoked with the [current][sample] value. */ fun <A> State<A>.observe(block: EffectScope.(A) -> Unit = {}): DisposableHandle = now.map { sample() }.mergeWith(changes) { _, new -> new }.observe { block(it) } fun <A> State<A>.observe( coroutineContext: CoroutineContext = EmptyCoroutineContext, block: EffectScope.(A) -> Unit = {}, ): DisposableHandle = now.map { sample() }.mergeWith(changes) { _, new -> new }.observe(coroutineContext, block) } /** Loading Loading @@ -724,14 +730,14 @@ fun <A> BuildScope.asyncEvent(block: suspend () -> A): Events<A> = * context: CoroutineContext = EmptyCoroutineContext, * block: EffectScope.() -> Unit, * ): Job = * launchScope { now.observe(context) { block() } } * launchScope(context) { now.observe { block() } } * ``` */ @ExperimentalKairosApi fun BuildScope.effect( context: CoroutineContext = EmptyCoroutineContext, block: EffectScope.() -> Unit, ): Job = launchScope { now.observe(context) { block() } } ): Job = launchScope(context) { now.observe { block() } } /** * Launches [block] in a new coroutine, returning a [Job] bound to the coroutine. Loading @@ -746,8 +752,10 @@ fun BuildScope.effect( * ``` */ @ExperimentalKairosApi fun BuildScope.launchEffect(block: suspend KairosCoroutineScope.() -> Unit): Job = asyncEffect(block) fun BuildScope.launchEffect( context: CoroutineContext = EmptyCoroutineContext, block: suspend KairosCoroutineScope.() -> Unit, ): Job = asyncEffect(context, block) /** * Launches [block] in a new coroutine, returning the result as a [Deferred]. Loading @@ -756,7 +764,6 @@ fun BuildScope.launchEffect(block: suspend KairosCoroutineScope.() -> Unit): Job * done because the current [BuildScope] might be deactivated within this transaction, perhaps due * to a -Latest combinator. If this happens, then the coroutine will never actually be started. * * Shorthand for: * ``` * fun <R> BuildScope.asyncEffect(block: suspend KairosScope.() -> R): Deferred<R> = * CompletableDeferred<R>.apply { Loading @@ -766,9 +773,12 @@ fun BuildScope.launchEffect(block: suspend KairosCoroutineScope.() -> Unit): Job * ``` */ @ExperimentalKairosApi fun <R> BuildScope.asyncEffect(block: suspend KairosCoroutineScope.() -> R): Deferred<R> { fun <R> BuildScope.asyncEffect( context: CoroutineContext = EmptyCoroutineContext, block: suspend KairosCoroutineScope.() -> R, ): Deferred<R> { val result = CompletableDeferred<R>() val job = effect { launch { result.complete(block()) } } val job = effect(context) { launch { result.complete(block()) } } val handle = job.invokeOnCompletion { result.cancel() } result.invokeOnCompletion { handle.dispose() Loading @@ -779,7 +789,10 @@ fun <R> BuildScope.asyncEffect(block: suspend KairosCoroutineScope.() -> R): Def /** Like [BuildScope.asyncScope], but ignores the result of [block]. */ @ExperimentalKairosApi fun BuildScope.launchScope(block: BuildSpec<*>): Job = asyncScope(block).second fun BuildScope.launchScope( coroutineContext: CoroutineContext = EmptyCoroutineContext, block: BuildSpec<*>, ): Job = asyncScope(coroutineContext, block).second /** * Creates an instance of an [Events] with elements that are emitted from [builder]. Loading packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/KairosNetwork.kt +34 −19 Original line number Diff line number Diff line Loading @@ -23,13 +23,18 @@ import com.android.systemui.kairos.internal.util.awaitCancellationAndThen import com.android.systemui.kairos.internal.util.childScope import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.coroutineContext import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Deferred import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.job import kotlinx.coroutines.launch import kotlinx.coroutines.plus /** Marks APIs that are still **experimental** and shouldn't be used in general production code. */ @RequiresOptIn( Loading Loading @@ -125,7 +130,10 @@ fun <T> ConflatedMutableEvents(network: KairosNetwork): CoalescingMutableEvents< * are unregistered and effects are cancelled. */ @ExperimentalKairosApi suspend fun <R> KairosNetwork.activateSpec(spec: BuildSpec<R>, block: suspend (R) -> Unit) { suspend fun <R> KairosNetwork.activateSpec( spec: BuildSpec<R>, block: suspend KairosCoroutineScope.(R) -> Unit, ) { activateSpec { val result = spec.applySpec() launchEffect { block(result) } Loading @@ -140,8 +148,11 @@ internal class LocalNetwork( override suspend fun <R> transact(block: TransactionScope.() -> R): R = network.transaction("KairosNetwork.transact") { block() }.awaitOrCancel() override suspend fun activateSpec(spec: BuildSpec<*>) { override suspend fun activateSpec(spec: BuildSpec<*>): Unit = coroutineScope { val stopEmitter = conflatedMutableEvents<Unit>() lateinit var completionHandle: DisposableHandle val job = launch(start = CoroutineStart.LAZY) { network .transaction("KairosNetwork.activateSpec") { val buildScope = Loading @@ -151,7 +162,7 @@ internal class LocalNetwork( evalScope = this, endSignalLazy = lazy { mergeLeft(stopEmitter, endSignal) }, ), coroutineScope = scope, coroutineScope = this@coroutineScope, ) buildScope.launchScope { spec.applySpec() Loading @@ -160,6 +171,10 @@ internal class LocalNetwork( } .awaitOrCancel() .joinOrCancel() completionHandle.dispose() } completionHandle = scope.coroutineContext.job.invokeOnCompletion { job.cancel() } job.start() } private suspend fun <T> Deferred<T>.awaitOrCancel(): T = Loading packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt +13 −17 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import com.android.systemui.kairos.util.Maybe.Absent import com.android.systemui.kairos.util.Maybe.Present import com.android.systemui.kairos.util.map import java.util.concurrent.atomic.AtomicReference import kotlin.coroutines.ContinuationInterceptor import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.CoroutineScope Loading @@ -68,12 +69,7 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope buildEvents( constructEvents = { inputNode -> val events = MutableEvents(network, inputNode) events to object : EventProducerScope<T> { override suspend fun emit(value: T) { events.emit(value) } } events to EventProducerScope<T> { value -> events.emit(value) } }, builder = builder, ) Loading @@ -93,18 +89,16 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope getInitialValue, inputNode, ) events to object : CoalescingEventProducerScope<In> { override fun emit(value: In) { events.emit(value) } } events to CoalescingEventProducerScope<In> { value -> events.emit(value) } }, builder = builder, ) override fun <A> asyncScope(block: BuildSpec<A>): Pair<DeferredValue<A>, Job> { val childScope = mutableChildBuildScope() override fun <A> asyncScope( coroutineContext: CoroutineContext, block: BuildSpec<A>, ): Pair<DeferredValue<A>, Job> { val childScope = mutableChildBuildScope(coroutineContext) return DeferredValue(deferAsync { block(childScope) }) to childScope.job } Loading Loading @@ -138,7 +132,9 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope val localNetwork = LocalNetwork(network, childScope, endSignal) val outputNode = Output<A>( context = coroutineContext, interceptor = coroutineContext[ContinuationInterceptor] ?: coroutineScope.coroutineContext[ContinuationInterceptor], onDeath = { subRef.set(Absent) }, onEmit = { output -> if (subRef.get() is Present) { Loading Loading @@ -301,8 +297,8 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope } } private fun mutableChildBuildScope(): BuildScopeImpl { val childScope = coroutineScope.childScope() private fun mutableChildBuildScope(coroutineContext: CoroutineContext): BuildScopeImpl { val childScope = coroutineScope.childScope(coroutineContext) val stopEmitter = lazy { newStopEmitter("mutableChildBuildScope").apply { childScope.invokeOnCancel { emit(Unit) } Loading packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt +2 −2 Original line number Diff line number Diff line Loading @@ -76,8 +76,8 @@ internal class Network(val coroutineScope: CoroutineScope) : NetworkScope { private val inputScheduleChan = Channel<ScheduledAction<*>>() override fun scheduleOutput(output: Output<*>) { val continuationInterceptor = output.context[ContinuationInterceptor] ?: Dispatchers.Unconfined val continuationInterceptor: ContinuationInterceptor = output.interceptor ?: Dispatchers.Unconfined outputsByDispatcher.computeIfAbsent(continuationInterceptor) { ArrayDeque() }.add(output) } Loading packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Output.kt +2 −3 Original line number Diff line number Diff line Loading @@ -16,11 +16,10 @@ package com.android.systemui.kairos.internal import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.ContinuationInterceptor internal class Output<A>( val context: CoroutineContext = EmptyCoroutineContext, val interceptor: ContinuationInterceptor? = null, val onDeath: () -> Unit = {}, val onEmit: EvalScope.(A) -> Unit, ) { Loading Loading
packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/BuildScope.kt +24 −11 Original line number Diff line number Diff line Loading @@ -184,7 +184,10 @@ interface BuildScope : HasNetwork, StateScope { * The return value from [block] can be accessed via the returned [DeferredValue]. */ // TODO: return a DisposableHandle instead of Job? fun <A> asyncScope(block: BuildSpec<A>): Pair<DeferredValue<A>, Job> fun <A> asyncScope( coroutineContext: CoroutineContext = EmptyCoroutineContext, block: BuildSpec<A>, ): Pair<DeferredValue<A>, Job> // TODO: once we have context params, these can all become extensions: Loading Loading @@ -689,8 +692,11 @@ interface BuildScope : HasNetwork, StateScope { * emitting) then [block] will be invoked for the first time with the new value; otherwise, it * will be invoked with the [current][sample] value. */ fun <A> State<A>.observe(block: EffectScope.(A) -> Unit = {}): DisposableHandle = now.map { sample() }.mergeWith(changes) { _, new -> new }.observe { block(it) } fun <A> State<A>.observe( coroutineContext: CoroutineContext = EmptyCoroutineContext, block: EffectScope.(A) -> Unit = {}, ): DisposableHandle = now.map { sample() }.mergeWith(changes) { _, new -> new }.observe(coroutineContext, block) } /** Loading Loading @@ -724,14 +730,14 @@ fun <A> BuildScope.asyncEvent(block: suspend () -> A): Events<A> = * context: CoroutineContext = EmptyCoroutineContext, * block: EffectScope.() -> Unit, * ): Job = * launchScope { now.observe(context) { block() } } * launchScope(context) { now.observe { block() } } * ``` */ @ExperimentalKairosApi fun BuildScope.effect( context: CoroutineContext = EmptyCoroutineContext, block: EffectScope.() -> Unit, ): Job = launchScope { now.observe(context) { block() } } ): Job = launchScope(context) { now.observe { block() } } /** * Launches [block] in a new coroutine, returning a [Job] bound to the coroutine. Loading @@ -746,8 +752,10 @@ fun BuildScope.effect( * ``` */ @ExperimentalKairosApi fun BuildScope.launchEffect(block: suspend KairosCoroutineScope.() -> Unit): Job = asyncEffect(block) fun BuildScope.launchEffect( context: CoroutineContext = EmptyCoroutineContext, block: suspend KairosCoroutineScope.() -> Unit, ): Job = asyncEffect(context, block) /** * Launches [block] in a new coroutine, returning the result as a [Deferred]. Loading @@ -756,7 +764,6 @@ fun BuildScope.launchEffect(block: suspend KairosCoroutineScope.() -> Unit): Job * done because the current [BuildScope] might be deactivated within this transaction, perhaps due * to a -Latest combinator. If this happens, then the coroutine will never actually be started. * * Shorthand for: * ``` * fun <R> BuildScope.asyncEffect(block: suspend KairosScope.() -> R): Deferred<R> = * CompletableDeferred<R>.apply { Loading @@ -766,9 +773,12 @@ fun BuildScope.launchEffect(block: suspend KairosCoroutineScope.() -> Unit): Job * ``` */ @ExperimentalKairosApi fun <R> BuildScope.asyncEffect(block: suspend KairosCoroutineScope.() -> R): Deferred<R> { fun <R> BuildScope.asyncEffect( context: CoroutineContext = EmptyCoroutineContext, block: suspend KairosCoroutineScope.() -> R, ): Deferred<R> { val result = CompletableDeferred<R>() val job = effect { launch { result.complete(block()) } } val job = effect(context) { launch { result.complete(block()) } } val handle = job.invokeOnCompletion { result.cancel() } result.invokeOnCompletion { handle.dispose() Loading @@ -779,7 +789,10 @@ fun <R> BuildScope.asyncEffect(block: suspend KairosCoroutineScope.() -> R): Def /** Like [BuildScope.asyncScope], but ignores the result of [block]. */ @ExperimentalKairosApi fun BuildScope.launchScope(block: BuildSpec<*>): Job = asyncScope(block).second fun BuildScope.launchScope( coroutineContext: CoroutineContext = EmptyCoroutineContext, block: BuildSpec<*>, ): Job = asyncScope(coroutineContext, block).second /** * Creates an instance of an [Events] with elements that are emitted from [builder]. Loading
packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/KairosNetwork.kt +34 −19 Original line number Diff line number Diff line Loading @@ -23,13 +23,18 @@ import com.android.systemui.kairos.internal.util.awaitCancellationAndThen import com.android.systemui.kairos.internal.util.childScope import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.coroutineContext import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Deferred import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.job import kotlinx.coroutines.launch import kotlinx.coroutines.plus /** Marks APIs that are still **experimental** and shouldn't be used in general production code. */ @RequiresOptIn( Loading Loading @@ -125,7 +130,10 @@ fun <T> ConflatedMutableEvents(network: KairosNetwork): CoalescingMutableEvents< * are unregistered and effects are cancelled. */ @ExperimentalKairosApi suspend fun <R> KairosNetwork.activateSpec(spec: BuildSpec<R>, block: suspend (R) -> Unit) { suspend fun <R> KairosNetwork.activateSpec( spec: BuildSpec<R>, block: suspend KairosCoroutineScope.(R) -> Unit, ) { activateSpec { val result = spec.applySpec() launchEffect { block(result) } Loading @@ -140,8 +148,11 @@ internal class LocalNetwork( override suspend fun <R> transact(block: TransactionScope.() -> R): R = network.transaction("KairosNetwork.transact") { block() }.awaitOrCancel() override suspend fun activateSpec(spec: BuildSpec<*>) { override suspend fun activateSpec(spec: BuildSpec<*>): Unit = coroutineScope { val stopEmitter = conflatedMutableEvents<Unit>() lateinit var completionHandle: DisposableHandle val job = launch(start = CoroutineStart.LAZY) { network .transaction("KairosNetwork.activateSpec") { val buildScope = Loading @@ -151,7 +162,7 @@ internal class LocalNetwork( evalScope = this, endSignalLazy = lazy { mergeLeft(stopEmitter, endSignal) }, ), coroutineScope = scope, coroutineScope = this@coroutineScope, ) buildScope.launchScope { spec.applySpec() Loading @@ -160,6 +171,10 @@ internal class LocalNetwork( } .awaitOrCancel() .joinOrCancel() completionHandle.dispose() } completionHandle = scope.coroutineContext.job.invokeOnCompletion { job.cancel() } job.start() } private suspend fun <T> Deferred<T>.awaitOrCancel(): T = Loading
packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt +13 −17 Original line number Diff line number Diff line Loading @@ -43,6 +43,7 @@ import com.android.systemui.kairos.util.Maybe.Absent import com.android.systemui.kairos.util.Maybe.Present import com.android.systemui.kairos.util.map import java.util.concurrent.atomic.AtomicReference import kotlin.coroutines.ContinuationInterceptor import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.CoroutineScope Loading @@ -68,12 +69,7 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope buildEvents( constructEvents = { inputNode -> val events = MutableEvents(network, inputNode) events to object : EventProducerScope<T> { override suspend fun emit(value: T) { events.emit(value) } } events to EventProducerScope<T> { value -> events.emit(value) } }, builder = builder, ) Loading @@ -93,18 +89,16 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope getInitialValue, inputNode, ) events to object : CoalescingEventProducerScope<In> { override fun emit(value: In) { events.emit(value) } } events to CoalescingEventProducerScope<In> { value -> events.emit(value) } }, builder = builder, ) override fun <A> asyncScope(block: BuildSpec<A>): Pair<DeferredValue<A>, Job> { val childScope = mutableChildBuildScope() override fun <A> asyncScope( coroutineContext: CoroutineContext, block: BuildSpec<A>, ): Pair<DeferredValue<A>, Job> { val childScope = mutableChildBuildScope(coroutineContext) return DeferredValue(deferAsync { block(childScope) }) to childScope.job } Loading Loading @@ -138,7 +132,9 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope val localNetwork = LocalNetwork(network, childScope, endSignal) val outputNode = Output<A>( context = coroutineContext, interceptor = coroutineContext[ContinuationInterceptor] ?: coroutineScope.coroutineContext[ContinuationInterceptor], onDeath = { subRef.set(Absent) }, onEmit = { output -> if (subRef.get() is Present) { Loading Loading @@ -301,8 +297,8 @@ internal class BuildScopeImpl(val stateScope: StateScopeImpl, val coroutineScope } } private fun mutableChildBuildScope(): BuildScopeImpl { val childScope = coroutineScope.childScope() private fun mutableChildBuildScope(coroutineContext: CoroutineContext): BuildScopeImpl { val childScope = coroutineScope.childScope(coroutineContext) val stopEmitter = lazy { newStopEmitter("mutableChildBuildScope").apply { childScope.invokeOnCancel { emit(Unit) } Loading
packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Network.kt +2 −2 Original line number Diff line number Diff line Loading @@ -76,8 +76,8 @@ internal class Network(val coroutineScope: CoroutineScope) : NetworkScope { private val inputScheduleChan = Channel<ScheduledAction<*>>() override fun scheduleOutput(output: Output<*>) { val continuationInterceptor = output.context[ContinuationInterceptor] ?: Dispatchers.Unconfined val continuationInterceptor: ContinuationInterceptor = output.interceptor ?: Dispatchers.Unconfined outputsByDispatcher.computeIfAbsent(continuationInterceptor) { ArrayDeque() }.add(output) } Loading
packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/Output.kt +2 −3 Original line number Diff line number Diff line Loading @@ -16,11 +16,10 @@ package com.android.systemui.kairos.internal import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.ContinuationInterceptor internal class Output<A>( val context: CoroutineContext = EmptyCoroutineContext, val interceptor: ContinuationInterceptor? = null, val onDeath: () -> Unit = {}, val onEmit: EvalScope.(A) -> Unit, ) { Loading