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

Commit eb057d3e authored by Steve Elliott's avatar Steve Elliott
Browse files

[kairos] introduce Incremental type

This type is a specialization of State<Map<K, V>> that models
"incremental" updates to the Map, via a new `MapPatch<K, V>` alias.

There are situations where it is useful to react to only what has
changed between two states; normally this is accomplished by diffing the
two states, but Incremental allows you to avoid performing the diff and
instead operating on the already-known "patch".

Flag: EXEMPT unused
Test: atest kairos-tests
Change-Id: I2829ffc149fb2e1888a11003908068e0ccc8fb35
parent cdff97f1
Loading
Loading
Loading
Loading
+16 −9
Original line number Diff line number Diff line
@@ -444,6 +444,13 @@ interface BuildScope : StateScope {
    ): Pair<Events<Map<K, Maybe<A>>>, DeferredValue<Map<K, B>>> =
        applyLatestSpecForKey(deferredOf(initialSpecs), numKeys)

    fun <K, V> Incremental<K, BuildSpec<V>>.applyLatestSpecForKey(
        numKeys: Int? = null
    ): Incremental<K, V> {
        val (events, initial) = updates.applyLatestSpecForKey(sampleDeferred(), numKeys)
        return events.foldStateMapIncrementally(initial)
    }

    /**
     * Returns an [Events] containing the results of applying each [BuildSpec] emitted from the
     * original [Events].
@@ -455,10 +462,10 @@ interface BuildScope : StateScope {
     * If the [Maybe] contained within the value for an associated key is [none], then the
     * previously-active [BuildSpec] will be undone with no replacement.
     */
    fun <K, A> Events<Map<K, Maybe<BuildSpec<A>>>>.applyLatestSpecForKey(
    fun <K, V> Events<Map<K, Maybe<BuildSpec<V>>>>.applyLatestSpecForKey(
        numKeys: Int? = null
    ): Events<Map<K, Maybe<A>>> =
        applyLatestSpecForKey<K, A, Nothing>(deferredOf(emptyMap()), numKeys).first
    ): Events<Map<K, Maybe<V>>> =
        applyLatestSpecForKey<K, V, Nothing>(deferredOf(emptyMap()), numKeys).first

    /**
     * Returns a [State] containing the latest results of applying each [BuildSpec] emitted from the
@@ -471,10 +478,10 @@ interface BuildScope : StateScope {
     * If the [Maybe] contained within the value for an associated key is [none], then the
     * previously-active [BuildSpec] will be undone with no replacement.
     */
    fun <K, A> Events<Map<K, Maybe<BuildSpec<A>>>>.holdLatestSpecForKey(
        initialSpecs: DeferredValue<Map<K, BuildSpec<A>>>,
    fun <K, V> Events<Map<K, Maybe<BuildSpec<V>>>>.holdLatestSpecForKey(
        initialSpecs: DeferredValue<Map<K, BuildSpec<V>>>,
        numKeys: Int? = null,
    ): State<Map<K, A>> {
    ): Incremental<K, V> {
        val (changes, initialValues) = applyLatestSpecForKey(initialSpecs, numKeys)
        return changes.foldStateMapIncrementally(initialValues)
    }
@@ -490,10 +497,10 @@ interface BuildScope : StateScope {
     * If the [Maybe] contained within the value for an associated key is [none], then the
     * previously-active [BuildSpec] will be undone with no replacement.
     */
    fun <K, A> Events<Map<K, Maybe<BuildSpec<A>>>>.holdLatestSpecForKey(
        initialSpecs: Map<K, BuildSpec<A>> = emptyMap(),
    fun <K, V> Events<Map<K, Maybe<BuildSpec<V>>>>.holdLatestSpecForKey(
        initialSpecs: Map<K, BuildSpec<V>> = emptyMap(),
        numKeys: Int? = null,
    ): State<Map<K, A>> = holdLatestSpecForKey(deferredOf(initialSpecs), numKeys)
    ): Incremental<K, V> = holdLatestSpecForKey(deferredOf(initialSpecs), numKeys)

    /**
     * Returns an [Events] containing the results of applying [transform] to each value of the
+1 −1
Original line number Diff line number Diff line
@@ -59,7 +59,7 @@ fun <A, B, C> Events<A>.samplePromptly(
    state: State<B>,
    transform: TransactionScope.(A, B) -> C,
): Events<C> =
    sample(state) { a, b -> These.thiz<Pair<A, B>, B>(a to b) }
    sample(state) { a, b -> These.thiz(a to b) }
        .mergeWith(state.changes.map { These.that(it) }) { thiz, that ->
            These.both((thiz as These.This).thiz, (that as These.That).that)
        }
+7 −9
Original line number Diff line number Diff line
@@ -31,7 +31,6 @@ import com.android.systemui.kairos.internal.demuxMap
import com.android.systemui.kairos.internal.filterImpl
import com.android.systemui.kairos.internal.filterJustImpl
import com.android.systemui.kairos.internal.init
import com.android.systemui.kairos.internal.map
import com.android.systemui.kairos.internal.mapImpl
import com.android.systemui.kairos.internal.mergeNodes
import com.android.systemui.kairos.internal.mergeNodesLeft
@@ -44,7 +43,6 @@ import com.android.systemui.kairos.util.Left
import com.android.systemui.kairos.util.Maybe
import com.android.systemui.kairos.util.Right
import com.android.systemui.kairos.util.just
import com.android.systemui.kairos.util.map
import com.android.systemui.kairos.util.toMaybe
import java.util.concurrent.atomic.AtomicReference
import kotlin.reflect.KProperty
@@ -58,11 +56,11 @@ import kotlinx.coroutines.coroutineScope
sealed class Events<out A> {
    companion object {
        /** An [Events] with no values. */
        val empty: Events<Nothing> = EmptyFlow
        val empty: Events<Nothing> = EmptyEvents
    }
}

