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.debug
18
19 import com.android.systemui.kairos.MutableTState
20 import com.android.systemui.kairos.TState
21 import com.android.systemui.kairos.TStateInit
22 import com.android.systemui.kairos.TStateLoop
23 import com.android.systemui.kairos.internal.DerivedFlatten
24 import com.android.systemui.kairos.internal.DerivedMap
25 import com.android.systemui.kairos.internal.DerivedMapCheap
26 import com.android.systemui.kairos.internal.DerivedZipped
27 import com.android.systemui.kairos.internal.Init
28 import com.android.systemui.kairos.internal.TStateDerived
29 import com.android.systemui.kairos.internal.TStateImpl
30 import com.android.systemui.kairos.internal.TStateSource
31 import com.android.systemui.kairos.util.Just
32 import com.android.systemui.kairos.util.Maybe
33 import com.android.systemui.kairos.util.None
34 import com.android.systemui.kairos.util.flatMap
35 import com.android.systemui.kairos.util.map
36 import com.android.systemui.kairos.util.none
37 import com.android.systemui.kairos.util.orElseGet
38
39 // object IdGen {
40 // private val counter = AtomicLong()
41 // fun getId() = counter.getAndIncrement()
42 // }
43
44 typealias StateGraph = Graph<ActivationInfo>
45
46 sealed class StateInfo(
47 val name: String,
48 val value: Maybe<Any?>,
49 val operator: String,
50 val epoch: Long?,
51 )
52
53 class Source(name: String, value: Maybe<Any?>, operator: String, epoch: Long) :
54 StateInfo(name, value, operator, epoch)
55
56 class Derived(
57 name: String,
58 val type: DerivedStateType,
59 value: Maybe<Any?>,
60 operator: String,
61 epoch: Long?,
62 ) : StateInfo(name, value, operator, epoch)
63
64 sealed interface DerivedStateType
65
66 data object Flatten : DerivedStateType
67
68 data class Mapped(val cheap: Boolean) : DerivedStateType
69
70 data object Combine : DerivedStateType
71
72 sealed class InitInfo(val name: String)
73
74 class Uninitialized(name: String) : InitInfo(name)
75
76 class Initialized(val state: StateInfo) : InitInfo(state.name)
77
78 sealed interface ActivationInfo
79
80 class Inactive(val name: String) : ActivationInfo
81
82 class Active(val nodeInfo: StateInfo) : ActivationInfo
83
84 class Dead(val name: String) : ActivationInfo
85
86 data class Edge(val upstream: Any, val downstream: Any, val tag: Any? = null)
87
88 data class Graph<T>(val nodes: Map<Any, T>, val edges: List<Edge>)
89
90 internal fun TState<*>.dump(infoMap: MutableMap<Any, InitInfo>, edges: MutableList<Edge>) {
91 val init: Init<TStateImpl<Any?>> =
92 when (this) {
93 is TStateInit -> init
94 is TStateLoop -> init
95 is MutableTState -> tState.init
96 }
97 when (val stateMaybe = init.getUnsafe()) {
98 None -> {
99 infoMap[this] = Uninitialized(init.name ?: init.toString())
100 }
101 is Just -> {
102 stateMaybe.value.dump(infoMap, edges)
103 }
104 }
105 }
106
dumpnull107 internal fun TStateImpl<*>.dump(infoById: MutableMap<Any, InitInfo>, edges: MutableList<Edge>) {
108 val state = this
109 if (state in infoById) return
110 val stateInfo =
111 when (state) {
112 is TStateDerived -> {
113 val type =
114 when (state) {
115 is DerivedFlatten -> {
116 state.upstream.dump(infoById, edges)
117 edges.add(
118 Edge(upstream = state.upstream, downstream = state, tag = "outer")
119 )
120 state.upstream
121 .getUnsafe()
122 .orElseGet { null }
123 ?.let {
124 edges.add(
125 Edge(upstream = it, downstream = state, tag = "inner")
126 )
127 it.dump(infoById, edges)
128 }
129 Flatten
130 }
131 is DerivedMap<*, *> -> {
132 state.upstream.dump(infoById, edges)
133 edges.add(Edge(upstream = state.upstream, downstream = state))
134 Mapped(cheap = false)
135 }
136 is DerivedZipped<*, *> -> {
137 state.upstream.forEach { (key, upstream) ->
138 edges.add(
139 Edge(upstream = upstream, downstream = state, tag = "key=$key")
140 )
141 upstream.dump(infoById, edges)
142 }
143 Combine
144 }
145 }
146 Derived(
147 state.name ?: state.operatorName,
148 type,
149 state.getCachedUnsafe(),
150 state.operatorName,
151 state.invalidatedEpoch,
152 )
153 }
154 is TStateSource ->
155 Source(
156 state.name ?: state.operatorName,
157 state.getStorageUnsafe(),
158 state.operatorName,
159 state.writeEpoch,
160 )
161 is DerivedMapCheap<*, *> -> {
162 state.upstream.dump(infoById, edges)
163 edges.add(Edge(upstream = state.upstream, downstream = state))
164 val type = Mapped(cheap = true)
165 Derived(
166 state.name ?: state.operatorName,
167 type,
168 state.getUnsafe(),
169 state.operatorName,
170 null,
171 )
172 }
173 }
174 infoById[state] = Initialized(stateInfo)
175 }
176
getUnsafenull177 private fun <A> TStateImpl<A>.getUnsafe(): Maybe<A> =
178 when (this) {
179 is TStateDerived -> getCachedUnsafe()
180 is TStateSource -> getStorageUnsafe()
181 is DerivedMapCheap<*, *> -> none
182 }
183
getUnsafeWithEpochnull184 private fun <A> TStateImpl<A>.getUnsafeWithEpoch(): Maybe<Pair<A, Long>> =
185 when (this) {
186 is TStateDerived -> getCachedUnsafe().map { it to invalidatedEpoch }
187 is TStateSource -> getStorageUnsafe().map { it to writeEpoch }
188 is DerivedMapCheap<*, *> -> none
189 }
190
191 /**
192 * Returns the current value held in this [TState], or [none] if the [TState] has not been
193 * initialized.
194 *
195 * The returned [Long] is the *epoch* at which the internal cache was last updated. This can be used
196 * to identify values which are out-of-date.
197 */
sampleUnsafenull198 fun <A> TState<A>.sampleUnsafe(): Maybe<Pair<A, Long>> =
199 when (this) {
200 is MutableTState -> tState.init.getUnsafe().flatMap { it.getUnsafeWithEpoch() }
201 is TStateInit -> init.getUnsafe().flatMap { it.getUnsafeWithEpoch() }
202 is TStateLoop -> this.init.getUnsafe().flatMap { it.getUnsafeWithEpoch() }
203 }
204