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.internal
18
19 import com.android.systemui.kairos.internal.util.Bag
20 import java.util.TreeMap
21
22 /**
23 * Tracks all upstream connections for Mux nodes.
24 *
25 * Connections come in two flavors:
26 * 1. **DIRECT** :: The upstream node may emit events that would cause the owner of this depth
27 * tracker to also emit.
28 * 2. **INDIRECT** :: The upstream node will not emit events, but may start doing so in a future
29 * transaction (at which point its depth will change to DIRECT).
30 *
31 * DIRECT connections are the standard, active connections that propagate events through the graph.
32 * They are used to calculate the evaluation depth of a node, so that it is only visited once it is
33 * certain that all DIRECT upstream connections have already been visited (or are not emitting in
34 * the current transaction).
35 *
36 * It is *invalid* for a node to be directly upstream of itself. Doing so is an error.
37 *
38 * INDIRECT connections identify nodes that are still "alive" (should not be garbage-collected) but
39 * are presently "dormant". This only occurs when a MuxDeferredNode has nothing switched-in, but is
40 * still connected to its "patches" upstream node, implying that something *may* be switched-in at a
41 * later time.
42 *
43 * It is *invalid* for a node to be indirectly upstream of itself. These connections are
44 * automatically filtered out.
45 *
46 * When there are no connections, either DIRECT or INDIRECT, a node *dies* and all incoming/outgoing
47 * connections are freed so that it can be garbage-collected.
48 *
49 * Note that there is an edge case where a MuxDeferredNode is connected to itself via its "patches"
50 * upstream node. In this case:
51 * 1. If the node has switched-in upstream nodes, then this is perfectly valid. Downstream nodes
52 * will see a direct connection to this MuxDeferredNode.
53 * 2. Otherwise, the node would normally be considered "dormant" and downstream nodes would see an
54 * indirect connection. However, because a node cannot be indirectly upstream of itself, then the
55 * MuxDeferredNode sees no connection via its patches upstream node, and so is considered "dead".
56 * Conceptually, this makes some sense: The only way for this recursive MuxDeferredNode to become
57 * non-dormant is to switch some upstream nodes back in, but since the patches node is itself,
58 * this will never happen.
59 *
60 * This behavior underpins the recursive definition of `nextOnly`.
61 */
62 internal class DepthTracker {
63
64 @Volatile var snapshotIsDirect = true
65 @Volatile private var snapshotIsIndirectRoot = false
66
67 private inline val snapshotIsIndirect: Boolean
68 get() = !snapshotIsDirect
69
70 @Volatile var snapshotIndirectDepth: Int = 0
71 @Volatile var snapshotDirectDepth: Int = 0
72
73 private val _snapshotIndirectRoots = HashSet<MuxDeferredNode<*, *, *>>()
74 val snapshotIndirectRoots
75 get() = _snapshotIndirectRoots.toSet()
76
77 private val indirectAdditions = HashSet<MuxDeferredNode<*, *, *>>()
78 private val indirectRemovals = HashSet<MuxDeferredNode<*, *, *>>()
79 private val dirty_directUpstreamDepths = TreeMap<Int, Int>()
80 private val dirty_indirectUpstreamDepths = TreeMap<Int, Int>()
81 private val dirty_indirectUpstreamRoots = Bag<MuxDeferredNode<*, *, *>>()
82 @Volatile var dirty_directDepth = 0
83 @Volatile private var dirty_indirectDepth = 0
84 @Volatile private var dirty_depthIsDirect = true
85 @Volatile private var dirty_isIndirectRoot = false
86
87 fun schedule(scheduler: Scheduler, node: MuxNode<*, *, *>) {
88 if (dirty_depthIsDirect) {
89 scheduler.schedule(dirty_directDepth, node)
90 } else {
91 scheduler.scheduleIndirect(dirty_indirectDepth, node)
92 }
93 }
94
95 // only used by MuxDeferred
96 // and only when there is a direct connection to the patch node
97 fun setIsIndirectRoot(isRoot: Boolean): Boolean {
98 if (isRoot != dirty_isIndirectRoot) {
99 dirty_isIndirectRoot = isRoot
100 return !dirty_depthIsDirect
101 }
102 return false
103 }
104
105 // adds an upstream connection, and recalcs depth
106 // returns true if depth has changed
107 fun addDirectUpstream(oldDepth: Int?, newDepth: Int): Boolean {
108 if (oldDepth != null) {
109 dirty_directUpstreamDepths.compute(oldDepth) { _, count ->
110 count?.minus(1)?.takeIf { it > 0 }
111 }
112 }
113 dirty_directUpstreamDepths.compute(newDepth) { _, current -> current?.plus(1) ?: 1 }
114 return recalcDepth()
115 }
116
117 private fun recalcDepth(): Boolean {
118 val newDepth =
119 dirty_directUpstreamDepths.lastEntry()?.let { (maxDepth, _) -> maxDepth + 1 } ?: 0
120
121 val isDirect = dirty_directUpstreamDepths.isNotEmpty()
122 val isDirectChanged = dirty_depthIsDirect != isDirect
123 dirty_depthIsDirect = isDirect
124
125 return (newDepth != dirty_directDepth).also { dirty_directDepth = newDepth } or
126 isDirectChanged
127 }
128
129 private fun recalcIndirDepth(): Boolean {
130 val newDepth =
131 dirty_indirectUpstreamDepths.lastEntry()?.let { (maxDepth, _) -> maxDepth + 1 } ?: 0
132 return (!dirty_depthIsDirect && !dirty_isIndirectRoot && newDepth != dirty_indirectDepth)
133 .also { dirty_indirectDepth = newDepth }
134 }
135
136 fun removeDirectUpstream(depth: Int): Boolean {
137 dirty_directUpstreamDepths.compute(depth) { _, count -> count?.minus(1)?.takeIf { it > 0 } }
138 return recalcDepth()
139 }
140
141 fun addIndirectUpstream(oldDepth: Int?, newDepth: Int): Boolean =
142 if (oldDepth == newDepth) {
143 false
144 } else {
145 if (oldDepth != null) {
146 dirty_indirectUpstreamDepths.compute(oldDepth) { _, current ->
147 current?.minus(1)?.takeIf { it > 0 }
148 }
149 }
150 dirty_indirectUpstreamDepths.compute(newDepth) { _, current -> current?.plus(1) ?: 1 }
151 recalcIndirDepth()
152 }
153
154 fun removeIndirectUpstream(depth: Int): Boolean {
155 dirty_indirectUpstreamDepths.compute(depth) { _, current ->
156 current?.minus(1)?.takeIf { it > 0 }
157 }
158 return recalcIndirDepth()
159 }
160
161 fun updateIndirectRoots(
162 additions: Set<MuxDeferredNode<*, *, *>>? = null,
163 removals: Set<MuxDeferredNode<*, *, *>>? = null,
164 butNot: MuxDeferredNode<*, *, *>? = null,
165 ): Boolean {
166 val addsChanged =
167 additions
168 ?.let { dirty_indirectUpstreamRoots.addAll(additions, butNot) }
169 ?.let {
170 indirectAdditions.addAll(indirectRemovals.applyRemovalDiff(it))
171 true
172 } ?: false
173 val removalsChanged =
174 removals
175 ?.let { dirty_indirectUpstreamRoots.removeAll(removals) }
176 ?.let {
177 indirectRemovals.addAll(indirectAdditions.applyRemovalDiff(it))
178 true
179 } ?: false
180 return (!dirty_depthIsDirect && (addsChanged || removalsChanged))
181 }
182
183 private fun <T> HashSet<T>.applyRemovalDiff(changeSet: Set<T>): Set<T> {
184 val remainder = HashSet<T>()
185 for (element in changeSet) {
186 if (!add(element)) {
187 remainder.add(element)
188 }
189 }
190 return remainder
191 }
192
193 fun propagateChanges(scheduler: Scheduler, muxNode: MuxNode<*, *, *>) {
194 if (isDirty()) {
195 schedule(scheduler, muxNode)
196 }
197 }
198
199 fun applyChanges(
200 scheduler: Scheduler,
201 downstreamSet: DownstreamSet,
202 muxNode: MuxNode<*, *, *>,
203 ) {
204 when {
205 dirty_depthIsDirect -> {
206 if (snapshotIsDirect) {
207 downstreamSet.adjustDirectUpstream(
208 scheduler,
209 oldDepth = snapshotDirectDepth,
210 newDepth = dirty_directDepth,
211 )
212 } else {
213 downstreamSet.moveIndirectUpstreamToDirect(
214 scheduler,
215 oldIndirectDepth = snapshotIndirectDepth,
216 oldIndirectSet =
217 buildSet {
218 addAll(snapshotIndirectRoots)
219 if (snapshotIsIndirectRoot) {
220 add(muxNode as MuxDeferredNode<*, *, *>)
221 }
222 },
223 newDirectDepth = dirty_directDepth,
224 )
225 }
226 }
227
228 dirty_hasIndirectUpstream() || dirty_isIndirectRoot -> {
229 if (snapshotIsDirect) {
230 downstreamSet.moveDirectUpstreamToIndirect(
231 scheduler,
232 oldDirectDepth = snapshotDirectDepth,
233 newIndirectDepth = dirty_indirectDepth,
234 newIndirectSet =
235 buildSet {
236 addAll(dirty_indirectUpstreamRoots)
237 if (dirty_isIndirectRoot) {
238 add(muxNode as MuxDeferredNode<*, *, *>)
239 }
240 },
241 )
242 } else {
243 downstreamSet.adjustIndirectUpstream(
244 scheduler,
245 oldDepth = snapshotIndirectDepth,
246 newDepth = dirty_indirectDepth,
247 removals =
248 buildSet {
249 addAll(indirectRemovals)
250 if (snapshotIsIndirectRoot && !dirty_isIndirectRoot) {
251 add(muxNode as MuxDeferredNode<*, *, *>)
252 }
253 },
254 additions =
255 buildSet {
256 addAll(indirectAdditions)
257 if (!snapshotIsIndirectRoot && dirty_isIndirectRoot) {
258 add(muxNode as MuxDeferredNode<*, *, *>)
259 }
260 },
261 )
262 }
263 }
264
265 else -> {
266 // die
267 muxNode.lifecycle.lifecycleState = MuxLifecycleState.Dead
268
269 if (snapshotIsDirect) {
270 downstreamSet.removeDirectUpstream(scheduler, depth = snapshotDirectDepth)
271 } else {
272 downstreamSet.removeIndirectUpstream(
273 scheduler,
274 depth = snapshotIndirectDepth,
275 indirectSet =
276 buildSet {
277 addAll(snapshotIndirectRoots)
278 if (snapshotIsIndirectRoot) {
279 add(muxNode as MuxDeferredNode<*, *, *>)
280 }
281 },
282 )
283 }
284 }
285 }
286 reset()
287 }
288
289 fun dirty_hasDirectUpstream(): Boolean = dirty_directUpstreamDepths.isNotEmpty()
290
291 private fun dirty_hasIndirectUpstream(): Boolean = dirty_indirectUpstreamRoots.isNotEmpty()
292
293 override fun toString(): String =
294 "DepthTracker(" +
295 "sIsDirect=$snapshotIsDirect, " +
296 "sDirectDepth=$snapshotDirectDepth, " +
297 "sIndirectDepth=$snapshotIndirectDepth, " +
298 "sIndirectRoots=$snapshotIndirectRoots, " +
299 "dIsIndirectRoot=$dirty_isIndirectRoot, " +
300 "dDirectDepths=$dirty_directUpstreamDepths, " +
301 "dIndirectDepths=$dirty_indirectUpstreamDepths, " +
302 "dIndirectRoots=$dirty_indirectUpstreamRoots" +
303 ")"
304
305 fun reset() {
306 snapshotIsDirect = dirty_hasDirectUpstream()
307 snapshotDirectDepth = dirty_directDepth
308 snapshotIndirectDepth = dirty_indirectDepth
309 snapshotIsIndirectRoot = dirty_isIndirectRoot
310 if (indirectAdditions.isNotEmpty() || indirectRemovals.isNotEmpty()) {
311 _snapshotIndirectRoots.clear()
312 _snapshotIndirectRoots.addAll(dirty_indirectUpstreamRoots)
313 }
314 indirectAdditions.clear()
315 indirectRemovals.clear()
316 // check(!isDirty()) { "should not be dirty after a reset" }
317 }
318
319 fun isDirty(): Boolean =
320 when {
321 snapshotIsDirect -> !dirty_depthIsDirect || snapshotDirectDepth != dirty_directDepth
322 snapshotIsIndirectRoot -> dirty_depthIsDirect || !dirty_isIndirectRoot
323 else ->
324 dirty_depthIsDirect ||
325 dirty_isIndirectRoot ||
326 snapshotIndirectDepth != dirty_indirectDepth ||
327 indirectAdditions.isNotEmpty() ||
328 indirectRemovals.isNotEmpty()
329 }
330
331 fun dirty_depthIncreased(): Boolean =
332 snapshotDirectDepth < dirty_directDepth || snapshotIsIndirect && dirty_hasDirectUpstream()
333 }
334
335 /**
336 * Tracks downstream nodes to be scheduled when the owner of this DownstreamSet produces a value in
337 * a transaction.
338 */
339 internal class DownstreamSet {
340
341 val outputs = HashSet<Output<*>>()
342 val stateWriters = mutableListOf<StateSource<*>>()
343 val muxMovers = HashSet<MuxDeferredNode<*, *, *>>()
344 val nodes = HashSet<SchedulableNode>()
345
addnull346 fun add(schedulable: Schedulable) {
347 when (schedulable) {
348 is Schedulable.S -> stateWriters.add(schedulable.state)
349 is Schedulable.M -> muxMovers.add(schedulable.muxMover)
350 is Schedulable.N -> nodes.add(schedulable.node)
351 is Schedulable.O -> outputs.add(schedulable.output)
352 }
353 }
354
removenull355 fun remove(schedulable: Schedulable) {
356 when (schedulable) {
357 is Schedulable.S -> error("WTF: latches are never removed")
358 is Schedulable.M -> muxMovers.remove(schedulable.muxMover)
359 is Schedulable.N -> nodes.remove(schedulable.node)
360 is Schedulable.O -> outputs.remove(schedulable.output)
361 }
362 }
363
adjustDirectUpstreamnull364 fun adjustDirectUpstream(scheduler: Scheduler, oldDepth: Int, newDepth: Int) {
365 for (node in nodes) {
366 node.adjustDirectUpstream(scheduler, oldDepth, newDepth)
367 }
368 }
369
moveIndirectUpstreamToDirectnull370 fun moveIndirectUpstreamToDirect(
371 scheduler: Scheduler,
372 oldIndirectDepth: Int,
373 oldIndirectSet: Set<MuxDeferredNode<*, *, *>>,
374 newDirectDepth: Int,
375 ) {
376 for (node in nodes) {
377 node.moveIndirectUpstreamToDirect(
378 scheduler,
379 oldIndirectDepth,
380 oldIndirectSet,
381 newDirectDepth,
382 )
383 }
384 for (mover in muxMovers) {
385 mover.moveIndirectPatchNodeToDirect(scheduler, oldIndirectDepth, oldIndirectSet)
386 }
387 }
388
adjustIndirectUpstreamnull389 fun adjustIndirectUpstream(
390 scheduler: Scheduler,
391 oldDepth: Int,
392 newDepth: Int,
393 removals: Set<MuxDeferredNode<*, *, *>>,
394 additions: Set<MuxDeferredNode<*, *, *>>,
395 ) {
396 for (node in nodes) {
397 node.adjustIndirectUpstream(scheduler, oldDepth, newDepth, removals, additions)
398 }
399 for (mover in muxMovers) {
400 mover.adjustIndirectPatchNode(scheduler, oldDepth, newDepth, removals, additions)
401 }
402 }
403
moveDirectUpstreamToIndirectnull404 fun moveDirectUpstreamToIndirect(
405 scheduler: Scheduler,
406 oldDirectDepth: Int,
407 newIndirectDepth: Int,
408 newIndirectSet: Set<MuxDeferredNode<*, *, *>>,
409 ) {
410 for (node in nodes) {
411 node.moveDirectUpstreamToIndirect(
412 scheduler,
413 oldDirectDepth,
414 newIndirectDepth,
415 newIndirectSet,
416 )
417 }
418 for (mover in muxMovers) {
419 mover.moveDirectPatchNodeToIndirect(scheduler, newIndirectDepth, newIndirectSet)
420 }
421 }
422
removeIndirectUpstreamnull423 fun removeIndirectUpstream(
424 scheduler: Scheduler,
425 depth: Int,
426 indirectSet: Set<MuxDeferredNode<*, *, *>>,
427 ) {
428 for (node in nodes) {
429 node.removeIndirectUpstream(scheduler, depth, indirectSet)
430 }
431 for (mover in muxMovers) {
432 mover.removeIndirectPatchNode(scheduler, depth, indirectSet)
433 }
434 for (output in outputs) {
435 output.kill()
436 }
437 }
438
removeDirectUpstreamnull439 fun removeDirectUpstream(scheduler: Scheduler, depth: Int) {
440 for (node in nodes) {
441 node.removeDirectUpstream(scheduler, depth)
442 }
443 for (mover in muxMovers) {
444 mover.removeDirectPatchNode(scheduler)
445 }
446 for (output in outputs) {
447 output.kill()
448 }
449 }
450
clearnull451 fun clear() {
452 outputs.clear()
453 stateWriters.clear()
454 muxMovers.clear()
455 nodes.clear()
456 }
457 }
458
459 // TODO: remove this indirection
460 internal sealed interface Schedulable {
461 data class S constructor(val state: StateSource<*>) : Schedulable
462
463 data class M constructor(val muxMover: MuxDeferredNode<*, *, *>) : Schedulable
464
465 data class N constructor(val node: SchedulableNode) : Schedulable
466
467 data class O constructor(val output: Output<*>) : Schedulable
468 }
469
isEmptynull470 internal fun DownstreamSet.isEmpty() =
471 nodes.isEmpty() && outputs.isEmpty() && muxMovers.isEmpty() && stateWriters.isEmpty()
472
473 @Suppress("NOTHING_TO_INLINE") internal inline fun DownstreamSet.isNotEmpty() = !isEmpty()
474
475 internal fun scheduleAll(
476 logIndent: Int,
477 downstreamSet: DownstreamSet,
478 evalScope: EvalScope,
479 ): Boolean {
480 downstreamSet.nodes.forEach { it.schedule(logIndent, evalScope) }
481 downstreamSet.muxMovers.forEach { it.scheduleMover(logIndent, evalScope) }
482 downstreamSet.outputs.forEach { it.schedule(logIndent, evalScope) }
483 downstreamSet.stateWriters.forEach { evalScope.schedule(it) }
484 return downstreamSet.isNotEmpty()
485 }
486