/** AN [Events] with no values. */
/** An [Events] with no values. */
@ExperimentalKairosApi val emptyEvents: Events<Nothing> = Events.empty

/**
@@ -82,7 +80,7 @@ class EventsLoop<A> : Events<A>() {
    var loopback: Events<A>? = null
        set(value) {
            value?.let {
                check(!deferred.isInitialized()) { "TFlowLoop.loopback has already been set." }
                check(!deferred.isInitialized()) { "EventsLoop.loopback has already been set." }
                deferred.setValue(value)
                field = value
            }
@@ -441,7 +439,7 @@ class GroupedEvents<in K, out A> internal constructor(internal val impl: DemuxIm
@ExperimentalKairosApi
fun <A> State<Events<A>>.switchEvents(name: String? = null): Events<A> {
    val patches =
        mapImpl({ init.connect(this).changes }) { newFlow, _ -> newFlow.init.connect(this) }
        mapImpl({ init.connect(this).changes }) { newEvents, _ -> newEvents.init.connect(this) }
    return EventsInit(
        constInit(
            name = null,
@@ -467,7 +465,7 @@ fun <A> State<Events<A>>.switchEvents(name: String? = null): Events<A> {
@ExperimentalKairosApi
fun <A> State<Events<A>>.switchEventsPromptly(): Events<A> {
    val patches =
        mapImpl({ init.connect(this).changes }) { newFlow, _ -> newFlow.init.connect(this) }
        mapImpl({ init.connect(this).changes }) { newEvents, _ -> newEvents.init.connect(this) }
    return EventsInit(
        constInit(
            name = null,
@@ -557,7 +555,7 @@ internal constructor(internal val network: Network, internal val impl: InputNode
    }
}

private data object EmptyFlow : Events<Nothing>()
private data object EmptyEvents : Events<Nothing>()

internal class EventsInit<out A>(val init: Init<EventsImpl<A>>) : Events<A>() {
    override fun toString(): String = "${this::class.simpleName}@$hashString"
@@ -566,7 +564,7 @@ internal class EventsInit<out A>(val init: Init<EventsImpl<A>>) : Events<A>() {
internal val <A> Events<A>.init: Init<EventsImpl<A>>
    get() =
        when (this) {
            is EmptyFlow -> constInit("EmptyFlow", neverImpl)
            is EmptyEvents -> constInit("EmptyEvents", neverImpl)
            is EventsInit -> init
            is EventsLoop -> init
            is CoalescingMutableEvents<*, A> -> constInit(name, impl.activated())
+297 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.kairos

import com.android.systemui.kairos.internal.CompletableLazy
import com.android.systemui.kairos.internal.IncrementalImpl
import com.android.systemui.kairos.internal.Init
import com.android.systemui.kairos.internal.InitScope
import com.android.systemui.kairos.internal.NoScope
import com.android.systemui.kairos.internal.awaitValues
import com.android.systemui.kairos.internal.constIncremental
import com.android.systemui.kairos.internal.constInit
import com.android.systemui.kairos.internal.init
import com.android.systemui.kairos.internal.mapImpl
import com.android.systemui.kairos.internal.mapValuesImpl
import com.android.systemui.kairos.internal.store.ConcurrentHashMapK
import com.android.systemui.kairos.internal.switchDeferredImpl
import com.android.systemui.kairos.internal.switchPromptImpl
import com.android.systemui.kairos.internal.util.hashString
import com.android.systemui.kairos.util.MapPatch
import com.android.systemui.kairos.util.map
import com.android.systemui.kairos.util.mapPatchFromFullDiff
import kotlin.reflect.KProperty

/** A [State] tracking a [Map] that receives incremental updates. */
sealed class Incremental<K, out V> : State<Map<K, V>>() {
    abstract override val init: Init<IncrementalImpl<K, V>>
}

