[go: nahoru, domu]

blob: 21d4e874f33d8f89a9e6f79c074acfb74e6f8a90 [file] [log] [blame]
Leland Richardson6cab6e32019-05-02 11:52:13 -07001/*
2 * Copyright 2019 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
Leland Richardson656c1132020-06-03 09:12:42 -070017@file:OptIn(
18 InternalComposeApi::class,
19 ExperimentalComposeApi::class,
20 ComposeCompilerApi::class
21)
Leland Richardson6cab6e32019-05-02 11:52:13 -070022package androidx.compose
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -070023
Chuck Jazdzewski776d8232019-08-21 16:24:03 -070024import androidx.compose.SlotTable.Companion.EMPTY
Chuck Jazdzewski4fab4b92020-06-15 09:51:37 -070025import androidx.compose.tooling.InspectionTables
Cătălin Tudora2ed24e2019-07-04 13:50:58 +010026
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -070027internal typealias Change<N> = (
28 applier: Applier<N>,
29 slots: SlotWriter,
Adam Powell8dec9b72020-06-19 14:24:14 -070030 lifecycleManager: LifecycleManager
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -070031) -> Unit
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -070032
33private class GroupInfo(
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -070034 /**
35 * The current location of the slot relative to the start location of the pending slot changes
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -070036 */
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -070037 var slotIndex: Int,
38
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -070039 /**
40 * The current location of the first node relative the start location of the pending node
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -070041 * changes
42 */
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -070043 var nodeIndex: Int,
44
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -070045 /**
46 * The current number of nodes the group contains after changes have been applied
47 */
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -070048 var nodeCount: Int
49)
50
Adam Powell8dec9b72020-06-19 14:24:14 -070051internal interface LifecycleManager {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -070052 fun entering(instance: CompositionLifecycleObserver)
53 fun leaving(instance: CompositionLifecycleObserver)
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -080054}
55
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -070056/**
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -070057 * Pending starts when the key is different than expected indicating that the structure of the tree
58 * changed. It is used to determine how to update the nodes and the slot table when changes to the
59 * structure of the tree is detected.
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -070060 */
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -070061private class Pending(
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -070062 val keyInfos: MutableList<KeyInfo>,
63 val startIndex: Int
64) {
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -070065 var groupIndex: Int = 0
66
67 init {
Cătălin Tudora2ed24e2019-07-04 13:50:58 +010068 require(startIndex >= 0) { "Invalid start index" }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -070069 }
Chuck Jazdzewski3d43fd82018-11-27 12:47:48 -080070
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -070071 private val usedKeys = mutableListOf<KeyInfo>()
72 private val groupInfos = run {
73 var runningNodeIndex = 0
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -070074 val result = hashMapOf<Group, GroupInfo>()
Chuck Jazdzewskia7f2c872020-03-05 13:49:29 -080075 for (index in 0 until keyInfos.size) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -070076 val keyInfo = keyInfos[index]
77 result[keyInfo.group] = GroupInfo(index, runningNodeIndex, keyInfo.nodes)
78 runningNodeIndex += keyInfo.nodes
Mikhail Levchenko059f6b62019-05-08 19:55:44 +030079 }
Chuck Jazdzewskia7f2c872020-03-05 13:49:29 -080080 result
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -070081 }
82
83 /**
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -070084 * A multi-map of keys from the previous composition. The keys can be retrieved in the order
85 * they were generated by the previous composition.
86 */
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -070087 val keyMap by lazy {
88 multiMap<Any, KeyInfo>().also {
Chuck Jazdzewskia7f2c872020-03-05 13:49:29 -080089 for (index in 0 until keyInfos.size) {
90 val keyInfo = keyInfos[index]
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -070091 @Suppress("ReplacePutWithAssignment")
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -070092 it.put(keyInfo.joinedKey, keyInfo)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -070093 }
94 }
95 }
96
97 /**
98 * Get the next key information for the given key.
99 */
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -0700100 fun getNext(key: Int, dataKey: Any?): KeyInfo? {
101 val joinedKey: Any = if (dataKey != null) JoinedKey(key, dataKey) else key
102 return keyMap.pop(joinedKey)
103 }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700104
105 /**
106 * Record that this key info was generated.
107 */
108 fun recordUsed(keyInfo: KeyInfo) = usedKeys.add(keyInfo)
109
110 val used: List<KeyInfo> get() = usedKeys
111
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -0700112 // TODO(chuckj): This is a correct but expensive implementation (worst cases of O(N^2)). Rework
113 // to O(N)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700114 fun registerMoveSlot(from: Int, to: Int) {
115 if (from > to) {
116 groupInfos.values.forEach { group ->
117 val position = group.slotIndex
118 if (position == from) group.slotIndex = to
119 else if (position in to until from) group.slotIndex = position + 1
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700120 }
121 } else if (to > from) {
122 groupInfos.values.forEach { group ->
123 val position = group.slotIndex
124 if (position == from) group.slotIndex = to
125 else if (position in (from + 1) until to) group.slotIndex = position - 1
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700126 }
127 }
128 }
129
130 fun registerMoveNode(from: Int, to: Int, count: Int) {
131 if (from > to) {
132 groupInfos.values.forEach { group ->
133 val position = group.nodeIndex
134 if (position in from until from + count) group.nodeIndex = to + (position - from)
135 else if (position in to until from) group.nodeIndex = position + count
136 }
137 } else if (to > from) {
138 groupInfos.values.forEach { group ->
139 val position = group.nodeIndex
140 if (position in from until from + count) group.nodeIndex = to + (position - from)
141 else if (position in (from + 1) until to) group.nodeIndex = position - count
142 }
143 }
144 }
145
146 fun registerInsert(keyInfo: KeyInfo, insertIndex: Int) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700147 groupInfos[keyInfo.group] = GroupInfo(-1, insertIndex, 0)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700148 }
149
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700150 fun updateNodeCount(group: Group, newCount: Int): Boolean {
151 val groupInfo = groupInfos[group]
152 if (groupInfo != null) {
153 val index = groupInfo.nodeIndex
154 val difference = newCount - groupInfo.nodeCount
155 groupInfo.nodeCount = newCount
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700156 if (difference != 0) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700157 groupInfos.values.forEach { childGroupInfo ->
158 if (childGroupInfo.nodeIndex >= index && childGroupInfo != groupInfo)
159 childGroupInfo.nodeIndex += difference
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -0700160 }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700161 }
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700162 return true
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700163 }
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700164 return false
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700165 }
166
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700167 fun slotPositionOf(keyInfo: KeyInfo) = groupInfos[keyInfo.group]?.slotIndex ?: -1
168 fun nodePositionOf(keyInfo: KeyInfo) = groupInfos[keyInfo.group]?.nodeIndex ?: -1
169 fun updatedNodeCountOf(keyInfo: KeyInfo) = groupInfos[keyInfo.group]?.nodeCount ?: keyInfo.nodes
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700170}
171
Leland Richardson9138cbe2019-04-24 14:53:02 -0700172private class Invalidation(
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -0700173 val scope: RecomposeScope,
Chuck Jazdzewskic9e0efe2019-05-13 16:02:27 -0700174 var location: Int
Leland Richardson9138cbe2019-04-24 14:53:02 -0700175)
Chuck Jazdzewski3d43fd82018-11-27 12:47:48 -0800176
Leland Richardson656c1132020-06-03 09:12:42 -0700177@ComposeCompilerApi
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700178interface ScopeUpdateScope {
Leland Richardson797bd0cd2020-05-07 15:16:51 -0700179 fun updateScope(block: (Composer<*>, Int, Int) -> Unit)
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700180}
181
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800182internal enum class InvalidationResult {
183 /**
184 * The invalidation was ignored because the associated recompose scope is no longer part of the
185 * composition or has yet to be entered in the composition. This could occur for invalidations
186 * called on scopes that are no longer part of composition or if the scope was invalidated
187 * before the applyChanges() was called that will enter the scope into the composition.
188 */
189 IGNORED,
190
191 /**
192 * The composition is not currently composing and the invalidation was recorded for a future
193 * composition. A recomposition requested to be scheduled.
194 */
195 SCHEDULED,
196
197 /**
198 * The composition that owns the recompose scope is actively composing but the scope has
199 * already been composed or is in the process of composing. The invalidation is treated as
200 * SCHEDULED above.
201 */
202 DEFERRED,
203
204 /**
205 * The composition that owns the recompose scope is actively composing and the invalidated
206 * scope has not been composed yet but will be recomposed before the composition completes. A
207 * new recomposition was not scheduled for this invalidation.
208 */
209 IMMINENT
210}
211
Chuck Jazdzewski0f26b682020-03-18 11:42:25 -0700212/**
213 * A RecomposeScope is created for a region of the composition that can be recomposed independently
214 * of the rest of the composition. The composer will position the slot table to the location
215 * stored in [anchor] and call [block] when recomposition is requested. It is created by
216 * [Composer.startRestartGroup] and is used to track how to restart the group.
217 */
Chuck Jazdzewski7d869002020-06-24 13:31:48 -0700218internal class RecomposeScope(var composer: Composer<*>?, val key: Int) : ScopeUpdateScope {
Chuck Jazdzewski0f26b682020-03-18 11:42:25 -0700219 /**
220 * An anchor to the location in the slot table that start the group associated with this
221 * recompose scope. This value is set by [composer] when the scope is committed to the slot
222 * table by [Composer.applyChanges].
223 */
Chuck Jazdzewskiab3c9522019-08-20 12:41:34 -0700224 var anchor: Anchor? = null
Chuck Jazdzewski0f26b682020-03-18 11:42:25 -0700225
226 /**
227 * Return whether the scope is valid. A scope becomes invalid when the slots it updates are
228 * removed from the slot table. For example, if the scope is in the then clause of an if
229 * statement that later becomes false.
230 */
Chuck Jazdzewski7d869002020-06-24 13:31:48 -0700231 val valid: Boolean get() = composer != null && anchor?.valid ?: false
Chuck Jazdzewski0f26b682020-03-18 11:42:25 -0700232
233 /**
234 * Used is set when the [RecomposeScope] is used by, for example, [invalidate]. This is used
235 * as the result of [Composer.endRestartGroup] and indicates whether the lambda that is stored
236 * in [block] will be used.
237 */
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700238 var used = false
239
Leland Richardson797bd0cd2020-05-07 15:16:51 -0700240 var defaultsInScope = false
241 var defaultsInvalid = false
242
Leland Richardson797bd0cd2020-05-07 15:16:51 -0700243 var requiresRecompose = false
244
Chuck Jazdzewski2a13c352020-03-19 13:50:58 -0700245 /**
Chuck Jazdzewski0f26b682020-03-18 11:42:25 -0700246 * The lambda to call to restart the scopes composition.
247 */
Leland Richardson797bd0cd2020-05-07 15:16:51 -0700248 private var block: ((Composer<*>, Int, Int) -> Unit)? = null
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700249
Chuck Jazdzewski0f26b682020-03-18 11:42:25 -0700250 /**
251 * Restart the scope's composition. It is an error if [block] was not updated. The code
252 * generated by the compiler ensures that when the recompose scope is used then [block] will
253 * be set but if might occur if the compiler is out-of-date (or ahead of the runtime) or
254 * incorrect direct calls to [Composer.startRestartGroup] and [Composer.endRestartGroup].
255 */
Leland Richardson76600e32020-01-15 17:22:29 -0800256 fun <N> compose(composer: Composer<N>) {
Leland Richardson797bd0cd2020-05-07 15:16:51 -0700257 block?.invoke(composer, key, 1) ?: error("Invalid restart scope")
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700258 }
259
Chuck Jazdzewski0f26b682020-03-18 11:42:25 -0700260 /**
261 * Invalidate the group which will cause [composer] to request this scope be recomposed.
262 */
Chuck Jazdzewski7d869002020-06-24 13:31:48 -0700263 fun invalidate(): InvalidationResult = composer?.invalidate(this) ?: InvalidationResult.IGNORED
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700264
Chuck Jazdzewski0f26b682020-03-18 11:42:25 -0700265 /**
266 * Update [block]. The scope is returned by [Composer.endRestartGroup] when [used] is true
267 * and implements [ScopeUpdateScope].
268 */
Leland Richardson797bd0cd2020-05-07 15:16:51 -0700269 override fun updateScope(block: (Composer<*>, Int, Int) -> Unit) { this.block = block }
Chuck Jazdzewski3d43fd82018-11-27 12:47:48 -0800270}
271
Chuck Jazdzewski0f26b682020-03-18 11:42:25 -0700272/**
273 * An instance to hold a value provided by [Providers] and is created by the
274 * [ProvidableAmbient.provides] infixed operator.
275 */
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800276class ProvidedValue<T> internal constructor(val ambient: Ambient<T>, val value: T)
277
Chuck Jazdzewski0f26b682020-03-18 11:42:25 -0700278/**
279 * An ambient map is is an immutable map that maps ambient keys to a provider of their current
280 * value. It is used both to represent the values provided by a [Providers] call and the combined
281 * scope of all provided ambients.
282 */
Chuck Jazdzewski7d0ef182020-01-23 14:46:22 -0800283internal typealias AmbientMap = BuildableMap<Ambient<Any?>, State<Any?>>
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800284
285@Suppress("UNCHECKED_CAST")
286internal fun <T> AmbientMap.contains(key: Ambient<T>) = this.containsKey(key as Ambient<Any?>)
287
288@Suppress("UNCHECKED_CAST")
289internal fun <T> AmbientMap.getValueOf(key: Ambient<T>) = this[key as Ambient<Any?>]?.value as T
290
Leland Richardson534a3462020-02-05 23:26:02 -0800291@Composable
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800292private fun ambientMapOf(values: Array<out ProvidedValue<*>>): AmbientMap {
293 val result: AmbientMap = buildableMapOf()
294 return result.mutate {
295 for (provided in values) {
296 @Suppress("UNCHECKED_CAST")
297 it[provided.ambient as Ambient<Any?>] = provided.ambient.provided(provided.value)
298 }
299 }
300}
301
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700302/**
303 * Implementation of a composer for mutable tree.
304 */
Leland Richardson19285b12020-06-18 13:37:21 -0700305class Composer<N>(
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700306 /**
307 * Backing storage for the composition
308 */
Chuck Jazdzewskie7725b92019-06-24 08:51:16 -0700309 val slotTable: SlotTable,
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700310
311 /**
312 * An adapter that applies changes to the tree using the Applier abstraction.
313 */
Leland Richardson19285b12020-06-18 13:37:21 -0700314 @ComposeCompilerApi
315 val applier: Applier<N>,
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700316
317 /**
318 * Manager for scheduling recompositions.
319 */
Leland Richardson656c1132020-06-03 09:12:42 -0700320 @ExperimentalComposeApi
Adam Powellc9167422020-04-08 16:50:37 -0700321 val recomposer: Recomposer
Leland Richardson7d078652020-06-02 09:05:03 -0700322) {
Leland Richardson19285b12020-06-18 13:37:21 -0700323 init {
324 FrameManager.ensureStarted()
325 }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700326 private val changes = mutableListOf<Change<N>>()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700327 private val lifecycleObservers = HashMap<
328 CompositionLifecycleObserverHolder,
329 CompositionLifecycleObserverHolder
330 >()
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700331 private val pendingStack = Stack<Pending?>()
332 private var pending: Pending? = null
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700333 private var nodeIndex: Int = 0
334 private var nodeIndexStack = IntStack()
335 private var groupNodeCount: Int = 0
336 private var groupNodeCountStack = IntStack()
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700337 private val nodeCountOverrides = HashMap<Group, Int>()
Chuck Jazdzewskie7725b92019-06-24 08:51:16 -0700338 private var collectKeySources = false
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700339
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700340 private var nodeExpected = false
Adam Powell11d00c72020-04-15 09:28:13 -0700341 private val invalidations: MutableList<Invalidation> = mutableListOf()
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700342 private val entersStack = IntStack()
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700343 private var parentProvider: AmbientMap = buildableMapOf()
344 private val providerUpdates = HashMap<Group, AmbientMap>()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700345 private var providersInvalid = false
346 private val providersInvalidStack = IntStack()
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800347
Chuck Jazdzewski3d43fd82018-11-27 12:47:48 -0800348 private val invalidateStack = Stack<RecomposeScope>()
Matvei Malkovac530be2019-05-14 19:43:14 +0100349
350 internal var parentReference: CompositionReference? = null
Leland Richardson0b5bf702019-05-24 17:07:21 -0700351 internal var isComposing = false
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700352
Matvei Malkov0b5d5962019-04-24 13:50:35 +0100353 private val changesAppliedObservers = mutableListOf<() -> Unit>()
354
355 private fun dispatchChangesAppliedObservers() {
Leland Richardsonbf5f6a62019-05-24 16:25:46 -0700356 trace("Compose:dispatchChangesAppliedObservers") {
357 val listeners = changesAppliedObservers.toTypedArray()
358 changesAppliedObservers.clear()
359 listeners.forEach { it() }
360 }
Matvei Malkov0b5d5962019-04-24 13:50:35 +0100361 }
362
363 internal fun addChangesAppliedObserver(l: () -> Unit) {
364 changesAppliedObservers.add(l)
365 }
366
367 internal fun removeChangesAppliedObserver(l: () -> Unit) {
368 changesAppliedObservers.remove(l)
369 }
370
Chuck Jazdzewski67e59272020-04-15 15:58:20 -0700371 private var reader: SlotReader = slotTable.openReader().also { it.close() }
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700372
373 private val insertTable = SlotTable()
374 private var writer: SlotWriter = insertTable.openWriter().also { it.close() }
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700375 private var hasProvider = false
Chuck Jazdzewski67e59272020-04-15 15:58:20 -0700376 private var insertAnchor: Anchor = insertTable.anchor(0)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700377 private val insertFixups = mutableListOf<Change<N>>()
Chuck Jazdzewski0a9f7fa2018-12-20 16:55:46 -0800378
Leland Richardson19285b12020-06-18 13:37:21 -0700379 @InternalComposeApi
380 fun composeRoot(block: () -> Unit) {
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800381 startRoot()
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -0700382 startGroup(invocationKey, invocation)
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800383 block()
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700384 endGroup()
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800385 endRoot()
Chuck Jazdzewski0a9f7fa2018-12-20 16:55:46 -0800386 }
387
Leland Richardsoncb229cb2020-03-11 19:07:23 -0700388 /**
389 * Inserts a "Replaceable Group" starting marker in the slot table at the current execution
390 * position. A Replaceable Group is a group which cannot be moved between its siblings, but
391 * can be removed or inserted. These groups are inserted by the compiler around branches of
392 * conditional logic in Composable functions such as if expressions, when expressions, early
393 * returns, and null-coalescing operators.
394 *
395 * A call to [startReplaceableGroup] must be matched with a corresponding call to
396 * [endReplaceableGroup].
397 *
398 * Warning: This is expected to be executed by the compiler only and should not be called
399 * directly from source code. Call this API at your own risk.
400 *
401 * @param key The source-location-based key for the group. Expected to be unique among its
402 * siblings.
403 *
404 * @see [endReplaceableGroup]
405 * @see [startMovableGroup]
Leland Richardsoncb229cb2020-03-11 19:07:23 -0700406 * @see [startRestartGroup]
407 */
Leland Richardson656c1132020-06-03 09:12:42 -0700408 @ComposeCompilerApi
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -0700409 fun startReplaceableGroup(key: Int) = start(key, null, false, null)
Leland Richardsoncb229cb2020-03-11 19:07:23 -0700410
Chuck Jazdzewski2ee92272020-07-07 08:35:12 -0700411 @ComposeCompilerApi
412 fun startReplaceableGroup(key: Int, sourceInformation: String?) =
413 start(key, null, false, sourceInformation)
414
Leland Richardsoncb229cb2020-03-11 19:07:23 -0700415 /**
416 * Indicates the end of a "Replaceable Group" at the current execution position. A
417 * Replaceable Group is a group which cannot be moved between its siblings, but
418 * can be removed or inserted. These groups are inserted by the compiler around branches of
419 * conditional logic in Composable functions such as if expressions, when expressions, early
420 * returns, and null-coalescing operators.
421 *
422 * Warning: This is expected to be executed by the compiler only and should not be called
423 * directly from source code. Call this API at your own risk.
424 *
425 * @see [startReplaceableGroup]
426 */
Leland Richardson656c1132020-06-03 09:12:42 -0700427 @ComposeCompilerApi
Leland Richardsoncb229cb2020-03-11 19:07:23 -0700428 fun endReplaceableGroup() = endGroup()
429
430 /**
Leland Richardson797bd0cd2020-05-07 15:16:51 -0700431 *
432 * Warning: This is expected to be executed by the compiler only and should not be called
433 * directly from source code. Call this API at your own risk.
434 *
435 */
Leland Richardson656c1132020-06-03 09:12:42 -0700436 @ComposeCompilerApi
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -0700437 fun startDefaults() = start(0, null, false, null)
Leland Richardson797bd0cd2020-05-07 15:16:51 -0700438
439 /**
440 *
441 * Warning: This is expected to be executed by the compiler only and should not be called
442 * directly from source code. Call this API at your own risk.
443 *
444 * @see [startReplaceableGroup]
445 */
Leland Richardson656c1132020-06-03 09:12:42 -0700446 @ComposeCompilerApi
Leland Richardson797bd0cd2020-05-07 15:16:51 -0700447 fun endDefaults() {
448 endGroup()
449 val scope = currentRecomposeScope
450 if (scope != null && scope.used) {
451 scope.defaultsInScope = true
452 }
453 }
454
Leland Richardson656c1132020-06-03 09:12:42 -0700455 @ComposeCompilerApi
Leland Richardson797bd0cd2020-05-07 15:16:51 -0700456 val defaultsInvalid: Boolean
457 get() {
458 return providersInvalid || currentRecomposeScope?.defaultsInvalid == true
459 }
460
461 /**
Leland Richardsoncb229cb2020-03-11 19:07:23 -0700462 * Inserts a "Movable Group" starting marker in the slot table at the current execution
463 * position. A Movable Group is a group which can be moved or reordered between its siblings
Leland Richardsonb4482ed2020-03-15 22:15:44 -0700464 * and retain slot table state, in addition to being removed or inserted. Movable Groups
Leland Richardsoncb229cb2020-03-11 19:07:23 -0700465 * are more expensive than other groups because when they are encountered with a mismatched
466 * key in the slot table, they must be held on to temporarily until the entire parent group
467 * finishes execution in case it moved to a later position in the group. Movable groups are
468 * only inserted by the compiler as a result of calls to [key].
469 *
470 * A call to [startMovableGroup] must be matched with a corresponding call to [endMovableGroup].
471 *
472 * Warning: This is expected to be executed by the compiler only and should not be called
473 * directly from source code. Call this API at your own risk.
474 *
Leland Richardson797bd0cd2020-05-07 15:16:51 -0700475 * @param key The source-location-based key for the group. Expected to be unique among its
476 * siblings.
477 *
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -0700478 * @param dataKey Additional identifying information to compound with [key]. If there are
Leland Richardson797bd0cd2020-05-07 15:16:51 -0700479 * multiple values, this is expected to be compounded together with [joinKey]. Whatever value
480 * is passed in here is expected to have a meaningful [equals] and [hashCode] implementation.
Leland Richardsoncb229cb2020-03-11 19:07:23 -0700481 *
482 * @see [endMovableGroup]
483 * @see [key]
484 * @see [joinKey]
485 * @see [startReplaceableGroup]
Leland Richardsoncb229cb2020-03-11 19:07:23 -0700486 * @see [startRestartGroup]
487 */
Leland Richardson656c1132020-06-03 09:12:42 -0700488 @ComposeCompilerApi
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -0700489 fun startMovableGroup(key: Int, dataKey: Any?) = start(key, dataKey, false, null)
Leland Richardsoncb229cb2020-03-11 19:07:23 -0700490
Chuck Jazdzewski2ee92272020-07-07 08:35:12 -0700491 @ComposeCompilerApi
492 fun startMovableGroup(key: Int, dataKey: Any?, sourceInformation: String?) =
493 start(key, dataKey, false, sourceInformation)
494
Leland Richardsoncb229cb2020-03-11 19:07:23 -0700495 /**
496 * Indicates the end of a "Movable Group" at the current execution position. A Movable Group is
497 * a group which can be moved or reordered between its siblings and retain slot table state,
498 * in addition to being removed or inserted. These groups are only valid when they are
499 * inserted as direct children of Container Groups. Movable Groups are more expensive than
500 * other groups because when they are encountered with a mismatched key in the slot table,
501 * they must be held on to temporarily until the entire parent group finishes execution in
502 * case it moved to a later position in the group. Movable groups are only inserted by the
503 * compiler as a result of calls to [key].
504 *
505 * Warning: This is expected to be executed by the compiler only and should not be called
506 * directly from source code. Call this API at your own risk.
507 *
508 * @see [startMovableGroup]
509 */
Leland Richardson656c1132020-06-03 09:12:42 -0700510 @ComposeCompilerApi
Leland Richardsoncb229cb2020-03-11 19:07:23 -0700511 fun endMovableGroup() = endGroup()
512
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700513 /**
514 * Start the composition. This should be called, and only be called, as the first group in
515 * the composition.
516 */
Leland Richardson656c1132020-06-03 09:12:42 -0700517 internal fun startRoot() {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700518 reader = slotTable.openReader()
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -0700519 startGroup(rootKey)
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800520 parentReference?.let { parentRef ->
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700521 parentProvider = parentRef.getAmbientScope()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700522 providersInvalidStack.push(providersInvalid.asInt())
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700523 providersInvalid = changed(parentProvider)
Chuck Jazdzewski4fab4b92020-06-15 09:51:37 -0700524 collectKeySources = parentRef.collectingKeySources
525 resolveAmbient(InspectionTables, parentProvider)?.let {
526 it.add(slotTable)
527 parentRef.recordInspectionTable(it)
528 }
Chuck Jazdzewski8e7017f2020-06-05 11:21:24 -0700529 startGroup(parentRef.compoundHashKey)
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800530 }
Chuck Jazdzewski0a9f7fa2018-12-20 16:55:46 -0800531 }
532
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700533 /**
534 * End the composition. This should be called, and only be called, to end the first group in
535 * the composition.
536 */
Leland Richardson656c1132020-06-03 09:12:42 -0700537 internal fun endRoot() {
Chuck Jazdzewski8e7017f2020-06-05 11:21:24 -0700538 if (parentReference != null) {
539 endGroup()
540 }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700541 endGroup()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700542 recordEndRoot()
Chuck Jazdzewski006734c2018-09-21 09:32:38 -0700543 finalizeCompose()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700544 reader.close()
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700545 }
546
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700547 /**
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800548 * Discard a pending composition because an error was encountered during composition
549 */
Leland Richardson656c1132020-06-03 09:12:42 -0700550 internal fun abortRoot() {
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800551 cleanUpCompose()
552 pendingStack.clear()
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800553 nodeIndexStack.clear()
554 groupNodeCountStack.clear()
555 entersStack.clear()
556 providersInvalidStack.clear()
557 invalidateStack.clear()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700558 reader.close()
Andrey Kulikov1e06a872020-02-28 15:52:00 +0000559 currentCompoundKeyHash = 0
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700560 nodeExpected = false
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800561 }
562
563 /**
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700564 * True if the composition is currently scheduling nodes to be inserted into the tree. During
565 * first composition this is always true. During recomposition this is true when new nodes
566 * are being scheduled to be added to the tree.
567 */
Leland Richardson656c1132020-06-03 09:12:42 -0700568 @ComposeCompilerApi
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700569 var inserting: Boolean = false
570 private set
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700571
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700572 /**
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800573 * True if the composition should be checking if the composable functions can be skipped.
574 */
Leland Richardson656c1132020-06-03 09:12:42 -0700575 @ComposeCompilerApi
Leland Richardson797bd0cd2020-05-07 15:16:51 -0700576 val skipping: Boolean get() {
577 return !inserting &&
578 !providersInvalid &&
579 currentRecomposeScope?.requiresRecompose == false
580 }
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800581
582 /**
Andrey Kulikov1e06a872020-02-28 15:52:00 +0000583 * Returns the hash of the compound key calculated as a combination of the keys of all the
584 * currently started groups via [startGroup].
585 */
Leland Richardson656c1132020-06-03 09:12:42 -0700586 @ExperimentalComposeApi
Andrey Kulikov1e06a872020-02-28 15:52:00 +0000587 var currentCompoundKeyHash: Int = 0
588 private set
589
590 /**
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700591 * Start collecting key source information. This enables enables the tool API to be able to
592 * determine the source location of where groups and nodes are created.
593 */
Leland Richardson656c1132020-06-03 09:12:42 -0700594 @InternalComposeApi
Chuck Jazdzewskie7725b92019-06-24 08:51:16 -0700595 fun collectKeySourceInformation() {
596 collectKeySources = true
597 }
598
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700599 /**
Adam Powell8dec9b72020-06-19 14:24:14 -0700600 * Helper for collecting lifecycle enter and leave events for later strictly ordered dispatch.
601 * [lifecycleObservers] should be the long-lived set of observers tracked over time; brand new
602 * additions or leaves from this set will be tracked by the [LifecycleManager] callback methods.
603 * Call [dispatchLifecycleObservers] to invoke the observers in LIFO order - last in,
604 * first out.
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700605 */
Adam Powell8dec9b72020-06-19 14:24:14 -0700606 private class LifecycleEventDispatcher(
607 private val lifecycleObservers: MutableMap<CompositionLifecycleObserverHolder,
608 CompositionLifecycleObserverHolder>
609 ) : LifecycleManager {
610 private val enters = mutableSetOf<CompositionLifecycleObserverHolder>()
611 private val leaves = mutableSetOf<CompositionLifecycleObserverHolder>()
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -0800612
Adam Powell8dec9b72020-06-19 14:24:14 -0700613 override fun entering(instance: CompositionLifecycleObserver) {
614 val holder = CompositionLifecycleObserverHolder(instance)
615 lifecycleObservers.getOrPut(holder) {
616 enters.add(holder)
617 holder
618 }.apply { count++ }
619 }
620
621 override fun leaving(instance: CompositionLifecycleObserver) {
622 val holder = CompositionLifecycleObserverHolder(instance)
623 val left = lifecycleObservers[holder]?.let {
624 if (--it.count == 0) {
625 leaves.add(it)
626 it
627 } else null
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -0800628 }
Adam Powell8dec9b72020-06-19 14:24:14 -0700629 if (left != null) lifecycleObservers.remove(left)
630 }
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -0800631
Adam Powell8dec9b72020-06-19 14:24:14 -0700632 fun dispatchLifecycleObservers() {
Leland Richardsonbf5f6a62019-05-24 16:25:46 -0700633 trace("Compose:lifecycles") {
634 // Send lifecycle leaves
Chuck Jazdzewskia7f2c872020-03-05 13:49:29 -0800635 if (leaves.isNotEmpty()) {
636 for (holder in leaves.reversed()) {
637 // The count of the holder might be greater than 0 here as it might leave one
638 // part of the composition and reappear in another. Only send a leave if the
639 // count is still 0 after all changes have been applied.
640 if (holder.count == 0) {
641 holder.instance.onLeave()
642 lifecycleObservers.remove(holder)
643 }
Leland Richardsonbf5f6a62019-05-24 16:25:46 -0700644 }
645 }
646
647 // Send lifecycle enters
Chuck Jazdzewskia7f2c872020-03-05 13:49:29 -0800648 if (enters.isNotEmpty()) {
649 for (holder in enters) {
650 holder.instance.onEnter()
651 }
Leland Richardsonbf5f6a62019-05-24 16:25:46 -0700652 }
653 }
Adam Powell8dec9b72020-06-19 14:24:14 -0700654 }
655 }
Leland Richardsonbf5f6a62019-05-24 16:25:46 -0700656
Adam Powell8dec9b72020-06-19 14:24:14 -0700657 /**
658 * Apply the changes to the tree that were collected during the last composition.
659 */
660 @InternalComposeApi
661 fun applyChanges() {
662 trace("Compose:applyChanges") {
663 invalidateStack.clear()
664 val invalidationAnchors = invalidations.map { slotTable.anchor(it.location) to it }
665 val manager = LifecycleEventDispatcher(lifecycleObservers)
666
667 // Apply all changes
668 slotTable.write { slots ->
669 changes.forEach { change -> change(applier, slots, manager) }
670 changes.clear()
671 }
672
673 providerUpdates.clear()
674
675 @Suppress("ReplaceManualRangeWithIndicesCalls") // Avoids allocation of an iterator
676 for (index in 0 until invalidationAnchors.size) {
677 val (anchor, invalidation) = invalidationAnchors[index]
678 invalidation.location = slotTable.anchorLocation(anchor)
679 }
680
681 manager.dispatchLifecycleObservers()
682 dispatchChangesAppliedObservers()
683 }
684 }
685
686 internal fun dispose() {
687 trace("Compose:Composer.dispose") {
688 parentReference?.unregisterComposer(this)
689 invalidateStack.clear()
690 invalidations.clear()
691 changes.clear()
692 applier.clear()
693 if (slotTable.size > 0) {
694 val manager = LifecycleEventDispatcher(lifecycleObservers)
695 slotTable.write { writer ->
696 writer.removeCurrentGroup(manager)
697 }
698 providerUpdates.clear()
699 manager.dispatchLifecycleObservers()
700 }
Leland Richardsonbf5f6a62019-05-24 16:25:46 -0700701 dispatchChangesAppliedObservers()
Leland Richardsonf87e0e92019-01-08 19:09:03 -0800702 }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700703 }
704
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700705 /**
706 * Start a group with the given key. During recomposition if the currently expected group does
707 * not match the given key a group the groups emitted in the same parent group are inspected
708 * to determine if one of them has this key and that group the first such group is moved
709 * (along with any nodes emitted by the group) to the current position and composition
710 * continues. If no group with this key is found, then the composition shifts into insert
711 * mode and new nodes are added at the current position.
712 *
713 * @param key The key for the group
714 */
Leland Richardson656c1132020-06-03 09:12:42 -0700715 internal fun startGroup(key: Int) = start(key, null, false, null)
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -0700716
Leland Richardson656c1132020-06-03 09:12:42 -0700717 internal fun startGroup(key: Int, dataKey: Any?) = start(key, dataKey, false, null)
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700718
719 /**
720 * End the current group.
721 */
Leland Richardson656c1132020-06-03 09:12:42 -0700722 internal fun endGroup() = end(false)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700723
Chuck Jazdzewski776d8232019-08-21 16:24:03 -0700724 private fun skipGroup() {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700725 groupNodeCount += reader.skipGroup()
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700726 }
727
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700728 /**
729 * Start emitting a node. It is required that one of [emitNode] or [createNode] is called
730 * after [startNode]. Similar to [startGroup], if, during recomposition, the current node
731 * does not have the provided key a node with that key is scanned for and moved into the
732 * current position if found, if no such node is found the composition switches into insert
733 * mode and a the node is scheduled to be inserted at the current location.
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700734 */
Leland Richardson656c1132020-06-03 09:12:42 -0700735 @ComposeCompilerApi
Leland Richardson19285b12020-06-18 13:37:21 -0700736 fun startNode() {
737 start(nodeKey, null, true, null)
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700738 nodeExpected = true
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700739 }
740
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700741 /**
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700742 * Schedule a node to be created and inserted at the current location. This is only valid to
743 * call when the composer is inserting.
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700744 */
745 @Suppress("UNUSED")
Leland Richardson656c1132020-06-03 09:12:42 -0700746 @ComposeCompilerApi
Chuck Jazdzewskif17fd4d2019-08-06 10:18:54 -0700747 fun <T : N> createNode(factory: () -> T) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700748 validateNodeExpected()
Adam Powell8dec9b72020-06-19 14:24:14 -0700749 check(inserting) { "createNode() can only be called when inserting" }
Chuck Jazdzewski08d023b2018-09-27 10:54:14 -0700750 val insertIndex = nodeIndexStack.peek()
Chuck Jazdzewskid9fc2ca2019-07-11 11:22:47 -0700751 // see emitNode
Chuck Jazdzewski08d023b2018-09-27 10:54:14 -0700752 groupNodeCount++
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700753 recordFixup { applier, slots, _ ->
Chuck Jazdzewski08d023b2018-09-27 10:54:14 -0700754 val node = factory()
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700755 slots.node = node
Chuck Jazdzewski08d023b2018-09-27 10:54:14 -0700756 applier.insert(insertIndex, node)
757 applier.down(node)
758 }
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700759 // Allocate a slot that will be fixed-up by the above operation.
760 writer.skip()
Chuck Jazdzewski08d023b2018-09-27 10:54:14 -0700761 }
762
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700763 /**
764 * Schedule the given node to be inserted. This is only valid to call when the composer is
765 * inserting.
766 */
Leland Richardson656c1132020-06-03 09:12:42 -0700767 @ComposeCompilerApi
Chuck Jazdzewskief18c632020-06-30 08:29:29 -0700768 fun emitNode(node: Any?) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700769 validateNodeExpected()
Adam Powell8dec9b72020-06-19 14:24:14 -0700770 check(inserting) { "emitNode() called when not inserting" }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700771 val insertIndex = nodeIndexStack.peek()
Chuck Jazdzewskid9fc2ca2019-07-11 11:22:47 -0700772 // see emitNode
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700773 groupNodeCount++
Leland Richardson19285b12020-06-18 13:37:21 -0700774 @Suppress("UNCHECKED_CAST")
775 writer.node = node as N
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700776 recordApplierOperation { applier, _, _ ->
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700777 applier.insert(insertIndex, node)
778 applier.down(node)
779 }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700780 }
781
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700782 /**
783 * Return the instance of the node that was inserted at the given location. This is only
784 * valid to call when the composition is not inserting. This must be called at the same
785 * location as [emitNode] or [createNode] as called even if the value is unused.
786 */
Leland Richardson656c1132020-06-03 09:12:42 -0700787 @ComposeCompilerApi
Chuck Jazdzewskif17fd4d2019-08-06 10:18:54 -0700788 fun useNode(): N {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700789 validateNodeExpected()
Adam Powell8dec9b72020-06-19 14:24:14 -0700790 check(!inserting) { "useNode() called while inserting" }
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700791 val result = reader.node
792 recordDown(result)
793 return result
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700794 }
795
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700796 /**
797 * Called to end the node group.
798 */
Leland Richardson656c1132020-06-03 09:12:42 -0700799 @ComposeCompilerApi
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700800 fun endNode() = end(true)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700801
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700802 /**
803 * Schedule a change to be applied to a node's property. This change will be applied to the
804 * node that is the current node in the tree which was either created by [createNode],
805 * emitted by [emitNode] or returned by [useNode].
806 */
Leland Richardson656c1132020-06-03 09:12:42 -0700807 internal fun <V, T> apply(value: V, block: T.(V) -> Unit) {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700808 recordApplierOperation { applier, _, _ ->
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700809 @Suppress("UNCHECKED_CAST")
810 (applier.current as T).block(value)
811 }
812 }
813
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700814 /**
815 * Create a composed key that can be used in calls to [startGroup] or [startNode]. This will
816 * use the key stored at the current location in the slot table to avoid allocating a new key.
817 */
Leland Richardson656c1132020-06-03 09:12:42 -0700818 @ComposeCompilerApi
Chuck Jazdzewskif17fd4d2019-08-06 10:18:54 -0700819 fun joinKey(left: Any?, right: Any?): Any =
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -0700820 getKey(reader.groupDataKey, left, right) ?: JoinedKey(left, right)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700821
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700822 /**
823 * Return the next value in the slot table and advance the current location.
824 */
Leland Richardson656c1132020-06-03 09:12:42 -0700825 @ComposeCompilerApi
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700826 fun nextSlot(): Any? = if (inserting) {
827 validateNodeNotExpected()
828 EMPTY
829 } else reader.next()
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700830
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700831 /**
832 * Determine if the current slot table value is equal to the given value, if true, the value
833 * is scheduled to be skipped during [applyChanges] and [changes] return false; otherwise
834 * [applyChanges] will update the slot table to [value]. In either case the composer's slot
835 * table is advanced.
836 *
837 * @param value the value to be compared.
838 */
Leland Richardson656c1132020-06-03 09:12:42 -0700839 @ComposeCompilerApi
Leland Richardson7d078652020-06-02 09:05:03 -0700840 fun changed(value: Any?): Boolean {
841 return if (nextSlot() != value) {
Leland Richardson534a3462020-02-05 23:26:02 -0800842 updateValue(value)
843 true
844 } else {
Leland Richardson534a3462020-02-05 23:26:02 -0800845 false
846 }
847 }
848
Leland Richardson656c1132020-06-03 09:12:42 -0700849 @ComposeCompilerApi
Leland Richardson7d078652020-06-02 09:05:03 -0700850 fun changed(value: Char): Boolean {
851 val next = nextSlot()
852 if (next is Char) {
853 val nextPrimitive: Char = next
854 if (value == nextPrimitive) return false
Leland Richardsonf87e0e92019-01-08 19:09:03 -0800855 }
Leland Richardson7d078652020-06-02 09:05:03 -0700856 updateValue(value)
857 return true
858 }
859
Leland Richardson656c1132020-06-03 09:12:42 -0700860 @ComposeCompilerApi
Leland Richardson7d078652020-06-02 09:05:03 -0700861 fun changed(value: Byte): Boolean {
862 val next = nextSlot()
863 if (next is Byte) {
864 val nextPrimitive: Byte = next
865 if (value == nextPrimitive) return false
866 }
867 updateValue(value)
868 return true
869 }
870
Leland Richardson656c1132020-06-03 09:12:42 -0700871 @ComposeCompilerApi
Leland Richardson7d078652020-06-02 09:05:03 -0700872 fun changed(value: Short): Boolean {
873 val next = nextSlot()
874 if (next is Short) {
875 val nextPrimitive: Short = next
876 if (value == nextPrimitive) return false
877 }
878 updateValue(value)
879 return true
880 }
881
Leland Richardson656c1132020-06-03 09:12:42 -0700882 @ComposeCompilerApi
Leland Richardson7d078652020-06-02 09:05:03 -0700883 fun changed(value: Boolean): Boolean {
884 val next = nextSlot()
885 if (next is Boolean) {
886 val nextPrimitive: Boolean = next
887 if (value == nextPrimitive) return false
888 }
889 updateValue(value)
890 return true
891 }
892
Leland Richardson656c1132020-06-03 09:12:42 -0700893 @ComposeCompilerApi
Leland Richardson7d078652020-06-02 09:05:03 -0700894 fun changed(value: Float): Boolean {
895 val next = nextSlot()
896 if (next is Float) {
897 val nextPrimitive: Float = next
898 if (value == nextPrimitive) return false
899 }
900 updateValue(value)
901 return true
902 }
903
Leland Richardson656c1132020-06-03 09:12:42 -0700904 @ComposeCompilerApi
Leland Richardson7d078652020-06-02 09:05:03 -0700905 fun changed(value: Long): Boolean {
906 val next = nextSlot()
907 if (next is Long) {
908 val nextPrimitive: Long = next
909 if (value == nextPrimitive) return false
910 }
911 updateValue(value)
912 return true
913 }
914
Leland Richardson656c1132020-06-03 09:12:42 -0700915 @ComposeCompilerApi
Leland Richardson7d078652020-06-02 09:05:03 -0700916 fun changed(value: Double): Boolean {
917 val next = nextSlot()
918 if (next is Double) {
919 val nextPrimitive: Double = next
920 if (value == nextPrimitive) return false
921 }
922 updateValue(value)
923 return true
924 }
925
Leland Richardson656c1132020-06-03 09:12:42 -0700926 @ComposeCompilerApi
Leland Richardson7d078652020-06-02 09:05:03 -0700927 fun changed(value: Int): Boolean {
928 val next = nextSlot()
929 if (next is Int) {
930 val nextPrimitive: Int = next
931 if (value == nextPrimitive) return false
932 }
933 updateValue(value)
934 return true
Leland Richardsonf87e0e92019-01-08 19:09:03 -0800935 }
936
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700937 /**
Leland Richardson02aa60d2020-07-17 15:55:09 -0700938 * Cache a value in the composition. During initial composition [block] is called to produce the
939 * value that is then stored in the slot table. During recomposition, if [invalid] is false
940 * the value is obtained from the slot table and [block] is not invoked. If [invalid] is
941 * false a new value is produced by calling [block] and the slot table is updated to contain
942 * the new value.
943 */
944 @ComposeCompilerApi
945 inline fun <T> cache(invalid: Boolean, block: () -> T): T {
946 var result = nextSlot()
947 if (result === EMPTY || invalid) {
948 val value = block()
949 updateValue(value)
950 result = value
951 }
952
953 @Suppress("UNCHECKED_CAST")
954 return result as T
955 }
956
957 /**
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -0700958 * Schedule the current value in the slot table to be updated to [value].
959 *
960 * @param value the value to schedule to be written to the slot table.
961 */
Leland Richardson656c1132020-06-03 09:12:42 -0700962 @PublishedApi
963 internal fun updateValue(value: Any?) {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700964 if (inserting) {
965 writer.update(value)
966 if (value is CompositionLifecycleObserver) {
967 record { _, _, lifecycleManager -> lifecycleManager.entering(value) }
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -0800968 }
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700969 } else {
970 recordSlotTableOperation(-1) { _, slots, lifecycleManager ->
971 if (value is CompositionLifecycleObserver)
972 lifecycleManager.entering(value)
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700973 when (val previous = slots.update(value)) {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700974 is CompositionLifecycleObserver ->
975 lifecycleManager.leaving(previous)
976 is RecomposeScope ->
Chuck Jazdzewski7d869002020-06-24 13:31:48 -0700977 previous.composer = null
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -0700978 }
979 }
980 // Advance the writers reader location to account for the update above.
981 writersReaderDelta++
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -0800982 }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -0700983 }
984
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800985 /**
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -0800986 * Return the current ambient scope which was provided by a parent group.
987 */
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700988 private fun currentAmbientScope(): AmbientMap {
989 if (inserting && hasProvider) {
990 var group: Group? = writer.group(writer.parentLocation)
991 while (group != null) {
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -0700992 if (group.key == ambientMapKey && group.dataKey === ambientMap) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -0700993 @Suppress("UNCHECKED_CAST")
994 return group.data as AmbientMap
995 }
996 group = group.parent
997 }
998 }
999 if (slotTable.size > 0) {
1000 var group: Group? = reader.group(reader.parentLocation)
1001 while (group != null) {
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001002 if (group.key == ambientMapKey && group.dataKey === ambientMap) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001003 @Suppress("UNCHECKED_CAST")
1004 return providerUpdates[group] ?: group.data as AmbientMap
1005 }
1006 group = group.parent
1007 }
1008 }
1009 return parentProvider
1010 }
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001011
1012 /**
1013 * Return the ambient scope for the location provided. If this is while the composer is
1014 * composing then this is a query from a sub-composition that is being recomposed by this
1015 * compose which might be inserting the sub-composition. In that case the current scope
1016 * is the correct scope.
1017 */
1018 private fun ambientScopeAt(location: Int): AmbientMap {
1019 if (isComposing) {
1020 // The sub-composer is being composed as part of a nested composition then use the
1021 // current ambient scope as the one in the slot table might be out of date.
1022 return currentAmbientScope()
1023 }
1024
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001025 if (location >= 0) {
1026 var group: Group? = slotTable.read { it.group(location) }
1027 while (group != null) {
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001028 if (group.key == ambientMapKey && group.dataKey === ambientMap) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001029 @Suppress("UNCHECKED_CAST")
1030 return providerUpdates[group] ?: group.data as AmbientMap
1031 }
1032 group = group.parent
1033 }
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001034 }
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001035 return parentProvider
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001036 }
1037
1038 /**
1039 * Update (or create) the slots to record the providers. The providers maps are first the
1040 * scope followed by the map used to augment the parent scope. Both are needed to detect
1041 * inserts, updates and deletes to the providers.
1042 */
1043 private fun updateProviderMapGroup(
1044 parentScope: AmbientMap,
1045 currentProviders: AmbientMap
1046 ): AmbientMap {
1047 val providerScope = parentScope.mutate { it.putAll(currentProviders) }
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001048 startGroup(providerMapsKey, providerMaps)
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001049 changed(providerScope)
1050 changed(currentProviders)
1051 endGroup()
1052 return providerScope
1053 }
1054
1055 internal fun startProviders(values: Array<out ProvidedValue<*>>) {
1056 val parentScope = currentAmbientScope()
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001057 startGroup(providerKey, provider)
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001058 // The group is needed here because ambientMapOf() might change the number or kind of
1059 // slots consumed depending on the content of values to remember, for example, the value
1060 // holders used last time.
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001061 startGroup(providerValuesKey, providerValues)
Leland Richardson534a3462020-02-05 23:26:02 -08001062 val currentProviders = invokeComposableForResult(this) { ambientMapOf(values) }
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001063 endGroup()
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001064 val providers: AmbientMap
1065 val invalid: Boolean
1066 if (inserting) {
1067 providers = updateProviderMapGroup(parentScope, currentProviders)
1068 invalid = false
1069 hasProvider = true
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001070 } else {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001071 val current = reader.current
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001072
1073 @Suppress("UNCHECKED_CAST")
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001074 val oldScope = reader.get(current + 1) as AmbientMap
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001075
1076 @Suppress("UNCHECKED_CAST")
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001077 val oldValues = reader.get(current + 2) as AmbientMap
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001078
1079 // skipping is true iff parentScope has not changed.
1080 if (!skipping || oldValues != currentProviders) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001081 providers = updateProviderMapGroup(parentScope, currentProviders)
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001082
1083 // Compare against the old scope as currentProviders might have modified the scope
1084 // back to the previous value. This could happen, for example, if currentProviders
1085 // and parentScope have a key in common and the oldScope had the same value as
1086 // currentProviders for that key. If the scope has not changed, because these
1087 // providers obscure a change in the parent as described above, re-enable skipping
1088 // for the child region.
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001089 invalid = providers != oldScope
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001090 } else {
1091 // Nothing has changed
1092 skipGroup()
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001093 providers = oldScope
1094 invalid = false
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001095 }
Leland Richardson9f5fe962019-04-10 11:57:36 -07001096 }
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001097
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001098 if (invalid && !inserting) {
1099 providerUpdates[reader.group] = providers
1100 }
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001101 providersInvalidStack.push(providersInvalid.asInt())
1102 providersInvalid = invalid
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001103 start(ambientMapKey, ambientMap, false, providers)
Leland Richardsonb0c2ea22018-12-18 18:56:41 -08001104 }
1105
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001106 internal fun endProviders() {
Leland Richardsonb0c2ea22018-12-18 18:56:41 -08001107 endGroup()
1108 endGroup()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001109 providersInvalid = providersInvalidStack.pop().asBool()
Leland Richardsonb0c2ea22018-12-18 18:56:41 -08001110 }
1111
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001112 @PublishedApi
1113 internal fun <T> consume(key: Ambient<T>): T = resolveAmbient(key, currentAmbientScope())
Leland Richardsonb0c2ea22018-12-18 18:56:41 -08001114
1115 /**
Leland Richardson3c0ed0c2019-04-18 17:11:48 -07001116 * Create or use a memoized `CompositionReference` instance at this position in the slot table.
Leland Richardsonb0c2ea22018-12-18 18:56:41 -08001117 */
Leland Richardson656c1132020-06-03 09:12:42 -07001118 internal fun buildReference(): CompositionReference {
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001119 startGroup(referenceKey, reference)
Leland Richardsonb0c2ea22018-12-18 18:56:41 -08001120
Adam Powell7a0bfd62020-07-08 12:26:43 -07001121 var ref = nextSlot() as? CompositionReferenceHolder<*>
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001122 if (ref == null || !inserting) {
Chuck Jazdzewskiab3c9522019-08-20 12:41:34 -07001123 val scope = invalidateStack.peek()
1124 scope.used = true
Adam Powell7a0bfd62020-07-08 12:26:43 -07001125 ref = CompositionReferenceHolder(
1126 CompositionReferenceImpl(scope, currentCompoundKeyHash, collectKeySources)
1127 )
Leland Richardsonb0c2ea22018-12-18 18:56:41 -08001128 updateValue(ref)
1129 }
1130 endGroup()
1131
Adam Powell7a0bfd62020-07-08 12:26:43 -07001132 return ref.ref
Leland Richardsonb0c2ea22018-12-18 18:56:41 -08001133 }
1134
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001135 private fun <T> resolveAmbient(key: Ambient<T>, scope: AmbientMap): T {
1136 if (scope.contains(key)) return scope.getValueOf(key)
Leland Richardsonb0c2ea22018-12-18 18:56:41 -08001137
Matvei Malkovac530be2019-05-14 19:43:14 +01001138 val ref = parentReference
Leland Richardsonb0c2ea22018-12-18 18:56:41 -08001139
1140 if (ref != null) {
1141 return ref.getAmbient(key)
1142 }
1143
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001144 return key.defaultValueHolder.value
Leland Richardsonb0c2ea22018-12-18 18:56:41 -08001145 }
1146
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001147 internal fun <T> parentAmbient(key: Ambient<T>): T = resolveAmbient(key, currentAmbientScope())
Leland Richardsonb0c2ea22018-12-18 18:56:41 -08001148
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001149 private fun <T> parentAmbient(key: Ambient<T>, location: Int): T =
1150 resolveAmbient(key, ambientScopeAt(location))
Leland Richardsonb0c2ea22018-12-18 18:56:41 -08001151
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001152 /**
1153 * The number of changes that have been scheduled to be applied during [applyChanges].
1154 *
1155 * Slot table movement (skipping groups and nodes) will be coalesced so this number is
1156 * possibly less than the total changes detected.
1157 */
Leland Richardson656c1132020-06-03 09:12:42 -07001158 internal val changeCount get() = changes.size
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001159
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001160 internal val currentRecomposeScope: RecomposeScope?
Matvei Malkovac530be2019-05-14 19:43:14 +01001161 get() =
1162 invalidateStack.let { if (it.isNotEmpty()) it.peek() else null }
Chuck Jazdzewski3d43fd82018-11-27 12:47:48 -08001163
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001164 private fun ensureWriter() {
1165 if (writer.closed) {
1166 writer = insertTable.openWriter()
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001167 hasProvider = false
1168 }
1169 }
1170
1171 /**
1172 * Start the reader group updating the data of the group if necessary
1173 */
1174 private fun startReaderGroup(isNode: Boolean, data: Any?) {
1175 if (isNode) {
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001176 reader.startNode()
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001177 } else {
1178 if (data != null && reader.groupData !== data) {
1179 recordSlotEditingOperation { _, slots, _ ->
1180 slots.updateData(data)
1181 }
1182 }
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001183 if (data != null)
1184 reader.startDataGroup()
1185 else
1186 reader.startGroup()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001187 }
1188 }
1189
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001190 private fun start(key: Int, dataKey: Any?, isNode: Boolean, data: Any?) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001191 validateNodeNotExpected()
Chuck Jazdzewskid9fc2ca2019-07-11 11:22:47 -07001192
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001193 updateCompoundKeyWhenWeEnterGroup(key, dataKey)
Andrey Kulikov1e06a872020-02-28 15:52:00 +00001194
Chuck Jazdzewskid9fc2ca2019-07-11 11:22:47 -07001195 // Check for the insert fast path. If we are already inserting (creating nodes) then
1196 // there is no need to track insert, deletes and moves with a pending changes object.
1197 if (inserting) {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001198 reader.beginEmpty()
Chuck Jazdzewskie7725b92019-06-24 08:51:16 -07001199 if (collectKeySources)
1200 recordSourceKeyInfo(key)
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001201 when {
Leland Richardson19285b12020-06-18 13:37:21 -07001202 isNode -> writer.startNode(null)
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001203 data != null -> writer.startData(key, dataKey, data)
1204 else -> writer.startGroup(key, dataKey)
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001205 }
Chuck Jazdzewskid9fc2ca2019-07-11 11:22:47 -07001206 pending?.let { pending ->
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001207 val insertKeyInfo = KeyInfo(key, -1, 0, -1, 0, writer.parentGroup)
Chuck Jazdzewskid9fc2ca2019-07-11 11:22:47 -07001208 pending.registerInsert(insertKeyInfo, nodeIndex - pending.startIndex)
1209 pending.recordUsed(insertKeyInfo)
1210 }
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001211 enterGroup(isNode, null)
Chuck Jazdzewskid9fc2ca2019-07-11 11:22:47 -07001212 return
1213 }
1214
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001215 if (pending == null) {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001216 val slotKey = reader.groupKey
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001217 if (slotKey == key && dataKey == reader.groupDataKey) {
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001218 // The group is the same as what was generated last time.
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001219 startReaderGroup(isNode, data)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001220 } else {
Leland Richardson6cab6e32019-05-02 11:52:13 -07001221 pending = Pending(
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001222 reader.extractKeys(),
Leland Richardson6cab6e32019-05-02 11:52:13 -07001223 nodeIndex
1224 )
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001225 }
1226 }
1227
1228 val pending = pending
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001229 var newPending: Pending? = null
1230 if (pending != null) {
1231 // Check to see if the key was generated last time from the keys collected above.
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001232 val keyInfo = pending.getNext(key, dataKey)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001233 if (keyInfo != null) {
1234 // This group was generated last time, use it.
1235 pending.recordUsed(keyInfo)
1236
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07001237 // Move the slot table to the location where the information about this group is
1238 // stored. The slot information will move once the changes are applied so moving the
1239 // current of the slot table is sufficient.
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001240 val location = keyInfo.location
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001241
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07001242 // Determine what index this group is in. This is used for inserting nodes into the
1243 // group.
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001244 nodeIndex = pending.nodePositionOf(keyInfo) + pending.startIndex
1245
1246 // Determine how to move the slot group to the correct position.
1247 val relativePosition = pending.slotPositionOf(keyInfo)
1248 val currentRelativePosition = relativePosition - pending.groupIndex
1249 pending.registerMoveSlot(relativePosition, pending.groupIndex)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001250 recordReaderMoving(location)
1251 reader.reposition(location)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001252 if (currentRelativePosition > 0) {
1253 // The slot group must be moved, record the move to be performed during apply.
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001254 recordSlotEditingOperation { _, slots, _ ->
Chuck Jazdzewski776d8232019-08-21 16:24:03 -07001255 slots.moveGroup(currentRelativePosition)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001256 }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001257 }
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001258 startReaderGroup(isNode, data)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001259 } else {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001260 // The group is new, go into insert mode. All child groups will written to the
1261 // insertTable until the group is complete which will schedule the groups to be
1262 // inserted into in the table.
1263 reader.beginEmpty()
1264 inserting = true
Chuck Jazdzewskie7725b92019-06-24 08:51:16 -07001265 if (collectKeySources)
1266 recordSourceKeyInfo(key)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001267
1268 ensureWriter()
1269 writer.beginInsert()
1270 val insertLocation = writer.current
Chuck Jazdzewski2ee92272020-07-07 08:35:12 -07001271 if (isNode) writer.startNode(null) else writer.startGroup(key, dataKey, data)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001272 insertAnchor = writer.anchor(insertLocation)
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001273 val insertKeyInfo = KeyInfo(key, -1, 0, -1, 0, writer.parentGroup)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001274 pending.registerInsert(insertKeyInfo, nodeIndex - pending.startIndex)
1275 pending.recordUsed(insertKeyInfo)
Leland Richardson6cab6e32019-05-02 11:52:13 -07001276 newPending = Pending(
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001277 mutableListOf(),
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001278 if (isNode) 0 else nodeIndex
Leland Richardson6cab6e32019-05-02 11:52:13 -07001279 )
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001280 }
1281 }
1282
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001283 enterGroup(isNode, newPending)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001284 }
1285
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001286 private fun enterGroup(isNode: Boolean, newPending: Pending?) {
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07001287 // When entering a group all the information about the parent should be saved, to be
1288 // restored when end() is called, and all the tracking counters set to initial state for the
1289 // group.
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001290 pendingStack.push(pending)
1291 this.pending = newPending
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001292 this.nodeIndexStack.push(nodeIndex)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001293 if (isNode) nodeIndex = 0
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001294 this.groupNodeCountStack.push(groupNodeCount)
1295 groupNodeCount = 0
1296 }
1297
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001298 private fun exitGroup(expectedNodeCount: Int, inserting: Boolean) {
1299 // Restore the parent's state updating them if they have changed based on changes in the
1300 // children. For example, if a group generates nodes then the number of generated nodes will
1301 // increment the node index and the group's node count. If the parent is tracking structural
1302 // changes in pending then restore that too.
1303 val previousPending = pendingStack.pop()
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001304 if (previousPending != null && !inserting) {
1305 previousPending.groupIndex++
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001306 }
1307 this.pending = previousPending
1308 this.nodeIndex = nodeIndexStack.pop() + expectedNodeCount
1309 this.groupNodeCount = this.groupNodeCountStack.pop() + expectedNodeCount
1310 }
1311
1312 private fun end(isNode: Boolean) {
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07001313 // All the changes to the group (or node) have been recorded. All new nodes have been
1314 // inserted but it has yet to determine which need to be removed or moved. Note that the
1315 // changes are relative to the first change in the list of nodes that are changing.
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001316
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001317 val group = if (inserting)
1318 writer.group(writer.parentLocation)
1319 else
1320 reader.group(reader.parentLocation)
1321 updateCompoundKeyWhenWeExitGroup(group.key, group.dataKey)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001322 var expectedNodeCount = groupNodeCount
1323 val pending = pending
1324 if (pending != null && pending.keyInfos.size > 0) {
1325 // previous contains the list of keys as they were generated in the previous composition
1326 val previous = pending.keyInfos
1327
1328 // current contains the list of keys in the order they need to be in the new composition
1329 val current = pending.used
1330
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07001331 // usedKeys contains the keys that were used in the new composition, therefore if a key
1332 // doesn't exist in this set, it needs to be removed.
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001333 val usedKeys = current.toSet()
1334
Chuck Jazdzewski88982512020-04-01 14:07:06 -07001335 val placedKeys = mutableSetOf<KeyInfo>()
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001336 var currentIndex = 0
1337 val currentEnd = current.size
1338 var previousIndex = 0
1339 val previousEnd = previous.size
1340
1341 // Traverse the list of changes to determine startNode movement
1342 var nodeOffset = 0
1343 while (previousIndex < previousEnd) {
1344 val previousInfo = previous[previousIndex]
1345 if (!usedKeys.contains(previousInfo)) {
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07001346 // If the key info was not used the group was deleted, remove the nodes in the
1347 // group
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001348 val deleteOffset = pending.nodePositionOf(previousInfo)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001349 recordRemoveNode(deleteOffset + pending.startIndex, previousInfo.nodes)
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001350 pending.updateNodeCount(previousInfo.group, 0)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001351 recordReaderMoving(previousInfo.location)
1352 reader.reposition(previousInfo.location)
1353 recordDelete()
1354 reader.skipGroup()
Chuck Jazdzewski8a990d12019-03-05 11:34:51 -08001355
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07001356 // Remove any invalidations pending for the group being removed. These are no
1357 // longer part of the composition. The group being composed is one after the
Chuck Jazdzewski776d8232019-08-21 16:24:03 -07001358 // start of the group.
Matvei Malkovac530be2019-05-14 19:43:14 +01001359 invalidations.removeRange(
Chuck Jazdzewski776d8232019-08-21 16:24:03 -07001360 previousInfo.location,
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001361 previousInfo.location + reader.groupSize(previousInfo.location)
Matvei Malkovac530be2019-05-14 19:43:14 +01001362 )
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001363 previousIndex++
1364 continue
1365 }
1366
Chuck Jazdzewski88982512020-04-01 14:07:06 -07001367 if (previousInfo in placedKeys) {
1368 // If the group was already placed in the correct location, skip it.
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001369 previousIndex++
1370 continue
1371 }
1372
1373 if (currentIndex < currentEnd) {
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07001374 // At this point current should match previous unless the group is new or was
1375 // moved.
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001376 val currentInfo = current[currentIndex]
1377 if (currentInfo !== previousInfo) {
1378 val nodePosition = pending.nodePositionOf(currentInfo)
Chuck Jazdzewski88982512020-04-01 14:07:06 -07001379 placedKeys.add(currentInfo)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001380 if (nodePosition != nodeOffset) {
1381 val updatedCount = pending.updatedNodeCountOf(currentInfo)
Matvei Malkovac530be2019-05-14 19:43:14 +01001382 recordMoveNode(
1383 nodePosition + pending.startIndex,
1384 nodeOffset + pending.startIndex, updatedCount
1385 )
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001386 pending.registerMoveNode(nodePosition, nodeOffset, updatedCount)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001387 } // else the nodes are already in the correct position
1388 } else {
1389 // The correct nodes are in the right location
1390 previousIndex++
1391 }
1392 currentIndex++
1393 nodeOffset += pending.updatedNodeCountOf(currentInfo)
1394 }
1395 }
1396
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07001397 // If there are any current nodes left they where inserted into the right location
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001398 // when the group began so the rest are ignored.
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001399 realizeMovement()
1400
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07001401 // We have now processed the entire list so move the slot table to the end of the list
1402 // by moving to the last key and skipping it.
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001403 if (previous.size > 0) {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001404 recordReaderMoving(reader.groupEnd)
1405 reader.skipToGroupEnd()
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001406 }
1407 }
1408
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07001409 // Detect removing nodes at the end. No pending is created in this case we just have more
1410 // nodes in the previous composition than we expect (i.e. we are not yet at an end)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001411 val removeIndex = nodeIndex
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001412 while (!reader.isGroupEnd) {
1413 val startSlot = reader.current
1414 recordDelete()
1415 val nodesToRemove = reader.skipGroup()
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001416 recordRemoveNode(removeIndex, nodesToRemove)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001417 invalidations.removeRange(startSlot, reader.current)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001418 }
1419
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001420 val inserting = inserting
Chuck Jazdzewski819e8652019-07-29 09:40:43 -07001421 if (inserting) {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001422 if (isNode) {
1423 recordInsertUp()
1424 expectedNodeCount = 1
1425 }
1426 reader.endEmpty()
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001427 val parentGroup = writer.parentGroup
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001428 writer.endGroup()
1429 if (!reader.inEmpty) {
1430 writer.endInsert()
1431 writer.close()
1432 recordInsert(insertAnchor)
1433 this.inserting = false
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001434 nodeCountOverrides[parentGroup] = 0
1435 updateNodeCountOverrides(parentGroup, expectedNodeCount)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001436 }
1437 } else {
1438 if (isNode) recordUp()
1439 recordEndGroup()
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001440 val parentGroup = reader.parentGroup
1441 if (expectedNodeCount != parentGroup.nodes) {
1442 updateNodeCountOverrides(parentGroup, expectedNodeCount)
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001443 }
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001444 if (isNode) {
1445 expectedNodeCount = 1
1446 reader.endNode()
1447 } else reader.endGroup()
1448
1449 realizeMovement()
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001450 }
1451
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001452 exitGroup(expectedNodeCount, inserting)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001453 }
1454
1455 /**
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001456 * Recompose any invalidate child groups of the current parent group. This should be called
1457 * after the group is started but on or before the first child group. It is intended to be
1458 * called instead of [skipReaderToGroupEnd] if any child groups are invalid. If no children
1459 * are invalid it will call [skipReaderToGroupEnd].
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001460 */
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001461 private fun recomposeToGroupEnd() {
Leland Richardson0b5bf702019-05-24 17:07:21 -07001462 val wasComposing = isComposing
1463 isComposing = true
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001464 var recomposed = false
1465
Leland Richardson797bd0cd2020-05-07 15:16:51 -07001466 val parent = reader.parentLocation
1467 val end = parent + reader.groupSize(parent)
1468 val recomposeGroup = reader.group(parent)
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001469 val recomposeIndex = nodeIndex
1470 val recomposeCompoundKey = currentCompoundKeyHash
1471 val oldGroupNodeCount = groupNodeCount
1472 var oldGroup = recomposeGroup
1473
Leland Richardson797bd0cd2020-05-07 15:16:51 -07001474 var firstInRange = invalidations.firstInRange(reader.current, end)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001475 while (firstInRange != null) {
1476 val location = firstInRange.location
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001477
1478 invalidations.removeLocation(location)
1479
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001480 recomposed = true
1481
1482 reader.reposition(location)
1483 val newGroup = reader.group
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001484 // Record the changes to the applier location
1485 recordUpsAndDowns(oldGroup, newGroup, recomposeGroup)
1486 oldGroup = newGroup
1487
1488 // Calculate the node index (the distance index in the node this groups nodes are
1489 // located in the parent node).
1490 nodeIndex = nodeIndexOf(
1491 location,
1492 newGroup,
Leland Richardson797bd0cd2020-05-07 15:16:51 -07001493 parent,
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001494 recomposeGroup,
1495 recomposeIndex
1496 )
1497
1498 // Calculate the compound hash code (a semi-unique code for every group in the
1499 // composition used to restore saved state).
1500 currentCompoundKeyHash = compoundKeyOf(
1501 newGroup.parent,
1502 recomposeGroup,
1503 recomposeCompoundKey
1504 )
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001505
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001506 firstInRange.scope.compose(this)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001507
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07001508 // Using slots.current here ensures composition always walks forward even if a component
1509 // before the current composition is invalidated when performing this composition. Any
1510 // such components will be considered invalid for the next composition. Skipping them
1511 // prevents potential infinite recomposes at the cost of potentially missing a compose
1512 // as well as simplifies the apply as it always modifies the slot table in a forward
1513 // direction.
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001514 firstInRange = invalidations.firstInRange(reader.current, end)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001515 }
1516
1517 if (recomposed) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001518 recordUpsAndDowns(oldGroup, recomposeGroup, recomposeGroup)
1519 val parentGroup = reader.parentGroup
1520 reader.skipToGroupEnd()
1521 val parentGroupNodes = (nodeCountOverrides[parentGroup] ?: parentGroup.nodes)
1522 nodeIndex = recomposeIndex + parentGroupNodes
1523 groupNodeCount = oldGroupNodeCount + parentGroupNodes
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001524 } else {
1525 // No recompositions were requested in the range, skip it.
Chuck Jazdzewskid7aa6bd2020-04-17 16:33:56 -07001526 skipReaderToGroupEnd()
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001527 }
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001528 currentCompoundKeyHash = recomposeCompoundKey
1529
Leland Richardson0b5bf702019-05-24 17:07:21 -07001530 isComposing = wasComposing
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001531 }
1532
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001533 /**
1534 * As operations to insert and remove nodes are recorded, the number of nodes that will be in
1535 * the group after changes are applied is maintained in a side overrides table. This method
1536 * updates that count and then updates any parent groups that include the nodes this group
1537 * emits.
1538 */
1539 private fun updateNodeCountOverrides(group: Group, newCount: Int) {
1540 val currentCount = nodeCountOverrides[group] ?: group.nodes
1541 if (currentCount != newCount) {
1542 // Update the overrides
1543 val delta = newCount - currentCount
1544 var current: Group? = group
1545
1546 var minPending = pendingStack.size - 1
1547 while (current != null) {
1548 val newCurrentNodes = (nodeCountOverrides[current] ?: current.nodes) + delta
1549 nodeCountOverrides[current] = newCurrentNodes
1550 for (pendingIndex in minPending downTo 0) {
1551 val pending = pendingStack.peek(pendingIndex)
1552 if (pending != null && pending.updateNodeCount(current, newCurrentNodes)) {
1553 minPending = pendingIndex - 1
1554 break
1555 }
1556 }
1557 if (current.isNode) break
1558 current = current.parent
1559 }
1560 }
1561 }
1562
1563 /**
1564 * Calculates the node index (the index in the child list of a node will appear in the
1565 * resulting tree) for [group]. Passing in [recomposeGroup] and its node index in
1566 * [recomposeIndex] allows the calculation to exit early if there is no node group between
1567 * [group] and [recomposeGroup].
1568 */
1569 private fun nodeIndexOf(
1570 groupLocation: Int,
1571 group: Group,
1572 recomposeLocation: Int,
1573 recomposeGroup: Group,
1574 recomposeIndex: Int
1575 ): Int {
1576 // Find the anchor group which is either the recomposeGroup or the first parent node
1577 var anchorGroup = group.parent ?: error("Invalid group")
1578 while (anchorGroup != recomposeGroup) {
1579 if (anchorGroup.isNode) break
1580 anchorGroup = anchorGroup.parent ?: error("group not contained in recompose group")
1581 }
1582
1583 var index = if (anchorGroup.isNode) 0 else recomposeIndex
1584
1585 // An early out if the group and anchor sizes are the same as the index must then be index
1586 if (anchorGroup.slots == group.slots) return index
1587
1588 // Find the location of the anchor group
1589 val anchorLocation =
1590 if (anchorGroup == recomposeGroup) {
1591 recomposeLocation
1592 } else {
1593 // anchor node must be between recomposeLocation and groupLocation but no farther
1594 // back than anchorGroup.size - group.size + 1 because anchorGroup contains group
1595 var location = recomposeLocation
1596 val anchorLimit = groupLocation - (anchorGroup.slots - group.slots + 1)
1597 if (location < anchorLimit) location = anchorLimit
1598 while (reader.get(location) !== anchorGroup)
1599 location++
1600 location
1601 }
1602
1603 // Walk down from the anchor group counting nodes of siblings in front of this group
1604 var current = anchorLocation
1605 val nodeIndexLimit = index + ((nodeCountOverrides[anchorGroup] ?: anchorGroup.nodes) -
1606 group.nodes)
1607 loop@ while (index < nodeIndexLimit) {
1608 if (current == groupLocation) break
1609 current++
1610 while (!reader.isGroup(current)) current++
1611 while (current < groupLocation) {
1612 val currentGroup = reader.group(current)
1613 val end = currentGroup.slots + current + 1
1614 if (groupLocation < end) continue@loop
1615 index += nodeCountOverrides[currentGroup] ?: currentGroup.nodes
1616
1617 current = end
1618 }
1619 break
1620 }
1621 return index
1622 }
1623
1624 /**
1625 * Records the operations necessary to move the applier the node affected by the previous
1626 * group to the new group.
1627 */
1628 private fun recordUpsAndDowns(oldGroup: Group, newGroup: Group, commonRoot: Group) {
1629 val nearestCommonRoot = nearestCommonRootOf(
1630 oldGroup,
1631 newGroup,
1632 commonRoot
1633 ) ?: commonRoot
1634
1635 // Record ups for the nodes between oldGroup and nearestCommonRoot
1636 var current: Group? = oldGroup
1637 while (current != null && current != nearestCommonRoot) {
1638 if (current.isNode) recordUp()
1639 current = current.parent
1640 }
1641
1642 // Record downs from nearestCommonRoot to newGroup
1643 doRecordDownsFor(newGroup, nearestCommonRoot)
1644 }
1645
1646 private fun doRecordDownsFor(group: Group?, nearestCommonRoot: Group?) {
1647 if (group != null && group != nearestCommonRoot) {
1648 doRecordDownsFor(group.parent, nearestCommonRoot)
1649 @Suppress("UNCHECKED_CAST")
1650 if (group.isNode) recordDown(group.node as N)
1651 }
1652 }
1653
1654 /**
1655 * Calculate the compound key (a semi-unique key produced for every group in the composition)
1656 * for [group]. Passing in the [recomposeGroup] and [recomposeKey] allows this method to exit
1657 * early.
1658 */
1659 private fun compoundKeyOf(group: Group?, recomposeGroup: Group, recomposeKey: Int): Int {
1660 return if (group == recomposeGroup) recomposeKey else (compoundKeyOf(
1661 (group ?: error("Detached group")).parent,
1662 recomposeGroup,
1663 recomposeKey
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001664 ) rol 3) xor (if (group.dataKey != null) group.dataKey.hashCode() else group.key)
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001665 }
1666
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001667 internal fun invalidate(scope: RecomposeScope): InvalidationResult {
Leland Richardson797bd0cd2020-05-07 15:16:51 -07001668 if (scope.defaultsInScope) {
1669 scope.defaultsInvalid = true
1670 }
Chuck Jazdzewskib15857c2020-05-26 17:14:02 -07001671 val anchor = scope.anchor
1672 if (anchor == null || insertTable.ownsAnchor(anchor))
1673 return InvalidationResult.IGNORED // The scope has not yet entered the composition
1674 val location = anchor.location(slotTable)
Chuck Jazdzewskid9fc2ca2019-07-11 11:22:47 -07001675 if (location < 0)
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001676 return InvalidationResult.IGNORED // The scope was removed from the composition
Chuck Jazdzewskid9fc2ca2019-07-11 11:22:47 -07001677
Chuck Jazdzewski3d43fd82018-11-27 12:47:48 -08001678 invalidations.insertIfMissing(location, scope)
Leland Richardson797bd0cd2020-05-07 15:16:51 -07001679 if (isComposing && location >= reader.current) {
Leland Richardson0b5bf702019-05-24 17:07:21 -07001680 // if we are invalidating a scope that is going to be traversed during this
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001681 // composition.
1682 return InvalidationResult.IMMINENT
Leland Richardson0b5bf702019-05-24 17:07:21 -07001683 }
1684 if (parentReference != null) {
Leland Richardson76600e32020-01-15 17:22:29 -08001685 parentReference?.invalidate()
Chuck Jazdzewski73a046b2019-02-04 14:22:16 -08001686 } else {
Leland Richardson76600e32020-01-15 17:22:29 -08001687 recomposer.scheduleRecompose(this)
Chuck Jazdzewski73a046b2019-02-04 14:22:16 -08001688 }
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001689 return if (isComposing) InvalidationResult.DEFERRED else InvalidationResult.SCHEDULED
Chuck Jazdzewski3d43fd82018-11-27 12:47:48 -08001690 }
1691
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001692 /**
Chuck Jazdzewskid7aa6bd2020-04-17 16:33:56 -07001693 * Skip a group. Skips the group at the current location. This is only valid to call if the
1694 * composition is not inserting.
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001695 */
Leland Richardson656c1132020-06-03 09:12:42 -07001696 @ComposeCompilerApi
Chuck Jazdzewski776d8232019-08-21 16:24:03 -07001697 fun skipCurrentGroup() {
Leland Richardsonf87e0e92019-01-08 19:09:03 -08001698 if (invalidations.isEmpty()) {
1699 skipGroup()
1700 } else {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001701 val reader = reader
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001702 val key = reader.groupKey
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001703 val dataKey = reader.groupDataKey
1704 updateCompoundKeyWhenWeEnterGroup(key, dataKey)
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001705 startReaderGroup(reader.isNode, reader.groupData)
1706 recomposeToGroupEnd()
1707 reader.endGroup()
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001708 updateCompoundKeyWhenWeExitGroup(key, dataKey)
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001709 }
1710 }
1711
Chuck Jazdzewskid7aa6bd2020-04-17 16:33:56 -07001712 private fun skipReaderToGroupEnd() {
1713 groupNodeCount = reader.parentNodes
1714 reader.skipToGroupEnd()
1715 }
1716
1717 /**
1718 * Skip to the end of the group opened by [startGroup].
1719 */
Leland Richardson656c1132020-06-03 09:12:42 -07001720 @ComposeCompilerApi
Chuck Jazdzewskid7aa6bd2020-04-17 16:33:56 -07001721 fun skipToGroupEnd() {
Adam Powell8dec9b72020-06-19 14:24:14 -07001722 check(groupNodeCount == 0) { "No nodes can be emitted before calling skipAndEndGroup" }
Chuck Jazdzewskid7aa6bd2020-04-17 16:33:56 -07001723 if (invalidations.isEmpty()) {
1724 skipReaderToGroupEnd()
1725 } else {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001726 recomposeToGroupEnd()
Chuck Jazdzewskid7aa6bd2020-04-17 16:33:56 -07001727 }
1728 }
1729
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001730 /**
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001731 * Start a restart group. A restart group creates a recompose scope and sets it as the current
1732 * recompose scope of the composition. If the recompose scope is invalidated then this group
1733 * will be recomposed. A recompose scope can be invalidated by calling the lambda returned by
1734 * [androidx.compose.invalidate].
1735 */
Leland Richardson656c1132020-06-03 09:12:42 -07001736 @ComposeCompilerApi
Chuck Jazdzewski0f26b682020-03-18 11:42:25 -07001737 fun startRestartGroup(key: Int) {
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07001738 start(key, null, false, null)
Chuck Jazdzewski2ee92272020-07-07 08:35:12 -07001739 addRecomposeScope(key)
1740 }
1741
1742 @ComposeCompilerApi
1743 fun startRestartGroup(key: Int, sourceInformation: String?) {
1744 start(key, null, false, sourceInformation)
1745 addRecomposeScope(key)
1746 }
1747
1748 private fun addRecomposeScope(key: Int) {
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001749 if (inserting) {
Leland Richardson797bd0cd2020-05-07 15:16:51 -07001750 val scope = RecomposeScope(this, key)
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001751 invalidateStack.push(scope)
1752 updateValue(scope)
1753 } else {
Leland Richardson797bd0cd2020-05-07 15:16:51 -07001754 val invalidation = invalidations.removeLocation(reader.parentLocation)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001755 val scope = reader.next() as RecomposeScope
Leland Richardson797bd0cd2020-05-07 15:16:51 -07001756 scope.requiresRecompose = invalidation != null
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001757 invalidateStack.push(scope)
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001758 }
1759 }
1760
1761 /**
1762 * End a restart group. If the recompose scope was marked used during composition then a
1763 * [ScopeUpdateScope] is returned that allows attaching a lambda that will produce the same
1764 * composition as was produced by this group (including calling [startRestartGroup] and
1765 * [endRestartGroup]).
1766 */
Leland Richardson656c1132020-06-03 09:12:42 -07001767 @ComposeCompilerApi
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001768 fun endRestartGroup(): ScopeUpdateScope? {
1769 // This allows for the invalidate stack to be out of sync since this might be called during exception stack
1770 // unwinding that might have not called the doneJoin/endRestartGroup in the wrong order.
Leland Richardson76600e32020-01-15 17:22:29 -08001771 val scope = if (invalidateStack.isNotEmpty()) invalidateStack.pop()
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001772 else null
Leland Richardson898e9e62020-05-18 12:21:22 -07001773 scope?.requiresRecompose = false
Chuck Jazdzewski607eef52020-05-15 13:36:46 -07001774 val result = if (scope != null && (scope.used || collectKeySources)) {
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001775 if (scope.anchor == null) {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001776 scope.anchor = if (inserting)
1777 insertTable.anchor(writer.parentLocation)
1778 else
1779 slotTable.anchor(reader.parentLocation)
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001780 }
Leland Richardson797bd0cd2020-05-07 15:16:51 -07001781 scope.defaultsInvalid = false
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001782 scope
1783 } else {
1784 null
1785 }
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001786 end(false)
Chuck Jazdzewski01ac3302019-08-13 10:44:45 -07001787 return result
1788 }
1789
1790 /**
1791 * Synchronously recompose all invalidated groups. This collects the changes which must be
1792 * applied by [applyChanges] to have an effect.
1793 */
Leland Richardson656c1132020-06-03 09:12:42 -07001794 @InternalComposeApi
Filip Pavlis20447902019-07-01 20:34:25 +01001795 fun recompose(): Boolean {
Chuck Jazdzewski006734c2018-09-21 09:32:38 -07001796 if (invalidations.isNotEmpty()) {
Leland Richardsonbf5f6a62019-05-24 16:25:46 -07001797 trace("Compose:recompose") {
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001798 nodeIndex = 0
1799 var complete = false
1800 try {
1801 startRoot()
1802 skipCurrentGroup()
1803 endRoot()
1804 complete = true
1805 } finally {
1806 if (!complete) abortRoot()
Leland Richardsonbf5f6a62019-05-24 16:25:46 -07001807 }
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08001808 finalizeCompose()
Chuck Jazdzewski0a9f7fa2018-12-20 16:55:46 -08001809 }
Chuck Jazdzewski6374c862020-03-23 14:36:57 -07001810 return true
Chuck Jazdzewski006734c2018-09-21 09:32:38 -07001811 }
Filip Pavlis20447902019-07-01 20:34:25 +01001812 return false
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001813 }
1814
Chuck Jazdzewski8a3c04d2020-02-11 11:10:24 -08001815 internal fun hasInvalidations() = invalidations.isNotEmpty()
1816
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001817 @Suppress("UNCHECKED_CAST")
1818 private var SlotWriter.node
1819 get() = nodeGroup.node as N
1820 set(value) { nodeGroup.node = value }
1821 private val SlotWriter.nodeGroup get() = get(current - 1) as NodeGroup
1822 private fun SlotWriter.nodeGroupAt(location: Int) = get(location) as NodeGroup
1823 private fun SlotWriter.nodeAt(location: Int) = nodeGroupAt(location).node
1824 @Suppress("UNCHECKED_CAST")
1825 private val SlotReader.node get() = nodeGroupAt(current - 1).node as N
1826 private fun SlotReader.nodeGroupAt(location: Int) = get(location) as NodeGroup
1827 @Suppress("UNCHECKED_CAST")
1828 private fun SlotReader.nodeAt(location: Int) = nodeGroupAt(location).node as N
1829
1830 private fun validateNodeExpected() {
Adam Powell8dec9b72020-06-19 14:24:14 -07001831 check(nodeExpected) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001832 "A call to createNode(), emitNode() or useNode() expected was not expected"
1833 }
1834 nodeExpected = false
1835 }
1836
1837 private fun validateNodeNotExpected() {
Adam Powell8dec9b72020-06-19 14:24:14 -07001838 check(!nodeExpected) { "A call to createNode(), emitNode() or useNode() expected" }
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001839 }
1840
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001841 /**
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001842 * Add a raw change to the change list. Once [record] is called, the operation is realized
1843 * into the change list. The helper routines below reduce the number of operations that must
1844 * be realized to change the previous tree to the new tree as well as update the slot table
1845 * to prepare for the next composition.
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001846 */
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001847 private fun record(change: Change<N>) {
1848 changes.add(change)
1849 }
1850
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001851 /**
1852 * Record a change ensuring that, when it is applied, the state of the writer reflects the
1853 * current expected state. This will ensure that the applier is focused on the correct node
1854 * and the slot table writer slot is the same as the current reader's slot.
1855 */
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001856 private fun recordOperation(change: Change<N>) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001857 realizeInsertUps()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001858 realizeUps()
1859 realizeDowns()
1860 realizeOperationLocation(0)
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001861 record(change)
1862 }
1863
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001864 /**
1865 * Record a change ensuring, when it is applied, that the applier is focused on the current
1866 * node.
1867 */
1868 private fun recordApplierOperation(change: Change<N>) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001869 realizeInsertUps()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001870 realizeUps()
1871 realizeDowns()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001872 record(change)
1873 }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001874
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001875 /**
1876 * Record a change that will insert, remove or move a slot table group. This ensures the slot
1877 * table is prepared for the change be ensuring the parent group is started and then ended
1878 * as the group is left.
1879 */
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001880 private fun recordSlotEditingOperation(offset: Int = 0, change: Change<N>) {
1881 realizeOperationLocation(offset)
1882 recordSlotEditing()
1883 record(change)
1884 }
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001885
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001886 /**
1887 * Record a change ensuring, when it is applied, the write matches the current slot in the
1888 * reader.
1889 */
1890 private fun recordSlotTableOperation(offset: Int = 0, change: Change<N>) {
1891 realizeOperationLocation(offset)
1892 record(change)
1893 }
1894
1895 // Navigation of the node tree is performed by recording all the locations of the nodes as
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001896 // they are traversed by the reader and recording them in the downNodes array. When the node
1897 // navigation is realized all the downs in the down nodes is played to the applier.
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001898 //
1899 // If an up is recorded before the corresponding down is realized then it is simply removed
1900 // from the downNodes stack.
1901
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001902 private var pendingUps = 0
1903 private var downNodes = Stack<N>()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001904
1905 private fun realizeUps() {
1906 val count = pendingUps
1907 if (count > 0) {
1908 pendingUps = 0
1909 record { applier, _, _ -> repeat(count) { applier.up() } }
1910 }
1911 }
1912
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001913 private fun realizeDowns(nodes: Array<N>) {
1914 record { applier, _, _ ->
1915 for (index in nodes.indices) {
1916 applier.down(nodes[index])
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001917 }
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001918 }
1919 }
1920
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001921 private fun realizeDowns() {
1922 if (downNodes.isNotEmpty()) {
1923 @Suppress("UNCHECKED_CAST")
1924 realizeDowns(downNodes.toArray())
1925 downNodes.clear()
1926 }
1927 }
1928
1929 private fun recordDown(node: N) {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001930 @Suppress("UNCHECKED_CAST")
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001931 downNodes.push(node)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001932 }
1933
1934 private fun recordUp() {
1935 if (downNodes.isNotEmpty()) {
1936 downNodes.pop()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001937 } else {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001938 pendingUps++
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07001939 }
1940 }
1941
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001942 private var pendingInsertUps = 0
1943
1944 private fun recordInsertUp() {
1945 pendingInsertUps++
1946 }
1947
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001948 private fun realizeInsertUps() {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001949 if (pendingInsertUps > 0) {
1950 val count = pendingInsertUps
1951 record { applier, _, _ -> repeat(count) { applier.up() } }
1952 pendingInsertUps = 0
1953 }
1954 }
1955
1956 // Navigating the writer slot is performed relatively as the location of a group in the writer
1957 // might be different than it is in the reader as groups can be inserted, deleted, or moved.
1958 //
1959 // writersReaderDelta tracks the difference between reader's current slot the current of
1960 // the writer must be before the recorded change is applied. Moving the writer to a location
1961 // is performed by advancing the writer the same the number of slots traversed by the reader
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001962 // since the last write change. This works transparently for inserts. For deletes the number
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001963 // of nodes deleted needs to be added to writersReaderDelta. When slots move the delta is
1964 // updated as if the move has already taken place. The delta is updated again once the group
1965 // begin edited is complete.
1966 //
1967 // The SlotTable requires that the group that contains any moves, inserts or removes must have
1968 // the group that contains the moved, inserted or removed groups be started with a startGroup
1969 // and terminated with a endGroup so the effects of the inserts, deletes, and moves can be
1970 // recorded correctly in its internal data structures. The startedGroups stack maintains the
1971 // groups that must be closed before we can move past the started group.
1972
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001973 /**
1974 * The skew or delta between where the writer will be and where the reader is now. This can
1975 * be thought of as the unrealized distance the writer must move to match the current slot in
1976 * the reader. When an operation affects the slot table the writer location must be realized
1977 * by moving the writer slot table the unrealized distance.
1978 */
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001979 private var writersReaderDelta = 0
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001980
1981 /**
1982 * Record whether any groups were stared. If no groups were started then the root group
1983 * doesn't need to be started or ended either.
1984 */
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001985 private var startedGroup = false
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07001986
1987 /**
1988 * A stack of the location of the groups that were started.
1989 */
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07001990 private val startedGroups = IntStack()
1991
1992 private fun realizeOperationLocation(offset: Int) {
1993 val location = reader.current + offset
1994 val distance = location - writersReaderDelta
1995 require(distance >= 0) { "Tried to seek backward" }
1996 if (distance > 0) {
1997 record { _, slots, _ -> slots.skip(distance) }
1998 writersReaderDelta = location
1999 }
2000 }
2001
2002 private fun recordInsert(anchor: Anchor) {
2003 if (insertFixups.isEmpty()) {
2004 recordSlotEditingOperation { _, slots, _ ->
2005 slots.beginInsert()
2006 slots.moveFrom(insertTable, anchor.location(insertTable))
2007 slots.endInsert()
2008 }
2009 } else {
2010 val fixups = insertFixups.toMutableList()
2011 insertFixups.clear()
2012 recordSlotEditing()
2013 recordOperation { applier, slots, lifecycleManager ->
2014 insertTable.write { writer ->
2015 for (fixup in fixups) {
2016 fixup(applier, writer, lifecycleManager)
2017 }
2018 }
2019 slots.beginInsert()
2020 slots.moveFrom(insertTable, anchor.location(insertTable))
2021 slots.endInsert()
2022 }
2023 }
2024 }
2025
2026 private fun recordFixup(change: Change<N>) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07002027 realizeInsertUps()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002028 val anchor = insertAnchor
2029 val start = insertTable.anchorLocation(anchor)
2030 val location = writer.current - start
2031 insertFixups.add { _, slots, _ ->
2032 slots.current = location + insertTable.anchorLocation(anchor)
2033 }
2034 insertFixups.add(change)
2035 }
2036
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002037 /**
2038 * When a group is removed the reader will move but the writer will not so to ensure both the
2039 * writer and reader are tracking the same slot we advance the [writersReaderDelta] to
2040 * account for the removal.
2041 */
2042 private fun recordDelete() {
2043 recordSlotEditingOperation(change = removeCurrentGroupInstance)
2044 writersReaderDelta += reader.groupSize + 1
2045 }
2046
2047 /**
2048 * Called when reader current is moved directly, such as when a group moves, to [location].
2049 */
2050 private fun recordReaderMoving(location: Int) {
2051 val distance = reader.current - writersReaderDelta
2052
2053 // Ensure the next skip will account for the distance we have already travelled.
2054 writersReaderDelta = location - distance
2055 }
2056
2057 private fun recordSlotEditing() {
2058 val location = reader.parentLocation
2059
2060 if (startedGroups.peekOr(-1) != location) {
2061 // During initial composition (when the parent and current are both 0), no group needs
2062 // to be started.
2063 if (reader.current != 0) {
2064 val anchor = slotTable.anchor(location)
2065 startedGroups.push(location)
2066 startedGroup = true
2067 recordSlotTableOperation { _, slots, _ -> slots.ensureStarted(anchor) }
2068 }
2069 }
2070 }
2071
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002072 private fun recordSkipToGroupEnd() {
2073 recordSlotTableOperation(change = skipToEndGroupInstance)
2074 writersReaderDelta = reader.current
2075 }
2076
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002077 private fun recordEndGroup() {
2078 val location = reader.parentLocation
2079 val currentStartedGroup = startedGroups.peekOr(-1)
Adam Powell8dec9b72020-06-19 14:24:14 -07002080 check(currentStartedGroup <= location) { "Missed recording an endGroup" }
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002081 if (startedGroups.peekOr(-1) == location) {
2082 startedGroups.pop()
2083 recordSlotTableOperation(change = endGroupInstance)
2084 }
2085 }
2086
2087 private fun recordEndRoot() {
2088 if (startedGroup) {
2089 recordSlotTableOperation(change = endGroupInstance)
2090 startedGroup = false
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002091 }
2092 }
2093
Chuck Jazdzewski776d8232019-08-21 16:24:03 -07002094 private fun finalizeCompose() {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07002095 realizeInsertUps()
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002096 realizeUps()
Adam Powell8dec9b72020-06-19 14:24:14 -07002097 check(pendingStack.isEmpty()) { "Start/end imbalance" }
2098 check(startedGroups.isEmpty()) { "Missed recording an endGroup()" }
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08002099 cleanUpCompose()
2100 }
2101
2102 private fun cleanUpCompose() {
Chuck Jazdzewski006734c2018-09-21 09:32:38 -07002103 pending = null
2104 nodeIndex = 0
2105 groupNodeCount = 0
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002106 writersReaderDelta = 0
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07002107 currentCompoundKeyHash = 0
2108 nodeExpected = false
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002109 startedGroup = false
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07002110 startedGroups.clear()
2111 nodeCountOverrides.clear()
Chuck Jazdzewski006734c2018-09-21 09:32:38 -07002112 }
2113
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002114 private var previousRemove = -1
2115 private var previousMoveFrom = -1
2116 private var previousMoveTo = -1
2117 private var previousCount = 0
2118
2119 private fun recordRemoveNode(nodeIndex: Int, count: Int) {
2120 if (count > 0) {
Adam Powell8dec9b72020-06-19 14:24:14 -07002121 check(nodeIndex >= 0) { "Invalid remove index $nodeIndex" }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002122 if (previousRemove == nodeIndex) previousCount += count
2123 else {
2124 realizeMovement()
2125 previousRemove = nodeIndex
2126 previousCount = count
2127 }
2128 }
2129 }
2130
2131 private fun recordMoveNode(from: Int, to: Int, count: Int) {
2132 if (count > 0) {
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07002133 if (previousCount > 0 && previousMoveFrom == from - previousCount &&
Matvei Malkovac530be2019-05-14 19:43:14 +01002134 previousMoveTo == to - previousCount
2135 ) {
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002136 previousCount += count
2137 } else {
2138 realizeMovement()
2139 previousMoveFrom = from
2140 previousMoveTo = to
2141 previousCount = count
2142 }
2143 }
2144 }
2145
2146 private fun realizeMovement() {
2147 val count = previousCount
2148 previousCount = 0
2149 if (count > 0) {
2150 if (previousRemove >= 0) {
2151 val removeIndex = previousRemove
2152 previousRemove = -1
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002153 recordApplierOperation { applier, _, _ -> applier.remove(removeIndex, count) }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002154 } else {
2155 val from = previousMoveFrom
2156 previousMoveFrom = -1
2157 val to = previousMoveTo
2158 previousMoveTo = -1
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002159 recordApplierOperation { applier, _, _ -> applier.move(from, to, count) }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002160 }
2161 }
2162 }
Leland Richardsonc80ff5e2019-03-11 09:40:30 -07002163
Adam Powell7a0bfd62020-07-08 12:26:43 -07002164 /**
2165 * A holder that will dispose of its [CompositionReference] when it leaves the composition
2166 * that will not have its reference made visible to user code.
2167 */
2168 // This warning becomes an error if its advice is followed since Composer needs its type param
2169 @Suppress("RemoveRedundantQualifierName")
2170 private class CompositionReferenceHolder<T>(
2171 val ref: Composer<T>.CompositionReferenceImpl
2172 ) : CompositionLifecycleObserver {
2173 override fun onLeave() {
2174 ref.dispose()
2175 }
2176 }
2177
Chuck Jazdzewski8e7017f2020-06-05 11:21:24 -07002178 private inner class CompositionReferenceImpl(
2179 val scope: RecomposeScope,
Chuck Jazdzewski4fab4b92020-06-15 09:51:37 -07002180 override val compoundHashKey: Int,
2181 override val collectingKeySources: Boolean
Adam Powell7a0bfd62020-07-08 12:26:43 -07002182 ) : CompositionReference() {
Chuck Jazdzewski4fab4b92020-06-15 09:51:37 -07002183 var inspectionTables: MutableSet<MutableSet<SlotTable>>? = null
Leland Richardsonc80ff5e2019-03-11 09:40:30 -07002184 val composers = mutableSetOf<Composer<*>>()
2185
Adam Powell7a0bfd62020-07-08 12:26:43 -07002186 fun dispose() {
Chuck Jazdzewski4fab4b92020-06-15 09:51:37 -07002187 if (composers.isNotEmpty()) {
2188 inspectionTables?.let {
2189 for (composer in composers) {
2190 for (table in it)
2191 table.remove(composer.slotTable)
2192 }
2193 }
2194 composers.clear()
2195 }
Leland Richardsonc80ff5e2019-03-11 09:40:30 -07002196 }
2197
Leland Richardsonc80ff5e2019-03-11 09:40:30 -07002198 override fun <N> registerComposer(composer: Composer<N>) {
2199 composers.add(composer)
2200 }
2201
Adam Powell8dec9b72020-06-19 14:24:14 -07002202 override fun unregisterComposer(composer: Composer<*>) {
2203 inspectionTables?.forEach { it.remove(composer.slotTable) }
2204 composers.remove(composer)
2205 }
2206
Leland Richardson76600e32020-01-15 17:22:29 -08002207 override fun invalidate() {
Leland Richardsonc80ff5e2019-03-11 09:40:30 -07002208 // continue invalidating up the spine of AmbientReferences
Leland Richardson76600e32020-01-15 17:22:29 -08002209 parentReference?.invalidate()
Leland Richardsonc80ff5e2019-03-11 09:40:30 -07002210
Leland Richardson76600e32020-01-15 17:22:29 -08002211 invalidate(scope)
Leland Richardsonc80ff5e2019-03-11 09:40:30 -07002212 }
2213
2214 override fun <T> getAmbient(key: Ambient<T>): T {
2215 val anchor = scope.anchor
2216 return if (anchor != null && anchor.valid) {
2217 parentAmbient(key, anchor.location(slotTable))
2218 } else {
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08002219 // The composition is composing and the ambient has not landed in the slot table
2220 // yet. This is a synchronous read from a sub-composition so the current ambient
Leland Richardsonc80ff5e2019-03-11 09:40:30 -07002221 parentAmbient(key)
2222 }
2223 }
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08002224
2225 override fun getAmbientScope(): AmbientMap {
2226 return ambientScopeAt(scope.anchor?.location(slotTable) ?: 0)
2227 }
Chuck Jazdzewski4fab4b92020-06-15 09:51:37 -07002228
2229 override fun recordInspectionTable(table: MutableSet<SlotTable>) {
2230 (inspectionTables ?: HashSet<MutableSet<SlotTable>>().also {
2231 inspectionTables = it
2232 }).add(table)
2233 }
Leland Richardsonc80ff5e2019-03-11 09:40:30 -07002234 }
Andrey Kulikov1e06a872020-02-28 15:52:00 +00002235
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07002236 private fun updateCompoundKeyWhenWeEnterGroup(groupKey: Int, dataKey: Any?) {
2237 if (dataKey == null)
2238 updateCompoundKeyWhenWeEnterGroupKeyHash(groupKey)
2239 else
2240 updateCompoundKeyWhenWeEnterGroupKeyHash(dataKey.hashCode())
Chuck Jazdzewski0f26b682020-03-18 11:42:25 -07002241 }
2242
Chuck Jazdzewski0f26b682020-03-18 11:42:25 -07002243 private fun updateCompoundKeyWhenWeEnterGroupKeyHash(keyHash: Int) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07002244 currentCompoundKeyHash = (currentCompoundKeyHash rol 3) xor keyHash
Andrey Kulikov1e06a872020-02-28 15:52:00 +00002245 }
2246
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07002247 private fun updateCompoundKeyWhenWeExitGroup(groupKey: Int, dataKey: Any?) {
2248 if (dataKey == null)
2249 updateCompoundKeyWhenWeExitGroupKeyHash(groupKey)
2250 else
2251 updateCompoundKeyWhenWeExitGroupKeyHash(dataKey.hashCode())
2252 }
2253
2254 private fun updateCompoundKeyWhenWeExitGroupKeyHash(groupKey: Int) {
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07002255 currentCompoundKeyHash = (currentCompoundKeyHash xor groupKey.hashCode()) ror 3
Andrey Kulikov1e06a872020-02-28 15:52:00 +00002256 }
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002257}
2258
Leland Richardson00850282020-03-04 14:40:37 -08002259@Suppress("UNCHECKED_CAST")
Leland Richardson19285b12020-06-18 13:37:21 -07002260/*inline */ class Updater<T>(val composer: Composer<*>, val node: T) {
Leland Richardson00850282020-03-04 14:40:37 -08002261 inline fun set(
2262 value: Int,
2263 /*crossinline*/
2264 block: T.(value: Int) -> Unit
2265 ) = with(composer) {
2266 if (inserting || nextSlot() != value) {
2267 updateValue(value)
2268 node.block(value)
2269// val appliedBlock: T.(value: Int) -> Unit = { block(it) }
2270// composer.apply(value, appliedBlock)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002271 }
Leland Richardson00850282020-03-04 14:40:37 -08002272 }
2273
2274 inline fun <reified V> set(
2275 value: V,
2276 /*crossinline*/
2277 block: T.(value: V) -> Unit
2278 ) = with(composer) {
2279 if (inserting || nextSlot() != value) {
2280 updateValue(value)
2281 node.block(value)
2282// val appliedBlock: T.(value: V) -> Unit = { block(it) }
2283// composer.apply(value, appliedBlock)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002284 }
Leland Richardson00850282020-03-04 14:40:37 -08002285 }
2286
2287 inline fun update(
2288 value: Int,
2289 /*crossinline*/
2290 block: T.(value: Int) -> Unit
2291 ) = with(composer) {
2292 if (inserting || nextSlot() != value) {
2293 updateValue(value)
2294 node.block(value)
2295// val appliedBlock: T.(value: Int) -> Unit = { block(it) }
2296// if (!inserting) composer.apply(value, appliedBlock)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002297 }
Leland Richardson00850282020-03-04 14:40:37 -08002298 }
2299
2300 inline fun <reified V> update(
2301 value: V,
2302 /*crossinline*/
2303 block: T.(value: V) -> Unit
2304 ) = with(composer) {
2305 if (inserting || nextSlot() != value) {
2306 updateValue(value)
2307 node.block(value)
2308// val appliedBlock: T.(value: V) -> Unit = { block(it) }
2309// if (!inserting) composer.apply(value, appliedBlock)
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002310 }
Leland Richardson00850282020-03-04 14:40:37 -08002311 }
Leland Richardson19285b12020-06-18 13:37:21 -07002312
2313 inline fun reconcile(
2314 block: T.() -> Unit
2315 ) {
2316 node.block()
2317 }
Leland Richardson00850282020-03-04 14:40:37 -08002318}
2319
Adam Powell8dec9b72020-06-19 14:24:14 -07002320private fun SlotWriter.removeCurrentGroup(lifecycleManager: LifecycleManager) {
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -08002321 // Notify the lifecycle manager of any observers leaving the slot table
Leland Richardsonf87e0e92019-01-08 19:09:03 -08002322 // The notification order should ensure that listeners are notified of leaving
2323 // in opposite order that they are notified of entering.
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -08002324
Leland Richardsonf87e0e92019-01-08 19:09:03 -08002325 // To ensure this order, we call `enters` as a pre-order traversal
2326 // of the group tree, and then call `leaves` in the inverse order.
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -08002327
2328 var groupEnd = Int.MAX_VALUE
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -08002329 var index = 0
2330 val groupEndStack = IntStack()
2331
Adam Powell8dec9b72020-06-19 14:24:14 -07002332 for (slot in groupSlots()) {
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -08002333 when (slot) {
Chuck Jazdzewski60cdc462020-03-30 09:12:15 -07002334 is CompositionLifecycleObserver -> {
Leland Richardsonf87e0e92019-01-08 19:09:03 -08002335 lifecycleManager.leaving(slot)
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -08002336 }
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07002337 is Group -> {
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -08002338 groupEndStack.push(groupEnd)
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -08002339 groupEnd = index + slot.slots
2340 }
Chuck Jazdzewski7d869002020-06-24 13:31:48 -07002341 is RecomposeScope -> {
2342 slot.composer = null
2343 }
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -08002344 }
2345
2346 index++
2347
2348 while (index >= groupEnd) {
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -08002349 groupEnd = groupEndStack.pop()
2350 }
2351 }
2352
2353 if (groupEndStack.isNotEmpty()) error("Invalid slot structure")
2354
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -08002355 // Remove the item from the slot table and notify the FrameManager if any
2356 // anchors are orphaned by removing the slots.
Adam Powell8dec9b72020-06-19 14:24:14 -07002357 if (removeGroup()) {
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -08002358 FrameManager.scheduleCleanup()
2359 }
2360}
2361
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002362// Mutable list
2363private fun <K, V> multiMap() = HashMap<K, LinkedHashSet<V>>()
Chuck Jazdzewski3d43fd82018-11-27 12:47:48 -08002364
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07002365private fun <K, V> HashMap<K, LinkedHashSet<V>>.put(key: K, value: V) = getOrPut(key) {
2366 LinkedHashSet()
2367}.add(value)
2368
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002369private fun <K, V> HashMap<K, LinkedHashSet<V>>.remove(key: K, value: V) =
2370 get(key)?.let {
2371 it.remove(value)
2372 if (it.isEmpty()) remove(key)
2373 }
Chuck Jazdzewski3d43fd82018-11-27 12:47:48 -08002374
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07002375private fun <K, V> HashMap<K, LinkedHashSet<V>>.pop(key: K) = get(key)?.firstOrNull()?.also {
2376 remove(key, it)
2377}
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002378
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002379private fun getKey(value: Any?, left: Any?, right: Any?): Any? = (value as? JoinedKey)?.let {
Chuck Jazdzewski9d160f32019-04-08 09:47:44 -07002380 if (it.left == left && it.right == right) value
Leland Richardson6cab6e32019-05-02 11:52:13 -07002381 else getKey(it.left, left, right) ?: getKey(
2382 it.right,
2383 left,
2384 right
2385 )
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002386}
2387
2388// Invalidation helpers
Chuck Jazdzewskiece91d32020-05-11 10:37:03 -07002389private fun MutableList<Invalidation>.findLocation(location: Int): Int {
2390 var low = 0
2391 var high = size - 1
2392
2393 while (low <= high) {
2394 val mid = (low + high).ushr(1) // safe from overflows
2395 val midVal = get(mid)
2396 val cmp = midVal.location.compareTo(location)
2397
2398 if (cmp < 0)
2399 low = mid + 1
2400 else if (cmp > 0)
2401 high = mid - 1
2402 else
2403 return mid // key found
2404 }
2405 return -(low + 1) // key not found
2406}
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002407
Chuck Jazdzewski3d43fd82018-11-27 12:47:48 -08002408private fun MutableList<Invalidation>.insertIfMissing(location: Int, scope: RecomposeScope) {
2409 val index = findLocation(location)
2410 if (index < 0) {
Leland Richardson9138cbe2019-04-24 14:53:02 -07002411 add(-(index + 1), Invalidation(scope, location))
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002412 }
2413}
2414
2415private fun MutableList<Invalidation>.firstInRange(start: Int, end: Int): Invalidation? {
2416 val index = findLocation(start).let { if (it < 0) -(it + 1) else it }
2417 if (index < size) {
2418 val firstInvalidation = get(index)
2419 if (firstInvalidation.location <= end) return firstInvalidation
2420 }
2421 return null
2422}
2423
Leland Richardson797bd0cd2020-05-07 15:16:51 -07002424private fun MutableList<Invalidation>.removeLocation(location: Int): Invalidation? {
Chuck Jazdzewski724fffc2018-08-10 13:42:56 -07002425 val index = findLocation(location)
Leland Richardson797bd0cd2020-05-07 15:16:51 -07002426 return if (index >= 0) removeAt(index) else null
Chuck Jazdzewskib59aa262019-01-03 16:17:09 -08002427}
Chuck Jazdzewski8a990d12019-03-05 11:34:51 -08002428
2429private fun MutableList<Invalidation>.removeRange(start: Int, end: Int) {
2430 val index = findLocation(start).let { if (it < 0) -(it + 1) else it }
2431 while (index < size) {
2432 val validation = get(index)
2433 if (validation.location <= end) removeAt(index)
2434 else break
2435 }
2436}
Leland Richardsona5396b42019-10-11 17:34:55 -07002437
Chuck Jazdzewskifb6db652019-11-25 08:30:51 -08002438private fun Boolean.asInt() = if (this) 1 else 0
2439private fun Int.asBool() = this != 0
2440
Leland Richardson0bb62a82020-01-06 15:07:37 -08002441@Composable
Leland Richardson985ef592020-02-14 15:18:10 -08002442val currentComposer: Composer<*> get() {
Leland Richardson0bb62a82020-01-06 15:07:37 -08002443 throw NotImplementedError("Implemented as an intrinsic")
Leland Richardson534a3462020-02-05 23:26:02 -08002444}
2445
Leland Richardson00850282020-03-04 14:40:37 -08002446// TODO: get rid of the need for this when we merge FrameManager and Recomposer together!
Leland Richardson985ef592020-02-14 15:18:10 -08002447internal var currentComposerInternal: Composer<*>? = null
2448
Louis Pullen-Freilich3a54b942020-05-07 13:23:03 +01002449internal fun invokeComposable(composer: Composer<*>, composable: @Composable () -> Unit) {
Leland Richardson534a3462020-02-05 23:26:02 -08002450 @Suppress("UNCHECKED_CAST")
Leland Richardson797bd0cd2020-05-07 15:16:51 -07002451 val realFn = composable as Function3<Composer<*>, Int, Int, Unit>
2452 realFn(composer, 0, 1)
Leland Richardson534a3462020-02-05 23:26:02 -08002453}
2454
2455internal fun <T> invokeComposableForResult(
2456 composer: Composer<*>,
Louis Pullen-Freilich3a54b942020-05-07 13:23:03 +01002457 composable: @Composable () -> T
Leland Richardson534a3462020-02-05 23:26:02 -08002458): T {
2459 @Suppress("UNCHECKED_CAST")
Leland Richardson797bd0cd2020-05-07 15:16:51 -07002460 val realFn = composable as Function3<Composer<*>, Int, Int, T>
2461 return realFn(composer, 0, 1)
Leland Richardson534a3462020-02-05 23:26:02 -08002462}
Leland Richardson00850282020-03-04 14:40:37 -08002463
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07002464private fun Group.distanceFrom(root: Group): Int {
2465 var count = 0
2466 var current: Group? = this
2467 while (current != null && current != root) {
2468 current = current.parent
2469 count++
2470 }
2471 return count
2472}
2473
2474// find the nearest common root
2475private fun nearestCommonRootOf(a: Group, b: Group, common: Group): Group? {
2476 // Early outs, to avoid calling distanceFrom in trivial cases
2477 if (a == b) return a // A group is the nearest common root of itself
2478 if (a == common || b == common) return common // If either is common then common is nearest
2479 if (a.parent == b) return b // if b is a's parent b is the nearest common root
2480 if (b.parent == a) return a // if a is b's parent a is the nearest common root
2481 if (a.parent == b.parent) return a.parent // if a an b share a parent it is the nearest common
2482
2483 // Find the nearest using distance from common
2484 var currentA: Group? = a
2485 var currentB: Group? = b
2486 val aDistance = a.distanceFrom(common)
2487 val bDistance = b.distanceFrom(common)
2488 repeat(aDistance - bDistance) { currentA = currentA?.parent }
2489 repeat(bDistance - aDistance) { currentB = currentB?.parent }
2490
2491 // Both ca and cb are now the same distance from a known common root,
2492 // therefore, the first parent that is the same is the lowest common root.
2493 while (currentA != currentB) {
2494 currentA = currentA?.parent
2495 currentB = currentB?.parent
2496 }
2497
2498 // ca == cb so it doesn't matter which is returned
2499 return currentA
2500}
2501
2502private val removeCurrentGroupInstance: Change<*> = { _, slots, lifecycleManager ->
Adam Powell8dec9b72020-06-19 14:24:14 -07002503 slots.removeCurrentGroup(lifecycleManager)
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07002504}
2505private val skipToEndGroupInstance: Change<*> = { _, slots, _ -> slots.skipToGroupEnd() }
2506private val endGroupInstance: Change<*> = { _, slots, _ -> slots.endGroup() }
2507
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07002508private val KeyInfo.joinedKey: Any get() = if (dataKey != null) JoinedKey(key, dataKey) else key
2509
2510/*
2511 * Integer keys are arbitrary values in the biload range. The do not need to be unique as if
2512 * there is a chance they will collide with a compiler generated key they are paired with a
2513 * OpaqueKey to ensure they are unique.
2514 */
2515
2516// rootKey doesn't need a corresponding OpaqueKey as it never has sibling nodes and will always
2517// a unique key.
2518private const val rootKey = 100
2519
2520// An arbitrary value paired with a boxed Int or a JoinKey data key.
2521private const val nodeKey = 125
2522
Leland Richardson00850282020-03-04 14:40:37 -08002523@PublishedApi
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07002524internal const val invocationKey = 200
2525
2526@PublishedApi
2527internal val invocation = OpaqueKey("provider")
2528
2529@PublishedApi
2530internal const val providerKey = 201
Leland Richardson00850282020-03-04 14:40:37 -08002531
2532@PublishedApi
Andrey Kulikov15f44212020-03-12 14:25:14 +00002533internal val provider = OpaqueKey("provider")
Leland Richardson00850282020-03-04 14:40:37 -08002534
2535@PublishedApi
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07002536internal const val ambientMapKey = 202
2537
2538@PublishedApi
Chuck Jazdzewskic14dc6e2020-04-13 16:26:35 -07002539internal val ambientMap = OpaqueKey("ambientMap")
2540
2541@PublishedApi
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07002542internal const val providerValuesKey = 203
2543
2544@PublishedApi
Andrey Kulikov15f44212020-03-12 14:25:14 +00002545internal val providerValues = OpaqueKey("providerValues")
Leland Richardson00850282020-03-04 14:40:37 -08002546
2547@PublishedApi
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07002548internal const val providerMapsKey = 204
Leland Richardson00850282020-03-04 14:40:37 -08002549
2550@PublishedApi
Chuck Jazdzewskibb0fab242020-05-08 14:11:33 -07002551internal val providerMaps = OpaqueKey("providers")
2552
2553@PublishedApi
2554internal const val referenceKey = 206
Leland Richardson00850282020-03-04 14:40:37 -08002555
2556@PublishedApi
Andrey Kulikov15f44212020-03-12 14:25:14 +00002557internal val reference = OpaqueKey("reference")