Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6e8854b2 authored by Steve Elliott's avatar Steve Elliott Committed by Android (Google) Code Review
Browse files

Merge "[kairos] automatically determine coroutinecontext" into main

parents b6d8dcd2 62fbd662
Loading
Loading
Loading
Loading
+24 −11
Original line number Diff line number Diff line
@@ -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:

@@ -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)
}

/**
@@ -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.
@@ -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].
@@ -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 {
@@ -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()
@@ -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].
+34 −19
Original line number Diff line number Diff line
@@ -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(
@@ -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) }
@@ -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 =
@@ -151,7 +162,7 @@ internal class LocalNetwork(
                                        evalScope = this,
                                        endSignalLazy = lazy { mergeLeft(stopEmitter, endSignal) },
                                    ),
                        coroutineScope = scope,
                                coroutineScope = this@coroutineScope,
                            )
                        buildScope.launchScope {
                            spec.applySpec()
@@ -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 =
+13 −17
Original line number Diff line number Diff line
@@ -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
@@ -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,
        )
@@ -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
    }

@@ -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) {
@@ -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) }
+2 −2
Original line number Diff line number Diff line
@@ -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)
    }

+2 −3
Original line number Diff line number Diff line
@@ -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,
) {