/** An [Incremental] that never changes. */
@ExperimentalKairosApi
fun <K, V> incrementalOf(value: Map<K, V>): Incremental<K, V> {
    val operatorName = "stateOf"
    val name = "$operatorName($value)"
    return IncrementalInit(constInit(name, constIncremental(name, operatorName, value)))
}

/**
 * Returns an [Incremental] that acts as a deferred-reference to the [Incremental] produced by this
 * [Lazy].
 *
 * When the returned [Incremental] is accessed by the Kairos network, the [Lazy]'s
 * [value][Lazy.value] will be queried and used.
 *
 * Useful for recursive definitions.
 */
@ExperimentalKairosApi
fun <K, V> Lazy<Incremental<K, V>>.defer(): Incremental<K, V> = deferInline { value }

/**
 * Returns an [Incremental] that acts as a deferred-reference to the [Incremental] produced by this
 * [DeferredValue].
 *
 * When the returned [Incremental] is accessed by the Kairos network, the [DeferredValue] will be
 * queried and used.
 *
 * Useful for recursive definitions.
 */
@ExperimentalKairosApi
fun <K, V> DeferredValue<Incremental<K, V>>.defer(): Incremental<K, V> = deferInline {
    unwrapped.value
}

/**
 * Returns an [Incremental] that acts as a deferred-reference to the [Incremental] produced by
 * [block].
 *
 * When the returned [Incremental] is accessed by the Kairos network, [block] will be invoked and
 * the returned [Incremental] will be used.
 *
 * Useful for recursive definitions.
 */
@ExperimentalKairosApi
fun <K, V> deferredIncremental(block: KairosScope.() -> Incremental<K, V>): Incremental<K, V> =
    deferInline {
        NoScope.block()
    }

/**
 * An [Events] that emits every time this [Incremental] changes, containing the subset of the map
 * that has changed.
 *
 * @see MapPatch
 */
