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