xref: /aosp_15_r20/frameworks/base/packages/SystemUI/utils/kairos/src/com/android/systemui/kairos/FrpNetwork.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * 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 kotlin.coroutines.coroutineContext
27 import kotlinx.coroutines.CompletableDeferred
28 import kotlinx.coroutines.CoroutineName
29 import kotlinx.coroutines.CoroutineScope
30 import kotlinx.coroutines.Job
31 import kotlinx.coroutines.job
32 import kotlinx.coroutines.launch
33 
34 /**
35  * Marks declarations that are still **experimental** and shouldn't be used in general production
36  * code.
37  */
38 @RequiresOptIn(
39     message = "This API is experimental and should not be used in general production code."
40 )
41 @Retention(AnnotationRetention.BINARY)
42 annotation class ExperimentalFrpApi
43 
44 /**
45  * External interface to an FRP network. Can be used to make transactional queries and modifications
46  * to the network.
47  */
48 @ExperimentalFrpApi
49 interface FrpNetwork {
50     /**
51      * Runs [block] inside of a transaction, suspending until the transaction is complete.
52      *
53      * The [FrpBuildScope] receiver exposes methods that can be used to query or modify the network.
54      * If the network is cancelled while the caller of [transact] is suspended, then the call will
55      * be cancelled.
56      */
transactnull57     @ExperimentalFrpApi suspend fun <R> transact(block: suspend FrpTransactionScope.() -> R): R
58 
59     /**
60      * Activates [spec] in a transaction, suspending indefinitely. While suspended, all observers
61      * and long-running effects are kept alive. When cancelled, observers are unregistered and
62      * effects are cancelled.
63      */
64     @ExperimentalFrpApi suspend fun activateSpec(spec: FrpSpec<*>)
65 
66     /** Returns a [CoalescingMutableTFlow] that can emit values into this [FrpNetwork]. */
67     @ExperimentalFrpApi
68     fun <In, Out> coalescingMutableTFlow(
69         coalesce: (old: Out, new: In) -> Out,
70         getInitialValue: () -> Out,
71     ): CoalescingMutableTFlow<In, Out>
72 
73     /** Returns a [MutableTFlow] that can emit values into this [FrpNetwork]. */
74     @ExperimentalFrpApi fun <T> mutableTFlow(): MutableTFlow<T>
75 
76     /** Returns a [MutableTState]. with initial state [initialValue]. */
77     @ExperimentalFrpApi
78     fun <T> mutableTStateDeferred(initialValue: FrpDeferredValue<T>): MutableTState<T>
79 }
80 
81 /** Returns a [CoalescingMutableTFlow] that can emit values into this [FrpNetwork]. */
82 @ExperimentalFrpApi
83 fun <In, Out> FrpNetwork.coalescingMutableTFlow(
84     coalesce: (old: Out, new: In) -> Out,
85     initialValue: Out,
86 ): CoalescingMutableTFlow<In, Out> =
87     coalescingMutableTFlow(coalesce, getInitialValue = { initialValue })
88 
89 /** Returns a [MutableTState]. with initial state [initialValue]. */
90 @ExperimentalFrpApi
mutableTStatenull91 fun <T> FrpNetwork.mutableTState(initialValue: T): MutableTState<T> =
92     mutableTStateDeferred(deferredOf(initialValue))
93 
94 /** Returns a [MutableTState]. with initial state [initialValue]. */
95 @ExperimentalFrpApi
96 fun <T> MutableTState(network: FrpNetwork, initialValue: T): MutableTState<T> =
97     network.mutableTState(initialValue)
98 
99 /** Returns a [MutableTFlow] that can emit values into this [FrpNetwork]. */
100 @ExperimentalFrpApi
101 fun <T> MutableTFlow(network: FrpNetwork): MutableTFlow<T> = network.mutableTFlow()
102 
103 /** Returns a [CoalescingMutableTFlow] that can emit values into this [FrpNetwork]. */
104 @ExperimentalFrpApi
105 fun <In, Out> CoalescingMutableTFlow(
106     network: FrpNetwork,
107     coalesce: (old: Out, new: In) -> Out,
108     initialValue: Out,
109 ): CoalescingMutableTFlow<In, Out> = network.coalescingMutableTFlow(coalesce) { initialValue }
110 
111 /** Returns a [CoalescingMutableTFlow] that can emit values into this [FrpNetwork]. */
112 @ExperimentalFrpApi
CoalescingMutableTFlownull113 fun <In, Out> CoalescingMutableTFlow(
114     network: FrpNetwork,
115     coalesce: (old: Out, new: In) -> Out,
116     getInitialValue: () -> Out,
117 ): CoalescingMutableTFlow<In, Out> = network.coalescingMutableTFlow(coalesce, getInitialValue)
118 
119 /**
120  * Activates [spec] in a transaction and invokes [block] with the result, suspending indefinitely.
121  * While suspended, all observers and long-running effects are kept alive. When cancelled, observers
122  * are unregistered and effects are cancelled.
123  */
124 @ExperimentalFrpApi
125 suspend fun <R> FrpNetwork.activateSpec(spec: FrpSpec<R>, block: suspend (R) -> Unit) {
126     activateSpec {
127         val result = spec.applySpec()
128         launchEffect { block(result) }
129     }
130 }
131 
132 internal class LocalFrpNetwork(
133     private val network: Network,
134     private val scope: CoroutineScope,
135     private val endSignal: TFlow<Any>,
136 ) : FrpNetwork {
transactnull137     override suspend fun <R> transact(block: suspend FrpTransactionScope.() -> R): R {
138         val result = CompletableDeferred<R>(coroutineContext[Job])
139         @Suppress("DeferredResultUnused")
140         network.transaction {
141             val buildScope =
142                 BuildScopeImpl(
143                     stateScope = StateScopeImpl(evalScope = this, endSignal = endSignal),
144                     coroutineScope = scope,
145                 )
146             buildScope.runInBuildScope { effect { result.complete(block()) } }
147         }
148         return result.await()
149     }
150 
activateSpecnull151     override suspend fun activateSpec(spec: FrpSpec<*>) {
152         val job =
153             network
154                 .transaction {
155                     val buildScope =
156                         BuildScopeImpl(
157                             stateScope = StateScopeImpl(evalScope = this, endSignal = endSignal),
158                             coroutineScope = scope,
159                         )
160                     buildScope.runInBuildScope { launchScope(spec) }
161                 }
162                 .await()
163         awaitCancellationAndThen { job.cancel() }
164     }
165 
coalescingMutableTFlownull166     override fun <In, Out> coalescingMutableTFlow(
167         coalesce: (old: Out, new: In) -> Out,
168         getInitialValue: () -> Out,
169     ): CoalescingMutableTFlow<In, Out> = CoalescingMutableTFlow(coalesce, network, getInitialValue)
170 
171     override fun <T> mutableTFlow(): MutableTFlow<T> = MutableTFlow(network)
172 
173     override fun <T> mutableTStateDeferred(initialValue: FrpDeferredValue<T>): MutableTState<T> =
174         MutableTState(network, initialValue.unwrapped)
175 }
176 
177 /**
178  * Combination of an [FrpNetwork] and a [Job] that, when cancelled, will cancel the entire FRP
179  * network.
180  */
181 @ExperimentalFrpApi
182 class RootFrpNetwork
183 internal constructor(private val network: Network, private val scope: CoroutineScope, job: Job) :
184     Job by job, FrpNetwork by LocalFrpNetwork(network, scope, emptyTFlow)
185 
186 /** Constructs a new [RootFrpNetwork] in the given [CoroutineScope]. */
187 @ExperimentalFrpApi
188 fun CoroutineScope.newFrpNetwork(
189     context: CoroutineContext = EmptyCoroutineContext
190 ): RootFrpNetwork {
191     val scope = childScope(context)
192     val network = Network(scope)
193     scope.launch(CoroutineName("newFrpNetwork scheduler")) { network.runInputScheduler() }
194     return RootFrpNetwork(network, scope, scope.coroutineContext.job)
195 }
196