val <K, V> Incremental<K, V>.updates: Events<MapPatch<K, V>>
    get() = EventsInit(init("patches") { init.connect(this).patches })

internal class IncrementalInit<K, V>(override val init: Init<IncrementalImpl<K, V>>) :
    Incremental<K, V>()

/**
 * Returns an [Incremental] that tracks the entries of the original incremental, but values replaced
 * with those obtained by applying [transform] to each original entry.
 */
fun <K, V, U> Incremental<K, V>.mapValues(
    transform: KairosScope.(Map.Entry<K, V>) -> U
): Incremental<K, U> {
    val operatorName = "mapValues"
    val name = operatorName
    return IncrementalInit(
        init(name) {
            mapValuesImpl({ init.connect(this) }, name, operatorName) { NoScope.transform(it) }
        }
    )
}

/**
 * Returns an [Events] that emits from a merged, incrementally-accumulated collection of [Events]
 * emitted from this, following the same "patch" rules as outlined in
 * [StateScope.foldStateMapIncrementally].
 *
 * Conceptually this is equivalent to:
 * ```kotlin
 *   fun <K, V> State<Map<K, V>>.mergeEventsIncrementally(): Events<Map<K, V>> =
 *     map { it.merge() }.switchEvents()
 * ```
 *
 * While the behavior is equivalent to the conceptual definition above, the implementation is
 * significantly more efficient.
 *
 * @see merge
 */
fun <K, V> Incremental<K, Events<V>>.mergeEventsIncrementally(): Events<Map<K, V>> {
    val operatorName = "mergeEventsIncrementally"
    val name = operatorName
    val patches =
        mapImpl({ init.connect(this).patches }) { patch, _ ->
            patch.mapValues { (_, m) -> m.map { events -> events.init.connect(this) } }.asIterable()
        }
    return EventsInit(
        constInit(
            name,
            switchDeferredImpl(
                    name = name,
                    getStorage = {
                        init
                            .connect(this)
                            .getCurrentWithEpoch(this)
                            .first
                            .mapValues { (_, events) -> events.init.connect(this) }
                            .asIterable()
                    },
                    getPatches = { patches },
                    storeFactory = ConcurrentHashMapK.Factory(),
                )
                .awaitValues(),
        )
    )
}

/**
 * Returns an [Events] that emits from a merged, incrementally-accumulated collection of [Events]
 * emitted from this, following the same "patch" rules as outlined in
 * [StateScope.foldStateMapIncrementally].
 *
 * Conceptually this is equivalent to:
 * ```kotlin
 *   fun <K, V> State<Map<K, V>>.mergeEventsIncrementallyPromptly(): Events<Map<K, V>> =
 *     map { it.merge() }.switchEventsPromptly()
 * ```
 *
 * While the behavior is equivalent to the conceptual definition above, the implementation is
 * significantly more efficient.
 *
 * @see merge
 */
fun <K, V> Incremental<K, Events<V>>.mergeEventsIncrementallyPromptly(): Events<Map<K, V>> {
    val operatorName = "mergeEventsIncrementally"
    val name = operatorName
    val patches =
        mapImpl({ init.connect(this).patches }) { patch, _ ->
            patch.mapValues { (_, m) -> m.map { events -> events.init.connect(this) } }.asIterable()
        }
    return EventsInit(
        constInit(
            name,
            switchPromptImpl(
                    name = name,
                    getStorage = {
                        init
                            .connect(this)
                            .getCurrentWithEpoch(this)
                            .first
                            .mapValues { (_, events) -> events.init.connect(this) }
                            .asIterable()
                    },
                    getPatches = { patches },
                    storeFactory = ConcurrentHashMapK.Factory(),
                )
                .awaitValues(),
        )
    )
}

/** A forward-reference to an [Incremental], allowing for recursive definitions. */
@ExperimentalKairosApi
class IncrementalLoop<K, V>(private val name: String? = null) : Incremental<K, V>() {

