Loading packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/BuildScope.kt +3 −4 Original line number Diff line number Diff line Loading @@ -854,11 +854,10 @@ internal fun BuildScope.effect( * Generally, you should prefer [effect] over this method. * * ``` * fun BuildScope.effectImmediate( * context: CoroutineContext = EmptyCoroutineContext, * block: EffectScope.() -> Unit, * fun BuildScope.effectSync( * block: TransactionEffectScope.() -> Unit, * ): Job = * launchScope(context) { now.observeImmediate { block() } } * launchScope { now.observeSync { block() } } * ``` * * @see effect Loading packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/KairosNetwork.kt +5 −5 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ import com.android.systemui.kairos.internal.Network import com.android.systemui.kairos.internal.NoScope import com.android.systemui.kairos.internal.StateScopeImpl import com.android.systemui.kairos.internal.util.childScope import com.android.systemui.kairos.internal.util.invokeOnCancel import com.android.systemui.kairos.util.FullNameTag import com.android.systemui.kairos.util.NameData import com.android.systemui.kairos.util.NameTag Loading Loading @@ -180,9 +179,10 @@ internal class LocalNetwork( override suspend fun activateSpec(name: NameTag?, spec: BuildSpec<*>): Unit = coroutineScope { val nameData = name.toNameData("KairosNetwork.activateSpec") lateinit var completionHandle: DisposableHandle val childEndSignal = conflatedMutableEvents<Unit>(nameData.mapName { "$it-specEndSignal" }).apply { invokeOnCancel { emit(Unit) } val childEndSignal = conflatedMutableEvents<Unit>(nameData.mapName { "$it-specEndSignal" }) coroutineContext.job.invokeOnCompletion { completionHandle.dispose() childEndSignal.emit(Unit) } val job = launch(start = CoroutineStart.LAZY) { Loading packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/StateScope.kt +4 −4 Original line number Diff line number Diff line Loading @@ -1370,9 +1370,9 @@ internal fun <A> StateScope.nextOnly(nameData: NameData, events: Events<A>): Eve events } else { EventsLoop<A>().apply { val shutoff = mapCheap(nameData + "shutoff") { emptyEvents } val state = holdState(nameData + "state", shutoff, events) loopback = state.switchEvents(nameData) val shutOff = mapCheap(nameData + "shutOff") { emptyEvents } val switchedIn = holdState(nameData + "switchedIn", shutOff, events) loopback = switchedIn.switchEvents(nameData) } } Loading @@ -1381,7 +1381,7 @@ internal fun <A> StateScope.skipNext(nameData: NameData, events: Events<A>): Eve events } else { val turnOn = nextOnly(nameData + "onlyOne", events).mapCheap(nameData + "turnOn") { events } holdState(nameData + "state", turnOn, emptyEvents).switchEvents(nameData) holdState(nameData + "switchedIn", turnOn, emptyEvents).switchEvents(nameData) } internal fun <A> StateScope.takeUntil( Loading packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt +38 −34 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import com.android.systemui.kairos.internal.util.childScope import com.android.systemui.kairos.internal.util.invokeOnCancel import com.android.systemui.kairos.internal.util.launchImmediate import com.android.systemui.kairos.launchEffect import com.android.systemui.kairos.mergeLeft import com.android.systemui.kairos.skipNext import com.android.systemui.kairos.takeUntil import com.android.systemui.kairos.util.Maybe Loading Loading @@ -241,9 +242,7 @@ internal class BuildScopeImpl( ): DisposableHandle { val subRef = AtomicReference<Maybe<Output<A>>?>(null) val childScope: CoroutineScope = coroutineScope.childScope(context) var cancelHandle: DisposableHandle? = null val handle = DisposableHandle { cancelHandle?.dispose() subRef.getAndSet(Absent)?.let { output -> if (output is Present) { @Suppress("DeferredResultUnused") Loading @@ -253,8 +252,6 @@ internal class BuildScopeImpl( } } } // When our scope is cancelled, deactivate this observer. cancelHandle = childScope.coroutineContext.job.invokeOnCompletion { handle.dispose() } val effectScope: EffectScope = effectScope(childScope, nameData + "effectScope") val outputNode = Output<A>( Loading @@ -269,7 +266,11 @@ internal class BuildScopeImpl( // Defer, in case any EventsLoops / StateLoops still need to be set deferAction { // Check for immediate cancellation if (subRef.get() != null) return@deferAction if (subRef.get() != null) { childScope.cancel() return@deferAction } // Stop observing when this scope dies truncateToScope(this@observeInternal, nameData + "truncateToScope") .init .connect(evalScope = stateScope.evalScope) Loading @@ -277,7 +278,7 @@ internal class BuildScopeImpl( ?.let { (conn, needsEval) -> outputNode.upstream = conn if (!subRef.compareAndSet(null, Maybe.present(outputNode))) { // Job's already been cancelled, schedule deactivation // Handle's already been disposed, schedule deactivation scheduleDeactivation(outputNode) } else if (needsEval) { outputNode.schedule(0, evalScope = stateScope.evalScope) Loading Loading @@ -369,10 +370,10 @@ internal class BuildScopeImpl( }, ) emitterAndScope = constructEvents(inputNode) return truncateToScope( takeUntil(nameData + "takeUntilStopped", emitterAndScope.first, stopEmitter), nameData + "scopeLifetimeBound", ) // Deactivate once scope dies, or once [builder] completes. val deactivateSignal: Events<Any> = mergeLeft(nameData + "deactivateSignal", deathSignal, stopEmitter) return takeUntil(nameData + "takeUntilStopped", emitterAndScope.first, deactivateSignal) } private fun newStopEmitter(nameData: NameData): CoalescingMutableEvents<Unit, Unit> = Loading @@ -385,39 +386,42 @@ internal class BuildScopeImpl( fun childBuildScope(newEnd: Events<Any>, nameData: NameData): BuildScopeImpl { val newCoroutineScope: CoroutineScope = coroutineScope.childScope() return BuildScopeImpl( nameData, epoch, stateScope = stateScope.childStateScope(newEnd, nameData), coroutineScope = newCoroutineScope, ) .apply { // Ensure that once this transaction is done, the new child scope enters the // completing state (kept alive so long as there are child jobs). scheduleOutput( OneShot(nameData + "completeJob") { // TODO: don't like this cast (newCoroutineScope.coroutineContext.job as CompletableJob).complete() } ) deathSignal.observeSync(nameData + "observeLifetime") { newCoroutineScope.cancel() } val newChildBuildScope = newChildBuildScope(newCoroutineScope, newEnd, nameData) // When the end signal emits, cancel all running coroutines in the new scope newChildBuildScope.deathSignal.observeSync(nameData + "observeLifetime") { newCoroutineScope.cancel() } return newChildBuildScope } private fun mutableChildBuildScope( childNameData: NameData, coroutineContext: CoroutineContext, ): BuildScopeImpl { val childScope = coroutineScope.childScope(coroutineContext) val stopEmitter = newStopEmitter(childNameData + "stopEmitter").apply { childScope.invokeOnCancel { emit(Unit) } val stopEmitter = newStopEmitter(childNameData + "stopEmitter") val newCoroutineScope = coroutineScope.childScope(coroutineContext) // If the job is cancelled, emit the stop signal newCoroutineScope.coroutineContext.job.invokeOnCompletion { stopEmitter.emit(Unit) } return newChildBuildScope(newCoroutineScope, stopEmitter, nameData) } private fun newChildBuildScope( newCoroutineScope: CoroutineScope, newEnd: Events<Any>, nameData: NameData, ): BuildScopeImpl { // Ensure that once this transaction is done, the new child scope enters the completing // state (kept alive so long as there are child jobs). scheduleOutput( OneShot(nameData + "completeJob") { (newCoroutineScope.coroutineContext.job as CompletableJob).complete() } ) return BuildScopeImpl( childNameData, nameData, epoch, stateScope = stateScope.childStateScope(stopEmitter, childNameData), coroutineScope = childScope, stateScope = stateScope.childStateScope(newEnd, nameData), coroutineScope = newCoroutineScope, ) } } Loading packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/EvalScopeImpl.kt +8 −16 Original line number Diff line number Diff line Loading @@ -54,32 +54,24 @@ internal class EvalScopeImpl(networkScope: NetworkScope, deferScope: DeferScope) override val now: Events<Unit> by lazy { var result by EventsLoop<Unit>() val switchOff = result.mapCheap { emptyEvents } val nameTag = nameTag { "now(epoc=$epoch)" }.toNameData("now") val nameTag = nameTag { "now(epoch=$epoch)" }.toNameData("TransactionScope.now") result = StateInit( constInit( nameTag, activatedStateSource( nameTag, nameTag + "switchedIn", this, { switchOff.init.connect(evalScope = this) }, lazyOf( EventsInit( constInit( nameTag, EventsImplCheap { ActivationResult( connection = NodeConnection(AlwaysNode, AlwaysNode), needsEval = true, ) }, ) ) ), lazyOf(EventsInit(constInit(nameTag + "always", alwaysImpl))), ), ) ) .switchEvents(nameTag + "onlyOnce") .switchEvents(nameTag) result } } private val alwaysImpl = EventsImplCheap { ActivationResult(connection = NodeConnection(AlwaysNode, AlwaysNode), needsEval = true) } Loading
packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/BuildScope.kt +3 −4 Original line number Diff line number Diff line Loading @@ -854,11 +854,10 @@ internal fun BuildScope.effect( * Generally, you should prefer [effect] over this method. * * ``` * fun BuildScope.effectImmediate( * context: CoroutineContext = EmptyCoroutineContext, * block: EffectScope.() -> Unit, * fun BuildScope.effectSync( * block: TransactionEffectScope.() -> Unit, * ): Job = * launchScope(context) { now.observeImmediate { block() } } * launchScope { now.observeSync { block() } } * ``` * * @see effect Loading
packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/KairosNetwork.kt +5 −5 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ import com.android.systemui.kairos.internal.Network import com.android.systemui.kairos.internal.NoScope import com.android.systemui.kairos.internal.StateScopeImpl import com.android.systemui.kairos.internal.util.childScope import com.android.systemui.kairos.internal.util.invokeOnCancel import com.android.systemui.kairos.util.FullNameTag import com.android.systemui.kairos.util.NameData import com.android.systemui.kairos.util.NameTag Loading Loading @@ -180,9 +179,10 @@ internal class LocalNetwork( override suspend fun activateSpec(name: NameTag?, spec: BuildSpec<*>): Unit = coroutineScope { val nameData = name.toNameData("KairosNetwork.activateSpec") lateinit var completionHandle: DisposableHandle val childEndSignal = conflatedMutableEvents<Unit>(nameData.mapName { "$it-specEndSignal" }).apply { invokeOnCancel { emit(Unit) } val childEndSignal = conflatedMutableEvents<Unit>(nameData.mapName { "$it-specEndSignal" }) coroutineContext.job.invokeOnCompletion { completionHandle.dispose() childEndSignal.emit(Unit) } val job = launch(start = CoroutineStart.LAZY) { Loading
packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/StateScope.kt +4 −4 Original line number Diff line number Diff line Loading @@ -1370,9 +1370,9 @@ internal fun <A> StateScope.nextOnly(nameData: NameData, events: Events<A>): Eve events } else { EventsLoop<A>().apply { val shutoff = mapCheap(nameData + "shutoff") { emptyEvents } val state = holdState(nameData + "state", shutoff, events) loopback = state.switchEvents(nameData) val shutOff = mapCheap(nameData + "shutOff") { emptyEvents } val switchedIn = holdState(nameData + "switchedIn", shutOff, events) loopback = switchedIn.switchEvents(nameData) } } Loading @@ -1381,7 +1381,7 @@ internal fun <A> StateScope.skipNext(nameData: NameData, events: Events<A>): Eve events } else { val turnOn = nextOnly(nameData + "onlyOne", events).mapCheap(nameData + "turnOn") { events } holdState(nameData + "state", turnOn, emptyEvents).switchEvents(nameData) holdState(nameData + "switchedIn", turnOn, emptyEvents).switchEvents(nameData) } internal fun <A> StateScope.takeUntil( Loading
packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/BuildScopeImpl.kt +38 −34 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import com.android.systemui.kairos.internal.util.childScope import com.android.systemui.kairos.internal.util.invokeOnCancel import com.android.systemui.kairos.internal.util.launchImmediate import com.android.systemui.kairos.launchEffect import com.android.systemui.kairos.mergeLeft import com.android.systemui.kairos.skipNext import com.android.systemui.kairos.takeUntil import com.android.systemui.kairos.util.Maybe Loading Loading @@ -241,9 +242,7 @@ internal class BuildScopeImpl( ): DisposableHandle { val subRef = AtomicReference<Maybe<Output<A>>?>(null) val childScope: CoroutineScope = coroutineScope.childScope(context) var cancelHandle: DisposableHandle? = null val handle = DisposableHandle { cancelHandle?.dispose() subRef.getAndSet(Absent)?.let { output -> if (output is Present) { @Suppress("DeferredResultUnused") Loading @@ -253,8 +252,6 @@ internal class BuildScopeImpl( } } } // When our scope is cancelled, deactivate this observer. cancelHandle = childScope.coroutineContext.job.invokeOnCompletion { handle.dispose() } val effectScope: EffectScope = effectScope(childScope, nameData + "effectScope") val outputNode = Output<A>( Loading @@ -269,7 +266,11 @@ internal class BuildScopeImpl( // Defer, in case any EventsLoops / StateLoops still need to be set deferAction { // Check for immediate cancellation if (subRef.get() != null) return@deferAction if (subRef.get() != null) { childScope.cancel() return@deferAction } // Stop observing when this scope dies truncateToScope(this@observeInternal, nameData + "truncateToScope") .init .connect(evalScope = stateScope.evalScope) Loading @@ -277,7 +278,7 @@ internal class BuildScopeImpl( ?.let { (conn, needsEval) -> outputNode.upstream = conn if (!subRef.compareAndSet(null, Maybe.present(outputNode))) { // Job's already been cancelled, schedule deactivation // Handle's already been disposed, schedule deactivation scheduleDeactivation(outputNode) } else if (needsEval) { outputNode.schedule(0, evalScope = stateScope.evalScope) Loading Loading @@ -369,10 +370,10 @@ internal class BuildScopeImpl( }, ) emitterAndScope = constructEvents(inputNode) return truncateToScope( takeUntil(nameData + "takeUntilStopped", emitterAndScope.first, stopEmitter), nameData + "scopeLifetimeBound", ) // Deactivate once scope dies, or once [builder] completes. val deactivateSignal: Events<Any> = mergeLeft(nameData + "deactivateSignal", deathSignal, stopEmitter) return takeUntil(nameData + "takeUntilStopped", emitterAndScope.first, deactivateSignal) } private fun newStopEmitter(nameData: NameData): CoalescingMutableEvents<Unit, Unit> = Loading @@ -385,39 +386,42 @@ internal class BuildScopeImpl( fun childBuildScope(newEnd: Events<Any>, nameData: NameData): BuildScopeImpl { val newCoroutineScope: CoroutineScope = coroutineScope.childScope() return BuildScopeImpl( nameData, epoch, stateScope = stateScope.childStateScope(newEnd, nameData), coroutineScope = newCoroutineScope, ) .apply { // Ensure that once this transaction is done, the new child scope enters the // completing state (kept alive so long as there are child jobs). scheduleOutput( OneShot(nameData + "completeJob") { // TODO: don't like this cast (newCoroutineScope.coroutineContext.job as CompletableJob).complete() } ) deathSignal.observeSync(nameData + "observeLifetime") { newCoroutineScope.cancel() } val newChildBuildScope = newChildBuildScope(newCoroutineScope, newEnd, nameData) // When the end signal emits, cancel all running coroutines in the new scope newChildBuildScope.deathSignal.observeSync(nameData + "observeLifetime") { newCoroutineScope.cancel() } return newChildBuildScope } private fun mutableChildBuildScope( childNameData: NameData, coroutineContext: CoroutineContext, ): BuildScopeImpl { val childScope = coroutineScope.childScope(coroutineContext) val stopEmitter = newStopEmitter(childNameData + "stopEmitter").apply { childScope.invokeOnCancel { emit(Unit) } val stopEmitter = newStopEmitter(childNameData + "stopEmitter") val newCoroutineScope = coroutineScope.childScope(coroutineContext) // If the job is cancelled, emit the stop signal newCoroutineScope.coroutineContext.job.invokeOnCompletion { stopEmitter.emit(Unit) } return newChildBuildScope(newCoroutineScope, stopEmitter, nameData) } private fun newChildBuildScope( newCoroutineScope: CoroutineScope, newEnd: Events<Any>, nameData: NameData, ): BuildScopeImpl { // Ensure that once this transaction is done, the new child scope enters the completing // state (kept alive so long as there are child jobs). scheduleOutput( OneShot(nameData + "completeJob") { (newCoroutineScope.coroutineContext.job as CompletableJob).complete() } ) return BuildScopeImpl( childNameData, nameData, epoch, stateScope = stateScope.childStateScope(stopEmitter, childNameData), coroutineScope = childScope, stateScope = stateScope.childStateScope(newEnd, nameData), coroutineScope = newCoroutineScope, ) } } Loading
packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/internal/EvalScopeImpl.kt +8 −16 Original line number Diff line number Diff line Loading @@ -54,32 +54,24 @@ internal class EvalScopeImpl(networkScope: NetworkScope, deferScope: DeferScope) override val now: Events<Unit> by lazy { var result by EventsLoop<Unit>() val switchOff = result.mapCheap { emptyEvents } val nameTag = nameTag { "now(epoc=$epoch)" }.toNameData("now") val nameTag = nameTag { "now(epoch=$epoch)" }.toNameData("TransactionScope.now") result = StateInit( constInit( nameTag, activatedStateSource( nameTag, nameTag + "switchedIn", this, { switchOff.init.connect(evalScope = this) }, lazyOf( EventsInit( constInit( nameTag, EventsImplCheap { ActivationResult( connection = NodeConnection(AlwaysNode, AlwaysNode), needsEval = true, ) }, ) ) ), lazyOf(EventsInit(constInit(nameTag + "always", alwaysImpl))), ), ) ) .switchEvents(nameTag + "onlyOnce") .switchEvents(nameTag) result } } private val alwaysImpl = EventsImplCheap { ActivationResult(connection = NodeConnection(AlwaysNode, AlwaysNode), needsEval = true) }