• 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.BuildScopeImpl
20 import com.android.systemui.kairos.internal.Network
21 import com.android.systemui.kairos.internal.StateScopeImpl
22 import com.android.systemui.kairos.internal.util.awaitCancellationAndThen
23 import com.android.systemui.kairos.internal.util.childScope
24 import kotlin.coroutines.CoroutineContext
25 import kotlin.coroutines.EmptyCoroutineContext
26 import kotlinx.coroutines.CancellationException
27 import kotlinx.coroutines.CoroutineName
28 import kotlinx.coroutines.CoroutineScope
29 import kotlinx.coroutines.Deferred
30 import kotlinx.coroutines.Job
31 import kotlinx.coroutines.job
32 import kotlinx.coroutines.launch
33 
34 /** Marks APIs that are still **experimental** and shouldn't be used in general production code. */
35 @RequiresOptIn(
36     message = "This API is experimental and should not be used in general production code."
37 )
38 @Retention(AnnotationRetention.BINARY)
39 annotation class ExperimentalKairosApi
40 
41 /**
42  * External interface to a Kairos network of reactive components. Can be used to make transactional
43  * queries and modifications to the network.
44  */
45 @ExperimentalKairosApi
46 interface KairosNetwork {
47     /**
48      * Runs [block] inside of a transaction, suspending until the transaction is complete.
49      *
50      * The [BuildScope] receiver exposes methods that can be used to query or modify the network. If
51      * the network is cancelled while the caller of [transact] is suspended, then the call will be
52      * cancelled.
53      */
54     suspend fun <R> transact(block: TransactionScope.() -> R): R
55 
56     /**
57      * Activates [spec] in a transaction, suspending indefinitely. While suspended, all observers
58      * and long-running effects are kept alive. When cancelled, observers are unregistered and
59      * effects are cancelled.
60      */
61     suspend fun activateSpec(spec: BuildSpec<*>)
62 
63     /** Returns a [CoalescingMutableEvents] that can emit values into this [KairosNetwork]. */
64     fun <In, Out> coalescingMutableEvents(
65         coalesce: (old: Out, new: In) -> Out,
66         getInitialValue: () -> Out,
67     ): CoalescingMutableEvents<In, Out>
68 
69     /** Returns a [MutableState] that can emit values into this [KairosNetwork]. */
70     fun <T> mutableEvents(): MutableEvents<T>
71 
72     /** Returns a [CoalescingMutableEvents] that can emit values into this [KairosNetwork]. */
73     fun <T> conflatedMutableEvents(): CoalescingMutableEvents<T, T>
74 
75     /** Returns a [MutableState]. with initial state [initialValue]. */
76     fun <T> mutableStateDeferred(initialValue: DeferredValue<T>): MutableState<T>
77 }
78 
79 /** Returns a [CoalescingMutableEvents] that can emit values into this [KairosNetwork]. */
80 @ExperimentalKairosApi
coalescingMutableEventsnull81 fun <In, Out> KairosNetwork.coalescingMutableEvents(
82     coalesce: (old: Out, new: In) -> Out,
83     initialValue: Out,
84 ): CoalescingMutableEvents<In, Out> =
85     coalescingMutableEvents(coalesce, getInitialValue = { initialValue })
86 
87 /** Returns a [MutableState] with initial state [initialValue]. */
88 @ExperimentalKairosApi
mutableStatenull89 fun <T> KairosNetwork.mutableState(initialValue: T): MutableState<T> =
90     mutableStateDeferred(deferredOf(initialValue))
91 
92 /** Returns a [MutableState] with initial state [initialValue]. */
93 @ExperimentalKairosApi
94 fun <T> MutableState(network: KairosNetwork, initialValue: T): MutableState<T> =
95     network.mutableState(initialValue)
96 
97 /** Returns a [MutableEvents] that can emit values into this [KairosNetwork]. */
98 @ExperimentalKairosApi
99 fun <T> MutableEvents(network: KairosNetwork): MutableEvents<T> = network.mutableEvents()
100 
101 /** Returns a [CoalescingMutableEvents] that can emit values into this [KairosNetwork]. */
102 @ExperimentalKairosApi
103 fun <In, Out> CoalescingMutableEvents(
104     network: KairosNetwork,
105     coalesce: (old: Out, new: In) -> Out,
106     initialValue: Out,
107 ): CoalescingMutableEvents<In, Out> = network.coalescingMutableEvents(coalesce) { initialValue }
108 
109 /** Returns a [CoalescingMutableEvents] that can emit values into this [KairosNetwork]. */
110 @ExperimentalKairosApi
CoalescingMutableEventsnull111 fun <In, Out> CoalescingMutableEvents(
112     network: KairosNetwork,
113     coalesce: (old: Out, new: In) -> Out,
114     getInitialValue: () -> Out,
115 ): CoalescingMutableEvents<In, Out> = network.coalescingMutableEvents(coalesce, getInitialValue)
116 
117 /** Returns a [CoalescingMutableEvents] that can emit values into this [KairosNetwork]. */
118 @ExperimentalKairosApi
119 fun <T> ConflatedMutableEvents(network: KairosNetwork): CoalescingMutableEvents<T, T> =
120     network.conflatedMutableEvents()
121 
122 /**
123  * Activates [spec] in a transaction and invokes [block] with the result, suspending indefinitely.
124  * While suspended, all observers and long-running effects are kept alive. When cancelled, observers
125  * are unregistered and effects are cancelled.
126  */
127 @ExperimentalKairosApi
128 suspend fun <R> KairosNetwork.activateSpec(spec: BuildSpec<R>, block: suspend (R) -> Unit) {
129     activateSpec {
130         val result = spec.applySpec()
131         launchEffect { block(result) }
132     }
133 }
134 
135 internal class LocalNetwork(
136     private val network: Network,
137     private val scope: CoroutineScope,
138     private val endSignal: Events<Any>,
139 ) : KairosNetwork {
transactnull140     override suspend fun <R> transact(block: TransactionScope.() -> R): R =
141         network.transaction("KairosNetwork.transact") { block() }.awaitOrCancel()
142 
activateSpecnull143     override suspend fun activateSpec(spec: BuildSpec<*>) {
144         val stopEmitter = conflatedMutableEvents<Unit>()
145         network
146             .transaction("KairosNetwork.activateSpec") {
147                 val buildScope =
148                     BuildScopeImpl(
149                         stateScope =
150                             StateScopeImpl(
151                                 evalScope = this,
152                                 endSignalLazy = lazy { mergeLeft(stopEmitter, endSignal) },
153                             ),
154                         coroutineScope = scope,
155                     )
156                 buildScope.launchScope {
157                     spec.applySpec()
158                     launchEffect { awaitCancellationAndThen { stopEmitter.emit(Unit) } }
159                 }
160             }
161             .awaitOrCancel()
162             .joinOrCancel()
163     }
164 
awaitOrCancelnull165     private suspend fun <T> Deferred<T>.awaitOrCancel(): T =
166         try {
167             await()
168         } catch (ex: CancellationException) {
169             cancel(ex)
170             throw ex
171         }
172 
joinOrCancelnull173     private suspend fun Job.joinOrCancel(): Unit =
174         try {
175             join()
176         } catch (ex: CancellationException) {
177             cancel(ex)
178             throw ex
179         }
180 
coalescingMutableEventsnull181     override fun <In, Out> coalescingMutableEvents(
182         coalesce: (old: Out, new: In) -> Out,
183         getInitialValue: () -> Out,
184     ): CoalescingMutableEvents<In, Out> =
185         CoalescingMutableEvents(
186             null,
187             coalesce = { old, new -> coalesce(old.value, new) },
188             network,
189             getInitialValue,
190         )
191 
conflatedMutableEventsnull192     override fun <T> conflatedMutableEvents(): CoalescingMutableEvents<T, T> =
193         CoalescingMutableEvents(
194             null,
195             coalesce = { _, new -> new },
196             network,
<lambda>null197             { error("WTF: init value accessed for conflatedMutableEvents") },
198         )
199 
mutableEventsnull200     override fun <T> mutableEvents(): MutableEvents<T> = MutableEvents(network)
201 
202     override fun <T> mutableStateDeferred(initialValue: DeferredValue<T>): MutableState<T> =
203         MutableState(network, initialValue.unwrapped)
204 }
205 
206 /**
207  * Combination of an [KairosNetwork] and a [Job] that, when cancelled, will cancel the entire Kairos
208  * network.
209  */
210 @ExperimentalKairosApi
211 class RootKairosNetwork
212 internal constructor(private val network: Network, private val scope: CoroutineScope, job: Job) :
213     Job by job, KairosNetwork by LocalNetwork(network, scope, emptyEvents)
214 
215 /** Constructs a new [RootKairosNetwork] in the given [CoroutineScope]. */
216 @ExperimentalKairosApi
217 fun CoroutineScope.launchKairosNetwork(
218     context: CoroutineContext = EmptyCoroutineContext
219 ): RootKairosNetwork {
220     val scope = childScope(context)
221     val network = Network(scope)
222     scope.launch(CoroutineName("launchKairosNetwork scheduler")) { network.runInputScheduler() }
223     return RootKairosNetwork(network, scope, scope.coroutineContext.job)
224 }
225 
226 @ExperimentalKairosApi
227 interface HasNetwork : KairosScope {
228     /**
229      * A [KairosNetwork] handle that is bound to the lifetime of a [BuildScope].
230      *
231      * It supports all of the standard functionality by which external code can interact with this
232      * Kairos network, but all [activated][KairosNetwork.activateSpec] [BuildSpec]s are bound as
233      * children to the [BuildScope], such that when the [BuildScope] is destroyed, all children are
234      * also destroyed.
235      */
236     val kairosNetwork: KairosNetwork
237 }
238 
239 /** Returns a [MutableEvents] that can emit values into this [KairosNetwork]. */
240 @ExperimentalKairosApi
MutableEventsnull241 fun <T> HasNetwork.MutableEvents(): MutableEvents<T> = MutableEvents(kairosNetwork)
242 
243 /** Returns a [MutableState] with initial state [initialValue]. */
244 @ExperimentalKairosApi
245 fun <T> HasNetwork.MutableState(initialValue: T): MutableState<T> =
246     MutableState(kairosNetwork, initialValue)
247 
248 /** Returns a [CoalescingMutableEvents] that can emit values into this [KairosNetwork]. */
249 @ExperimentalKairosApi
250 fun <In, Out> HasNetwork.CoalescingMutableEvents(
251     coalesce: (old: Out, new: In) -> Out,
252     initialValue: Out,
253 ): CoalescingMutableEvents<In, Out> = CoalescingMutableEvents(kairosNetwork, coalesce, initialValue)
254 
255 /** Returns a [CoalescingMutableEvents] that can emit values into this [KairosNetwork]. */
256 @ExperimentalKairosApi
257 fun <In, Out> HasNetwork.CoalescingMutableEvents(
258     coalesce: (old: Out, new: In) -> Out,
259     getInitialValue: () -> Out,
260 ): CoalescingMutableEvents<In, Out> =
261     CoalescingMutableEvents(kairosNetwork, coalesce, getInitialValue)
262 
263 /** Returns a [CoalescingMutableEvents] that can emit values into this [KairosNetwork]. */
264 @ExperimentalKairosApi
265 fun <T> HasNetwork.ConflatedMutableEvents(): CoalescingMutableEvents<T, T> =
266     ConflatedMutableEvents(kairosNetwork)
267