    private val deferred = CompletableLazy<Incremental<K, V>>(name = name)

    override val init: Init<IncrementalImpl<K, V>> =
        init(name) { deferred.value.init.connect(evalScope = this) }

    /** The [Incremental] this [IncrementalLoop] will forward to. */
    var loopback: Incremental<K, V>? = null
        set(value) {
            value?.let {
                check(!deferred.isInitialized()) {
                    "IncrementalLoop($name).loopback has already been set."
                }
                deferred.setValue(value)
                field = value
            }
        }

    operator fun getValue(thisRef: Any?, property: KProperty<*>): Incremental<K, V> = this

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Incremental<K, V>) {
        loopback = value
    }

    override fun toString(): String = "${this::class.simpleName}($name)@$hashString"
}

/**
 * Returns an [Incremental] whose [updates] are calculated by diffing the given [State]'s
 * [transitions].
 */
fun <K, V> State<Map<K, V>>.asIncremental(): Incremental<K, V> {
    if (this is Incremental<K, V>) return this

    val hashState = map { if (it is HashMap) it else HashMap(it) }

    val patches =
        transitions.mapNotNull { (old, new) ->
            mapPatchFromFullDiff(old, new).takeIf { it.isNotEmpty() }
        }

    return IncrementalInit(
        init("asIncremental") {
            val upstream = hashState.init.connect(this)
            IncrementalImpl(
                upstream.name,
                upstream.operatorName,
                upstream.changes,
                patches.init.connect(this),
                upstream.store,
            )
        }
    )
}

/** Returns an [Incremental] that acts like the current value of the given [State]. */
fun <K, V> State<Incremental<K, V>>.switchIncremental(): Incremental<K, V> {
    val stateChangePatches =
        transitions.mapNotNull { (old, new) ->
            mapPatchFromFullDiff(old.sample(), new.sample()).takeIf { it.isNotEmpty() }
        }
    val innerChanges =
        map { inner ->
                merge(stateChangePatches, inner.updates) { switchPatch, upcomingPatch ->
                    switchPatch + upcomingPatch
                }
            }
            .switchEventsPromptly()
    val flattened = flatten()
    return IncrementalInit(
        init("switchIncremental") {
            val upstream = flattened.init.connect(this)
            IncrementalImpl(
                "switchIncremental",
                "switchIncremental",
                upstream.changes,
                innerChanges.init.connect(this),
                upstream.store,
            )
        }
    )
}

private inline fun <K, V> deferInline(
    crossinline block: InitScope.() -> Incremental<K, V>
): Incremental<K, V> = IncrementalInit(init(name = null) { block().init.connect(evalScope = this) })
+50 −48
Original line number Diff line number Diff line
@@ -31,11 +31,11 @@ import com.android.systemui.kairos.internal.cached
import com.android.systemui.kairos.internal.constInit
import com.android.systemui.kairos.internal.constState
import com.android.systemui.kairos.internal.filterImpl
import com.android.systemui.kairos.internal.flatMap
import com.android.systemui.kairos.internal.flatMapStateImpl
import com.android.systemui.kairos.internal.init
import com.android.systemui.kairos.internal.map
import com.android.systemui.kairos.internal.mapCheap
import com.android.systemui.kairos.internal.mapImpl
import com.android.systemui.kairos.internal.mapStateImpl
import com.android.systemui.kairos.internal.mapStateImplCheap
import com.android.systemui.kairos.internal.util.hashString
import com.android.systemui.kairos.internal.zipStateMap
import com.android.systemui.kairos.internal.zipStates
@@ -45,7 +45,10 @@ import kotlin.reflect.KProperty
 * A time-varying value with discrete changes. Essentially, a combination of a [Transactional] that
 * holds a value, and an [Events] that emits when the value changes.
 */
