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