• 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.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