@ExperimentalKairosApi sealed class State<out A>
@ExperimentalKairosApi
sealed class State<out A> {
    internal abstract val init: Init<StateImpl<A>>
}

/** A [State] that never changes. */
@ExperimentalKairosApi
@@ -98,7 +101,7 @@ fun <A, B> State<A>.map(transform: KairosScope.(A) -> B): State<B> {
    val name = operatorName
    return StateInit(
        init(name) {
            init.connect(evalScope = this).map(name, operatorName) { NoScope.transform(it) }
            mapStateImpl({ init.connect(this) }, name, operatorName) { NoScope.transform(it) }
        }
    )
}
@@ -117,9 +120,7 @@ fun <A, B> State<A>.mapCheapUnsafe(transform: KairosScope.(A) -> B): State<B> {
    val operatorName = "map"
    val name = operatorName
    return StateInit(
        init(name) {
            init.connect(evalScope = this).mapCheap(name, operatorName) { NoScope.transform(it) }
        }
        init(name) { mapStateImplCheap(init, name, operatorName) { NoScope.transform(it) } }
    )
}

@@ -160,7 +161,13 @@ fun <A> Iterable<State<A>>.combine(): State<List<A>> {
    val name = operatorName
    return StateInit(
        init(name) {
            zipStates(name, operatorName, states = map { it.init.connect(evalScope = this) })
            val states = map { it.init }
            zipStates(
                name,
                operatorName,
                states.size,
                states = init(null) { states.map { it.connect(this) } },
            )
        }
    )
}
@@ -179,7 +186,8 @@ fun <K, A> Map<K, State<A>>.combine(): State<Map<K, A>> {
            zipStateMap(
                name,
                operatorName,
                states = mapValues { it.value.init.connect(evalScope = this) },
                size,
                states = init(null) { mapValues { it.value.init.connect(evalScope = this) } },
            )
        }
    )
