• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.kairos
18 
19 import com.android.systemui.kairos.internal.CompletableLazy
20 import com.android.systemui.kairos.internal.EventsImpl
21 import com.android.systemui.kairos.internal.Init
22 import com.android.systemui.kairos.internal.InitScope
23 import com.android.systemui.kairos.internal.Network
24 import com.android.systemui.kairos.internal.NoScope
25 import com.android.systemui.kairos.internal.Schedulable
26 import com.android.systemui.kairos.internal.StateImpl
27 import com.android.systemui.kairos.internal.StateSource
28 import com.android.systemui.kairos.internal.activated
29 import com.android.systemui.kairos.internal.cached
30 import com.android.systemui.kairos.internal.constInit
31 import com.android.systemui.kairos.internal.constState
32 import com.android.systemui.kairos.internal.filterImpl
33 import com.android.systemui.kairos.internal.flatMapStateImpl
34 import com.android.systemui.kairos.internal.init
35 import com.android.systemui.kairos.internal.mapImpl
36 import com.android.systemui.kairos.internal.mapStateImpl
37 import com.android.systemui.kairos.internal.mapStateImplCheap
38 import com.android.systemui.kairos.internal.util.hashString
39 import com.android.systemui.kairos.util.WithPrev
40 import kotlin.reflect.KProperty
41 
42 /**
43  * A time-varying value with discrete changes. Conceptually, a combination of a [Transactional] that
44  * holds a value, and an [Events] that emits when the value [changes].
45  *
46  * [States][State] follow these rules:
47  * 1. In the same transaction that [changes] emits a new value, [sample] will continue to return the
48  *    previous value.
49  * 2. Unless it is [constant][stateOf], [States][State] can only be created via [StateScope]
50  *    operations, or derived from other existing [States][State] via [State.map], [combine], etc.
51  * 3. [States][State] can only be [sampled][TransactionScope.sample] within a [TransactionScope].
52  *
53  * @sample com.android.systemui.kairos.KairosSamples.states
54  */
55 @ExperimentalKairosApi
56 sealed class State<out A> {
57     internal abstract val init: Init<StateImpl<A>>
58 }
59 
60 /**
61  * Returns a constant [State] that never changes. [changes] is equivalent to [emptyEvents] and
62  * [TransactionScope.sample] will always produce [value].
63  */
64 @ExperimentalKairosApi
stateOfnull65 fun <A> stateOf(value: A): State<A> {
66     val operatorName = "stateOf"
67     val name = "$operatorName($value)"
68     return StateInit(constInit(name, constState(name, operatorName, value)))
69 }
70 
71 /**
72  * Returns a [State] that acts as a deferred-reference to the [State] produced by this [Lazy].
73  *
74  * When the returned [State] is accessed by the Kairos network, the [Lazy]'s [value][Lazy.value]
75  * will be queried and used.
76  *
77  * Useful for recursive definitions.
78  *
79  * ```
80  *   fun <A> Lazy<State<A>>.defer() = deferredState { value }
81  * ```
82  */
<lambda>null83 @ExperimentalKairosApi fun <A> Lazy<State<A>>.defer(): State<A> = deferInline { value }
84 
85 /**
86  * Returns a [State] that acts as a deferred-reference to the [State] produced by this
87  * [DeferredValue].
88  *
89  * When the returned [State] is accessed by the Kairos network, the [DeferredValue] will be queried
90  * and used.
91  *
92  * Useful for recursive definitions.
93  *
94  * ```
95  *   fun <A> DeferredValue<State<A>>.defer() = deferredState { get() }
96  * ```
97  */
98 @ExperimentalKairosApi
<lambda>null99 fun <A> DeferredValue<State<A>>.defer(): State<A> = deferInline { unwrapped.value }
100 
101 /**
102  * Returns a [State] that acts as a deferred-reference to the [State] produced by [block].
103  *
104  * When the returned [State] is accessed by the Kairos network, [block] will be invoked and the
105  * returned [State] will be used.
106  *
107  * Useful for recursive definitions.
108  */
109 @ExperimentalKairosApi
<lambda>null110 fun <A> deferredState(block: KairosScope.() -> State<A>): State<A> = deferInline { NoScope.block() }
111 
112 /**
113  * Returns a [State] containing the results of applying [transform] to the value held by the
114  * original [State].
115  *
116  * @sample com.android.systemui.kairos.KairosSamples.mapState
117  */
118 @ExperimentalKairosApi
mapnull119 fun <A, B> State<A>.map(transform: KairosScope.(A) -> B): State<B> {
120     val operatorName = "map"
121     val name = operatorName
122     return StateInit(
123         init(name) {
124             mapStateImpl({ init.connect(this) }, name, operatorName) { NoScope.transform(it) }
125         }
126     )
127 }
128 
129 /**
130  * Returns a [State] that transforms the value held inside this [State] by applying it to the
131  * [transform].
132  *
133  * Note that unlike [State.map], the result is not cached. This means that not only should
134  * [transform] be fast and pure, it should be *monomorphic* (1-to-1). Failure to do this means that
135  * [changes] for the returned [State] will operate unexpectedly, emitting at rates that do not
136  * reflect an observable change to the returned [State].
137  *
138  * @see State.map
139  */
140 @ExperimentalKairosApi
mapCheapUnsafenull141 fun <A, B> State<A>.mapCheapUnsafe(transform: KairosScope.(A) -> B): State<B> {
142     val operatorName = "map"
143     val name = operatorName
144     return StateInit(
145         init(name) { mapStateImplCheap(init, name, operatorName) { NoScope.transform(it) } }
146     )
147 }
148 
149 /**
150  * Splits a [State] of pairs into a pair of [Events][State], where each returned [State] holds half
151  * of the original.
152  *
153  * ```
154  *   fun <A, B> State<Pair<A, B>>.unzip(): Pair<State<A>, State<B>> {
155  *       val first = map { it.first }
156  *       val second = map { it.second }
157  *       return first to second
158  *   }
159  * ```
160  */
161 @ExperimentalKairosApi
unzipnull162 fun <A, B> State<Pair<A, B>>.unzip(): Pair<State<A>, State<B>> {
163     val first = map { it.first }
164     val second = map { it.second }
165     return first to second
166 }
167 
168 /**
169  * Returns a [State] by applying [transform] to the value held by the original [State].
170  *
171  * @sample com.android.systemui.kairos.KairosSamples.flatMap
172  */
173 @ExperimentalKairosApi
flatMapnull174 fun <A, B> State<A>.flatMap(transform: KairosScope.(A) -> State<B>): State<B> {
175     val operatorName = "flatMap"
176     val name = operatorName
177     return StateInit(
178         init(name) {
179             flatMapStateImpl({ init.connect(this) }, name, operatorName) { a ->
180                 NoScope.transform(a).init.connect(this)
181             }
182         }
183     )
184 }
185 
186 /**
187  * Returns a [State] that behaves like the current value of the original [State].
188  *
189  * ```
190  *   fun <A> State<State<A>>.flatten() = flatMap { it }
191  * ```
192  *
193  * @see flatMap
194  */
flattennull195 @ExperimentalKairosApi fun <A> State<State<A>>.flatten() = flatMap { it }
196 
197 /**
198  * A mutable [State] that provides the ability to manually [set its value][setValue].
199  *
200  * Multiple invocations of [setValue] that occur before a transaction are conflated; only the most
201  * recent value is used.
202  *
203  * Effectively equivalent to:
204  * ```
205  *     ConflatedMutableEvents(kairosNetwork).holdState(initialValue)
206  * ```
207  */
208 @ExperimentalKairosApi
209 class MutableState<T> internal constructor(internal val network: Network, initialValue: Lazy<T>) :
210     State<T>() {
211 
212     private val input: CoalescingMutableEvents<Lazy<T>, Lazy<T>?> =
213         CoalescingMutableEvents(
214             name = null,
newnull215             coalesce = { _, new -> new },
216             network = network,
<lambda>null217             getInitialValue = { null },
218         )
219 
220     override val init: Init<StateImpl<T>>
221         get() = state.init
222 
223     // TODO: not convinced this is totally safe
224     //  - at least for the BuildScope smart-constructor, we can avoid the network.transaction { }
225     //    call since we're already in a transaction
<lambda>null226     internal val state = run {
227         val changes = input.impl
228         val name = null
229         val operatorName = "MutableState"
230         val state: StateSource<T> = StateSource(initialValue)
231         val mapImpl = mapImpl(upstream = { changes.activated() }) { it, _ -> it!!.value }
232         val calm: EventsImpl<T> =
233             filterImpl({ mapImpl }) { new ->
234                     new != state.getCurrentWithEpoch(evalScope = this).first
235                 }
236                 .cached()
237         @Suppress("DeferredResultUnused")
238         network.transaction("MutableState.init") {
239             calm.activate(evalScope = this, downstream = Schedulable.S(state))?.let {
240                 (connection, needsEval) ->
241                 state.upstreamConnection = connection
242                 if (needsEval) {
243                     schedule(state)
244                 }
245             }
246         }
247         StateInit(constInit(name, StateImpl(name, operatorName, calm, state)))
248     }
249 
250     /**
251      * Sets the value held by this [State].
252      *
253      * Invoking will cause a [state change event][State.changes] to emit with the new value, which
254      * will then be applied (and thus returned by [TransactionScope.sample]) after the transaction
255      * is complete.
256      *
257      * Multiple invocations of [setValue] that occur before a transaction are conflated; only the
258      * most recent value is used.
259      */
setValuenull260     fun setValue(value: T) = input.emit(CompletableLazy(value))
261 
262     /**
263      * Sets the value held by this [State]. The [DeferredValue] will not be queried until this
264      * [State] is explicitly [sampled][TransactionScope.sample] or [observed][BuildScope.observe].
265      *
266      * Invoking will cause a [state change event][State.changes] to emit with the new value, which
267      * will then be applied (and thus returned by [TransactionScope.sample]) after the transaction
268      * is complete.
269      *
270      * Multiple invocations of [setValue] that occur before a transaction are conflated; only the
271      * most recent value is used.
272      */
273     fun setValueDeferred(value: DeferredValue<T>) = input.emit(value.unwrapped)
274 }
275 
276 /**
277  * A forward-reference to a [State]. Useful for recursive definitions.
278  *
279  * This reference can be used like a standard [State], but will throw an error if its [loopback] is
280  * unset before it is [observed][BuildScope.observe] or [sampled][TransactionScope.sample].
281  *
282  * Note that it is safe to invoke [TransactionScope.sampleDeferred] before [loopback] is set,
283  * provided the returned [DeferredValue] is not [queried][KairosScope.get].
284  *
285  * @sample com.android.systemui.kairos.KairosSamples.stateLoop
286  */
287 @ExperimentalKairosApi
288 class StateLoop<A> : State<A>() {
289 
290     private val name: String? = null
291 
292     private val deferred = CompletableLazy<State<A>>()
293 
294     override val init: Init<StateImpl<A>> =
295         init(name) { deferred.value.init.connect(evalScope = this) }
296 
297     /**
298      * The [State] this reference is referring to. Must be set before this [StateLoop] is
299      * [observed][BuildScope.observe] or [sampled][TransactionScope.sample].
300      */
301     var loopback: State<A>? = null
302         set(value) {
303             value?.let {
304                 check(!deferred.isInitialized()) { "StateLoop.loopback has already been set." }
305                 deferred.setValue(value)
306                 field = value
307             }
308         }
309 
310     operator fun getValue(thisRef: Any?, property: KProperty<*>): State<A> = this
311 
312     operator fun setValue(thisRef: Any?, property: KProperty<*>, value: State<A>) {
313         loopback = value
314     }
315 
316     override fun toString(): String = "${this::class.simpleName}@$hashString"
317 }
318 
319 internal class StateInit<A> internal constructor(override val init: Init<StateImpl<A>>) :
320     State<A>() {
toStringnull321     override fun toString(): String = "${this::class.simpleName}@$hashString"
322 }
323 
324 private inline fun <A> deferInline(crossinline block: InitScope.() -> State<A>): State<A> =
325     StateInit(init(name = null) { block().init.connect(evalScope = this) })
326 
327 /**
328  * Like [changes] but also includes the old value of this [State].
329  *
330  * Shorthand for:
331  * ```
332  *     stateChanges.map { WithPrev(previousValue = sample(), newValue = it) }
333  * ```
334  */
335 @ExperimentalKairosApi
336 val <A> State<A>.transitions: Events<WithPrev<A, A>>
<lambda>null337     get() = changes.map { WithPrev(previousValue = sample(), newValue = it) }
338 
339 /**
340  * Returns an [Events] that emits the new value of this [State] when it changes.
341  *
342  * @sample com.android.systemui.kairos.KairosSamples.changes
343  */
344 @ExperimentalKairosApi
345 val <A> State<A>.changes: Events<A>
<lambda>null346     get() = EventsInit(init(name = null) { init.connect(evalScope = this).changes })
347