@@ -229,9 +237,9 @@ fun <A, B, Z> combine(
    val name = operatorName
    return StateInit(
        init(name) {
            val dl1 = stateA.init.connect(evalScope = this@init)
            val dl2 = stateB.init.connect(evalScope = this@init)
            zipStates(name, operatorName, dl1, dl2) { a, b -> NoScope.transform(a, b) }
            zipStates(name, operatorName, stateA.init, stateB.init) { a, b ->
                NoScope.transform(a, b)
            }
        }
    )
}
@@ -253,10 +261,9 @@ fun <A, B, C, Z> combine(
    val name = operatorName
    return StateInit(
        init(name) {
            val dl1 = stateA.init.connect(evalScope = this@init)
            val dl2 = stateB.init.connect(evalScope = this@init)
            val dl3 = stateC.init.connect(evalScope = this@init)
            zipStates(name, operatorName, dl1, dl2, dl3) { a, b, c -> NoScope.transform(a, b, c) }
            zipStates(name, operatorName, stateA.init, stateB.init, stateC.init) { a, b, c ->
                NoScope.transform(a, b, c)
            }
        }
    )
}
@@ -279,11 +286,11 @@ fun <A, B, C, D, Z> combine(
    val name = operatorName
    return StateInit(
        init(name) {
            val dl1 = stateA.init.connect(evalScope = this@init)
            val dl2 = stateB.init.connect(evalScope = this@init)
            val dl3 = stateC.init.connect(evalScope = this@init)
            val dl4 = stateD.init.connect(evalScope = this@init)
            zipStates(name, operatorName, dl1, dl2, dl3, dl4) { a, b, c, d ->
            zipStates(name, operatorName, stateA.init, stateB.init, stateC.init, stateD.init) {
                a,
                b,
                c,
                d ->
                NoScope.transform(a, b, c, d)
            }
        }
@@ -309,12 +316,15 @@ fun <A, B, C, D, E, Z> combine(
    val name = operatorName
    return StateInit(
        init(name) {
            val dl1 = stateA.init.connect(evalScope = this@init)
            val dl2 = stateB.init.connect(evalScope = this@init)
            val dl3 = stateC.init.connect(evalScope = this@init)
            val dl4 = stateD.init.connect(evalScope = this@init)
            val dl5 = stateE.init.connect(evalScope = this@init)
            zipStates(name, operatorName, dl1, dl2, dl3, dl4, dl5) { a, b, c, d, e ->
            zipStates(
                name,
                operatorName,
                stateA.init,
                stateB.init,
                stateC.init,
                stateD.init,
                stateE.init,
            ) { a, b, c, d, e ->
                NoScope.transform(a, b, c, d, e)
            }
        }
@@ -328,7 +338,7 @@ fun <A, B> State<A>.flatMap(transform: KairosScope.(A) -> State<B>): State<B> {
    val name = operatorName
    return StateInit(
        init(name) {
            init.connect(this).flatMap(name, operatorName) { a ->
            flatMapStateImpl({ init.connect(this) }, name, operatorName) { a ->
                NoScope.transform(a).init.connect(this)
            }
        }
@@ -392,14 +402,12 @@ internal constructor(
        val name = "$operatorName[$value]"
        return StateInit(
            init(name) {
                DerivedMapCheap(
                StateImpl(
                    name,
                    operatorName,
                    upstream = upstream.init.connect(evalScope = this),
                    changes = groupedChanges.impl.eventsForKey(value),
                ) {
                    it == value
                }
                    groupedChanges.impl.eventsForKey(value),
                    DerivedMapCheap(upstream.init) { it == value },
                )
            }
        )
    }
@@ -430,18 +438,20 @@ class MutableState<T> internal constructor(internal val network: Network, initia
            getInitialValue = { null },
        )

    override val init: Init<StateImpl<T>>
        get() = state.init

    internal val state = run {
        val changes = input.impl
        val name = null
        val operatorName = "MutableState"
        lateinit var state: StateSource<T>
        val state: StateSource<T> = StateSource(initialValue)
        val mapImpl = mapImpl(upstream = { changes.activated() }) { it, _ -> it!!.value }
        val calm: EventsImpl<T> =
            filterImpl({ mapImpl }) { new ->
                    new != state.getCurrentWithEpoch(evalScope = this).first
                }
                .cached()
        state = StateSource(name, operatorName, initialValue, calm)
        @Suppress("DeferredResultUnused")
        network.transaction("MutableState.init") {
            calm.activate(evalScope = this, downstream = Schedulable.S(state))?.let {
@@ -452,7 +462,7 @@ class MutableState<T> internal constructor(internal val network: Network, initia
                }
            }
        }
        StateInit(constInit(name, state))
        StateInit(constInit(name, StateImpl(name, operatorName, calm, state)))
    }

    /**
@@ -489,7 +499,7 @@ class StateLoop<A> : State<A>() {

    private val deferred = CompletableLazy<State<A>>()

    internal val init: Init<StateImpl<A>> =
    override val init: Init<StateImpl<A>> =
        init(name) { deferred.value.init.connect(evalScope = this) }

    /** The [State] this [StateLoop] will forward to. */
@@ -511,18 +521,10 @@ class StateLoop<A> : State<A>() {
    override fun toString(): String = "${this::class.simpleName}@$hashString"
}

internal class StateInit<A> internal constructor(internal val init: Init<StateImpl<A>>) :
internal class StateInit<A> internal constructor(override val init: Init<StateImpl<A>>) :
    State<A>() {
    override fun toString(): String = "${this::class.simpleName}@$hashString"
}

internal val <A> State<A>.init: Init<StateImpl<A>>
    get() =
        when (this) {
            is StateInit -> init
            is StateLoop -> init
            is MutableState -> state.init
        }

private inline fun <A> deferInline(crossinline block: InitScope.() -> State<A>): State<A> =
    StateInit(init(name = null) { block().init.connect(evalScope = this) })
Loading