1 /*
<lambda>null2  * 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 
17 @file:OptIn(
18     InternalComposeApi::class,
19 )
20 @file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress")
21 
22 package androidx.compose.runtime
23 
24 import androidx.collection.MutableIntIntMap
25 import androidx.collection.MutableIntObjectMap
26 import androidx.collection.MutableScatterMap
27 import androidx.collection.MutableScatterSet
28 import androidx.collection.ObjectList
29 import androidx.collection.ScatterMap
30 import androidx.collection.ScatterSet
31 import androidx.collection.emptyScatterMap
32 import androidx.collection.mutableScatterMapOf
33 import androidx.collection.mutableScatterSetOf
34 import androidx.compose.runtime.changelist.ChangeList
35 import androidx.compose.runtime.changelist.ComposerChangeListWriter
36 import androidx.compose.runtime.changelist.FixupList
37 import androidx.compose.runtime.collection.MultiValueMap
38 import androidx.compose.runtime.collection.ScopeMap
39 import androidx.compose.runtime.collection.fastFilter
40 import androidx.compose.runtime.collection.sortedBy
41 import androidx.compose.runtime.internal.IntRef
42 import androidx.compose.runtime.internal.invokeComposable
43 import androidx.compose.runtime.internal.persistentCompositionLocalHashMapOf
44 import androidx.compose.runtime.internal.trace
45 import androidx.compose.runtime.snapshots.currentSnapshot
46 import androidx.compose.runtime.snapshots.fastForEach
47 import androidx.compose.runtime.snapshots.fastMap
48 import androidx.compose.runtime.snapshots.fastToSet
49 import androidx.compose.runtime.tooling.ComposeStackTraceFrame
50 import androidx.compose.runtime.tooling.CompositionData
51 import androidx.compose.runtime.tooling.CompositionErrorContextImpl
52 import androidx.compose.runtime.tooling.CompositionGroup
53 import androidx.compose.runtime.tooling.CompositionInstance
54 import androidx.compose.runtime.tooling.LocalCompositionErrorContext
55 import androidx.compose.runtime.tooling.LocalInspectionTables
56 import androidx.compose.runtime.tooling.attachComposeStackTrace
57 import androidx.compose.runtime.tooling.buildTrace
58 import androidx.compose.runtime.tooling.findLocation
59 import androidx.compose.runtime.tooling.findSubcompositionContextGroup
60 import androidx.compose.runtime.tooling.traceForGroup
61 import kotlin.contracts.ExperimentalContracts
62 import kotlin.contracts.contract
63 import kotlin.coroutines.CoroutineContext
64 import kotlin.coroutines.EmptyCoroutineContext
65 import kotlin.jvm.JvmInline
66 import kotlin.jvm.JvmName
67 
68 private class GroupInfo(
69     /**
70      * The current location of the slot relative to the start location of the pending slot changes
71      */
72     var slotIndex: Int,
73 
74     /**
75      * The current location of the first node relative the start location of the pending node
76      * changes
77      */
78     var nodeIndex: Int,
79 
80     /** The current number of nodes the group contains after changes have been applied */
81     var nodeCount: Int
82 )
83 
84 /**
85  * An interface used during [ControlledComposition.applyChanges] and [Composition.dispose] to track
86  * when [RememberObserver] instances and leave the composition an also allows recording [SideEffect]
87  * calls.
88  */
89 internal interface RememberManager {
90     /** The [RememberObserver] is being remembered by a slot in the slot table. */
91     fun remembering(instance: RememberObserverHolder)
92 
93     /** The [RememberObserver] is being forgotten by a slot in the slot table. */
94     fun forgetting(
95         instance: RememberObserverHolder,
96         endRelativeOrder: Int,
97         priority: Int,
98         endRelativeAfter: Int
99     )
100 
101     /**
102      * The [effect] should be called when changes are being applied but after the remember/forget
103      * notifications are sent.
104      */
105     fun sideEffect(effect: () -> Unit)
106 
107     /** The [ComposeNodeLifecycleCallback] is being deactivated. */
108     fun deactivating(
109         instance: ComposeNodeLifecycleCallback,
110         endRelativeOrder: Int,
111         priority: Int,
112         endRelativeAfter: Int
113     )
114 
115     /** The [ComposeNodeLifecycleCallback] is being released. */
116     fun releasing(
117         instance: ComposeNodeLifecycleCallback,
118         endRelativeOrder: Int,
119         priority: Int,
120         endRelativeAfter: Int
121     )
122 
123     /** The restart scope is pausing */
124     fun rememberPausingScope(scope: RecomposeScopeImpl)
125 
126     /** The restart scope is resuming */
127     fun startResumingScope(scope: RecomposeScopeImpl)
128 
129     /** The restart scope is finished resuming */
130     fun endResumingScope(scope: RecomposeScopeImpl)
131 }
132 
133 /**
134  * Pending starts when the key is different than expected indicating that the structure of the tree
135  * changed. It is used to determine how to update the nodes and the slot table when changes to the
136  * structure of the tree is detected.
137  */
138 private class Pending(val keyInfos: MutableList<KeyInfo>, val startIndex: Int) {
139     var groupIndex: Int = 0
140 
141     init {
<lambda>null142         requirePrecondition(startIndex >= 0) { "Invalid start index" }
143     }
144 
145     private val usedKeys = mutableListOf<KeyInfo>()
<lambda>null146     private val groupInfos = run {
147         var runningNodeIndex = 0
148         val result = MutableIntObjectMap<GroupInfo>()
149         for (index in 0 until keyInfos.size) {
150             val keyInfo = keyInfos[index]
151             result[keyInfo.location] = GroupInfo(index, runningNodeIndex, keyInfo.nodes)
152             runningNodeIndex += keyInfo.nodes
153         }
154         result
155     }
156 
157     /**
158      * A multi-map of keys from the previous composition. The keys can be retrieved in the order
159      * they were generated by the previous composition.
160      */
<lambda>null161     val keyMap by lazy {
162         multiMap<Any, KeyInfo>(keyInfos.size).also {
163             for (index in 0 until keyInfos.size) {
164                 val keyInfo = keyInfos[index]
165                 it.add(keyInfo.joinedKey, keyInfo)
166             }
167         }
168     }
169 
170     /** Get the next key information for the given key. */
getNextnull171     fun getNext(key: Int, dataKey: Any?): KeyInfo? {
172         val joinedKey: Any = if (dataKey != null) JoinedKey(key, dataKey) else key
173         return keyMap.removeFirst(joinedKey)
174     }
175 
176     /** Record that this key info was generated. */
recordUsednull177     fun recordUsed(keyInfo: KeyInfo) = usedKeys.add(keyInfo)
178 
179     val used: List<KeyInfo>
180         get() = usedKeys
181 
182     // TODO(chuckj): This is a correct but expensive implementation (worst cases of O(N^2)). Rework
183     // to O(N)
184     fun registerMoveSlot(from: Int, to: Int) {
185         if (from > to) {
186             groupInfos.forEachValue { group ->
187                 val position = group.slotIndex
188                 if (position == from) group.slotIndex = to
189                 else if (position in to until from) group.slotIndex = position + 1
190             }
191         } else if (to > from) {
192             groupInfos.forEachValue { group ->
193                 val position = group.slotIndex
194                 if (position == from) group.slotIndex = to
195                 else if (position in (from + 1) until to) group.slotIndex = position - 1
196             }
197         }
198     }
199 
registerMoveNodenull200     fun registerMoveNode(from: Int, to: Int, count: Int) {
201         if (from > to) {
202             groupInfos.forEachValue { group ->
203                 val position = group.nodeIndex
204                 if (position in from until from + count) group.nodeIndex = to + (position - from)
205                 else if (position in to until from) group.nodeIndex = position + count
206             }
207         } else if (to > from) {
208             groupInfos.forEachValue { group ->
209                 val position = group.nodeIndex
210                 if (position in from until from + count) group.nodeIndex = to + (position - from)
211                 else if (position in (from + 1) until to) group.nodeIndex = position - count
212             }
213         }
214     }
215 
216     @OptIn(InternalComposeApi::class)
registerInsertnull217     fun registerInsert(keyInfo: KeyInfo, insertIndex: Int) {
218         groupInfos[keyInfo.location] = GroupInfo(-1, insertIndex, 0)
219     }
220 
updateNodeCountnull221     fun updateNodeCount(group: Int, newCount: Int): Boolean {
222         val groupInfo = groupInfos[group]
223         if (groupInfo != null) {
224             val index = groupInfo.nodeIndex
225             val difference = newCount - groupInfo.nodeCount
226             groupInfo.nodeCount = newCount
227             if (difference != 0) {
228                 groupInfos.forEachValue { childGroupInfo ->
229                     if (childGroupInfo.nodeIndex >= index && childGroupInfo != groupInfo) {
230                         val newIndex = childGroupInfo.nodeIndex + difference
231                         if (newIndex >= 0) childGroupInfo.nodeIndex = newIndex
232                     }
233                 }
234             }
235             return true
236         }
237         return false
238     }
239 
240     @OptIn(InternalComposeApi::class)
slotPositionOfnull241     fun slotPositionOf(keyInfo: KeyInfo) = groupInfos[keyInfo.location]?.slotIndex ?: -1
242 
243     @OptIn(InternalComposeApi::class)
244     fun nodePositionOf(keyInfo: KeyInfo) = groupInfos[keyInfo.location]?.nodeIndex ?: -1
245 
246     @OptIn(InternalComposeApi::class)
247     fun updatedNodeCountOf(keyInfo: KeyInfo) =
248         groupInfos[keyInfo.location]?.nodeCount ?: keyInfo.nodes
249 }
250 
251 private class Invalidation(
252     /** The recompose scope being invalidate */
253     val scope: RecomposeScopeImpl,
254 
255     /** The index of the group in the slot table being invalidated. */
256     val location: Int,
257 
258     /**
259      * The instances invalidating the scope. If this is `null` or empty then the scope is
260      * unconditionally invalid. If it contains instances it is only invalid if at least on of the
261      * instances is changed. This is used to track `DerivedState<*>` changes and only treat the
262      * scope as invalid if the instance has changed.
263      *
264      * Can contain a [ScatterSet] of instances, single instance or null.
265      */
266     var instances: Any?
267 ) {
268     fun isInvalid(): Boolean = scope.isInvalidFor(instances)
269 }
270 
271 /**
272  * Internal compose compiler plugin API that is used to update the function the composer will call
273  * to recompose a recomposition scope. This should not be used or called directly.
274  */
275 @ComposeCompilerApi
276 interface ScopeUpdateScope {
277     /**
278      * Called by generated code to update the recomposition scope with the function to call
279      * recompose the scope. This is called by code generated by the compose compiler plugin and
280      * should not be called directly.
281      */
updateScopenull282     fun updateScope(block: (Composer, Int) -> Unit)
283 }
284 
285 internal enum class InvalidationResult {
286     /**
287      * The invalidation was ignored because the associated recompose scope is no longer part of the
288      * composition or has yet to be entered in the composition. This could occur for invalidations
289      * called on scopes that are no longer part of composition or if the scope was invalidated
290      * before [ControlledComposition.applyChanges] was called that will enter the scope into the
291      * composition.
292      */
293     IGNORED,
294 
295     /**
296      * The composition is not currently composing and the invalidation was recorded for a future
297      * composition. A recomposition requested to be scheduled.
298      */
299     SCHEDULED,
300 
301     /**
302      * The composition that owns the recompose scope is actively composing but the scope has already
303      * been composed or is in the process of composing. The invalidation is treated as SCHEDULED
304      * above.
305      */
306     DEFERRED,
307 
308     /**
309      * The composition that owns the recompose scope is actively composing and the invalidated scope
310      * has not been composed yet but will be recomposed before the composition completes. A new
311      * recomposition was not scheduled for this invalidation.
312      */
313     IMMINENT
314 }
315 
316 /**
317  * An instance to hold a value provided by [CompositionLocalProvider] and is created by the
318  * [ProvidableCompositionLocal.provides] infix operator. If [canOverride] is `false`, the provided
319  * value will not overwrite a potentially already existing value in the scope.
320  *
321  * This value cannot be created directly. It can only be created by using one of the `provides`
322  * operators of [ProvidableCompositionLocal].
323  *
324  * @see ProvidableCompositionLocal.provides
325  * @see ProvidableCompositionLocal.providesDefault
326  * @see ProvidableCompositionLocal.providesComputed
327  */
328 class ProvidedValue<T>
329 internal constructor(
330     /**
331      * The composition local that is provided by this value. This is the left-hand side of the
332      * [ProvidableCompositionLocal.provides] infix operator.
333      */
334     val compositionLocal: CompositionLocal<T>,
335     value: T?,
336     private val explicitNull: Boolean,
337     internal val mutationPolicy: SnapshotMutationPolicy<T>?,
338     internal val state: MutableState<T>?,
339     internal val compute: (CompositionLocalAccessorScope.() -> T)?,
340     internal val isDynamic: Boolean
341 ) {
342     private val providedValue: T? = value
343 
344     /**
345      * The value provided by the [ProvidableCompositionLocal.provides] infix operator. This is the
346      * right-hand side of the operator.
347      */
348     @Suppress("UNCHECKED_CAST")
349     val value: T
350         get() = providedValue as T
351 
352     /**
353      * This value is `true` if the provided value will override any value provided above it. This
354      * value is `true` when using [ProvidableCompositionLocal.provides] but `false` when using
355      * [ProvidableCompositionLocal.providesDefault].
356      *
357      * @see ProvidableCompositionLocal.provides
358      * @see ProvidableCompositionLocal.providesDefault
359      */
360     @get:JvmName("getCanOverride")
361     var canOverride: Boolean = true
362         private set
363 
364     @Suppress("UNCHECKED_CAST")
365     internal val effectiveValue: T
366         get() =
367             when {
368                 explicitNull -> null as T
369                 state != null -> state.value
370                 providedValue != null -> providedValue
371                 else -> composeRuntimeError("Unexpected form of a provided value")
372             }
373 
374     internal val isStatic
375         get() = (explicitNull || value != null) && !isDynamic
376 
<lambda>null377     internal fun ifNotAlreadyProvided() = this.also { canOverride = false }
378 }
379 
380 /**
381  * A Compose compiler plugin API. DO NOT call directly.
382  *
383  * An instance used to track the identity of the movable content. Using a holder object allows
384  * creating unique movable content instances from the same instance of a lambda. This avoids using
385  * the identity of a lambda instance as it can be merged into a singleton or merged by later
386  * rewritings and using its identity might lead to unpredictable results that might change from the
387  * debug and release builds.
388  */
389 @InternalComposeApi class MovableContent<P>(val content: @Composable (parameter: P) -> Unit)
390 
391 /**
392  * A Compose compiler plugin API. DO NOT call directly.
393  *
394  * A reference to the movable content state prior to changes being applied.
395  */
396 @InternalComposeApi
397 class MovableContentStateReference
398 internal constructor(
399     internal val content: MovableContent<Any?>,
400     internal val parameter: Any?,
401     internal val composition: ControlledComposition,
402     internal val slotTable: SlotTable,
403     internal val anchor: Anchor,
404     internal var invalidations: List<Pair<RecomposeScopeImpl, Any?>>,
405     internal val locals: PersistentCompositionLocalMap,
406     internal val nestedReferences: List<MovableContentStateReference>?
407 )
408 
409 /**
410  * A Compose compiler plugin API. DO NOT call directly.
411  *
412  * A reference to the state of a [MovableContent] after changes have being applied. This is the
413  * state that was removed from the `from` composition during [ControlledComposition.applyChanges]
414  * and before it is inserted during [ControlledComposition.insertMovableContent].
415  */
416 @InternalComposeApi
417 class MovableContentState internal constructor(internal val slotTable: SlotTable) {
418 
419     /** Extract one or more states for movable content that is nested in the [slotTable]. */
extractNestedStatesnull420     internal fun extractNestedStates(
421         applier: Applier<*>,
422         references: ObjectList<MovableContentStateReference>
423     ): ScatterMap<MovableContentStateReference, MovableContentState> {
424         // We can only remove states that are contained in this states slot table so the references
425         // with anchors not owned by the slotTable should be removed. We also should traverse the
426         // slot table in order to avoid thrashing the gap buffer so the references are sorted.
427         val referencesToExtract =
428             references
429                 .fastFilter { slotTable.ownsAnchor(it.anchor) }
430                 .sortedBy { slotTable.anchorIndex(it.anchor) }
431         if (referencesToExtract.isEmpty()) return emptyScatterMap()
432         val result = mutableScatterMapOf<MovableContentStateReference, MovableContentState>()
433         slotTable.write { writer ->
434             fun closeToGroupContaining(group: Int) {
435                 while (writer.parent >= 0 && writer.currentGroupEnd <= group) {
436                     writer.skipToGroupEnd()
437                     writer.endGroup()
438                 }
439             }
440             fun openParent(parent: Int) {
441                 closeToGroupContaining(parent)
442                 while (writer.currentGroup != parent && !writer.isGroupEnd) {
443                     if (parent < writer.nextGroup) {
444                         writer.startGroup()
445                     } else {
446                         writer.skipGroup()
447                     }
448                 }
449                 runtimeCheck(writer.currentGroup == parent) { "Unexpected slot table structure" }
450                 writer.startGroup()
451             }
452             referencesToExtract.forEach { reference ->
453                 val newGroup = writer.anchorIndex(reference.anchor)
454                 val newParent = writer.parent(newGroup)
455                 closeToGroupContaining(newParent)
456                 openParent(newParent)
457                 writer.advanceBy(newGroup - writer.currentGroup)
458                 val content =
459                     extractMovableContentAtCurrent(
460                         composition = reference.composition,
461                         reference = reference,
462                         slots = writer,
463                         applier = applier,
464                     )
465                 result[reference] = content
466             }
467             closeToGroupContaining(Int.MAX_VALUE)
468         }
469         return result
470     }
471 }
472 
473 private val SlotWriter.nextGroup
474     get() = currentGroup + groupSize(currentGroup)
475 
476 /**
477  * Composer is the interface that is targeted by the Compose Kotlin compiler plugin and used by code
478  * generation helpers. It is highly recommended that direct calls these be avoided as the runtime
479  * assumes that the calls are generated by the compiler and contain only a minimum amount of state
480  * validation.
481  */
482 sealed interface Composer {
483     /**
484      * A Compose compiler plugin API. DO NOT call directly.
485      *
486      * Changes calculated and recorded during composition and are sent to [applier] which makes the
487      * physical changes to the node tree implied by a composition.
488      *
489      * Composition has two discrete phases, 1) calculate and record changes and 2) making the
490      * changes via the [applier]. While a [Composable] functions is executing, none of the [applier]
491      * methods are called. The recorded changes are sent to the [applier] all at once after all
492      * [Composable] functions have completed.
493      */
494     @ComposeCompilerApi val applier: Applier<*>
495 
496     /**
497      * A Compose compiler plugin API. DO NOT call directly.
498      *
499      * Reflects that a new part of the composition is being created, that is, the composition will
500      * insert new nodes into the resulting tree.
501      */
502     @ComposeCompilerApi val inserting: Boolean
503 
504     /**
505      * A Compose compiler plugin API. DO NOT call directly.
506      *
507      * Reflects whether the [Composable] function can skip. Even if a [Composable] function is
508      * called with the same parameters it might still need to run because, for example, a new value
509      * was provided for a [CompositionLocal] created by [staticCompositionLocalOf].
510      */
511     @ComposeCompilerApi val skipping: Boolean
512 
513     /**
514      * A Compose compiler plugin API. DO NOT call directly.
515      *
516      * Reflects whether the default parameter block of a [Composable] function is valid. This is
517      * `false` if a [State] object read in the [startDefaults] group was modified since the last
518      * time the [Composable] function was run.
519      */
520     @ComposeCompilerApi val defaultsInvalid: Boolean
521 
522     /**
523      * A Compose internal property. DO NOT call directly. Use [currentRecomposeScope] instead.
524      *
525      * The invalidation current invalidation scope. An new invalidation scope is created whenever
526      * [startRestartGroup] is called. when this scope's [RecomposeScope.invalidate] is called then
527      * lambda supplied to [endRestartGroup]'s [ScopeUpdateScope] will be scheduled to be run.
528      */
529     @InternalComposeApi val recomposeScope: RecomposeScope?
530 
531     /**
532      * A Compose compiler plugin API. DO NOT call directly.
533      *
534      * Return an object that can be used to uniquely identity of the current recomposition scope.
535      * This identity will be the same even if the recompose scope instance changes.
536      *
537      * This is used internally by tooling track composable function invocations.
538      */
539     @ComposeCompilerApi val recomposeScopeIdentity: Any?
540 
541     /**
542      * A Compose internal property. DO NOT call directly. Use [currentCompositeKeyHash] instead.
543      *
544      * This a hash value used to map externally stored state to the composition. For example, this
545      * is used by saved instance state to preserve state across activity lifetime boundaries.
546      *
547      * This value is likely but not guaranteed to be unique. There are known cases, such as for
548      * loops without a unique [key], where the runtime does not have enough information to make the
549      * compound key hash unique.
550      */
551     @Deprecated(
552         "Prefer the higher-precision compositeKeyHashCode instead",
553         ReplaceWith("compositeKeyHashCode")
554     )
555     @InternalComposeApi
556     val compoundKeyHash: Int
557         get() = compositeKeyHashCode.hashCode()
558 
559     /**
560      * A Compose internal property. DO NOT call directly. Use [currentCompositeKeyHashCode] instead.
561      *
562      * This a hash value used to map externally stored state to the composition. For example, this
563      * is used by saved instance state to preserve state across activity lifetime boundaries.
564      *
565      * This value is likely but not guaranteed to be unique. There are known cases, such as for
566      * loops without a unique [key], where the runtime does not have enough information to make the
567      * compound key hash unique.
568      */
569     @InternalComposeApi val compositeKeyHashCode: CompositeKeyHashCode
570 
571     // Groups
572 
573     /**
574      * A Compose compiler plugin API. DO NOT call directly.
575      *
576      * Start a replaceable group. A replaceable group is a group that cannot be moved during
577      * execution and can only either inserted, removed, or replaced. For example, the group created
578      * by most control flow constructs such as an `if` statement are replaceable groups.
579      *
580      * Warning: Versions of the compiler that generate calls to this function also contain subtle
581      * bug that does not generate a group around a loop containing code that just creates composable
582      * lambdas (AnimatedContent from androidx.compose.animation, for example) which makes replacing
583      * the group unsafe and the this must treat this like a movable group. [startReplaceGroup] was
584      * added that will replace the group as described above and is only called by versions of the
585      * compiler that correctly generate code around loops that create lambdas. This method is kept
586      * to maintain compatibility with code generated by older versions of the compose compiler
587      * plugin.
588      *
589      * @param key A compiler generated key based on the source location of the call.
590      */
startReplaceableGroupnull591     @ComposeCompilerApi fun startReplaceableGroup(key: Int)
592 
593     /**
594      * A Compose compiler plugin API. DO NOT call directly.
595      *
596      * Called at the end of a replaceable group.
597      *
598      * @see startRestartGroup
599      */
600     @ComposeCompilerApi fun endReplaceableGroup()
601 
602     /**
603      * A Compose compiler plugin API. DO NOT call directly.
604      *
605      * Start a replace group. A replace group is a group that cannot be moved during must only
606      * either be inserted, removed, or replaced. For example, the group created by most control flow
607      * constructs such as an `if` statement are replaceable groups.
608      *
609      * Note: This method replaces [startReplaceableGroup] which is only generated by older versions
610      * of the compose compiler plugin that predate the addition of this method. The runtime is now
611      * required to replace the group if a different group is detected instead of treating it like a
612      * movable group.
613      *
614      * @param key A compiler generated key based on the source location of the call.
615      * @see endReplaceGroup
616      */
617     @ComposeCompilerApi fun startReplaceGroup(key: Int)
618 
619     /**
620      * A Compose compiler plugin API. DO NOT call directly.
621      *
622      * Called at the end of a replace group.
623      *
624      * @see startReplaceGroup
625      */
626     @ComposeCompilerApi fun endReplaceGroup()
627 
628     /**
629      * A Compose compiler plugin API. DO NOT call directly.
630      *
631      * Start a movable group. A movable group is one that can be moved based on the value of
632      * [dataKey] which is typically supplied by the [key][androidx.compose.runtime.key] pseudo
633      * compiler function.
634      *
635      * A movable group implements the semantics of [key][androidx.compose.runtime.key] which allows
636      * the state and nodes generated by a loop to move with the composition implied by the key
637      * passed to [key][androidx.compose.runtime.key].
638      *
639      * @param key a compiler generated key based on the source location of the call.
640      * @param dataKey an additional object that is used as a second part of the key. This key
641      *   produced from the `keys` parameter supplied to the [key][androidx.compose.runtime.key]
642      *   pseudo compiler function.
643      */
644     @ComposeCompilerApi fun startMovableGroup(key: Int, dataKey: Any?)
645 
646     /**
647      * A Compose compiler plugin API. DO NOT call directly.
648      *
649      * Called at the end of a movable group.
650      *
651      * @see startMovableGroup
652      */
653     @ComposeCompilerApi fun endMovableGroup()
654 
655     /**
656      * A Compose compiler plugin API. DO NOT call directly.
657      *
658      * Called to start the group that calculates the default parameters of a [Composable] function.
659      *
660      * This method is called near the beginning of a [Composable] function with default parameters
661      * and surrounds the remembered values or [Composable] calls necessary to produce the default
662      * parameters. For example, for `model: Model = remember { DefaultModel() }` the call to
663      * [remember] is called inside a [startDefaults] group.
664      */
665     @ComposeCompilerApi fun startDefaults()
666 
667     /**
668      * A Compose compiler plugin API. DO NOT call directly.
669      *
670      * Called at the end of defaults group.
671      *
672      * @see startDefaults
673      */
674     @ComposeCompilerApi fun endDefaults()
675 
676     /**
677      * A Compose compiler plugin API. DO NOT call directly.
678      *
679      * Called to record a group for a [Composable] function and starts a group that can be
680      * recomposed on demand based on the lambda passed to
681      * [updateScope][ScopeUpdateScope.updateScope] when [endRestartGroup] is called
682      *
683      * @param key A compiler generated key based on the source location of the call.
684      * @return the instance of the composer to use for the rest of the function.
685      */
686     @ComposeCompilerApi fun startRestartGroup(key: Int): Composer
687 
688     /**
689      * A Compose compiler plugin API. DO NOT call directly.
690      *
691      * Called to end a restart group.
692      */
693     @ComposeCompilerApi fun endRestartGroup(): ScopeUpdateScope?
694 
695     /**
696      * A Compose internal API. DO NOT call directly.
697      *
698      * Request movable content be inserted at the current location. This will schedule with the root
699      * composition parent a call to [insertMovableContent] with the correct [MovableContentState] if
700      * one was released in another part of composition.
701      */
702     @InternalComposeApi fun insertMovableContent(value: MovableContent<*>, parameter: Any?)
703 
704     /**
705      * A Compose internal API. DO NOT call directly.
706      *
707      * Perform a late composition that adds to the current late apply that will insert the given
708      * references to [MovableContent] into the composition. If a [MovableContent] is paired then
709      * this is a request to move a released [MovableContent] from a different location or from a
710      * different composition. If it is not paired (i.e. the `second` [MovableContentStateReference]
711      * is `null`) then new state for the [MovableContent] is inserted into the composition.
712      */
713     @InternalComposeApi
714     fun insertMovableContentReferences(
715         references: List<Pair<MovableContentStateReference, MovableContentStateReference?>>
716     )
717 
718     /**
719      * A Compose compiler plugin API. DO NOT call directly.
720      *
721      * Record the source information string for a group. This must be immediately called after the
722      * start of a group.
723      *
724      * @param sourceInformation An string value to that provides the compose tools enough
725      *   information to calculate the source location of calls to composable functions.
726      */
727     fun sourceInformation(sourceInformation: String)
728 
729     /**
730      * A compose compiler plugin API. DO NOT call directly.
731      *
732      * Record a source information marker. This marker can be used in place of a group that would
733      * have contained the information but was elided as the compiler plugin determined the group was
734      * not necessary such as when a function is marked with [ReadOnlyComposable].
735      *
736      * @param key A compiler generated key based on the source location of the call.
737      * @param sourceInformation An string value to that provides the compose tools enough
738      *   information to calculate the source location of calls to composable functions.
739      */
740     fun sourceInformationMarkerStart(key: Int, sourceInformation: String)
741 
742     /**
743      * A compose compiler plugin API. DO NOT call directly.
744      *
745      * Record the end of the marked source information range.
746      */
747     fun sourceInformationMarkerEnd()
748 
749     /**
750      * A Compose compiler plugin API. DO NOT call directly.
751      *
752      * Skips the composer to the end of the current group. This generated by the compiler to when
753      * the body of a [Composable] function can be skipped typically because the parameters to the
754      * function are equal to the values passed to it in the previous composition.
755      */
756     @ComposeCompilerApi fun skipToGroupEnd()
757 
758     /**
759      * A Compose compiler plugin API. DO NOT call directly.
760      *
761      * Deactivates the content to the end of the group by treating content as if it was deleted and
762      * replaces all slot table entries for calls to [cache] to be [Empty]. This must be called as
763      * the first call for a group.
764      */
765     @ComposeCompilerApi fun deactivateToEndGroup(changed: Boolean)
766 
767     /**
768      * A Compose compiler plugin API. DO NOT call directly.
769      *
770      * Skips the current group. This called by the compiler to indicate that the current group can
771      * be skipped, for example, this is generated to skip the [startDefaults] group the default
772      * group is was not invalidated.
773      */
774     @ComposeCompilerApi fun skipCurrentGroup()
775 
776     // Nodes
777 
778     /**
779      * A Compose compiler plugin API. DO NOT call directly.
780      *
781      * Start a group that tracks a the code that will create or update a node that is generated as
782      * part of the tree implied by the composition.
783      */
784     @ComposeCompilerApi fun startNode()
785 
786     /**
787      * A Compose compiler plugin API. DO NOT call directly.
788      *
789      * Start a group that tracks a the code that will create or update a node that is generated as
790      * part of the tree implied by the composition. A reusable node can be reused in a reusable
791      * group even if the group key is changed.
792      */
793     @ComposeCompilerApi fun startReusableNode()
794 
795     /**
796      * A Compose compiler plugin API. DO NOT call directly.
797      *
798      * Report the [factory] that will be used to create the node that will be generated into the
799      * tree implied by the composition. This will only be called if [inserting] is is `true`.
800      *
801      * @param factory a factory function that will generate a node that will eventually be supplied
802      *   to [applier] though [Applier.insertBottomUp] and [Applier.insertTopDown].
803      */
804     @ComposeCompilerApi fun <T> createNode(factory: () -> T)
805 
806     /**
807      * A Compose compiler plugin API. DO NOT call directly.
808      *
809      * Report that the node is still being used. This will be called in the same location as the
810      * corresponding [createNode] when [inserting] is `false`.
811      */
812     @ComposeCompilerApi fun useNode()
813 
814     /**
815      * A Compose compiler plugin API. DO NOT call directly.
816      *
817      * Called at the end of a node group.
818      */
819     @ComposeCompilerApi fun endNode()
820 
821     /**
822      * A Compose compiler plugin API. DO NOT call directly.
823      *
824      * Start a reuse group. Unlike a movable group, in a reuse group if the [dataKey] changes the
825      * composition shifts into a reusing state cause the composer to act like it is inserting (e.g.
826      * [cache] acts as if all values are invalid, [changed] always returns true, etc.) even though
827      * it is recomposing until it encounters a reusable node. If the node is reusable it temporarily
828      * shifts into recomposition for the node and then shifts back to reusing for the children. If a
829      * non-reusable node is generated the composer shifts to inserting for the node and all of its
830      * children.
831      *
832      * @param key An compiler generated key based on the source location of the call.
833      * @param dataKey A key provided by the [ReusableContent] composable function that is used to
834      *   determine if the composition shifts into a reusing state for this group.
835      */
836     @ComposeCompilerApi fun startReusableGroup(key: Int, dataKey: Any?)
837 
838     /**
839      * A Compose compiler plugin API. DO NOT call directly.
840      *
841      * Called at the end of a reusable group.
842      */
843     @ComposeCompilerApi fun endReusableGroup()
844 
845     /**
846      * A Compose compiler plugin API. DO NOT call directly.
847      *
848      * Temporarily disable reusing if it is enabled.
849      */
850     @ComposeCompilerApi fun disableReusing()
851 
852     /**
853      * A Compose compiler plugin API. DO NOT call directly.
854      *
855      * Reenable reusing if it was previously enabled before the last call to [disableReusing].
856      */
857     @ComposeCompilerApi fun enableReusing()
858 
859     /**
860      * A Compose compiler plugin API. DO NOT call directly.
861      *
862      * Return a marker for the current group that can be used in a call to [endToMarker].
863      */
864     @ComposeCompilerApi val currentMarker: Int
865 
866     /**
867      * Compose compiler plugin API. DO NOT call directly.
868      *
869      * Ends all the groups up to but not including the group that is the parent group when
870      * [currentMarker] was called to produce [marker]. All groups ended must have been started with
871      * either [startReplaceableGroup] or [startMovableGroup]. Ending other groups can cause the
872      * state of the composer to become inconsistent.
873      */
874     @ComposeCompilerApi fun endToMarker(marker: Int)
875 
876     /**
877      * A Compose compiler plugin API. DO NOT call directly.
878      *
879      * Schedule [block] to called with [value]. This is intended to update the node generated by
880      * [createNode] to changes discovered by composition.
881      *
882      * @param value the new value to be set into some property of the node.
883      * @param block the block that sets the some property of the node to [value].
884      */
885     @ComposeCompilerApi fun <V, T> apply(value: V, block: T.(V) -> Unit)
886 
887     // State
888 
889     /**
890      * A Compose compiler plugin API. DO NOT call directly.
891      *
892      * Produce an object that will compare equal an iff [left] and [right] compare equal to some
893      * [left] and [right] of a previous call to [joinKey]. This is used by [key] to handle multiple
894      * parameters. Since the previous composition stored [left] and [right] in a "join key" object
895      * this call is used to return the previous value without an allocation instead of blindly
896      * creating a new value that will be immediately discarded.
897      *
898      * @param left the first part of a a joined key.
899      * @param right the second part of a joined key.
900      * @return an object that will compare equal to a value previously returned by [joinKey] iff
901      *   [left] and [right] compare equal to the [left] and [right] passed to the previous call.
902      */
903     @ComposeCompilerApi fun joinKey(left: Any?, right: Any?): Any
904 
905     /**
906      * A Compose compiler plugin API. DO NOT call directly.
907      *
908      * Remember a value into the composition state. This is a primitive method used to implement
909      * [remember].
910      *
911      * @return [Composer.Empty] when [inserting] is `true` or the value passed to
912      *   [updateRememberedValue] from the previous composition.
913      * @see cache
914      */
915     @ComposeCompilerApi fun rememberedValue(): Any?
916 
917     /**
918      * A Compose compiler plugin API. DO NOT call directly.
919      *
920      * Update the remembered value correspond to the previous call to [rememberedValue]. The [value]
921      * will be returned by [rememberedValue] for the next composition.
922      */
923     @ComposeCompilerApi fun updateRememberedValue(value: Any?)
924 
925     /**
926      * A Compose compiler plugin API. DO NOT call directly.
927      *
928      * Check [value] is different than the value used in the previous composition. This is used, for
929      * example, to check parameter values to determine if they have changed.
930      *
931      * @param value the value to check
932      * @return `true` if the value if [equals] of the previous value returns `false` when passed
933      *   [value].
934      */
935     @ComposeCompilerApi fun changed(value: Any?): Boolean
936 
937     /**
938      * A Compose compiler plugin API. DO NOT call directly.
939      *
940      * Check [value] is different than the value used in the previous composition. This is used, for
941      * example, to check parameter values to determine if they have changed.
942      *
943      * This overload is provided to avoid boxing [value] to compare with a potentially boxed version
944      * of [value] in the composition state.
945      *
946      * @param value the value to check
947      * @return `true` if the value if [equals] of the previous value returns `false` when passed
948      *   [value].
949      */
950     @ComposeCompilerApi fun changed(value: Boolean): Boolean = changed(value)
951 
952     /**
953      * A Compose compiler plugin API. DO NOT call directly.
954      *
955      * Check [value] is different than the value used in the previous composition. This is used, for
956      * example, to check parameter values to determine if they have changed.
957      *
958      * This overload is provided to avoid boxing [value] to compare with a potentially boxed version
959      * of [value] in the composition state.
960      *
961      * @param value the value to check
962      * @return `true` if the value if [equals] of the previous value returns `false` when passed
963      *   [value].
964      */
965     @ComposeCompilerApi fun changed(value: Char): Boolean = changed(value)
966 
967     /**
968      * A Compose compiler plugin API. DO NOT call directly.
969      *
970      * Check [value] is different than the value used in the previous composition. This is used, for
971      * example, to check parameter values to determine if they have changed.
972      *
973      * This overload is provided to avoid boxing [value] to compare with a potentially boxed version
974      * of [value] in the composition state.
975      *
976      * @param value the value to check
977      * @return `true` if the value if [equals] of the previous value returns `false` when passed
978      *   [value].
979      */
980     @ComposeCompilerApi fun changed(value: Byte): Boolean = changed(value)
981 
982     /**
983      * A Compose compiler plugin API. DO NOT call directly.
984      *
985      * Check [value] is different than the value used in the previous composition. This is used, for
986      * example, to check parameter values to determine if they have changed.
987      *
988      * This overload is provided to avoid boxing [value] to compare with a potentially boxed version
989      * of [value] in the composition state.
990      *
991      * @param value the value to check
992      * @return `true` if the value if [equals] of the previous value returns `false` when passed
993      *   [value].
994      */
995     @ComposeCompilerApi fun changed(value: Short): Boolean = changed(value)
996 
997     /**
998      * A Compose compiler plugin API. DO NOT call directly.
999      *
1000      * Check [value] is different than the value used in the previous composition. This is used, for
1001      * example, to check parameter values to determine if they have changed.
1002      *
1003      * This overload is provided to avoid boxing [value] to compare with a potentially boxed version
1004      * of [value] in the composition state.
1005      *
1006      * @param value the value to check
1007      * @return `true` if the value if [equals] of the previous value returns `false` when passed
1008      *   [value].
1009      */
1010     @ComposeCompilerApi fun changed(value: Int): Boolean = changed(value)
1011 
1012     /**
1013      * A Compose compiler plugin API. DO NOT call directly.
1014      *
1015      * Check [value] is different than the value used in the previous composition. This is used, for
1016      * example, to check parameter values to determine if they have changed.
1017      *
1018      * This overload is provided to avoid boxing [value] to compare with a potentially boxed version
1019      * of [value] in the composition state.
1020      *
1021      * @param value the value to check
1022      * @return `true` if the value if [equals] of the previous value returns `false` when passed
1023      *   [value].
1024      */
1025     @ComposeCompilerApi fun changed(value: Float): Boolean = changed(value)
1026 
1027     /**
1028      * A Compose compiler plugin API. DO NOT call directly.
1029      *
1030      * Check [value] is different than the value used in the previous composition. This is used, for
1031      * example, to check parameter values to determine if they have changed.
1032      *
1033      * This overload is provided to avoid boxing [value] to compare with a potentially boxed version
1034      * of [value] in the composition state.
1035      *
1036      * @param value the value to check
1037      * @return `true` if the value if [equals] of the previous value returns `false` when passed
1038      *   [value].
1039      */
1040     @ComposeCompilerApi fun changed(value: Long): Boolean = changed(value)
1041 
1042     /**
1043      * A Compose compiler plugin API. DO NOT call directly.
1044      *
1045      * Check [value] is different than the value used in the previous composition. This is used, for
1046      * example, to check parameter values to determine if they have changed.
1047      *
1048      * This overload is provided to avoid boxing [value] to compare with a potentially boxed version
1049      * of [value] in the composition state.
1050      *
1051      * @param value the value to check
1052      * @return `true` if the value if [equals] of the previous value returns `false` when passed
1053      *   [value].
1054      */
1055     @ComposeCompilerApi fun changed(value: Double): Boolean = changed(value)
1056 
1057     /**
1058      * A Compose compiler plugin API. DO NOT call directly.
1059      *
1060      * Check [value] is different than the value used in the previous composition using `===`
1061      * instead of `==` equality. This is used, for example, to check parameter values to determine
1062      * if they have changed for values that use value equality but, for correct behavior, the
1063      * composer needs reference equality.
1064      *
1065      * @param value the value to check
1066      * @return `true` if the value is === equal to the previous value and returns `false` when
1067      *   [value] is different.
1068      */
1069     @ComposeCompilerApi fun changedInstance(value: Any?): Boolean = changed(value)
1070 
1071     // Scopes
1072 
1073     /**
1074      * A Compose compiler plugin API. DO NOT call directly.
1075      *
1076      * Mark [scope] as used. [endReplaceableGroup] will return `null` unless [recordUsed] is called
1077      * on the corresponding [scope]. This is called implicitly when [State] objects are read during
1078      * composition is called when [currentRecomposeScope] is called in the [Composable] function.
1079      */
1080     @InternalComposeApi fun recordUsed(scope: RecomposeScope)
1081 
1082     /**
1083      * A Compose compiler plugin API. DO NOT call directly.
1084      *
1085      * Generated by the compile to determine if the composable function should be executed. It may
1086      * not execute if parameter has not changed and the nothing else is forcing the function to
1087      * execute (such as its scope was invalidated or a static composition local it was changed) or
1088      * the composition is pausable and the composition is pausing.
1089      *
1090      * @param parametersChanged `true` if the parameters to the composable function have changed.
1091      *   This is also `true` if the composition is [inserting] or if content is being reused.
1092      * @param flags The `$changed` parameter that contains the forced recompose bit to allow the
1093      *   composer to disambiguate when the parameters changed due the execution being forced or if
1094      *   the parameters actually changed. This is only ambiguous in a [PausableComposition] and is
1095      *   necessary to determine if the function can be paused. The bits, other than 0, are reserved
1096      *   for future use (which would required the bit 31, which is unused in `$changed` values, to
1097      *   be set to indicate that the flags carry additional information). Passing the `$changed`
1098      *   flags directly, instead of masking the 0 bit, is more efficient as it allows less code to
1099      *   be generated per call to `shouldExecute` which is every called in every restartable
1100      *   function, as well as allowing for the API to be extended without a breaking changed.
1101      */
1102     @InternalComposeApi fun shouldExecute(parametersChanged: Boolean, flags: Int): Boolean
1103 
1104     // Internal API
1105 
1106     /**
1107      * A Compose internal function. DO NOT call directly.
1108      *
1109      * Record a function to call when changes to the corresponding tree are applied to the
1110      * [applier]. This is used to implement [SideEffect].
1111      *
1112      * @param effect a lambda to invoke after the changes calculated up to this point have been
1113      *   applied.
1114      */
1115     @InternalComposeApi fun recordSideEffect(effect: () -> Unit)
1116 
1117     /**
1118      * Returns the active set of CompositionLocals at the current position in the composition
1119      * hierarchy. This is a lower level API that can be used to export and access CompositionLocal
1120      * values outside of Composition.
1121      *
1122      * This API does not track reads of CompositionLocals and does not automatically dispatch new
1123      * values to previous readers when the value of a CompositionLocal changes. To use this API as
1124      * intended, you must set up observation manually. This means:
1125      * - For [non-static CompositionLocals][compositionLocalOf], composables reading this map need
1126      *   to observe the snapshot state for CompositionLocals being read to be notified when their
1127      *   values in this map change.
1128      * - For [static CompositionLocals][staticCompositionLocalOf], all composables including the
1129      *   composable reading this map will be recomposed and you will need to re-obtain this map to
1130      *   get the latest values.
1131      *
1132      * Most applications shouldn't use this API directly, and should instead use
1133      * [CompositionLocal.current].
1134      */
1135     val currentCompositionLocalMap: CompositionLocalMap
1136 
1137     /**
1138      * A Compose internal function. DO NOT call directly.
1139      *
1140      * Return the [CompositionLocal] value associated with [key]. This is the primitive function
1141      * used to implement [CompositionLocal.current].
1142      *
1143      * @param key the [CompositionLocal] value to be retrieved.
1144      */
1145     @InternalComposeApi fun <T> consume(key: CompositionLocal<T>): T
1146 
1147     /**
1148      * A Compose internal function. DO NOT call directly.
1149      *
1150      * Provide the given values for the associated [CompositionLocal] keys. This is the primitive
1151      * function used to implement [CompositionLocalProvider].
1152      *
1153      * @param values an array of value to provider key pairs.
1154      */
1155     @InternalComposeApi fun startProviders(values: Array<out ProvidedValue<*>>)
1156 
1157     /**
1158      * A Compose internal function. DO NOT call directly.
1159      *
1160      * End the provider group.
1161      *
1162      * @see startProviders
1163      */
1164     @InternalComposeApi fun endProviders()
1165 
1166     /**
1167      * A Compose internal function. DO NOT call directly.
1168      *
1169      * Provide the given value for the associated [CompositionLocal] key. This is the primitive
1170      * function used to implement [CompositionLocalProvider].
1171      *
1172      * @param value a value to provider key pairs.
1173      */
1174     @InternalComposeApi fun startProvider(value: ProvidedValue<*>)
1175 
1176     /**
1177      * A Compose internal function. DO NOT call directly.
1178      *
1179      * End the provider group.
1180      *
1181      * @see startProvider
1182      */
1183     @InternalComposeApi fun endProvider()
1184 
1185     /**
1186      * A tooling API function. DO NOT call directly.
1187      *
1188      * The data stored for the composition. This is used by Compose tools, such as the preview and
1189      * the inspector, to display or interpret the result of composition.
1190      */
1191     val compositionData: CompositionData
1192 
1193     /**
1194      * A tooling API function. DO NOT call directly.
1195      *
1196      * Called by the inspector to inform the composer that it should collect additional information
1197      * about call parameters. By default, only collect parameter information for scopes that are
1198      * [recordUsed] has been called on. If [collectParameterInformation] is called it will attempt
1199      * to collect all calls even if the runtime doesn't need them.
1200      *
1201      * WARNING: calling this will result in a significant number of additional allocations that are
1202      * typically avoided.
1203      */
1204     fun collectParameterInformation()
1205 
1206     /**
1207      * A Compose internal function. DO NOT call directly.
1208      *
1209      * Build a composition context that can be used to created a subcomposition. A composition
1210      * reference is used to communicate information from this composition to the subcompositions
1211      * such as the all the [CompositionLocal]s provided at the point the reference is created.
1212      */
1213     @InternalComposeApi fun buildContext(): CompositionContext
1214 
1215     /**
1216      * A Compose internal function. DO NOT call directly.
1217      *
1218      * The coroutine context for the composition. This is used, for example, to implement
1219      * [LaunchedEffect]. This context is managed by the [Recomposer].
1220      */
1221     @InternalComposeApi
1222     val applyCoroutineContext: CoroutineContext
1223         @TestOnly get
1224 
1225     /** The composition that is used to control this composer. */
1226     val composition: ControlledComposition
1227         @TestOnly get
1228 
1229     /**
1230      * Disable the collection of source information, that may introduce groups to store the source
1231      * information, in order to be able to more accurately calculate the actual number of groups a
1232      * composable function generates in a release build.
1233      *
1234      * This function is only safe to call in a test and will produce incorrect composition results
1235      * if called on a composer not under test.
1236      */
1237     @TestOnly fun disableSourceInformation()
1238 
1239     companion object {
1240         /**
1241          * A special value used to represent no value was stored (e.g. an empty slot). This is
1242          * returned, for example by [Composer.rememberedValue] while it is [Composer.inserting] is
1243          * `true`.
1244          */
1245         val Empty =
1246             object {
1247                 override fun toString() = "Empty"
1248             }
1249 
1250         /**
1251          * Internal API for specifying a tracer used for instrumenting frequent operations, e.g.
1252          * recompositions.
1253          */
1254         @InternalComposeTracingApi
1255         fun setTracer(tracer: CompositionTracer?) {
1256             compositionTracer = tracer
1257         }
1258 
1259         /**
1260          * Enable composition stack traces based on the source information. When this flag is
1261          * enabled, composition will record source information at runtime. When crash occurs,
1262          * Compose will append a suppressed exception that contains a stack trace pointing to the
1263          * place in composition closest to the crash.
1264          *
1265          * Note that:
1266          * - Recording source information introduces additional performance overhead, so this option
1267          *   should NOT be enabled in release builds.
1268          * - Compose ships with a minifier config that removes source information from the release
1269          *   builds. Enabling this flag in minified builds will have no effect.
1270          */
1271         fun setDiagnosticStackTraceEnabled(enabled: Boolean) {
1272             composeStackTraceEnabled = enabled
1273         }
1274     }
1275 }
1276 
1277 /**
1278  * A Compose compiler plugin API. DO NOT call directly.
1279  *
1280  * Cache, that is remember, a value in the composition data of a composition. This is used to
1281  * implement [remember] and used by the compiler plugin to generate more efficient calls to
1282  * [remember] when it determines these optimizations are safe.
1283  */
1284 @ComposeCompilerApi
1285 inline fun <T> Composer.cache(invalid: Boolean, block: @DisallowComposableCalls () -> T): T {
1286     @Suppress("UNCHECKED_CAST")
<lambda>null1287     return rememberedValue().let {
1288         if (invalid || it === Composer.Empty) {
1289             val value = block()
1290             updateRememberedValue(value)
1291             value
1292         } else it
1293     } as T
1294 }
1295 
1296 /**
1297  * A Compose internal function. DO NOT call directly.
1298  *
1299  * Records source information that can be used for tooling to determine the source location of the
1300  * corresponding composable function. By default, this function is declared as having no
1301  * side-effects. It is safe for code shrinking tools (such as R8 or ProGuard) to remove it.
1302  */
1303 @ComposeCompilerApi
sourceInformationnull1304 fun sourceInformation(composer: Composer, sourceInformation: String) {
1305     composer.sourceInformation(sourceInformation)
1306 }
1307 
1308 /**
1309  * A Compose internal function. DO NOT call directly.
1310  *
1311  * Records the start of a source information marker that can be used for tooling to determine the
1312  * source location of the corresponding composable function that otherwise don't require tracking
1313  * information such as [ReadOnlyComposable] functions. By default, this function is declared as
1314  * having no side-effects. It is safe for code shrinking tools (such as R8 or ProGuard) to remove
1315  * it.
1316  *
1317  * Important that both [sourceInformationMarkerStart] and [sourceInformationMarkerEnd] are removed
1318  * together or both kept. Removing only one will cause incorrect runtime behavior.
1319  */
1320 @ComposeCompilerApi
sourceInformationMarkerStartnull1321 fun sourceInformationMarkerStart(composer: Composer, key: Int, sourceInformation: String) {
1322     composer.sourceInformationMarkerStart(key, sourceInformation)
1323 }
1324 
1325 /**
1326  * Internal tracing API.
1327  *
1328  * Should be called without thread synchronization with occasional information loss.
1329  */
1330 @InternalComposeTracingApi
1331 interface CompositionTracer {
traceEventStartnull1332     fun traceEventStart(key: Int, dirty1: Int, dirty2: Int, info: String): Unit
1333 
1334     fun traceEventEnd(): Unit
1335 
1336     fun isTraceInProgress(): Boolean
1337 }
1338 
1339 @OptIn(InternalComposeTracingApi::class) private var compositionTracer: CompositionTracer? = null
1340 
1341 internal var composeStackTraceEnabled: Boolean = false
1342 
1343 /**
1344  * Internal tracing API.
1345  *
1346  * Should be called without thread synchronization with occasional information loss.
1347  */
1348 @OptIn(InternalComposeTracingApi::class)
1349 @ComposeCompilerApi
1350 fun isTraceInProgress(): Boolean = compositionTracer.let { it != null && it.isTraceInProgress() }
1351 
1352 @OptIn(InternalComposeTracingApi::class)
1353 @ComposeCompilerApi
1354 @Deprecated(
1355     message = "Use the overload with \$dirty metadata instead",
1356     ReplaceWith("traceEventStart(key, dirty1, dirty2, info)"),
1357     DeprecationLevel.HIDDEN
1358 )
traceEventStartnull1359 fun traceEventStart(key: Int, info: String): Unit = traceEventStart(key, -1, -1, info)
1360 
1361 /**
1362  * Internal tracing API.
1363  *
1364  * Should be called without thread synchronization with occasional information loss.
1365  *
1366  * @param key is a group key generated by the compiler plugin for the function being traced. This
1367  *   key is unique the function.
1368  * @param dirty1 $dirty metadata: forced-recomposition and function parameters 1..10 if present
1369  * @param dirty2 $dirty2 metadata: forced-recomposition and function parameters 11..20 if present
1370  * @param info is a user displayable string that describes the function for which this is the start
1371  *   event.
1372  */
1373 @OptIn(InternalComposeTracingApi::class)
1374 @ComposeCompilerApi
1375 fun traceEventStart(key: Int, dirty1: Int, dirty2: Int, info: String) {
1376     compositionTracer?.traceEventStart(key, dirty1, dirty2, info)
1377 }
1378 
1379 /**
1380  * Internal tracing API.
1381  *
1382  * Should be called without thread synchronization with occasional information loss.
1383  */
1384 @OptIn(InternalComposeTracingApi::class)
1385 @ComposeCompilerApi
traceEventEndnull1386 fun traceEventEnd() {
1387     compositionTracer?.traceEventEnd()
1388 }
1389 
1390 /**
1391  * A Compose internal function. DO NOT call directly.
1392  *
1393  * Records the end of a source information marker that can be used for tooling to determine the
1394  * source location of the corresponding composable function that otherwise don't require tracking
1395  * information such as [ReadOnlyComposable] functions. By default, this function is declared as
1396  * having no side-effects. It is safe for code shrinking tools (such as R8 or ProGuard) to remove
1397  * it.
1398  *
1399  * Important that both [sourceInformationMarkerStart] and [sourceInformationMarkerEnd] are removed
1400  * together or both kept. Removing only one will cause incorrect runtime behavior.
1401  */
1402 @ComposeCompilerApi
sourceInformationMarkerEndnull1403 fun sourceInformationMarkerEnd(composer: Composer) {
1404     composer.sourceInformationMarkerEnd()
1405 }
1406 
1407 /** Implementation of a composer for a mutable tree. */
1408 @OptIn(ExperimentalComposeRuntimeApi::class)
1409 internal class ComposerImpl(
1410     /** An adapter that applies changes to the tree using the Applier abstraction. */
1411     override val applier: Applier<*>,
1412 
1413     /** Parent of this composition; a [Recomposer] for root-level compositions. */
1414     private val parentContext: CompositionContext,
1415 
1416     /** The slot table to use to store composition data */
1417     private val slotTable: SlotTable,
1418     private val abandonSet: MutableSet<RememberObserver>,
1419     private var changes: ChangeList,
1420     private var lateChanges: ChangeList,
1421 
1422     /** The composition that owns this composer */
1423     override val composition: ControlledComposition
1424 ) : Composer {
1425     private val pendingStack = Stack<Pending?>()
1426     private var pending: Pending? = null
1427     private var nodeIndex: Int = 0
1428     private var groupNodeCount: Int = 0
1429     private var rGroupIndex: Int = 0
1430     private val parentStateStack = IntStack()
1431     private var nodeCountOverrides: IntArray? = null
1432     private var nodeCountVirtualOverrides: MutableIntIntMap? = null
1433     private var forceRecomposeScopes = false
1434     private var forciblyRecompose = false
1435     private var nodeExpected = false
1436     private val invalidations: MutableList<Invalidation> = mutableListOf()
1437     private val entersStack = IntStack()
1438     private var rootProvider: PersistentCompositionLocalMap = persistentCompositionLocalHashMapOf()
1439     private var providerUpdates: MutableIntObjectMap<PersistentCompositionLocalMap>? = null
1440     private var providersInvalid = false
1441     private val providersInvalidStack = IntStack()
1442     private var reusing = false
1443     private var reusingGroup = -1
1444     private var childrenComposing: Int = 0
1445     private var compositionToken: Int = 0
1446 
1447     private var sourceMarkersEnabled =
1448         parentContext.collectingSourceInformation || parentContext.collectingCallByInformation
1449 
1450     private val derivedStateObserver =
1451         object : DerivedStateObserver {
startnull1452             override fun start(derivedState: DerivedState<*>) {
1453                 childrenComposing++
1454             }
1455 
donenull1456             override fun done(derivedState: DerivedState<*>) {
1457                 childrenComposing--
1458             }
1459         }
1460 
1461     private val invalidateStack = Stack<RecomposeScopeImpl>()
1462 
1463     internal var isComposing = false
1464         private set
1465 
1466     internal var isDisposed = false
1467         private set
1468 
1469     internal val areChildrenComposing
1470         get() = childrenComposing > 0
1471 
1472     internal val hasPendingChanges: Boolean
1473         get() = changes.isNotEmpty()
1474 
<lambda>null1475     internal var reader: SlotReader = slotTable.openReader().also { it.close() }
1476 
1477     internal var insertTable =
<lambda>null1478         SlotTable().apply {
1479             if (parentContext.collectingSourceInformation) collectSourceInformation()
1480             if (parentContext.collectingCallByInformation) collectCalledByInformation()
1481         }
1482 
<lambda>null1483     private var writer: SlotWriter = insertTable.openWriter().also { it.close(true) }
1484     private var writerHasAProvider = false
1485     private var providerCache: PersistentCompositionLocalMap? = null
1486     internal var deferredChanges: ChangeList? = null
1487 
1488     private val changeListWriter = ComposerChangeListWriter(this, changes)
<lambda>null1489     private var insertAnchor: Anchor = insertTable.read { it.anchor(0) }
1490     private var insertFixups = FixupList()
1491 
1492     private var pausable: Boolean = false
1493     private var shouldPauseCallback: ShouldPauseCallback? = null
1494 
1495     internal val errorContext: CompositionErrorContextImpl? = CompositionErrorContextImpl(this)
1496         get() = if (sourceMarkersEnabled) field else null
1497 
1498     override val applyCoroutineContext: CoroutineContext =
1499         parentContext.effectCoroutineContext + (errorContext ?: EmptyCoroutineContext)
1500 
1501     /**
1502      * Inserts a "Replaceable Group" starting marker in the slot table at the current execution
1503      * position. A Replaceable Group is a group which cannot be moved between its siblings, but can
1504      * be removed or inserted. These groups are inserted by the compiler around branches of
1505      * conditional logic in Composable functions such as if expressions, when expressions, early
1506      * returns, and null-coalescing operators.
1507      *
1508      * A call to [startReplaceableGroup] must be matched with a corresponding call to
1509      * [endReplaceableGroup].
1510      *
1511      * Warning: Versions of the compiler that generate calls to this function also contain subtle
1512      * bug that does not generate a group around a loop containing code that just creates composable
1513      * lambdas (AnimatedContent from androidx.compose.animation, for example) which makes replacing
1514      * the group unsafe and the this must treat this like a movable group. [startReplaceGroup] was
1515      * added that will replace the group as described above and is only called by versions of the
1516      * compiler that correctly generate code around loops that create lambdas.
1517      *
1518      * Warning: This is expected to be executed by the compiler only and should not be called
1519      * directly from source code. Call this API at your own risk.
1520      *
1521      * @param key The source-location-based key for the group. Expected to be unique among its
1522      *   siblings.
1523      * @see [endReplaceableGroup]
1524      * @see [startMovableGroup]
1525      * @see [startRestartGroup]
1526      */
1527     @ComposeCompilerApi
startReplaceableGroupnull1528     override fun startReplaceableGroup(key: Int) = start(key, null, GroupKind.Group, null)
1529 
1530     /**
1531      * Indicates the end of a "Replaceable Group" at the current execution position. A Replaceable
1532      * Group is a group which cannot be moved between its siblings, but can be removed or inserted.
1533      * These groups are inserted by the compiler around branches of conditional logic in Composable
1534      * functions such as if expressions, when expressions, early returns, and null-coalescing
1535      * operators.
1536      *
1537      * Warning: This is expected to be executed by the compiler only and should not be called
1538      * directly from source code. Call this API at your own risk.
1539      *
1540      * @see [startReplaceableGroup]
1541      */
1542     @ComposeCompilerApi override fun endReplaceableGroup() = endGroup()
1543 
1544     /** See [Composer.startReplaceGroup] */
1545     @ComposeCompilerApi
1546     override fun startReplaceGroup(key: Int) {
1547         val pending = pending
1548         if (pending != null) {
1549             start(key, null, GroupKind.Group, null)
1550             return
1551         }
1552         validateNodeNotExpected()
1553 
1554         updateCompositeKeyWhenWeEnterGroup(key, rGroupIndex, null, null)
1555 
1556         rGroupIndex++
1557 
1558         val reader = reader
1559         if (inserting) {
1560             reader.beginEmpty()
1561             writer.startGroup(key, Composer.Empty)
1562             enterGroup(false, null)
1563             return
1564         }
1565         val slotKey = reader.groupKey
1566         if (slotKey == key && !reader.hasObjectKey) {
1567             reader.startGroup()
1568             enterGroup(false, null)
1569             return
1570         }
1571 
1572         if (!reader.isGroupEnd) {
1573             // Delete the group that was not expected
1574             val removeIndex = nodeIndex
1575             val startSlot = reader.currentGroup
1576             recordDelete()
1577             val nodesToRemove = reader.skipGroup()
1578             changeListWriter.removeNode(removeIndex, nodesToRemove)
1579 
1580             invalidations.removeRange(startSlot, reader.currentGroup)
1581         }
1582 
1583         // Insert the new group
1584         reader.beginEmpty()
1585         inserting = true
1586         providerCache = null
1587         ensureWriter()
1588         val writer = writer
1589         writer.beginInsert()
1590         val startIndex = writer.currentGroup
1591         writer.startGroup(key, Composer.Empty)
1592         insertAnchor = writer.anchor(startIndex)
1593         enterGroup(false, null)
1594     }
1595 
1596     /** See [Composer.endReplaceGroup] */
endReplaceGroupnull1597     @ComposeCompilerApi override fun endReplaceGroup() = endGroup()
1598 
1599     /**
1600      * Warning: This is expected to be executed by the compiler only and should not be called
1601      * directly from source code. Call this API at your own risk.
1602      */
1603     @ComposeCompilerApi
1604     @Suppress("unused")
1605     override fun startDefaults() = start(defaultsKey, null, GroupKind.Group, null)
1606 
1607     /**
1608      * Warning: This is expected to be executed by the compiler only and should not be called
1609      * directly from source code. Call this API at your own risk.
1610      *
1611      * @see [startReplaceableGroup]
1612      */
1613     @ComposeCompilerApi
1614     @Suppress("unused")
1615     override fun endDefaults() {
1616         endGroup()
1617         val scope = currentRecomposeScope
1618         if (scope != null && scope.used) {
1619             scope.defaultsInScope = true
1620         }
1621     }
1622 
1623     @ComposeCompilerApi
1624     @Suppress("unused")
1625     override val defaultsInvalid: Boolean
1626         get() {
1627             return !skipping || providersInvalid || currentRecomposeScope?.defaultsInvalid == true
1628         }
1629 
1630     /**
1631      * Inserts a "Movable Group" starting marker in the slot table at the current execution
1632      * position. A Movable Group is a group which can be moved or reordered between its siblings and
1633      * retain slot table state, in addition to being removed or inserted. Movable Groups are more
1634      * expensive than other groups because when they are encountered with a mismatched key in the
1635      * slot table, they must be held on to temporarily until the entire parent group finishes
1636      * execution in case it moved to a later position in the group. Movable groups are only inserted
1637      * by the compiler as a result of calls to [key].
1638      *
1639      * A call to [startMovableGroup] must be matched with a corresponding call to [endMovableGroup].
1640      *
1641      * Warning: This is expected to be executed by the compiler only and should not be called
1642      * directly from source code. Call this API at your own risk.
1643      *
1644      * @param key The source-location-based key for the group. Expected to be unique among its
1645      *   siblings.
1646      * @param dataKey Additional identifying information to compound with [key]. If there are
1647      *   multiple values, this is expected to be compounded together with [joinKey]. Whatever value
1648      *   is passed in here is expected to have a meaningful [equals] and [hashCode] implementation.
1649      * @see [endMovableGroup]
1650      * @see [key]
1651      * @see [joinKey]
1652      * @see [startReplaceableGroup]
1653      * @see [startRestartGroup]
1654      */
1655     @ComposeCompilerApi
startMovableGroupnull1656     override fun startMovableGroup(key: Int, dataKey: Any?) =
1657         start(key, dataKey, GroupKind.Group, null)
1658 
1659     /**
1660      * Indicates the end of a "Movable Group" at the current execution position. A Movable Group is
1661      * a group which can be moved or reordered between its siblings and retain slot table state, in
1662      * addition to being removed or inserted. These groups are only valid when they are inserted as
1663      * direct children of Container Groups. Movable Groups are more expensive than other groups
1664      * because when they are encountered with a mismatched key in the slot table, they must be held
1665      * on to temporarily until the entire parent group finishes execution in case it moved to a
1666      * later position in the group. Movable groups are only inserted by the compiler as a result of
1667      * calls to [key].
1668      *
1669      * Warning: This is expected to be executed by the compiler only and should not be called
1670      * directly from source code. Call this API at your own risk.
1671      *
1672      * @see [startMovableGroup]
1673      */
1674     @ComposeCompilerApi override fun endMovableGroup() = endGroup()
1675 
1676     /**
1677      * Start the composition. This should be called, and only be called, as the first group in the
1678      * composition.
1679      */
1680     @OptIn(InternalComposeApi::class)
1681     private fun startRoot() {
1682         rGroupIndex = 0
1683         reader = slotTable.openReader()
1684         startGroup(rootKey)
1685 
1686         // parent reference management
1687         parentContext.startComposing()
1688         val parentProvider = parentContext.getCompositionLocalScope()
1689         providersInvalidStack.push(providersInvalid.asInt())
1690         providersInvalid = changed(parentProvider)
1691         providerCache = null
1692 
1693         // Inform observer if one is defined
1694         if (!forceRecomposeScopes) {
1695             forceRecomposeScopes = parentContext.collectingParameterInformation
1696         }
1697 
1698         // Propagate collecting source information
1699         if (!sourceMarkersEnabled) {
1700             sourceMarkersEnabled = parentContext.collectingSourceInformation
1701         }
1702 
1703         rootProvider =
1704             if (sourceMarkersEnabled) {
1705                 @Suppress("UNCHECKED_CAST") // ProvidableCompositionLocal to CompositionLocal
1706                 parentProvider.putValue(
1707                     LocalCompositionErrorContext as CompositionLocal<Any?>,
1708                     StaticValueHolder(errorContext)
1709                 )
1710             } else {
1711                 parentProvider
1712             }
1713 
1714         rootProvider.read(LocalInspectionTables)?.let {
1715             it.add(compositionData)
1716             parentContext.recordInspectionTable(it)
1717         }
1718 
1719         startGroup(parentContext.compositeKeyHashCode.hashCode())
1720     }
1721 
1722     /**
1723      * End the composition. This should be called, and only be called, to end the first group in the
1724      * composition.
1725      */
1726     @OptIn(InternalComposeApi::class)
endRootnull1727     private fun endRoot() {
1728         endGroup()
1729         parentContext.doneComposing()
1730         endGroup()
1731         changeListWriter.endRoot()
1732         finalizeCompose()
1733         reader.close()
1734         forciblyRecompose = false
1735         providersInvalid = providersInvalidStack.pop().asBool()
1736     }
1737 
1738     /** Discard a pending composition because an error was encountered during composition */
1739     @OptIn(InternalComposeApi::class)
abortRootnull1740     private fun abortRoot() {
1741         cleanUpCompose()
1742         pendingStack.clear()
1743         parentStateStack.clear()
1744         entersStack.clear()
1745         providersInvalidStack.clear()
1746         providerUpdates = null
1747         insertFixups.clear()
1748         compositeKeyHashCode = CompositeKeyHashCode(0)
1749         childrenComposing = 0
1750         nodeExpected = false
1751         inserting = false
1752         reusing = false
1753         isComposing = false
1754         forciblyRecompose = false
1755         reusingGroup = -1
1756         if (!reader.closed) {
1757             reader.close()
1758         }
1759         if (!writer.closed) {
1760             // We cannot just close the insert table as the state of the table is uncertain
1761             // here and writer.close() might throw.
1762             forceFreshInsertTable()
1763         }
1764     }
1765 
changesAppliednull1766     internal fun changesApplied() {
1767         providerUpdates = null
1768     }
1769 
1770     /**
1771      * True if the composition is currently scheduling nodes to be inserted into the tree. During
1772      * first composition this is always true. During recomposition this is true when new nodes are
1773      * being scheduled to be added to the tree.
1774      */
1775     @ComposeCompilerApi
1776     override var inserting: Boolean = false
1777         private set
1778 
1779     /** True if the composition should be checking if the composable functions can be skipped. */
1780     @ComposeCompilerApi
1781     override val skipping: Boolean
1782         get() {
1783             return !inserting &&
1784                 !reusing &&
1785                 !providersInvalid &&
1786                 currentRecomposeScope?.requiresRecompose == false &&
1787                 !forciblyRecompose
1788         }
1789 
1790     /**
1791      * Returns the hash of the composite key calculated as a combination of the keys of all the
1792      * currently started groups via [startGroup].
1793      */
1794     @InternalComposeApi
1795     override var compositeKeyHashCode: CompositeKeyHashCode = EmptyCompositeKeyHashCode
1796         private set
1797 
1798     /**
1799      * Start collecting parameter information and line number information. This enables the tools
1800      * API to always be able to determine the parameter values of composable calls as well as the
1801      * source location of calls.
1802      */
collectParameterInformationnull1803     override fun collectParameterInformation() {
1804         forceRecomposeScopes = true
1805         sourceMarkersEnabled = true
1806         slotTable.collectSourceInformation()
1807         insertTable.collectSourceInformation()
1808         writer.updateToTableMaps()
1809     }
1810 
1811     @OptIn(InternalComposeApi::class)
disposenull1812     internal fun dispose() {
1813         trace("Compose:Composer.dispose") {
1814             parentContext.unregisterComposer(this)
1815             deactivate()
1816             applier.clear()
1817             isDisposed = true
1818         }
1819     }
1820 
deactivatenull1821     internal fun deactivate() {
1822         invalidateStack.clear()
1823         invalidations.clear()
1824         changes.clear()
1825         providerUpdates = null
1826     }
1827 
forceRecomposeScopesnull1828     internal fun forceRecomposeScopes(): Boolean {
1829         return if (!forceRecomposeScopes) {
1830             forceRecomposeScopes = true
1831             forciblyRecompose = true
1832             true
1833         } else {
1834             false
1835         }
1836     }
1837 
1838     /**
1839      * Start a group with the given key. During recomposition if the currently expected group does
1840      * not match the given key a group the groups emitted in the same parent group are inspected to
1841      * determine if one of them has this key and that group the first such group is moved (along
1842      * with any nodes emitted by the group) to the current position and composition continues. If no
1843      * group with this key is found, then the composition shifts into insert mode and new nodes are
1844      * added at the current position.
1845      *
1846      * @param key The key for the group
1847      */
startGroupnull1848     private fun startGroup(key: Int) = start(key, null, GroupKind.Group, null)
1849 
1850     private fun startGroup(key: Int, dataKey: Any?) = start(key, dataKey, GroupKind.Group, null)
1851 
1852     /** End the current group. */
1853     private fun endGroup() = end(isNode = false)
1854 
1855     @OptIn(InternalComposeApi::class)
1856     private fun skipGroup() {
1857         groupNodeCount += reader.skipGroup()
1858     }
1859 
1860     /**
1861      * Start emitting a node. It is required that [createNode] is called after [startNode]. Similar
1862      * to [startGroup], if, during recomposition, the current node does not have the provided key a
1863      * node with that key is scanned for and moved into the current position if found, if no such
1864      * node is found the composition switches into insert mode and a the node is scheduled to be
1865      * inserted at the current location.
1866      */
startNodenull1867     override fun startNode() {
1868         start(nodeKey, null, GroupKind.Node, null)
1869         nodeExpected = true
1870     }
1871 
startReusableNodenull1872     override fun startReusableNode() {
1873         start(nodeKey, null, GroupKind.ReusableNode, null)
1874         nodeExpected = true
1875     }
1876 
1877     /**
1878      * Schedule a node to be created and inserted at the current location. This is only valid to
1879      * call when the composer is inserting.
1880      */
1881     @Suppress("UNUSED")
createNodenull1882     override fun <T> createNode(factory: () -> T) {
1883         validateNodeExpected()
1884         runtimeCheck(inserting) { "createNode() can only be called when inserting" }
1885         val insertIndex = parentStateStack.peek()
1886         val groupAnchor = writer.anchor(writer.parent)
1887         groupNodeCount++
1888         insertFixups.createAndInsertNode(factory, insertIndex, groupAnchor)
1889     }
1890 
1891     /** Mark the node that was created by [createNode] as used by composition. */
1892     @OptIn(InternalComposeApi::class)
useNodenull1893     override fun useNode() {
1894         validateNodeExpected()
1895         runtimeCheck(!inserting) { "useNode() called while inserting" }
1896         val node = reader.node
1897         changeListWriter.moveDown(node)
1898 
1899         if (reusing && node is ComposeNodeLifecycleCallback) {
1900             changeListWriter.useNode(node)
1901         }
1902     }
1903 
1904     /** Called to end the node group. */
endNodenull1905     override fun endNode() = end(isNode = true)
1906 
1907     override fun startReusableGroup(key: Int, dataKey: Any?) {
1908         if (!inserting) {
1909             if (reader.groupKey == key && reader.groupAux != dataKey && reusingGroup < 0) {
1910                 // Starting to reuse nodes
1911                 reusingGroup = reader.currentGroup
1912                 reusing = true
1913             }
1914         }
1915         start(key, null, GroupKind.Group, dataKey)
1916     }
1917 
endReusableGroupnull1918     override fun endReusableGroup() {
1919         if (reusing && reader.parent == reusingGroup) {
1920             reusingGroup = -1
1921             reusing = false
1922         }
1923         end(isNode = false)
1924     }
1925 
disableReusingnull1926     override fun disableReusing() {
1927         reusing = false
1928     }
1929 
enableReusingnull1930     override fun enableReusing() {
1931         reusing = reusingGroup >= 0
1932     }
1933 
startReuseFromRootnull1934     fun startReuseFromRoot() {
1935         reusingGroup = rootKey
1936         reusing = true
1937     }
1938 
endReuseFromRootnull1939     fun endReuseFromRoot() {
1940         requirePrecondition(!isComposing && reusingGroup == rootKey) {
1941             "Cannot disable reuse from root if it was caused by other groups"
1942         }
1943         reusingGroup = -1
1944         reusing = false
1945     }
1946 
1947     override val currentMarker: Int
1948         get() = if (inserting) -writer.parent else reader.parent
1949 
endToMarkernull1950     override fun endToMarker(marker: Int) {
1951         if (marker < 0) {
1952             // If the marker is negative then the marker is for the writer
1953             val writerLocation = -marker
1954             val writer = writer
1955             while (true) {
1956                 val parent = writer.parent
1957                 if (parent <= writerLocation) break
1958                 end(writer.isNode(parent))
1959             }
1960         } else {
1961             // If the marker is positive then the marker is for the reader. However, if we are
1962             // inserting then we need to close the inserting groups first.
1963             if (inserting) {
1964                 // We might be inserting, we need to close all the groups until we are no longer
1965                 // inserting.
1966                 val writer = writer
1967                 while (inserting) {
1968                     end(writer.isNode(writer.parent))
1969                 }
1970             }
1971             val reader = reader
1972             while (true) {
1973                 val parent = reader.parent
1974                 if (parent <= marker) break
1975                 end(reader.isNode(parent))
1976             }
1977         }
1978     }
1979 
1980     /**
1981      * Schedule a change to be applied to a node's property. This change will be applied to the node
1982      * that is the current node in the tree which was either created by [createNode].
1983      */
applynull1984     override fun <V, T> apply(value: V, block: T.(V) -> Unit) {
1985         if (inserting) {
1986             insertFixups.updateNode(value, block)
1987         } else {
1988             changeListWriter.updateNode(value, block)
1989         }
1990     }
1991 
1992     /**
1993      * Create a composed key that can be used in calls to [startGroup] or [startNode]. This will use
1994      * the key stored at the current location in the slot table to avoid allocating a new key.
1995      */
1996     @ComposeCompilerApi
1997     @OptIn(InternalComposeApi::class)
joinKeynull1998     override fun joinKey(left: Any?, right: Any?): Any =
1999         getKey(reader.groupObjectKey, left, right) ?: JoinedKey(left, right)
2000 
2001     /** Return the next value in the slot table and advance the current location. */
2002     @PublishedApi
2003     @OptIn(InternalComposeApi::class)
2004     internal fun nextSlot(): Any? =
2005         if (inserting) {
2006             validateNodeNotExpected()
2007             Composer.Empty
2008         } else
<lambda>null2009             reader.next().let {
2010                 if (reusing && it !is ReusableRememberObserver) Composer.Empty else it
2011             }
2012 
2013     @PublishedApi
2014     @OptIn(InternalComposeApi::class)
nextSlotForCachenull2015     internal fun nextSlotForCache(): Any? {
2016         return if (inserting) {
2017             validateNodeNotExpected()
2018             Composer.Empty
2019         } else
2020             reader.next().let {
2021                 if (reusing && it !is ReusableRememberObserver) Composer.Empty
2022                 else if (it is RememberObserverHolder) it.wrapped else it
2023             }
2024     }
2025 
2026     /**
2027      * Determine if the current slot table value is equal to the given value, if true, the value is
2028      * scheduled to be skipped during [ControlledComposition.applyChanges] and [changes] return
2029      * false; otherwise [ControlledComposition.applyChanges] will update the slot table to [value].
2030      * In either case the composer's slot table is advanced.
2031      *
2032      * @param value the value to be compared.
2033      */
2034     @ComposeCompilerApi
changednull2035     override fun changed(value: Any?): Boolean {
2036         return if (nextSlot() != value) {
2037             updateValue(value)
2038             true
2039         } else {
2040             false
2041         }
2042     }
2043 
2044     @ComposeCompilerApi
changedInstancenull2045     override fun changedInstance(value: Any?): Boolean {
2046         return if (nextSlot() !== value) {
2047             updateValue(value)
2048             true
2049         } else {
2050             false
2051         }
2052     }
2053 
2054     @ComposeCompilerApi
changednull2055     override fun changed(value: Char): Boolean {
2056         val next = nextSlot()
2057         if (next is Char) {
2058             val nextPrimitive: Char = next
2059             if (value == nextPrimitive) return false
2060         }
2061         updateValue(value)
2062         return true
2063     }
2064 
2065     @ComposeCompilerApi
changednull2066     override fun changed(value: Byte): Boolean {
2067         val next = nextSlot()
2068         if (next is Byte) {
2069             val nextPrimitive: Byte = next
2070             if (value == nextPrimitive) return false
2071         }
2072         updateValue(value)
2073         return true
2074     }
2075 
2076     @ComposeCompilerApi
changednull2077     override fun changed(value: Short): Boolean {
2078         val next = nextSlot()
2079         if (next is Short) {
2080             val nextPrimitive: Short = next
2081             if (value == nextPrimitive) return false
2082         }
2083         updateValue(value)
2084         return true
2085     }
2086 
2087     @ComposeCompilerApi
changednull2088     override fun changed(value: Boolean): Boolean {
2089         val next = nextSlot()
2090         if (next is Boolean) {
2091             val nextPrimitive: Boolean = next
2092             if (value == nextPrimitive) return false
2093         }
2094         updateValue(value)
2095         return true
2096     }
2097 
2098     @ComposeCompilerApi
changednull2099     override fun changed(value: Float): Boolean {
2100         val next = nextSlot()
2101         if (next is Float) {
2102             val nextPrimitive: Float = next
2103             if (value == nextPrimitive) return false
2104         }
2105         updateValue(value)
2106         return true
2107     }
2108 
2109     @ComposeCompilerApi
changednull2110     override fun changed(value: Long): Boolean {
2111         val next = nextSlot()
2112         if (next is Long) {
2113             val nextPrimitive: Long = next
2114             if (value == nextPrimitive) return false
2115         }
2116         updateValue(value)
2117         return true
2118     }
2119 
2120     @ComposeCompilerApi
changednull2121     override fun changed(value: Double): Boolean {
2122         val next = nextSlot()
2123         if (next is Double) {
2124             val nextPrimitive: Double = next
2125             if (value == nextPrimitive) return false
2126         }
2127         updateValue(value)
2128         return true
2129     }
2130 
2131     @ComposeCompilerApi
changednull2132     override fun changed(value: Int): Boolean {
2133         val next = nextSlot()
2134         if (next is Int) {
2135             val nextPrimitive: Int = next
2136             if (value == nextPrimitive) return false
2137         }
2138         updateValue(value)
2139         return true
2140     }
2141 
2142     /**
2143      * Cache a value in the composition. During initial composition [block] is called to produce the
2144      * value that is then stored in the slot table. During recomposition, if [invalid] is false the
2145      * value is obtained from the slot table and [block] is not invoked. If [invalid] is false a new
2146      * value is produced by calling [block] and the slot table is updated to contain the new value.
2147      */
2148     @ComposeCompilerApi
cachenull2149     inline fun <T> cache(invalid: Boolean, block: () -> T): T {
2150         var result = nextSlotForCache()
2151         if (result === Composer.Empty || invalid) {
2152             val value = block()
2153             updateCachedValue(value)
2154             result = value
2155         }
2156 
2157         @Suppress("UNCHECKED_CAST") return result as T
2158     }
2159 
updateSlotnull2160     private fun updateSlot(value: Any?) {
2161         nextSlot()
2162         updateValue(value)
2163     }
2164 
2165     /**
2166      * Schedule the current value in the slot table to be updated to [value].
2167      *
2168      * @param value the value to schedule to be written to the slot table.
2169      */
2170     @PublishedApi
2171     @OptIn(InternalComposeApi::class)
updateValuenull2172     internal fun updateValue(value: Any?) {
2173         if (inserting) {
2174             writer.update(value)
2175         } else {
2176             if (reader.hadNext) {
2177                 // We need to update the slot we just read so which is is one previous to the
2178                 // current group slot index.
2179                 val groupSlotIndex = reader.groupSlotIndex - 1
2180                 if (changeListWriter.pastParent) {
2181                     // The reader is after the first child of the group so we cannot reposition the
2182                     // writer to the parent to update it as this will cause the writer to navigate
2183                     // backward which violates the single pass, forward walking  nature of update.
2184                     // Using an anchored updated allows to to violate this principle just for
2185                     // updating slots as this is required if the update occurs after the writer has
2186                     // been moved past the parent.
2187                     changeListWriter.updateAnchoredValue(
2188                         value,
2189                         reader.anchor(reader.parent),
2190                         groupSlotIndex
2191                     )
2192                 } else {
2193                     // No children have been seen yet so we are still in a position where we can
2194                     // directly update the parent.
2195                     changeListWriter.updateValue(value, groupSlotIndex)
2196                 }
2197             } else {
2198                 // This uses an anchor for the same reason as `updateAnchoredValue` uses and anchor,
2199                 // the writer might have advanced past the parent and we need to go back and update
2200                 // the parent. As this is likely to never occur in an empty group, we don't bother
2201                 // checking if the reader has moved so we don't need an anchored and un-anchored
2202                 // version of the same function.
2203                 changeListWriter.appendValue(reader.anchor(reader.parent), value)
2204             }
2205         }
2206     }
2207 
2208     /**
2209      * Schedule the current value in the slot table to be updated to [value].
2210      *
2211      * @param value the value to schedule to be written to the slot table.
2212      */
2213     @PublishedApi
2214     @OptIn(InternalComposeApi::class)
updateCachedValuenull2215     internal fun updateCachedValue(value: Any?) {
2216         val toStore =
2217             if (value is RememberObserver) {
2218                 val holder = RememberObserverHolder(value, rememberObserverAnchor())
2219                 if (inserting) {
2220                     changeListWriter.remember(holder)
2221                 }
2222                 abandonSet.add(value)
2223                 holder
2224             } else value
2225         updateValue(toStore)
2226     }
2227 
rememberObserverAnchornull2228     private fun rememberObserverAnchor(): Anchor? =
2229         if (inserting) {
2230             if (writer.isAfterFirstChild) {
2231                 var group = writer.currentGroup - 1
2232                 var parent = writer.parent(group)
2233                 while (parent != writer.parent && parent >= 0) {
2234                     group = parent
2235                     parent = writer.parent(group)
2236                 }
2237                 writer.anchor(group)
2238             } else null
2239         } else {
2240             if (reader.isAfterFirstChild) {
2241                 var group = reader.currentGroup - 1
2242                 var parent = reader.parent(group)
2243                 while (parent != reader.parent && parent >= 0) {
2244                     group = parent
2245                     parent = reader.parent(group)
2246                 }
2247                 reader.anchor(group)
2248             } else null
2249         }
2250 
2251     private var _compositionData: CompositionData? = null
2252 
2253     override val compositionData: CompositionData
2254         get() {
2255             val data = _compositionData
2256             if (data == null) {
2257                 val newData = CompositionDataImpl(composition)
2258                 _compositionData = newData
2259                 return newData
2260             }
2261             return data
2262         }
2263 
2264     /** Schedule a side effect to run when we apply composition changes. */
recordSideEffectnull2265     override fun recordSideEffect(effect: () -> Unit) {
2266         changeListWriter.sideEffect(effect)
2267     }
2268 
currentCompositionLocalScopenull2269     private fun currentCompositionLocalScope(): PersistentCompositionLocalMap {
2270         providerCache?.let {
2271             return it
2272         }
2273         return currentCompositionLocalScope(reader.parent)
2274     }
2275 
2276     override val currentCompositionLocalMap: CompositionLocalMap
2277         get() = currentCompositionLocalScope()
2278 
2279     /** Return the current [CompositionLocal] scope which was provided by a parent group. */
currentCompositionLocalScopenull2280     private fun currentCompositionLocalScope(group: Int): PersistentCompositionLocalMap {
2281         if (inserting && writerHasAProvider) {
2282             var current = writer.parent
2283             while (current > 0) {
2284                 if (
2285                     writer.groupKey(current) == compositionLocalMapKey &&
2286                         writer.groupObjectKey(current) == compositionLocalMap
2287                 ) {
2288                     val providers = writer.groupAux(current) as PersistentCompositionLocalMap
2289                     providerCache = providers
2290                     return providers
2291                 }
2292                 current = writer.parent(current)
2293             }
2294         }
2295         if (reader.size > 0) {
2296             var current = group
2297             while (current > 0) {
2298                 if (
2299                     reader.groupKey(current) == compositionLocalMapKey &&
2300                         reader.groupObjectKey(current) == compositionLocalMap
2301                 ) {
2302                     val providers =
2303                         providerUpdates?.get(current)
2304                             ?: reader.groupAux(current) as PersistentCompositionLocalMap
2305                     providerCache = providers
2306                     return providers
2307                 }
2308                 current = reader.parent(current)
2309             }
2310         }
2311         providerCache = rootProvider
2312         return rootProvider
2313     }
2314 
2315     /**
2316      * Update (or create) the slots to record the providers. The providers maps are first the scope
2317      * followed by the map used to augment the parent scope. Both are needed to detect inserts,
2318      * updates and deletes to the providers.
2319      */
updateProviderMapGroupnull2320     private fun updateProviderMapGroup(
2321         parentScope: PersistentCompositionLocalMap,
2322         currentProviders: PersistentCompositionLocalMap
2323     ): PersistentCompositionLocalMap {
2324         val providerScope = parentScope.mutate { it.putAll(currentProviders) }
2325         startGroup(providerMapsKey, providerMaps)
2326         updateSlot(providerScope)
2327         updateSlot(currentProviders)
2328         endGroup()
2329         return providerScope
2330     }
2331 
2332     @InternalComposeApi
2333     @Suppress("UNCHECKED_CAST")
startProvidernull2334     override fun startProvider(value: ProvidedValue<*>) {
2335         val parentScope = currentCompositionLocalScope()
2336         startGroup(providerKey, provider)
2337         val oldState =
2338             rememberedValue().let { if (it == Composer.Empty) null else it as ValueHolder<Any?> }
2339         val local = value.compositionLocal as CompositionLocal<Any?>
2340         val state = local.updatedStateOf(value as ProvidedValue<Any?>, oldState)
2341         val change = state != oldState
2342         if (change) {
2343             updateRememberedValue(state)
2344         }
2345         val providers: PersistentCompositionLocalMap
2346         val invalid: Boolean
2347         if (inserting) {
2348             providers =
2349                 if (value.canOverride || !parentScope.contains(local)) {
2350                     parentScope.putValue(local, state)
2351                 } else {
2352                     parentScope
2353                 }
2354             invalid = false
2355             writerHasAProvider = true
2356         } else {
2357             val oldScope = reader.groupAux(reader.currentGroup) as PersistentCompositionLocalMap
2358             providers =
2359                 when {
2360                     (!skipping || change) && (value.canOverride || !parentScope.contains(local)) ->
2361                         parentScope.putValue(local, state)
2362                     !change && !providersInvalid -> oldScope
2363                     providersInvalid -> parentScope
2364                     else -> oldScope
2365                 }
2366             invalid = reusing || oldScope !== providers
2367         }
2368         if (invalid && !inserting) {
2369             recordProviderUpdate(providers)
2370         }
2371         providersInvalidStack.push(providersInvalid.asInt())
2372         providersInvalid = invalid
2373         providerCache = providers
2374         start(compositionLocalMapKey, compositionLocalMap, GroupKind.Group, providers)
2375     }
2376 
recordProviderUpdatenull2377     private fun recordProviderUpdate(providers: PersistentCompositionLocalMap) {
2378         val providerUpdates =
2379             providerUpdates
2380                 ?: run {
2381                     val newProviderUpdates = MutableIntObjectMap<PersistentCompositionLocalMap>()
2382                     this.providerUpdates = newProviderUpdates
2383                     newProviderUpdates
2384                 }
2385         providerUpdates[reader.currentGroup] = providers
2386     }
2387 
2388     @InternalComposeApi
endProvidernull2389     override fun endProvider() {
2390         endGroup()
2391         endGroup()
2392         providersInvalid = providersInvalidStack.pop().asBool()
2393         providerCache = null
2394     }
2395 
2396     @InternalComposeApi
startProvidersnull2397     override fun startProviders(values: Array<out ProvidedValue<*>>) {
2398         val parentScope = currentCompositionLocalScope()
2399         startGroup(providerKey, provider)
2400         val providers: PersistentCompositionLocalMap
2401         val invalid: Boolean
2402         if (inserting) {
2403             val currentProviders = updateCompositionMap(values, parentScope)
2404             providers = updateProviderMapGroup(parentScope, currentProviders)
2405             invalid = false
2406             writerHasAProvider = true
2407         } else {
2408             val oldScope = reader.groupGet(0) as PersistentCompositionLocalMap
2409             val oldValues = reader.groupGet(1) as PersistentCompositionLocalMap
2410             val currentProviders = updateCompositionMap(values, parentScope, oldValues)
2411             // skipping is true iff parentScope has not changed.
2412             if (!skipping || reusing || oldValues != currentProviders) {
2413                 providers = updateProviderMapGroup(parentScope, currentProviders)
2414 
2415                 // Compare against the old scope as currentProviders might have modified the scope
2416                 // back to the previous value. This could happen, for example, if currentProviders
2417                 // and parentScope have a key in common and the oldScope had the same value as
2418                 // currentProviders for that key. If the scope has not changed, because these
2419                 // providers obscure a change in the parent as described above, re-enable skipping
2420                 // for the child region.
2421                 invalid = reusing || providers != oldScope
2422             } else {
2423                 // Nothing has changed
2424                 skipGroup()
2425                 providers = oldScope
2426                 invalid = false
2427             }
2428         }
2429 
2430         if (invalid && !inserting) {
2431             recordProviderUpdate(providers)
2432         }
2433         providersInvalidStack.push(providersInvalid.asInt())
2434         providersInvalid = invalid
2435         providerCache = providers
2436         start(compositionLocalMapKey, compositionLocalMap, GroupKind.Group, providers)
2437     }
2438 
2439     @InternalComposeApi
endProvidersnull2440     override fun endProviders() {
2441         endGroup()
2442         endGroup()
2443         providersInvalid = providersInvalidStack.pop().asBool()
2444         providerCache = null
2445     }
2446 
2447     @InternalComposeApi
consumenull2448     override fun <T> consume(key: CompositionLocal<T>): T = currentCompositionLocalScope().read(key)
2449 
2450     /**
2451      * Create or use a memoized [CompositionContext] instance at this position in the slot table.
2452      */
2453     override fun buildContext(): CompositionContext {
2454         startGroup(referenceKey, reference)
2455         if (inserting) writer.markGroup()
2456 
2457         var holder = nextSlot() as? CompositionContextHolder
2458         if (holder == null) {
2459             holder =
2460                 CompositionContextHolder(
2461                     CompositionContextImpl(
2462                         this@ComposerImpl.compositeKeyHashCode,
2463                         forceRecomposeScopes,
2464                         sourceMarkersEnabled,
2465                         (composition as? CompositionImpl)?.observerHolder
2466                     )
2467                 )
2468             updateValue(holder)
2469         }
2470         holder.ref.updateCompositionLocalScope(currentCompositionLocalScope())
2471         endGroup()
2472 
2473         return holder.ref
2474     }
2475 
2476     /**
2477      * The number of changes that have been scheduled to be applied during
2478      * [ControlledComposition.applyChanges].
2479      *
2480      * Slot table movement (skipping groups and nodes) will be coalesced so this number is possibly
2481      * less than the total changes detected.
2482      */
2483     internal val changeCount
2484         get() = changes.size
2485 
2486     internal val currentRecomposeScope: RecomposeScopeImpl?
2487         get() =
<lambda>null2488             invalidateStack.let {
2489                 if (childrenComposing == 0 && it.isNotEmpty()) it.peek() else null
2490             }
2491 
ensureWriternull2492     private fun ensureWriter() {
2493         if (writer.closed) {
2494             writer = insertTable.openWriter()
2495             // Append to the end of the table
2496             writer.skipToGroupEnd()
2497             writerHasAProvider = false
2498             providerCache = null
2499         }
2500     }
2501 
createFreshInsertTablenull2502     private fun createFreshInsertTable() {
2503         runtimeCheck(writer.closed)
2504         forceFreshInsertTable()
2505     }
2506 
forceFreshInsertTablenull2507     private fun forceFreshInsertTable() {
2508         insertTable =
2509             SlotTable().apply {
2510                 if (sourceMarkersEnabled) collectSourceInformation()
2511                 if (parentContext.collectingCallByInformation) collectCalledByInformation()
2512             }
2513         writer = insertTable.openWriter().also { it.close(true) }
2514     }
2515 
2516     /** Start the reader group updating the data of the group if necessary */
startReaderGroupnull2517     private fun startReaderGroup(isNode: Boolean, data: Any?) {
2518         if (isNode) {
2519             reader.startNode()
2520         } else {
2521             if (data != null && reader.groupAux !== data) {
2522                 changeListWriter.updateAuxData(data)
2523             }
2524             reader.startGroup()
2525         }
2526     }
2527 
startnull2528     private fun start(key: Int, objectKey: Any?, kind: GroupKind, data: Any?) {
2529         validateNodeNotExpected()
2530 
2531         updateCompositeKeyWhenWeEnterGroup(key, rGroupIndex, objectKey, data)
2532 
2533         if (objectKey == null) rGroupIndex++
2534 
2535         // Check for the insert fast path. If we are already inserting (creating nodes) then
2536         // there is no need to track insert, deletes and moves with a pending changes object.
2537         val isNode = kind.isNode
2538         if (inserting) {
2539             reader.beginEmpty()
2540             val startIndex = writer.currentGroup
2541             when {
2542                 isNode -> writer.startNode(key, Composer.Empty)
2543                 data != null -> writer.startData(key, objectKey ?: Composer.Empty, data)
2544                 else -> writer.startGroup(key, objectKey ?: Composer.Empty)
2545             }
2546             pending?.let { pending ->
2547                 val insertKeyInfo =
2548                     KeyInfo(
2549                         key = key,
2550                         objectKey = -1,
2551                         location = insertedGroupVirtualIndex(startIndex),
2552                         nodes = -1,
2553                         index = 0
2554                     )
2555                 pending.registerInsert(insertKeyInfo, nodeIndex - pending.startIndex)
2556                 pending.recordUsed(insertKeyInfo)
2557             }
2558             enterGroup(isNode, null)
2559             return
2560         }
2561 
2562         val forceReplace = !kind.isReusable && reusing
2563         if (pending == null) {
2564             val slotKey = reader.groupKey
2565             if (!forceReplace && slotKey == key && objectKey == reader.groupObjectKey) {
2566                 // The group is the same as what was generated last time.
2567                 startReaderGroup(isNode, data)
2568             } else {
2569                 pending = Pending(reader.extractKeys(), nodeIndex)
2570             }
2571         }
2572 
2573         val pending = pending
2574         var newPending: Pending? = null
2575         if (pending != null) {
2576             // Check to see if the key was generated last time from the keys collected above.
2577             val keyInfo = pending.getNext(key, objectKey)
2578             if (!forceReplace && keyInfo != null) {
2579                 // This group was generated last time, use it.
2580                 pending.recordUsed(keyInfo)
2581 
2582                 // Move the slot table to the location where the information about this group is
2583                 // stored. The slot information will move once the changes are applied so moving the
2584                 // current of the slot table is sufficient.
2585                 val location = keyInfo.location
2586 
2587                 // Determine what index this group is in. This is used for inserting nodes into the
2588                 // group.
2589                 nodeIndex = pending.nodePositionOf(keyInfo) + pending.startIndex
2590 
2591                 // Determine how to move the slot group to the correct position.
2592                 val relativePosition = pending.slotPositionOf(keyInfo)
2593                 val currentRelativePosition = relativePosition - pending.groupIndex
2594                 pending.registerMoveSlot(relativePosition, pending.groupIndex)
2595                 changeListWriter.moveReaderRelativeTo(location)
2596                 reader.reposition(location)
2597                 if (currentRelativePosition > 0) {
2598                     // The slot group must be moved, record the move to be performed during apply.
2599                     changeListWriter.moveCurrentGroup(currentRelativePosition)
2600                 }
2601                 startReaderGroup(isNode, data)
2602             } else {
2603                 // The group is new, go into insert mode. All child groups will written to the
2604                 // insertTable until the group is complete which will schedule the groups to be
2605                 // inserted into in the table.
2606                 reader.beginEmpty()
2607                 inserting = true
2608                 providerCache = null
2609                 ensureWriter()
2610                 writer.beginInsert()
2611                 val startIndex = writer.currentGroup
2612                 when {
2613                     isNode -> writer.startNode(key, Composer.Empty)
2614                     data != null -> writer.startData(key, objectKey ?: Composer.Empty, data)
2615                     else -> writer.startGroup(key, objectKey ?: Composer.Empty)
2616                 }
2617                 insertAnchor = writer.anchor(startIndex)
2618                 val insertKeyInfo =
2619                     KeyInfo(
2620                         key = key,
2621                         objectKey = -1,
2622                         location = insertedGroupVirtualIndex(startIndex),
2623                         nodes = -1,
2624                         index = 0
2625                     )
2626                 pending.registerInsert(insertKeyInfo, nodeIndex - pending.startIndex)
2627                 pending.recordUsed(insertKeyInfo)
2628                 newPending = Pending(mutableListOf(), if (isNode) 0 else nodeIndex)
2629             }
2630         }
2631 
2632         enterGroup(isNode, newPending)
2633     }
2634 
enterGroupnull2635     private fun enterGroup(isNode: Boolean, newPending: Pending?) {
2636         // When entering a group all the information about the parent should be saved, to be
2637         // restored when end() is called, and all the tracking counters set to initial state for the
2638         // group.
2639         pendingStack.push(pending)
2640         this.pending = newPending
2641         this.parentStateStack.push(groupNodeCount)
2642         this.parentStateStack.push(rGroupIndex)
2643         this.parentStateStack.push(nodeIndex)
2644         if (isNode) nodeIndex = 0
2645         groupNodeCount = 0
2646         rGroupIndex = 0
2647     }
2648 
exitGroupnull2649     private fun exitGroup(expectedNodeCount: Int, inserting: Boolean) {
2650         // Restore the parent's state updating them if they have changed based on changes in the
2651         // children. For example, if a group generates nodes then the number of generated nodes will
2652         // increment the node index and the group's node count. If the parent is tracking structural
2653         // changes in pending then restore that too.
2654         val previousPending = pendingStack.pop()
2655         if (previousPending != null && !inserting) {
2656             previousPending.groupIndex++
2657         }
2658         this.pending = previousPending
2659         this.nodeIndex = parentStateStack.pop() + expectedNodeCount
2660         this.rGroupIndex = parentStateStack.pop()
2661         this.groupNodeCount = parentStateStack.pop() + expectedNodeCount
2662     }
2663 
endnull2664     private fun end(isNode: Boolean) {
2665         // All the changes to the group (or node) have been recorded. All new nodes have been
2666         // inserted but it has yet to determine which need to be removed or moved. Note that the
2667         // changes are relative to the first change in the list of nodes that are changing.
2668 
2669         // The rGroupIndex for parent is two pack from the current stack top which has already been
2670         // incremented past this group needs to be offset by one.
2671         val rGroupIndex = parentStateStack.peek2() - 1
2672         if (inserting) {
2673             val parent = writer.parent
2674             updateCompositeKeyWhenWeExitGroup(
2675                 writer.groupKey(parent),
2676                 rGroupIndex,
2677                 writer.groupObjectKey(parent),
2678                 writer.groupAux(parent)
2679             )
2680         } else {
2681             val parent = reader.parent
2682             updateCompositeKeyWhenWeExitGroup(
2683                 reader.groupKey(parent),
2684                 rGroupIndex,
2685                 reader.groupObjectKey(parent),
2686                 reader.groupAux(parent)
2687             )
2688         }
2689         var expectedNodeCount = groupNodeCount
2690         val pending = pending
2691         if (pending != null && pending.keyInfos.size > 0) {
2692             // previous contains the list of keys as they were generated in the previous composition
2693             val previous = pending.keyInfos
2694 
2695             // current contains the list of keys in the order they need to be in the new composition
2696             val current = pending.used
2697 
2698             // usedKeys contains the keys that were used in the new composition, therefore if a key
2699             // doesn't exist in this set, it needs to be removed.
2700             val usedKeys = current.fastToSet()
2701 
2702             val placedKeys = mutableSetOf<KeyInfo>()
2703             var currentIndex = 0
2704             val currentEnd = current.size
2705             var previousIndex = 0
2706             val previousEnd = previous.size
2707 
2708             // Traverse the list of changes to determine startNode movement
2709             var nodeOffset = 0
2710             while (previousIndex < previousEnd) {
2711                 val previousInfo = previous[previousIndex]
2712                 if (!usedKeys.contains(previousInfo)) {
2713                     // If the key info was not used the group was deleted, remove the nodes in the
2714                     // group
2715                     val deleteOffset = pending.nodePositionOf(previousInfo)
2716                     changeListWriter.removeNode(
2717                         nodeIndex = deleteOffset + pending.startIndex,
2718                         count = previousInfo.nodes
2719                     )
2720                     pending.updateNodeCount(previousInfo.location, 0)
2721                     changeListWriter.moveReaderRelativeTo(previousInfo.location)
2722                     reader.reposition(previousInfo.location)
2723                     recordDelete()
2724                     reader.skipGroup()
2725 
2726                     // Remove any invalidations pending for the group being removed. These are no
2727                     // longer part of the composition. The group being composed is one after the
2728                     // start of the group.
2729                     invalidations.removeRange(
2730                         previousInfo.location,
2731                         previousInfo.location + reader.groupSize(previousInfo.location)
2732                     )
2733                     previousIndex++
2734                     continue
2735                 }
2736 
2737                 if (previousInfo in placedKeys) {
2738                     // If the group was already placed in the correct location, skip it.
2739                     previousIndex++
2740                     continue
2741                 }
2742 
2743                 if (currentIndex < currentEnd) {
2744                     // At this point current should match previous unless the group is new or was
2745                     // moved.
2746                     val currentInfo = current[currentIndex]
2747                     if (currentInfo !== previousInfo) {
2748                         val nodePosition = pending.nodePositionOf(currentInfo)
2749                         placedKeys.add(currentInfo)
2750                         if (nodePosition != nodeOffset) {
2751                             val updatedCount = pending.updatedNodeCountOf(currentInfo)
2752                             changeListWriter.moveNode(
2753                                 from = nodePosition + pending.startIndex,
2754                                 to = nodeOffset + pending.startIndex,
2755                                 count = updatedCount
2756                             )
2757                             pending.registerMoveNode(nodePosition, nodeOffset, updatedCount)
2758                         } // else the nodes are already in the correct position
2759                     } else {
2760                         // The correct nodes are in the right location
2761                         previousIndex++
2762                     }
2763                     currentIndex++
2764                     nodeOffset += pending.updatedNodeCountOf(currentInfo)
2765                 }
2766             }
2767 
2768             // If there are any current nodes left they where inserted into the right location
2769             // when the group began so the rest are ignored.
2770             changeListWriter.endNodeMovement()
2771 
2772             // We have now processed the entire list so move the slot table to the end of the list
2773             // by moving to the last key and skipping it.
2774             if (previous.size > 0) {
2775                 changeListWriter.moveReaderRelativeTo(reader.groupEnd)
2776                 reader.skipToGroupEnd()
2777             }
2778         }
2779 
2780         val inserting = inserting
2781         if (!inserting) {
2782             // Detect when slots were not used. This happens when a `remember` was removed at the
2783             // end of a group. Due to code generation issues (b/346821372) this may also see
2784             // remembers that were removed prior to the children being called so this must be done
2785             // before the children are deleted to ensure that the `RememberEventDispatcher` receives
2786             // the `leaving()` call in the correct order so the `onForgotten` is dispatched in the
2787             // correct order for the values being removed.
2788             val remainingSlots = reader.remainingSlots
2789             if (remainingSlots > 0) {
2790                 changeListWriter.trimValues(remainingSlots)
2791             }
2792         }
2793 
2794         // Detect removing nodes at the end. No pending is created in this case we just have more
2795         // nodes in the previous composition than we expect (i.e. we are not yet at an end)
2796         val removeIndex = nodeIndex
2797         while (!reader.isGroupEnd) {
2798             val startSlot = reader.currentGroup
2799             recordDelete()
2800             val nodesToRemove = reader.skipGroup()
2801             changeListWriter.removeNode(removeIndex, nodesToRemove)
2802             invalidations.removeRange(startSlot, reader.currentGroup)
2803         }
2804 
2805         if (inserting) {
2806             if (isNode) {
2807                 insertFixups.endNodeInsert()
2808                 expectedNodeCount = 1
2809             }
2810             reader.endEmpty()
2811             val parentGroup = writer.parent
2812             writer.endGroup()
2813             if (!reader.inEmpty) {
2814                 val virtualIndex = insertedGroupVirtualIndex(parentGroup)
2815                 writer.endInsert()
2816                 writer.close(true)
2817                 recordInsert(insertAnchor)
2818                 this.inserting = false
2819                 if (!slotTable.isEmpty) {
2820                     updateNodeCount(virtualIndex, 0)
2821                     updateNodeCountOverrides(virtualIndex, expectedNodeCount)
2822                 }
2823             }
2824         } else {
2825             if (isNode) changeListWriter.moveUp()
2826             changeListWriter.endCurrentGroup()
2827             val parentGroup = reader.parent
2828             val parentNodeCount = updatedNodeCount(parentGroup)
2829             if (expectedNodeCount != parentNodeCount) {
2830                 updateNodeCountOverrides(parentGroup, expectedNodeCount)
2831             }
2832             if (isNode) {
2833                 expectedNodeCount = 1
2834             }
2835 
2836             reader.endGroup()
2837             changeListWriter.endNodeMovement()
2838         }
2839 
2840         exitGroup(expectedNodeCount, inserting)
2841     }
2842 
2843     /**
2844      * Recompose any invalidate child groups of the current parent group. This should be called
2845      * after the group is started but on or before the first child group. It is intended to be
2846      * called instead of [skipReaderToGroupEnd] if any child groups are invalid. If no children are
2847      * invalid it will call [skipReaderToGroupEnd].
2848      */
recomposeToGroupEndnull2849     private fun recomposeToGroupEnd() {
2850         val wasComposing = isComposing
2851         isComposing = true
2852         var recomposed = false
2853 
2854         val parent = reader.parent
2855         val end = parent + reader.groupSize(parent)
2856         val recomposeIndex = nodeIndex
2857         val recomposeCompositeKey = this@ComposerImpl.compositeKeyHashCode
2858         val oldGroupNodeCount = groupNodeCount
2859         val oldRGroupIndex = rGroupIndex
2860         var oldGroup = parent
2861 
2862         var firstInRange = invalidations.firstInRange(reader.currentGroup, end)
2863         while (firstInRange != null) {
2864             val location = firstInRange.location
2865 
2866             invalidations.removeLocation(location)
2867 
2868             if (firstInRange.isInvalid()) {
2869                 recomposed = true
2870 
2871                 reader.reposition(location)
2872                 val newGroup = reader.currentGroup
2873                 // Record the changes to the applier location
2874                 recordUpsAndDowns(oldGroup, newGroup, parent)
2875                 oldGroup = newGroup
2876 
2877                 // Calculate the node index (the distance index in the node this groups nodes are
2878                 // located in the parent node).
2879                 nodeIndex = nodeIndexOf(location, newGroup, parent, recomposeIndex)
2880 
2881                 // Calculate the current rGroupIndex for this node, storing any parent rGroup
2882                 // indexes we needed into the rGroup IntList
2883                 rGroupIndex = rGroupIndexOf(newGroup)
2884 
2885                 // Calculate the composite hash code (a semi-unique code for every group in the
2886                 // composition used to restore saved state).
2887                 val newParent = reader.parent(newGroup)
2888                 this@ComposerImpl.compositeKeyHashCode =
2889                     compositeKeyOf(newParent, parent, recomposeCompositeKey)
2890 
2891                 // We have moved so the cached lookup of the provider is invalid
2892                 providerCache = null
2893 
2894                 // Invoke the scope's composition function
2895                 val shouldRestartReusing = !reusing && firstInRange.scope.reusing
2896                 if (shouldRestartReusing) reusing = true
2897                 firstInRange.scope.compose(this)
2898                 if (shouldRestartReusing) reusing = false
2899 
2900                 // We could have moved out of a provider so the provider cache is invalid.
2901                 providerCache = null
2902 
2903                 // Restore the parent of the reader to the previous parent
2904                 reader.restoreParent(parent)
2905             } else {
2906                 // If the invalidation is not used restore the reads that were removed when the
2907                 // the invalidation was recorded. This happens, for example, when on of a derived
2908                 // state's dependencies changed but the derived state itself was not changed.
2909                 invalidateStack.push(firstInRange.scope)
2910                 firstInRange.scope.rereadTrackedInstances()
2911                 invalidateStack.pop()
2912             }
2913 
2914             // Using slots.current here ensures composition always walks forward even if a component
2915             // before the current composition is invalidated when performing this composition. Any
2916             // such components will be considered invalid for the next composition. Skipping them
2917             // prevents potential infinite recomposes at the cost of potentially missing a compose
2918             // as well as simplifies the apply as it always modifies the slot table in a forward
2919             // direction.
2920             firstInRange = invalidations.firstInRange(reader.currentGroup, end)
2921         }
2922 
2923         if (recomposed) {
2924             recordUpsAndDowns(oldGroup, parent, parent)
2925             reader.skipToGroupEnd()
2926             val parentGroupNodes = updatedNodeCount(parent)
2927             nodeIndex = recomposeIndex + parentGroupNodes
2928             groupNodeCount = oldGroupNodeCount + parentGroupNodes
2929             rGroupIndex = oldRGroupIndex
2930         } else {
2931             // No recompositions were requested in the range, skip it.
2932             skipReaderToGroupEnd()
2933 
2934             // No need to restore the parent state for nodeIndex, groupNodeCount and
2935             // rGroupIndex as they are going to be restored immediately by the endGroup
2936         }
2937         this@ComposerImpl.compositeKeyHashCode = recomposeCompositeKey
2938 
2939         isComposing = wasComposing
2940     }
2941 
2942     /**
2943      * The index in the insertTable overlap with indexes the slotTable so the group index used to
2944      * track newly inserted groups is set to be negative offset from -2. This reserves -1 as the
2945      * root index which is the parent value returned by the root groups of the slot table.
2946      *
2947      * This function will also restore a virtual index to its index in the insertTable which is not
2948      * needed here but could be useful for debugging.
2949      */
insertedGroupVirtualIndexnull2950     private fun insertedGroupVirtualIndex(index: Int) = -2 - index
2951 
2952     /**
2953      * As operations to insert and remove nodes are recorded, the number of nodes that will be in
2954      * the group after changes are applied is maintained in a side overrides table. This method
2955      * updates that count and then updates any parent groups that include the nodes this group
2956      * emits.
2957      */
2958     private fun updateNodeCountOverrides(group: Int, newCount: Int) {
2959         // The value of group can be negative which indicates it is tracking an inserted group
2960         // instead of an existing group. The index is a virtual index calculated by
2961         // insertedGroupVirtualIndex which corresponds to the location of the groups to insert in
2962         // the insertTable.
2963         val currentCount = updatedNodeCount(group)
2964         if (currentCount != newCount) {
2965             // Update the overrides
2966             val delta = newCount - currentCount
2967             var current = group
2968 
2969             var minPending = pendingStack.size - 1
2970             while (current != -1) {
2971                 val newCurrentNodes = updatedNodeCount(current) + delta
2972                 updateNodeCount(current, newCurrentNodes)
2973                 for (pendingIndex in minPending downTo 0) {
2974                     val pending = pendingStack.peek(pendingIndex)
2975                     if (pending != null && pending.updateNodeCount(current, newCurrentNodes)) {
2976                         minPending = pendingIndex - 1
2977                         break
2978                     }
2979                 }
2980                 @Suppress("LiftReturnOrAssignment")
2981                 if (current < 0) {
2982                     current = reader.parent
2983                 } else {
2984                     if (reader.isNode(current)) break
2985                     current = reader.parent(current)
2986                 }
2987             }
2988         }
2989     }
2990 
2991     /**
2992      * Calculates the node index (the index in the child list of a node will appear in the resulting
2993      * tree) for [group]. Passing in [recomposeGroup] and its node index in [recomposeIndex] allows
2994      * the calculation to exit early if there is no node group between [group] and [recomposeGroup].
2995      */
nodeIndexOfnull2996     private fun nodeIndexOf(
2997         groupLocation: Int,
2998         group: Int,
2999         recomposeGroup: Int,
3000         recomposeIndex: Int
3001     ): Int {
3002         // Find the anchor group which is either the recomposeGroup or the first parent node
3003         var anchorGroup = reader.parent(group)
3004         while (anchorGroup != recomposeGroup) {
3005             if (reader.isNode(anchorGroup)) break
3006             anchorGroup = reader.parent(anchorGroup)
3007         }
3008 
3009         var index = if (reader.isNode(anchorGroup)) 0 else recomposeIndex
3010 
3011         // An early out if the group and anchor are the same
3012         if (anchorGroup == group) return index
3013 
3014         // Walk down from the anchor group counting nodes of siblings in front of this group
3015         var current = anchorGroup
3016         val nodeIndexLimit = index + (updatedNodeCount(anchorGroup) - reader.nodeCount(group))
3017         loop@ while (index < nodeIndexLimit) {
3018             if (current == groupLocation) break
3019             current++
3020             while (current < groupLocation) {
3021                 val end = current + reader.groupSize(current)
3022                 if (groupLocation < end) continue@loop
3023                 index += if (reader.isNode(current)) 1 else updatedNodeCount(current)
3024                 current = end
3025             }
3026             break
3027         }
3028         return index
3029     }
3030 
rGroupIndexOfnull3031     private fun rGroupIndexOf(group: Int): Int {
3032         var result = 0
3033         val parent = reader.parent(group)
3034         var child = parent + 1
3035         while (child < group) {
3036             if (!reader.hasObjectKey(child)) result++
3037             child += reader.groupSize(child)
3038         }
3039         return result
3040     }
3041 
updatedNodeCountnull3042     private fun updatedNodeCount(group: Int): Int {
3043         if (group < 0)
3044             return nodeCountVirtualOverrides?.let { if (it.contains(group)) it[group] else 0 } ?: 0
3045         val nodeCounts = nodeCountOverrides
3046         if (nodeCounts != null) {
3047             val override = nodeCounts[group]
3048             if (override >= 0) return override
3049         }
3050         return reader.nodeCount(group)
3051     }
3052 
updateNodeCountnull3053     private fun updateNodeCount(group: Int, count: Int) {
3054         if (updatedNodeCount(group) != count) {
3055             if (group < 0) {
3056                 val virtualCounts =
3057                     nodeCountVirtualOverrides
3058                         ?: run {
3059                             val newCounts = MutableIntIntMap()
3060                             nodeCountVirtualOverrides = newCounts
3061                             newCounts
3062                         }
3063                 virtualCounts[group] = count
3064             } else {
3065                 val nodeCounts =
3066                     nodeCountOverrides
3067                         ?: run {
3068                             val newCounts = IntArray(reader.size)
3069                             newCounts.fill(-1)
3070                             nodeCountOverrides = newCounts
3071                             newCounts
3072                         }
3073                 nodeCounts[group] = count
3074             }
3075         }
3076     }
3077 
clearUpdatedNodeCountsnull3078     private fun clearUpdatedNodeCounts() {
3079         nodeCountOverrides = null
3080         nodeCountVirtualOverrides = null
3081     }
3082 
3083     /**
3084      * Records the operations necessary to move the applier the node affected by the previous group
3085      * to the new group.
3086      */
recordUpsAndDownsnull3087     private fun recordUpsAndDowns(oldGroup: Int, newGroup: Int, commonRoot: Int) {
3088         val reader = reader
3089         val nearestCommonRoot = reader.nearestCommonRootOf(oldGroup, newGroup, commonRoot)
3090 
3091         // Record ups for the nodes between oldGroup and nearestCommonRoot
3092         var current = oldGroup
3093         while (current > 0 && current != nearestCommonRoot) {
3094             if (reader.isNode(current)) changeListWriter.moveUp()
3095             current = reader.parent(current)
3096         }
3097 
3098         // Record downs from nearestCommonRoot to newGroup
3099         doRecordDownsFor(newGroup, nearestCommonRoot)
3100     }
3101 
doRecordDownsFornull3102     private fun doRecordDownsFor(group: Int, nearestCommonRoot: Int) {
3103         if (group > 0 && group != nearestCommonRoot) {
3104             doRecordDownsFor(reader.parent(group), nearestCommonRoot)
3105             if (reader.isNode(group)) changeListWriter.moveDown(reader.nodeAt(group))
3106         }
3107     }
3108 
3109     /**
3110      * Calculate the composite key (a semi-unique key produced for every group in the composition)
3111      * for [group]. Passing in the [recomposeGroup] and [recomposeKey] allows this method to exit
3112      * early.
3113      */
compositeKeyOfnull3114     private fun compositeKeyOf(
3115         group: Int,
3116         recomposeGroup: Int,
3117         recomposeKey: CompositeKeyHashCode
3118     ): CompositeKeyHashCode {
3119         // The general form of a group's compositeKey can be solved by recursively evaluating:
3120         // compositeKey(group) = ((compositeKey(parent(group)) rol 3)
3121         //      xor compositeKeyPart(group) rol 3) xor effectiveRGroupIndex
3122         //
3123         // To solve this without recursion, first expand the terms:
3124         // compositeKey(group) = (compositeKey(parent(group)) rol 6)
3125         //                      xor (compositeKeyPart(group) rol 3)
3126         //                      xor effectiveRGroupIndex
3127         //
3128         // Then rewrite this as an iterative XOR sum, where n represents the distance from the
3129         // starting node and takes the range 0 <= n < depth(group) and g - n represents the n-th
3130         // parent of g, and all terms are XOR-ed together:
3131         //
3132         // [compositeKeyPart(g - n) rol (6n + 3)] xor [rGroupIndexOf(g - n) rol (6n)]
3133         //
3134         // Because compositeKey(g - n) is known when (g - n) == recomposeGroup, we can terminate
3135         // early and substitute that iteration's terms with recomposeKey rol (6n).
3136 
3137         var keyRot = 3
3138         var rgiRot = 0
3139         var result = CompositeKeyHashCode(0)
3140 
3141         var parent = group
3142         while (parent >= 0) {
3143             if (parent == recomposeGroup) {
3144                 result = result.bottomUpCompoundWith(recomposeKey, rgiRot)
3145                 return result
3146             }
3147 
3148             val groupKey = reader.groupCompositeKeyPart(parent)
3149             if (groupKey == movableContentKey) {
3150                 result = result.bottomUpCompoundWith(groupKey, rgiRot)
3151                 return result
3152             }
3153 
3154             val effectiveRGroupIndex = if (reader.hasObjectKey(parent)) 0 else rGroupIndexOf(parent)
3155             result =
3156                 result
3157                     .bottomUpCompoundWith(groupKey, keyRot)
3158                     .bottomUpCompoundWith(effectiveRGroupIndex, rgiRot)
3159             keyRot = (keyRot + 6) % CompositeKeyHashSizeBits
3160             rgiRot = (rgiRot + 6) % CompositeKeyHashSizeBits
3161 
3162             parent = reader.parent(parent)
3163         }
3164 
3165         return result
3166     }
3167 
groupCompositeKeyPartnull3168     private fun SlotReader.groupCompositeKeyPart(group: Int): Int =
3169         if (hasObjectKey(group)) {
3170             groupObjectKey(group)?.let {
3171                 when (it) {
3172                     is Enum<*> -> it.ordinal
3173                     is MovableContent<*> -> movableContentKey
3174                     else -> it.hashCode()
3175                 }
3176             } ?: 0
3177         } else
<lambda>null3178             groupKey(group).let {
3179                 if (it == reuseKey)
3180                     groupAux(group)?.let { aux ->
3181                         if (aux == Composer.Empty) it else aux.hashCode()
3182                     } ?: it
3183                 else it
3184             }
3185 
tryImminentInvalidationnull3186     internal fun tryImminentInvalidation(scope: RecomposeScopeImpl, instance: Any?): Boolean {
3187         val anchor = scope.anchor ?: return false
3188         val slotTable = reader.table
3189         val location = anchor.toIndexFor(slotTable)
3190         if (isComposing && location >= reader.currentGroup) {
3191             // if we are invalidating a scope that is going to be traversed during this
3192             // composition.
3193             invalidations.insertIfMissing(location, scope, instance)
3194             return true
3195         }
3196         return false
3197     }
3198 
3199     @TestOnly
parentKeynull3200     internal fun parentKey(): Int {
3201         return if (inserting) {
3202             writer.groupKey(writer.parent)
3203         } else {
3204             reader.groupKey(reader.parent)
3205         }
3206     }
3207 
3208     /**
3209      * Skip a group. Skips the group at the current location. This is only valid to call if the
3210      * composition is not inserting.
3211      */
3212     @ComposeCompilerApi
skipCurrentGroupnull3213     override fun skipCurrentGroup() {
3214         if (invalidations.isEmpty()) {
3215             skipGroup()
3216         } else {
3217             val reader = reader
3218             val key = reader.groupKey
3219             val dataKey = reader.groupObjectKey
3220             val aux = reader.groupAux
3221             val rGroupIndex = rGroupIndex
3222             updateCompositeKeyWhenWeEnterGroup(key, rGroupIndex, dataKey, aux)
3223             startReaderGroup(reader.isNode, null)
3224             recomposeToGroupEnd()
3225             reader.endGroup()
3226             updateCompositeKeyWhenWeExitGroup(key, rGroupIndex, dataKey, aux)
3227         }
3228     }
3229 
skipReaderToGroupEndnull3230     private fun skipReaderToGroupEnd() {
3231         groupNodeCount = reader.parentNodes
3232         reader.skipToGroupEnd()
3233     }
3234 
3235     @ComposeCompilerApi
shouldExecutenull3236     override fun shouldExecute(parametersChanged: Boolean, flags: Int): Boolean {
3237         // We only want to pause when we are not resuming and only when inserting new content or
3238         // when reusing content. This 0 bit of `flags` is only 1 if this function was restarted by
3239         // the restart lambda. The other bits of this flags are currently all 0's and are reserved
3240         // for future use.
3241         if (((flags and 1) == 0) && (inserting || reusing)) {
3242             val callback = shouldPauseCallback ?: return true
3243             val scope = currentRecomposeScope ?: return true
3244             val pausing = callback.shouldPause()
3245             if (pausing) {
3246                 scope.used = true
3247                 // Force the composer back into the reusing state when this scope restarts.
3248                 scope.reusing = reusing
3249                 scope.paused = true
3250                 // Remember a place-holder object to ensure all remembers are sent in the correct
3251                 // order. The remember manager will record the remember callback for the resumed
3252                 // content into a place-holder to ensure that, when the remember callbacks are
3253                 // dispatched, the callbacks for the resumed content are dispatched in the same
3254                 // order they would have been had the content not paused.
3255                 changeListWriter.rememberPausingScope(scope)
3256                 parentContext.reportPausedScope(scope)
3257                 return false
3258             }
3259             return true
3260         }
3261 
3262         // Otherwise we should execute the function if the parameters have changed or when
3263         // skipping is disabled.
3264         return parametersChanged || !skipping
3265     }
3266 
3267     /** Skip to the end of the group opened by [startGroup]. */
3268     @ComposeCompilerApi
skipToGroupEndnull3269     override fun skipToGroupEnd() {
3270         runtimeCheck(groupNodeCount == 0) {
3271             "No nodes can be emitted before calling skipAndEndGroup"
3272         }
3273 
3274         // This can be called when inserting is true and `shouldExecute` returns false.
3275         // When `inserting` the writer is already at the end of the group so we don't need to
3276         // move the writer.
3277         if (!inserting) {
3278             currentRecomposeScope?.scopeSkipped()
3279             if (invalidations.isEmpty()) {
3280                 skipReaderToGroupEnd()
3281             } else {
3282                 recomposeToGroupEnd()
3283             }
3284         }
3285     }
3286 
3287     @ComposeCompilerApi
deactivateToEndGroupnull3288     override fun deactivateToEndGroup(changed: Boolean) {
3289         runtimeCheck(groupNodeCount == 0) {
3290             "No nodes can be emitted before calling dactivateToEndGroup"
3291         }
3292         if (!inserting) {
3293             if (!changed) {
3294                 skipReaderToGroupEnd()
3295                 return
3296             }
3297             val start = reader.currentGroup
3298             val end = reader.currentEnd
3299             changeListWriter.deactivateCurrentGroup()
3300             invalidations.removeRange(start, end)
3301             reader.skipToGroupEnd()
3302         }
3303     }
3304 
3305     /**
3306      * Start a restart group. A restart group creates a recompose scope and sets it as the current
3307      * recompose scope of the composition. If the recompose scope is invalidated then this group
3308      * will be recomposed. A recompose scope can be invalidated by calling invalidate on the object
3309      * returned by [androidx.compose.runtime.currentRecomposeScope].
3310      */
3311     @ComposeCompilerApi
startRestartGroupnull3312     override fun startRestartGroup(key: Int): Composer {
3313         startReplaceGroup(key)
3314         addRecomposeScope()
3315         return this
3316     }
3317 
addRecomposeScopenull3318     private fun addRecomposeScope() {
3319         if (inserting) {
3320             val scope = RecomposeScopeImpl(composition as CompositionImpl)
3321             invalidateStack.push(scope)
3322             updateValue(scope)
3323             scope.start(compositionToken)
3324         } else {
3325             val invalidation = invalidations.removeLocation(reader.parent)
3326             val slot = reader.next()
3327             val scope =
3328                 if (slot == Composer.Empty) {
3329                     // This code is executed when a previously deactivate region is becomes active
3330                     // again. See Composer.deactivateToEndGroup()
3331                     val newScope = RecomposeScopeImpl(composition as CompositionImpl)
3332                     updateValue(newScope)
3333                     newScope
3334                 } else slot as RecomposeScopeImpl
3335             scope.requiresRecompose =
3336                 invalidation != null ||
3337                     scope.forcedRecompose.also { forced ->
3338                         if (forced) scope.forcedRecompose = false
3339                     }
3340             invalidateStack.push(scope)
3341             scope.start(compositionToken)
3342             if (scope.paused) {
3343                 scope.paused = false
3344                 scope.resuming = true
3345                 changeListWriter.startResumingScope(scope)
3346             }
3347         }
3348     }
3349 
3350     /**
3351      * End a restart group. If the recompose scope was marked used during composition then a
3352      * [ScopeUpdateScope] is returned that allows attaching a lambda that will produce the same
3353      * composition as was produced by this group (including calling [startRestartGroup] and
3354      * [endRestartGroup]).
3355      */
3356     @ComposeCompilerApi
endRestartGroupnull3357     override fun endRestartGroup(): ScopeUpdateScope? {
3358         // This allows for the invalidate stack to be out of sync since this might be called during
3359         // exception stack unwinding that might have not called the doneJoin/endRestartGroup in the
3360         // the correct order.
3361         val scope = if (invalidateStack.isNotEmpty()) invalidateStack.pop() else null
3362         if (scope != null) {
3363             scope.requiresRecompose = false
3364             scope.end(compositionToken)?.let {
3365                 changeListWriter.endCompositionScope(it, composition)
3366             }
3367             if (scope.resuming) {
3368                 scope.resuming = false
3369                 changeListWriter.endResumingScope(scope)
3370             }
3371         }
3372         val result =
3373             if (scope != null && !scope.skipped && (scope.used || forceRecomposeScopes)) {
3374                 if (scope.anchor == null) {
3375                     scope.anchor =
3376                         if (inserting) {
3377                             writer.anchor(writer.parent)
3378                         } else {
3379                             reader.anchor(reader.parent)
3380                         }
3381                 }
3382                 scope.defaultsInvalid = false
3383                 scope
3384             } else {
3385                 null
3386             }
3387         end(isNode = false)
3388         return result
3389     }
3390 
3391     @InternalComposeApi
insertMovableContentnull3392     override fun insertMovableContent(value: MovableContent<*>, parameter: Any?) {
3393         @Suppress("UNCHECKED_CAST")
3394         invokeMovableContentLambda(
3395             value as MovableContent<Any?>,
3396             currentCompositionLocalScope(),
3397             parameter,
3398             force = false
3399         )
3400     }
3401 
invokeMovableContentLambdanull3402     private fun invokeMovableContentLambda(
3403         content: MovableContent<Any?>,
3404         locals: PersistentCompositionLocalMap,
3405         parameter: Any?,
3406         force: Boolean
3407     ) {
3408         // Start the movable content group
3409         startMovableGroup(movableContentKey, content)
3410         updateSlot(parameter)
3411 
3412         // All movable content has a composite hash value rooted at the content itself so the hash
3413         // value doesn't change as the content moves in the tree.
3414         val savedCompositeKeyHash = compositeKeyHashCode
3415 
3416         try {
3417             compositeKeyHashCode = CompositeKeyHashCode(movableContentKey)
3418 
3419             if (inserting) writer.markGroup()
3420 
3421             // Capture the local providers at the point of the invocation. This allows detecting
3422             // changes to the locals as the value moves well as enables finding the correct
3423             // providers
3424             // when applying late changes which might be very complicated otherwise.
3425             val providersChanged = if (inserting) false else reader.groupAux != locals
3426             if (providersChanged) recordProviderUpdate(locals)
3427             start(compositionLocalMapKey, compositionLocalMap, GroupKind.Group, locals)
3428             providerCache = null
3429 
3430             // Either insert a place-holder to be inserted later (either created new or moved from
3431             // another location) or (re)compose the movable content. This is forced if a new value
3432             // needs to be created as a late change.
3433             if (inserting && !force) {
3434                 writerHasAProvider = true
3435 
3436                 // Create an anchor to the movable group
3437                 val anchor = writer.anchor(writer.parent(writer.parent))
3438                 val reference =
3439                     MovableContentStateReference(
3440                         content,
3441                         parameter,
3442                         composition,
3443                         insertTable,
3444                         anchor,
3445                         emptyList(),
3446                         currentCompositionLocalScope(),
3447                         null
3448                     )
3449                 parentContext.insertMovableContent(reference)
3450             } else {
3451                 val savedProvidersInvalid = providersInvalid
3452                 providersInvalid = providersChanged
3453                 invokeComposable(this, { content.content(parameter) })
3454                 providersInvalid = savedProvidersInvalid
3455             }
3456         } finally {
3457             // Restore the state back to what is expected by the caller.
3458             endGroup()
3459             providerCache = null
3460             compositeKeyHashCode = savedCompositeKeyHash
3461             endMovableGroup()
3462         }
3463     }
3464 
3465     @InternalComposeApi
insertMovableContentReferencesnull3466     override fun insertMovableContentReferences(
3467         references: List<Pair<MovableContentStateReference, MovableContentStateReference?>>
3468     ) {
3469         var completed = false
3470         try {
3471             insertMovableContentGuarded(references)
3472             completed = true
3473         } finally {
3474             if (completed) {
3475                 cleanUpCompose()
3476             } else {
3477                 // if we finished with error, cleanup more aggressively
3478                 abortRoot()
3479             }
3480         }
3481     }
3482 
insertMovableContentGuardednull3483     private fun insertMovableContentGuarded(
3484         references: List<Pair<MovableContentStateReference, MovableContentStateReference?>>
3485     ) {
3486         changeListWriter.withChangeList(lateChanges) {
3487             changeListWriter.resetSlots()
3488             references.fastForEach { (to, from) ->
3489                 val anchor = to.anchor
3490                 val location = to.slotTable.anchorIndex(anchor)
3491                 val effectiveNodeIndex = IntRef()
3492                 // Insert content at the anchor point
3493                 changeListWriter.determineMovableContentNodeIndex(effectiveNodeIndex, anchor)
3494                 if (from == null) {
3495                     val toSlotTable = to.slotTable
3496                     if (toSlotTable == insertTable) {
3497                         // We are going to compose reading the insert table which will also
3498                         // perform an insert. This would then cause both a reader and a writer to
3499                         // be created simultaneously which will throw an exception. To prevent
3500                         // that we release the old insert table and replace it with a fresh one.
3501                         // This allows us to read from the old table and write to the new table.
3502 
3503                         // This occurs when the placeholder version of movable content was inserted
3504                         // but no content was available to move so we now need to create the
3505                         // content.
3506 
3507                         createFreshInsertTable()
3508                     }
3509                     to.slotTable.read { reader ->
3510                         reader.reposition(location)
3511                         changeListWriter.moveReaderToAbsolute(location)
3512                         val offsetChanges = ChangeList()
3513                         recomposeMovableContent {
3514                             changeListWriter.withChangeList(offsetChanges) {
3515                                 withReader(reader) {
3516                                     changeListWriter.withoutImplicitRootStart {
3517                                         invokeMovableContentLambda(
3518                                             to.content,
3519                                             to.locals,
3520                                             to.parameter,
3521                                             force = true
3522                                         )
3523                                     }
3524                                 }
3525                             }
3526                         }
3527                         changeListWriter.includeOperationsIn(
3528                             other = offsetChanges,
3529                             effectiveNodeIndex = effectiveNodeIndex
3530                         )
3531                     }
3532                 } else {
3533                     // If the state was already removed from the from table then it will have a
3534                     // state recorded in the recomposer, retrieve that now if we can. If not the
3535                     // state is still in its original location, recompose over it there.
3536                     val resolvedState = parentContext.movableContentStateResolve(from)
3537                     val fromTable = resolvedState?.slotTable ?: from.slotTable
3538                     val fromAnchor = resolvedState?.slotTable?.anchor(0) ?: from.anchor
3539                     val nodesToInsert = fromTable.collectNodesFrom(fromAnchor)
3540 
3541                     // Insert nodes if necessary
3542                     if (nodesToInsert.isNotEmpty()) {
3543                         changeListWriter.copyNodesToNewAnchorLocation(
3544                             nodesToInsert,
3545                             effectiveNodeIndex
3546                         )
3547                         if (to.slotTable == slotTable) {
3548                             // Inserting the content into the current slot table then we need to
3549                             // update the virtual node counts. Otherwise, we are inserting into
3550                             // a new slot table which is being created, not updated, so the virtual
3551                             // node counts do not need to be updated.
3552                             val group = slotTable.anchorIndex(anchor)
3553                             updateNodeCount(group, updatedNodeCount(group) + nodesToInsert.size)
3554                         }
3555                     }
3556 
3557                     // Copy the slot table into the anchor location
3558                     changeListWriter.copySlotTableToAnchorLocation(
3559                         resolvedState = resolvedState,
3560                         parentContext = parentContext,
3561                         from = from,
3562                         to = to
3563                     )
3564 
3565                     fromTable.read { reader ->
3566                         withReader(reader) {
3567                             val newLocation = fromTable.anchorIndex(fromAnchor)
3568                             reader.reposition(newLocation)
3569                             changeListWriter.moveReaderToAbsolute(newLocation)
3570                             val offsetChanges = ChangeList()
3571                             changeListWriter.withChangeList(offsetChanges) {
3572                                 changeListWriter.withoutImplicitRootStart {
3573                                     recomposeMovableContent(
3574                                         from = from.composition,
3575                                         to = to.composition,
3576                                         reader.currentGroup,
3577                                         invalidations = from.invalidations
3578                                     ) {
3579                                         invokeMovableContentLambda(
3580                                             to.content,
3581                                             to.locals,
3582                                             to.parameter,
3583                                             force = true
3584                                         )
3585                                     }
3586                                 }
3587                             }
3588                             changeListWriter.includeOperationsIn(
3589                                 other = offsetChanges,
3590                                 effectiveNodeIndex = effectiveNodeIndex
3591                             )
3592                         }
3593                     }
3594                 }
3595                 changeListWriter.skipToEndOfCurrentGroup()
3596             }
3597             changeListWriter.endMovableContentPlacement()
3598             changeListWriter.moveReaderToAbsolute(0)
3599         }
3600     }
3601 
withReadernull3602     private inline fun <R> withReader(reader: SlotReader, block: () -> R): R {
3603         val savedReader = this.reader
3604         val savedCountOverrides = nodeCountOverrides
3605         val savedProviderUpdates = providerUpdates
3606         nodeCountOverrides = null
3607         providerUpdates = null
3608         try {
3609             this.reader = reader
3610             return block()
3611         } finally {
3612             this.reader = savedReader
3613             nodeCountOverrides = savedCountOverrides
3614             providerUpdates = savedProviderUpdates
3615         }
3616     }
3617 
recomposeMovableContentnull3618     private fun <R> recomposeMovableContent(
3619         from: ControlledComposition? = null,
3620         to: ControlledComposition? = null,
3621         index: Int? = null,
3622         invalidations: List<Pair<RecomposeScopeImpl, Any?>> = emptyList(),
3623         block: () -> R
3624     ): R {
3625         val savedIsComposing = isComposing
3626         val savedNodeIndex = nodeIndex
3627         try {
3628             isComposing = true
3629             nodeIndex = 0
3630             invalidations.fastForEach { (scope, instances) ->
3631                 if (instances != null) {
3632                     tryImminentInvalidation(scope, instances)
3633                 } else {
3634                     tryImminentInvalidation(scope, null)
3635                 }
3636             }
3637             return from?.delegateInvalidations(to, index ?: -1, block) ?: block()
3638         } finally {
3639             isComposing = savedIsComposing
3640             nodeIndex = savedNodeIndex
3641         }
3642     }
3643 
3644     @ComposeCompilerApi
sourceInformationnull3645     override fun sourceInformation(sourceInformation: String) {
3646         if (inserting && sourceMarkersEnabled) {
3647             writer.recordGroupSourceInformation(sourceInformation)
3648         }
3649     }
3650 
3651     @ComposeCompilerApi
sourceInformationMarkerStartnull3652     override fun sourceInformationMarkerStart(key: Int, sourceInformation: String) {
3653         if (inserting && sourceMarkersEnabled) {
3654             writer.recordGrouplessCallSourceInformationStart(key, sourceInformation)
3655         }
3656     }
3657 
3658     @ComposeCompilerApi
sourceInformationMarkerEndnull3659     override fun sourceInformationMarkerEnd() {
3660         if (inserting && sourceMarkersEnabled) {
3661             writer.recordGrouplessCallSourceInformationEnd()
3662         }
3663     }
3664 
disableSourceInformationnull3665     override fun disableSourceInformation() {
3666         sourceMarkersEnabled = false
3667     }
3668 
stackTraceForValuenull3669     internal fun stackTraceForValue(value: Any?): List<ComposeStackTraceFrame> {
3670         if (!sourceMarkersEnabled) return emptyList()
3671 
3672         return slotTable
3673             .findLocation { it === value || (it as? RememberObserverHolder)?.wrapped === value }
3674             ?.let { (groupIndex, dataIndex) ->
3675                 stackTraceForGroup(groupIndex, dataIndex) + parentStackTrace()
3676             } ?: emptyList()
3677     }
3678 
currentStackTracenull3679     private fun currentStackTrace(): List<ComposeStackTraceFrame> {
3680         if (!sourceMarkersEnabled) return emptyList()
3681 
3682         val trace = mutableListOf<ComposeStackTraceFrame>()
3683         trace.addAll(writer.buildTrace())
3684         trace.addAll(reader.buildTrace())
3685 
3686         return trace.apply { addAll(parentStackTrace()) }
3687     }
3688 
stackTraceForGroupnull3689     private fun stackTraceForGroup(group: Int, dataOffset: Int?): List<ComposeStackTraceFrame> {
3690         if (!sourceMarkersEnabled) return emptyList()
3691 
3692         return slotTable.read { it.traceForGroup(group, dataOffset) }
3693     }
3694 
parentStackTracenull3695     fun parentStackTrace(): List<ComposeStackTraceFrame> {
3696         val composition = parentContext.composition as? CompositionImpl ?: return emptyList()
3697         val position = composition.slotTable.findSubcompositionContextGroup(parentContext)
3698 
3699         return if (position != null) {
3700             composition.slotTable.read { reader -> reader.traceForGroup(position, 0) }
3701         } else {
3702             emptyList()
3703         }
3704     }
3705 
3706     /**
3707      * Synchronously compose the initial composition of [content]. This collects all the changes
3708      * which must be applied by [ControlledComposition.applyChanges] to build the tree implied by
3709      * [content].
3710      */
3711     internal fun composeContent(
3712         invalidationsRequested: ScopeMap<RecomposeScopeImpl, Any>,
3713         content: @Composable () -> Unit,
3714         shouldPause: ShouldPauseCallback?
3715     ) {
<lambda>null3716         runtimeCheck(changes.isEmpty()) { "Expected applyChanges() to have been called" }
3717         this.shouldPauseCallback = shouldPause
3718         try {
3719             doCompose(invalidationsRequested, content)
3720         } finally {
3721             this.shouldPauseCallback = null
3722         }
3723     }
3724 
prepareComposenull3725     internal fun prepareCompose(block: () -> Unit) {
3726         runtimeCheck(!isComposing) { "Preparing a composition while composing is not supported" }
3727         isComposing = true
3728         try {
3729             block()
3730         } finally {
3731             isComposing = false
3732         }
3733     }
3734 
3735     /**
3736      * Synchronously recompose all invalidated groups. This collects the changes which must be
3737      * applied by [ControlledComposition.applyChanges] to have an effect.
3738      */
recomposenull3739     internal fun recompose(
3740         invalidationsRequested: ScopeMap<RecomposeScopeImpl, Any>,
3741         shouldPause: ShouldPauseCallback?
3742     ): Boolean {
3743         runtimeCheck(changes.isEmpty()) { "Expected applyChanges() to have been called" }
3744         // even if invalidationsRequested is empty we still need to recompose if the Composer has
3745         // some invalidations scheduled already. it can happen when during some parent composition
3746         // there were a change for a state which was used by the child composition. such changes
3747         // will be tracked and added into `invalidations` list.
3748         if (invalidationsRequested.size > 0 || invalidations.isNotEmpty() || forciblyRecompose) {
3749             shouldPauseCallback = shouldPause
3750             try {
3751                 doCompose(invalidationsRequested, null)
3752             } finally {
3753                 shouldPauseCallback = null
3754             }
3755             return changes.isNotEmpty()
3756         }
3757         return false
3758     }
3759 
updateComposerInvalidationsnull3760     fun updateComposerInvalidations(invalidationsRequested: ScopeMap<RecomposeScopeImpl, Any>) {
3761         invalidationsRequested.map.forEach { scope, instances ->
3762             scope as RecomposeScopeImpl
3763             val location = scope.anchor?.location ?: return@forEach
3764             invalidations.add(
3765                 Invalidation(scope, location, instances.takeUnless { it === ScopeInvalidated })
3766             )
3767         }
3768         invalidations.sortWith(InvalidationLocationAscending)
3769     }
3770 
doComposenull3771     private fun doCompose(
3772         invalidationsRequested: ScopeMap<RecomposeScopeImpl, Any>,
3773         content: (@Composable () -> Unit)?
3774     ) {
3775         runtimeCheck(!isComposing) { "Reentrant composition is not supported" }
3776         trace("Compose:recompose") {
3777             compositionToken = currentSnapshot().snapshotId.hashCode()
3778             providerUpdates = null
3779             updateComposerInvalidations(invalidationsRequested)
3780             nodeIndex = 0
3781             var complete = false
3782             isComposing = true
3783             try {
3784                 startRoot()
3785 
3786                 // vv Experimental for forced
3787                 val savedContent = nextSlot()
3788                 if (savedContent !== content && content != null) {
3789                     updateValue(content as Any?)
3790                 }
3791                 // ^^ Experimental for forced
3792 
3793                 // Ignore reads of derivedStateOf recalculations
3794                 observeDerivedStateRecalculations(derivedStateObserver) {
3795                     if (content != null) {
3796                         startGroup(invocationKey, invocation)
3797                         invokeComposable(this, content)
3798                         endGroup()
3799                     } else if (
3800                         (forciblyRecompose || providersInvalid) &&
3801                             savedContent != null &&
3802                             savedContent != Composer.Empty
3803                     ) {
3804                         startGroup(invocationKey, invocation)
3805                         @Suppress("UNCHECKED_CAST")
3806                         invokeComposable(this, savedContent as @Composable () -> Unit)
3807                         endGroup()
3808                     } else {
3809                         skipCurrentGroup()
3810                     }
3811                 }
3812                 endRoot()
3813                 complete = true
3814             } catch (e: Throwable) {
3815                 throw e.attachComposeStackTrace { currentStackTrace() }
3816             } finally {
3817                 isComposing = false
3818                 invalidations.clear()
3819                 if (!complete) abortRoot()
3820                 createFreshInsertTable()
3821             }
3822         }
3823     }
3824 
3825     val hasInvalidations
3826         get() = invalidations.isNotEmpty()
3827 
3828     private val SlotReader.node
3829         get() = node(parent)
3830 
nodeAtnull3831     private fun SlotReader.nodeAt(index: Int) = node(index)
3832 
3833     private fun validateNodeExpected() {
3834         runtimeCheck(nodeExpected) {
3835             "A call to createNode(), emitNode() or useNode() expected was not expected"
3836         }
3837         nodeExpected = false
3838     }
3839 
validateNodeNotExpectednull3840     private fun validateNodeNotExpected() {
3841         runtimeCheck(!nodeExpected) { "A call to createNode(), emitNode() or useNode() expected" }
3842     }
3843 
recordInsertnull3844     private fun recordInsert(anchor: Anchor) {
3845         if (insertFixups.isEmpty()) {
3846             changeListWriter.insertSlots(anchor, insertTable)
3847         } else {
3848             changeListWriter.insertSlots(anchor, insertTable, insertFixups)
3849             insertFixups = FixupList()
3850         }
3851     }
3852 
recordDeletenull3853     private fun recordDelete() {
3854         // It is import that the movable content is reported first so it can be removed before the
3855         // group itself is removed.
3856         reportFreeMovableContent(reader.currentGroup)
3857         changeListWriter.removeCurrentGroup()
3858     }
3859 
3860     /**
3861      * Report any movable content that the group contains as being removed and ready to be moved.
3862      * Returns true if the group itself was removed.
3863      *
3864      * Returns the number of nodes left in place which is used to calculate the node index of any
3865      * nested calls.
3866      *
3867      * @param groupBeingRemoved The group that is being removed from the table or 0 if the entire
3868      *   table is being removed.
3869      */
reportFreeMovableContentnull3870     private fun reportFreeMovableContent(groupBeingRemoved: Int) {
3871 
3872         fun createMovableContentReferenceForGroup(
3873             group: Int,
3874             nestedStates: List<MovableContentStateReference>?
3875         ): MovableContentStateReference {
3876             @Suppress("UNCHECKED_CAST")
3877             val movableContent = reader.groupObjectKey(group) as MovableContent<Any?>
3878             val parameter = reader.groupGet(group, 0)
3879             val anchor = reader.anchor(group)
3880             val end = group + reader.groupSize(group)
3881             val invalidations =
3882                 this.invalidations.filterToRange(group, end).fastMap { it.scope to it.instances }
3883             val reference =
3884                 MovableContentStateReference(
3885                     movableContent,
3886                     parameter,
3887                     composition,
3888                     slotTable,
3889                     anchor,
3890                     invalidations,
3891                     currentCompositionLocalScope(group),
3892                     nestedStates
3893                 )
3894             return reference
3895         }
3896 
3897         fun movableContentReferenceFor(group: Int): MovableContentStateReference? {
3898             val key = reader.groupKey(group)
3899             val objectKey = reader.groupObjectKey(group)
3900             return if (key == movableContentKey && objectKey is MovableContent<*>) {
3901                 val nestedStates =
3902                     if (reader.containsMark(group)) {
3903                         val nestedStates = mutableListOf<MovableContentStateReference>()
3904                         fun traverseGroups(group: Int) {
3905                             val size = reader.groupSize(group)
3906                             val end = group + size
3907                             var current = group + 1
3908                             while (current < end) {
3909                                 if (reader.hasMark(current)) {
3910                                     movableContentReferenceFor(current)?.let {
3911                                         nestedStates.add(it)
3912                                     }
3913                                 } else if (reader.containsMark(current)) traverseGroups(current)
3914                                 current += reader.groupSize(current)
3915                             }
3916                         }
3917                         traverseGroups(group)
3918                         nestedStates.takeIf { it.isNotEmpty() }
3919                     } else null
3920                 createMovableContentReferenceForGroup(group, nestedStates)
3921             } else null
3922         }
3923 
3924         fun reportGroup(group: Int, needsNodeDelete: Boolean, nodeIndex: Int): Int {
3925             val reader = reader
3926             return if (reader.hasMark(group)) {
3927                 // If the group has a mark then it is either a movable content group or a
3928                 // composition context group
3929                 val key = reader.groupKey(group)
3930                 val objectKey = reader.groupObjectKey(group)
3931                 if (key == movableContentKey && objectKey is MovableContent<*>) {
3932                     // If the group is a movable content block schedule it to be removed and report
3933                     // that it is free to be moved to the parentContext. Nested movable content is
3934                     // recomposed if necessary once the group has been claimed by another insert.
3935                     // reportMovableContentForGroup(group)
3936                     // reportMovableContentAt(group)
3937                     val reference = movableContentReferenceFor(group)
3938                     if (reference != null) {
3939                         parentContext.deletedMovableContent(reference)
3940                         changeListWriter.recordSlotEditing()
3941                         changeListWriter.releaseMovableGroupAtCurrent(
3942                             composition,
3943                             parentContext,
3944                             reference
3945                         )
3946                     }
3947                     if (needsNodeDelete && group != groupBeingRemoved) {
3948                         changeListWriter.endNodeMovementAndDeleteNode(nodeIndex, group)
3949                         0 // These nodes were deleted
3950                     } else reader.nodeCount(group)
3951                 } else if (key == referenceKey && objectKey == reference) {
3952                     // Group is a composition context reference. As this is being removed assume
3953                     // all movable groups in the composition that have this context will also be
3954                     // released when the compositions are disposed.
3955                     val contextHolder = reader.groupGet(group, 0) as? CompositionContextHolder
3956                     if (contextHolder != null) {
3957                         // The contextHolder can be EMPTY in cases where the content has been
3958                         // deactivated. Content is deactivated if the content is just being
3959                         // held onto for recycling and is not otherwise active. In this case
3960                         // the composers we are likely to find here have already been disposed.
3961                         val compositionContext = contextHolder.ref
3962                         compositionContext.composers.forEach { composer ->
3963                             composer.reportAllMovableContent()
3964 
3965                             // Mark the composition as being removed so it will not be recomposed
3966                             // this turn.
3967                             parentContext.reportRemovedComposition(composer.composition)
3968                         }
3969                     }
3970                     reader.nodeCount(group)
3971                 } else if (reader.isNode(group)) 1 else reader.nodeCount(group)
3972             } else if (reader.containsMark(group)) {
3973                 // Traverse the group freeing the child movable content. This group is known to
3974                 // have at least one child that contains movable content because the group is
3975                 // marked as containing a mark
3976                 val size = reader.groupSize(group)
3977                 val end = group + size
3978                 var current = group + 1
3979                 var runningNodeCount = 0
3980                 while (current < end) {
3981                     // A tree is not disassembled when it is removed, the root nodes of the
3982                     // sub-trees are removed, therefore, if we enter a node that contains movable
3983                     // content, the nodes should be removed so some future composition can
3984                     // re-insert them at a new location. Otherwise the applier will attempt to
3985                     // insert a node that already has a parent. If there is no node between the
3986                     // group removed and this group then the nodes will be removed by normal
3987                     // recomposition.
3988                     val isNode = reader.isNode(current)
3989                     if (isNode) {
3990                         changeListWriter.endNodeMovement()
3991                         changeListWriter.moveDown(reader.node(current))
3992                     }
3993                     runningNodeCount +=
3994                         reportGroup(
3995                             group = current,
3996                             needsNodeDelete = isNode || needsNodeDelete,
3997                             nodeIndex = if (isNode) 0 else nodeIndex + runningNodeCount
3998                         )
3999                     if (isNode) {
4000                         changeListWriter.endNodeMovement()
4001                         changeListWriter.moveUp()
4002                     }
4003                     current += reader.groupSize(current)
4004                 }
4005                 if (reader.isNode(group)) 1 else runningNodeCount
4006             } else if (reader.isNode(group)) 1 else reader.nodeCount(group)
4007         }
4008         // If the group that is being deleted is a node we need to remove any children that
4009         // are moved.
4010         val rootIsNode = reader.isNode(groupBeingRemoved)
4011         if (rootIsNode) {
4012             changeListWriter.endNodeMovement()
4013             changeListWriter.moveDown(reader.node(groupBeingRemoved))
4014         }
4015         reportGroup(groupBeingRemoved, needsNodeDelete = rootIsNode, nodeIndex = 0)
4016         changeListWriter.endNodeMovement()
4017         if (rootIsNode) {
4018             changeListWriter.moveUp()
4019         }
4020     }
4021 
4022     /**
4023      * Called during composition to report all the content of the composition will be released as
4024      * this composition is to be disposed.
4025      */
reportAllMovableContentnull4026     private fun reportAllMovableContent() {
4027         if (slotTable.containsMark()) {
4028             (composition as CompositionImpl).updateMovingInvalidations()
4029             val changes = ChangeList()
4030             deferredChanges = changes
4031             slotTable.read { reader ->
4032                 this.reader = reader
4033                 changeListWriter.withChangeList(changes) {
4034                     reportFreeMovableContent(0)
4035                     changeListWriter.releaseMovableContent()
4036                 }
4037             }
4038         }
4039     }
4040 
finalizeComposenull4041     private fun finalizeCompose() {
4042         changeListWriter.finalizeComposition()
4043         runtimeCheck(pendingStack.isEmpty()) { "Start/end imbalance" }
4044         cleanUpCompose()
4045     }
4046 
cleanUpComposenull4047     private fun cleanUpCompose() {
4048         pending = null
4049         nodeIndex = 0
4050         groupNodeCount = 0
4051         compositeKeyHashCode = EmptyCompositeKeyHashCode
4052         nodeExpected = false
4053         changeListWriter.resetTransientState()
4054         invalidateStack.clear()
4055         clearUpdatedNodeCounts()
4056     }
4057 
verifyConsistentnull4058     internal fun verifyConsistent() {
4059         insertTable.verifyWellFormed()
4060     }
4061 
4062     /**
4063      * A holder that will dispose of its [CompositionContext] when it leaves the composition that
4064      * will not have its reference made visible to user code.
4065      */
4066     internal class CompositionContextHolder(val ref: ComposerImpl.CompositionContextImpl) :
4067         ReusableRememberObserver {
4068 
onRememberednull4069         override fun onRemembered() {}
4070 
onAbandonednull4071         override fun onAbandoned() {
4072             ref.dispose()
4073         }
4074 
onForgottennull4075         override fun onForgotten() {
4076             ref.dispose()
4077         }
4078     }
4079 
4080     @OptIn(ExperimentalComposeRuntimeApi::class)
4081     internal inner class CompositionContextImpl(
4082         override val compositeKeyHashCode: CompositeKeyHashCode,
4083         override val collectingParameterInformation: Boolean,
4084         override val collectingSourceInformation: Boolean,
4085         override val observerHolder: CompositionObserverHolder?
4086     ) : CompositionContext() {
4087         var inspectionTables: MutableSet<MutableSet<CompositionData>>? = null
4088         val composers = mutableSetOf<ComposerImpl>()
4089 
4090         override val collectingCallByInformation: Boolean
4091             get() = parentContext.collectingCallByInformation
4092 
disposenull4093         fun dispose() {
4094             if (composers.isNotEmpty()) {
4095                 inspectionTables?.let {
4096                     for (composer in composers) {
4097                         for (table in it) table.remove(composer.slotTable)
4098                     }
4099                 }
4100                 composers.clear()
4101             }
4102         }
4103 
registerComposernull4104         override fun registerComposer(composer: Composer) {
4105             super.registerComposer(composer as ComposerImpl)
4106             composers.add(composer)
4107         }
4108 
unregisterComposernull4109         override fun unregisterComposer(composer: Composer) {
4110             inspectionTables?.forEach { it.remove((composer as ComposerImpl).slotTable) }
4111             composers.remove(composer)
4112         }
4113 
registerCompositionnull4114         override fun registerComposition(composition: ControlledComposition) {
4115             parentContext.registerComposition(composition)
4116         }
4117 
unregisterCompositionnull4118         override fun unregisterComposition(composition: ControlledComposition) {
4119             parentContext.unregisterComposition(composition)
4120         }
4121 
reportPausedScopenull4122         override fun reportPausedScope(scope: RecomposeScopeImpl) {
4123             parentContext.reportPausedScope(scope)
4124         }
4125 
4126         override val effectCoroutineContext: CoroutineContext
4127             get() = parentContext.effectCoroutineContext
4128 
4129         @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
4130         @OptIn(ExperimentalComposeApi::class)
4131         @get:OptIn(ExperimentalComposeApi::class)
4132         override val recomposeCoroutineContext: CoroutineContext
4133             get() = this@ComposerImpl.composition.recomposeCoroutineContext
4134 
4135         override fun composeInitial(
4136             composition: ControlledComposition,
4137             content: @Composable () -> Unit
4138         ) {
4139             parentContext.composeInitial(composition, content)
4140         }
4141 
4142         override fun composeInitialPaused(
4143             composition: ControlledComposition,
4144             shouldPause: ShouldPauseCallback,
4145             content: @Composable () -> Unit
4146         ): ScatterSet<RecomposeScopeImpl> =
4147             parentContext.composeInitialPaused(composition, shouldPause, content)
4148 
recomposePausednull4149         override fun recomposePaused(
4150             composition: ControlledComposition,
4151             shouldPause: ShouldPauseCallback,
4152             invalidScopes: ScatterSet<RecomposeScopeImpl>
4153         ): ScatterSet<RecomposeScopeImpl> =
4154             parentContext.recomposePaused(composition, shouldPause, invalidScopes)
4155 
4156         override fun invalidate(composition: ControlledComposition) {
4157             // Invalidate ourselves with our parent before we invalidate a child composer.
4158             // This ensures that when we are scheduling recompositions, parents always
4159             // recompose before their children just in case a recomposition in the parent
4160             // would also cause other recomposition in the child.
4161             // If the parent ends up having no real invalidations to process we will skip work
4162             // for that composer along a fast path later.
4163             // This invalidation process could be made more efficient as it's currently N^2 with
4164             // subcomposition meta-tree depth thanks to the double recursive parent walk
4165             // performed here, but we currently assume a low N.
4166             parentContext.invalidate(this@ComposerImpl.composition)
4167             parentContext.invalidate(composition)
4168         }
4169 
invalidateScopenull4170         override fun invalidateScope(scope: RecomposeScopeImpl) {
4171             parentContext.invalidateScope(scope)
4172         }
4173 
4174         // This is snapshot state not because we need it to be observable, but because
4175         // we need changes made to it in composition to be visible for the rest of the current
4176         // composition and not become visible outside of the composition process until composition
4177         // succeeds.
4178         private var compositionLocalScope by
4179             mutableStateOf<PersistentCompositionLocalMap>(
4180                 persistentCompositionLocalHashMapOf(),
4181                 referentialEqualityPolicy()
4182             )
4183 
getCompositionLocalScopenull4184         override fun getCompositionLocalScope(): PersistentCompositionLocalMap =
4185             compositionLocalScope
4186 
4187         fun updateCompositionLocalScope(scope: PersistentCompositionLocalMap) {
4188             compositionLocalScope = scope
4189         }
4190 
recordInspectionTablenull4191         override fun recordInspectionTable(table: MutableSet<CompositionData>) {
4192             (inspectionTables
4193                     ?: HashSet<MutableSet<CompositionData>>().also { inspectionTables = it })
4194                 .add(table)
4195         }
4196 
startComposingnull4197         override fun startComposing() {
4198             childrenComposing++
4199         }
4200 
doneComposingnull4201         override fun doneComposing() {
4202             childrenComposing--
4203         }
4204 
insertMovableContentnull4205         override fun insertMovableContent(reference: MovableContentStateReference) {
4206             parentContext.insertMovableContent(reference)
4207         }
4208 
deletedMovableContentnull4209         override fun deletedMovableContent(reference: MovableContentStateReference) {
4210             parentContext.deletedMovableContent(reference)
4211         }
4212 
movableContentStateResolvenull4213         override fun movableContentStateResolve(
4214             reference: MovableContentStateReference
4215         ): MovableContentState? = parentContext.movableContentStateResolve(reference)
4216 
4217         override fun movableContentStateReleased(
4218             reference: MovableContentStateReference,
4219             data: MovableContentState,
4220             applier: Applier<*>,
4221         ) {
4222             parentContext.movableContentStateReleased(reference, data, applier)
4223         }
4224 
reportRemovedCompositionnull4225         override fun reportRemovedComposition(composition: ControlledComposition) {
4226             parentContext.reportRemovedComposition(composition)
4227         }
4228 
4229         override val composition: Composition
4230             get() = this@ComposerImpl.composition
4231     }
4232 
updateCompositeKeyWhenWeEnterGroupnull4233     private inline fun updateCompositeKeyWhenWeEnterGroup(
4234         groupKey: Int,
4235         rGroupIndex: Int,
4236         dataKey: Any?,
4237         data: Any?
4238     ) {
4239         if (dataKey == null)
4240             if (data != null && groupKey == reuseKey && data != Composer.Empty)
4241                 updateCompositeKeyWhenWeEnterGroupKeyHash(data.hashCode(), rGroupIndex)
4242             else updateCompositeKeyWhenWeEnterGroupKeyHash(groupKey, rGroupIndex)
4243         else if (dataKey is Enum<*>) updateCompositeKeyWhenWeEnterGroupKeyHash(dataKey.ordinal, 0)
4244         else updateCompositeKeyWhenWeEnterGroupKeyHash(dataKey.hashCode(), 0)
4245     }
4246 
updateCompositeKeyWhenWeEnterGroupKeyHashnull4247     private inline fun updateCompositeKeyWhenWeEnterGroupKeyHash(groupKey: Int, rGroupIndex: Int) {
4248         compositeKeyHashCode =
4249             compositeKeyHashCode.compoundWith(groupKey, 3).compoundWith(rGroupIndex, 3)
4250     }
4251 
updateCompositeKeyWhenWeExitGroupnull4252     private inline fun updateCompositeKeyWhenWeExitGroup(
4253         groupKey: Int,
4254         rGroupIndex: Int,
4255         dataKey: Any?,
4256         data: Any?
4257     ) {
4258         if (dataKey == null)
4259             if (data != null && groupKey == reuseKey && data != Composer.Empty)
4260                 updateCompositeKeyWhenWeExitGroupKeyHash(data.hashCode(), rGroupIndex)
4261             else updateCompositeKeyWhenWeExitGroupKeyHash(groupKey, rGroupIndex)
4262         else if (dataKey is Enum<*>) updateCompositeKeyWhenWeExitGroupKeyHash(dataKey.ordinal, 0)
4263         else updateCompositeKeyWhenWeExitGroupKeyHash(dataKey.hashCode(), 0)
4264     }
4265 
updateCompositeKeyWhenWeExitGroupKeyHashnull4266     private inline fun updateCompositeKeyWhenWeExitGroupKeyHash(groupKey: Int, rGroupIndex: Int) {
4267         compositeKeyHashCode =
4268             compositeKeyHashCode.unCompoundWith(rGroupIndex, 3).unCompoundWith(groupKey, 3)
4269     }
4270 
4271     // This is only used in tests to ensure the stacks do not silently leak.
stacksSizenull4272     internal fun stacksSize(): Int {
4273         return entersStack.size +
4274             invalidateStack.size +
4275             providersInvalidStack.size +
4276             pendingStack.size +
4277             parentStateStack.size
4278     }
4279 
4280     override val recomposeScope: RecomposeScope?
4281         get() = currentRecomposeScope
4282 
4283     override val recomposeScopeIdentity: Any?
4284         get() = currentRecomposeScope?.anchor
4285 
rememberedValuenull4286     override fun rememberedValue(): Any? = nextSlotForCache()
4287 
4288     override fun updateRememberedValue(value: Any?) = updateCachedValue(value)
4289 
4290     override fun recordUsed(scope: RecomposeScope) {
4291         (scope as? RecomposeScopeImpl)?.used = true
4292     }
4293 }
4294 
4295 /**
4296  * A helper receiver scope class used by [ComposeNode] to help write code to initialized and update
4297  * a node.
4298  *
4299  * @see ComposeNode
4300  */
4301 @JvmInline
4302 value class Updater<T> constructor(@PublishedApi internal val composer: Composer) {
4303     /**
4304      * Set the value property of the emitted node.
4305      *
4306      * Schedules [block] to be run when the node is first created or when [value] is different than
4307      * the previous composition.
4308      *
4309      * @see update
4310      */
4311     @Suppress("NOTHING_TO_INLINE") // Inlining the compare has noticeable impact
setnull4312     inline fun set(value: Int, noinline block: T.(value: Int) -> Unit) =
4313         with(composer) {
4314             if (inserting || rememberedValue() != value) {
4315                 updateRememberedValue(value)
4316                 composer.apply(value, block)
4317             }
4318         }
4319 
4320     /**
4321      * Set the value property of the emitted node.
4322      *
4323      * Schedules [block] to be run when the node is first created or when [value] is different than
4324      * the previous composition.
4325      *
4326      * @see update
4327      */
setnull4328     fun <V> set(value: V, block: T.(value: V) -> Unit) =
4329         with(composer) {
4330             if (inserting || rememberedValue() != value) {
4331                 updateRememberedValue(value)
4332                 composer.apply(value, block)
4333             }
4334         }
4335 
4336     /**
4337      * Update the value of a property of the emitted node.
4338      *
4339      * Schedules [block] to be run when [value] is different than the previous composition. It is
4340      * different than [set] in that it does not run when the node is created. This is used when
4341      * initial value set by the [ComposeNode] in the constructor callback already has the correct
4342      * value. For example, use [update} when [value] is passed into of the classes constructor
4343      * parameters.
4344      *
4345      * @see set
4346      */
4347     @Suppress("NOTHING_TO_INLINE") // Inlining the compare has noticeable impact
updatenull4348     inline fun update(value: Int, noinline block: T.(value: Int) -> Unit) =
4349         with(composer) {
4350             val inserting = inserting
4351             if (inserting || rememberedValue() != value) {
4352                 updateRememberedValue(value)
4353                 if (!inserting) apply(value, block)
4354             }
4355         }
4356 
4357     /**
4358      * Update the value of a property of the emitted node.
4359      *
4360      * Schedules [block] to be run when [value] is different than the previous composition. It is
4361      * different than [set] in that it does not run when the node is created. This is used when
4362      * initial value set by the [ComposeNode] in the constructor callback already has the correct
4363      * value. For example, use [update} when [value] is passed into of the classes constructor
4364      * parameters.
4365      *
4366      * @see set
4367      */
updatenull4368     fun <V> update(value: V, block: T.(value: V) -> Unit) =
4369         with(composer) {
4370             val inserting = inserting
4371             if (inserting || rememberedValue() != value) {
4372                 updateRememberedValue(value)
4373                 if (!inserting) apply(value, block)
4374             }
4375         }
4376 
4377     /**
4378      * Initialize emitted node.
4379      *
4380      * Schedule [block] to be executed after the node is created.
4381      *
4382      * This is only executed once. The can be used to call a method or set a value on a node
4383      * instance that is required to be set after one or more other properties have been set.
4384      *
4385      * @see reconcile
4386      */
initnull4387     fun init(block: T.() -> Unit) {
4388         if (composer.inserting) composer.apply<Unit, T>(Unit) { block() }
4389     }
4390 
4391     /**
4392      * Reconcile the node to the current state.
4393      *
4394      * This is used when [set] and [update] are insufficient to update the state of the node based
4395      * on changes passed to the function calling [ComposeNode].
4396      *
4397      * Schedules [block] to execute. As this unconditionally schedules [block] to executed it might
4398      * be executed unnecessarily as no effort is taken to ensure it only executes when the values
4399      * [block] captures have changed. It is highly recommended that [set] and [update] be used
4400      * instead as they will only schedule their blocks to executed when the value passed to them has
4401      * changed.
4402      */
4403     @Suppress("MemberVisibilityCanBePrivate")
reconcilenull4404     fun reconcile(block: T.() -> Unit) {
4405         composer.apply<Unit, T>(Unit) { this.block() }
4406     }
4407 }
4408 
4409 @JvmInline
4410 value class SkippableUpdater<T> constructor(@PublishedApi internal val composer: Composer) {
updatenull4411     inline fun update(block: Updater<T>.() -> Unit) {
4412         composer.startReplaceableGroup(0x1e65194f)
4413         Updater<T>(composer).block()
4414         composer.endReplaceableGroup()
4415     }
4416 }
4417 
removeCurrentGroupnull4418 internal fun SlotWriter.removeCurrentGroup(rememberManager: RememberManager) {
4419     // Notify the lifecycle manager of any observers leaving the slot table
4420     // The notification order should ensure that listeners are notified of leaving
4421     // in opposite order that they are notified of entering.
4422 
4423     // To ensure this order, we call `enters` as a pre-order traversal
4424     // of the group tree, and then call `leaves` in the inverse order.
4425 
4426     forAllData(currentGroup) { slotIndex, slot ->
4427         // even that in the documentation we claim ComposeNodeLifecycleCallback should be only
4428         // implemented on the nodes we do not really enforce it here as doing so will be expensive.
4429         if (slot is ComposeNodeLifecycleCallback) {
4430             val endRelativeOrder = slotsSize - slotIndex
4431             rememberManager.releasing(slot, endRelativeOrder, -1, -1)
4432         }
4433         if (slot is RememberObserverHolder) {
4434             val endRelativeSlotIndex = slotsSize - slotIndex
4435             withAfterAnchorInfo(slot.after) { priority, endRelativeAfter ->
4436                 rememberManager.forgetting(slot, endRelativeSlotIndex, priority, endRelativeAfter)
4437             }
4438         }
4439         if (slot is RecomposeScopeImpl) {
4440             slot.release()
4441         }
4442     }
4443 
4444     removeGroup()
4445 }
4446 
withAfterAnchorInfonull4447 internal inline fun <R> SlotWriter.withAfterAnchorInfo(anchor: Anchor?, cb: (Int, Int) -> R) {
4448     var priority = -1
4449     var endRelativeAfter = -1
4450     if (anchor != null && anchor.valid) {
4451         priority = anchorIndex(anchor)
4452         endRelativeAfter = slotsSize - slotsEndAllIndex(priority)
4453     }
4454     cb(priority, endRelativeAfter)
4455 }
4456 
4457 internal val SlotWriter.isAfterFirstChild
4458     get() = currentGroup > parent + 1
4459 internal val SlotReader.isAfterFirstChild
4460     get() = currentGroup > parent + 1
4461 
deactivateCurrentGroupnull4462 internal fun SlotWriter.deactivateCurrentGroup(rememberManager: RememberManager) {
4463     // Notify the lifecycle manager of any observers leaving the slot table
4464     // The notification order should ensure that listeners are notified of leaving
4465     // in opposite order that they are notified of entering.
4466 
4467     // To ensure this order, we call `enters` as a pre-order traversal
4468     // of the group tree, and then call `leaves` in the inverse order.
4469     forAllData(currentGroup) { slotIndex, data ->
4470         when (data) {
4471             is ComposeNodeLifecycleCallback -> {
4472                 val endRelativeOrder = slotsSize - slotIndex
4473                 rememberManager.deactivating(data, endRelativeOrder, -1, -1)
4474             }
4475             is RememberObserverHolder -> {
4476                 val wrapped = data.wrapped
4477                 if (wrapped is ReusableRememberObserver) {
4478                     // do nothing, the value should be preserved on reuse
4479                 } else {
4480                     removeData(slotIndex, data)
4481                     val endRelativeOrder = slotsSize - slotIndex
4482                     withAfterAnchorInfo(data.after) { priority, endRelativeAfter ->
4483                         rememberManager.forgetting(
4484                             data,
4485                             endRelativeOrder,
4486                             priority,
4487                             endRelativeAfter
4488                         )
4489                     }
4490                 }
4491             }
4492             is RecomposeScopeImpl -> {
4493                 removeData(slotIndex, data)
4494                 data.release()
4495             }
4496         }
4497     }
4498 }
4499 
SlotWriternull4500 private fun SlotWriter.removeData(index: Int, data: Any?) {
4501     val result = clear(index)
4502     runtimeCheck(data === result) { "Slot table is out of sync (expected $data, got $result)" }
4503 }
4504 
multiMapnull4505 private fun <K : Any, V : Any> multiMap(initialCapacity: Int) =
4506     MultiValueMap<K, V>(MutableScatterMap(initialCapacity))
4507 
4508 private fun getKey(value: Any?, left: Any?, right: Any?): Any? =
4509     (value as? JoinedKey)?.let {
4510         if (it.left == left && it.right == right) value
4511         else getKey(it.left, left, right) ?: getKey(it.right, left, right)
4512     }
4513 
4514 // Invalidation helpers
MutableListnull4515 private fun MutableList<Invalidation>.findLocation(location: Int): Int {
4516     var low = 0
4517     var high = size - 1
4518 
4519     while (low <= high) {
4520         val mid = (low + high).ushr(1) // safe from overflows
4521         val midVal = get(mid)
4522         val cmp = midVal.location.compareTo(location)
4523 
4524         when {
4525             cmp < 0 -> low = mid + 1
4526             cmp > 0 -> high = mid - 1
4527             else -> return mid // key found
4528         }
4529     }
4530     return -(low + 1) // key not found
4531 }
4532 
MutableListnull4533 private fun MutableList<Invalidation>.findInsertLocation(location: Int): Int =
4534     findLocation(location).let { if (it < 0) -(it + 1) else it }
4535 
MutableListnull4536 private fun MutableList<Invalidation>.insertIfMissing(
4537     location: Int,
4538     scope: RecomposeScopeImpl,
4539     instance: Any?
4540 ) {
4541     val index = findLocation(location)
4542     if (index < 0) {
4543         add(
4544             -(index + 1),
4545             Invalidation(
4546                 scope,
4547                 location,
4548                 // Only derived state instance is important for composition
4549                 instance.takeIf { it is DerivedState<*> }
4550             )
4551         )
4552     } else {
4553         val invalidation = get(index)
4554         // Only derived state instance is important for composition
4555         if (instance is DerivedState<*>) {
4556             when (val oldInstance = invalidation.instances) {
4557                 null -> invalidation.instances = instance
4558                 is MutableScatterSet<*> -> {
4559                     @Suppress("UNCHECKED_CAST")
4560                     oldInstance as MutableScatterSet<Any?>
4561                     oldInstance.add(instance)
4562                 }
4563                 else -> {
4564                     invalidation.instances = mutableScatterSetOf(oldInstance, instance)
4565                 }
4566             }
4567         } else {
4568             invalidation.instances = null
4569         }
4570     }
4571 }
4572 
MutableListnull4573 private fun MutableList<Invalidation>.firstInRange(start: Int, end: Int): Invalidation? {
4574     val index = findInsertLocation(start)
4575     if (index < size) {
4576         val firstInvalidation = get(index)
4577         if (firstInvalidation.location < end) return firstInvalidation
4578     }
4579     return null
4580 }
4581 
MutableListnull4582 private fun MutableList<Invalidation>.removeLocation(location: Int): Invalidation? {
4583     val index = findLocation(location)
4584     return if (index >= 0) removeAt(index) else null
4585 }
4586 
MutableListnull4587 private fun MutableList<Invalidation>.removeRange(start: Int, end: Int) {
4588     val index = findInsertLocation(start)
4589     while (index < size) {
4590         val validation = get(index)
4591         if (validation.location < end) removeAt(index) else break
4592     }
4593 }
4594 
MutableListnull4595 private fun MutableList<Invalidation>.filterToRange(
4596     start: Int,
4597     end: Int
4598 ): MutableList<Invalidation> {
4599     val result = mutableListOf<Invalidation>()
4600     var index = findInsertLocation(start)
4601     while (index < size) {
4602         val invalidation = get(index)
4603         if (invalidation.location < end) result.add(invalidation) else break
4604         index++
4605     }
4606     return result
4607 }
4608 
asIntnull4609 private fun Boolean.asInt() = if (this) 1 else 0
4610 
4611 private fun Int.asBool() = this != 0
4612 
4613 private fun SlotTable.collectNodesFrom(anchor: Anchor): List<Any?> {
4614     val result = mutableListOf<Any?>()
4615     read { reader ->
4616         val index = anchorIndex(anchor)
4617         fun collectFromGroup(group: Int) {
4618             if (reader.isNode(group)) {
4619                 result.add(reader.node(group))
4620             } else {
4621                 var current = group + 1
4622                 val end = group + reader.groupSize(group)
4623                 while (current < end) {
4624                     collectFromGroup(current)
4625                     current += reader.groupSize(current)
4626                 }
4627             }
4628         }
4629         collectFromGroup(index)
4630     }
4631     return result
4632 }
4633 
distanceFromnull4634 private fun SlotReader.distanceFrom(index: Int, root: Int): Int {
4635     var count = 0
4636     var current = index
4637     while (current > 0 && current != root) {
4638         current = parent(current)
4639         count++
4640     }
4641     return count
4642 }
4643 
4644 // find the nearest common root
nearestCommonRootOfnull4645 private fun SlotReader.nearestCommonRootOf(a: Int, b: Int, common: Int): Int {
4646     // Early outs, to avoid calling distanceFrom in trivial cases
4647     if (a == b) return a // A group is the nearest common root of itself
4648     if (a == common || b == common) return common // If either is common then common is nearest
4649     if (parent(a) == b) return b // if b is a's parent b is the nearest common root
4650     if (parent(b) == a) return a // if a is b's parent a is the nearest common root
4651     if (parent(a) == parent(b)) return parent(a) // if a an b share a parent it is common
4652 
4653     // Find the nearest using distance from common
4654     var currentA = a
4655     var currentB = b
4656     val aDistance = distanceFrom(a, common)
4657     val bDistance = distanceFrom(b, common)
4658     repeat(aDistance - bDistance) { currentA = parent(currentA) }
4659     repeat(bDistance - aDistance) { currentB = parent(currentB) }
4660 
4661     // Both ca and cb are now the same distance from a known common root,
4662     // therefore, the first parent that is the same is the lowest common root.
4663     while (currentA != currentB) {
4664         currentA = parent(currentA)
4665         currentB = parent(currentB)
4666     }
4667 
4668     // ca == cb so it doesn't matter which is returned
4669     return currentA
4670 }
4671 
4672 private val KeyInfo.joinedKey: Any
4673     get() = if (objectKey != null) JoinedKey(key, objectKey) else key
4674 
4675 /*
4676  * Group types used with [Composer.start] to differentiate between different types of groups
4677  */
4678 @JvmInline
4679 private value class GroupKind private constructor(val value: Int) {
4680     inline val isNode
4681         get() = value != Group.value
4682 
4683     inline val isReusable
4684         get() = value != Node.value
4685 
4686     companion object {
4687         val Group = GroupKind(0)
4688         val Node = GroupKind(1)
4689         val ReusableNode = GroupKind(2)
4690     }
4691 }
4692 
4693 /*
4694  * Remember observer which is not removed during reuse/deactivate of the group.
4695  * It is used to preserve composition locals between group deactivation.
4696  */
4697 internal interface ReusableRememberObserver : RememberObserver
4698 
4699 internal class RememberObserverHolder(var wrapped: RememberObserver, var after: Anchor?)
4700 
4701 /*
4702  * Integer keys are arbitrary values in the biload range. The do not need to be unique as if
4703  * there is a chance they will collide with a compiler generated key they are paired with a
4704  * OpaqueKey to ensure they are unique.
4705  */
4706 
4707 // rootKey doesn't need a corresponding OpaqueKey as it never has sibling nodes and will always
4708 // a unique key.
4709 private const val rootKey = 100
4710 
4711 // An arbitrary key value for a node.
4712 private const val nodeKey = 125
4713 
4714 // An arbitrary key value that marks the default parameter group
4715 internal const val defaultsKey = -127
4716 
4717 @PublishedApi internal const val invocationKey = 200
4718 
4719 @PublishedApi internal val invocation: Any = OpaqueKey("provider")
4720 
4721 @PublishedApi internal const val providerKey = 201
4722 
4723 @PublishedApi internal val provider: Any = OpaqueKey("provider")
4724 
4725 @PublishedApi internal const val compositionLocalMapKey = 202
4726 
4727 @PublishedApi internal val compositionLocalMap: Any = OpaqueKey("compositionLocalMap")
4728 
4729 @PublishedApi internal const val providerValuesKey = 203
4730 
4731 @PublishedApi internal val providerValues: Any = OpaqueKey("providerValues")
4732 
4733 @PublishedApi internal const val providerMapsKey = 204
4734 
4735 @PublishedApi internal val providerMaps: Any = OpaqueKey("providers")
4736 
4737 @PublishedApi internal const val referenceKey = 206
4738 
4739 @PublishedApi internal val reference: Any = OpaqueKey("reference")
4740 
4741 @PublishedApi internal const val reuseKey = 207
4742 
4743 private const val invalidGroupLocation = -2
4744 
4745 internal class ComposeRuntimeError(override val message: String) : IllegalStateException()
4746 
4747 @Suppress("BanInlineOptIn")
4748 @OptIn(ExperimentalContracts::class)
runtimeChecknull4749 internal inline fun runtimeCheck(value: Boolean, lazyMessage: () -> String) {
4750     contract { returns() implies value }
4751     if (!value) {
4752         composeImmediateRuntimeError(lazyMessage())
4753     }
4754 }
4755 
4756 internal const val EnableDebugRuntimeChecks = false
4757 
4758 /**
4759  * A variation of [composeRuntimeError] that gets stripped from R8-minified builds. Use this for
4760  * more expensive checks or assertions along a hotpath that, if failed, would still lead to an
4761  * application crash that could be traced back to this assertion if removed from the final program
4762  * binary.
4763  */
debugRuntimeChecknull4764 internal inline fun debugRuntimeCheck(value: Boolean, lazyMessage: () -> String) {
4765     if (EnableDebugRuntimeChecks && !value) {
4766         composeImmediateRuntimeError(lazyMessage())
4767     }
4768 }
4769 
<lambda>null4770 internal inline fun debugRuntimeCheck(value: Boolean) = debugRuntimeCheck(value) { "Check failed" }
4771 
<lambda>null4772 internal inline fun runtimeCheck(value: Boolean) = runtimeCheck(value) { "Check failed" }
4773 
composeRuntimeErrornull4774 internal fun composeRuntimeError(message: String): Nothing {
4775     throw ComposeRuntimeError(
4776         "Compose Runtime internal error. Unexpected or incorrect use of the Compose " +
4777             "internal runtime API ($message). Please report to Google or use " +
4778             "https://goo.gle/compose-feedback"
4779     )
4780 }
4781 
4782 // Unit variant of composeRuntimeError() so the call site doesn't add 3 extra
4783 // instructions to throw a KotlinNothingValueException
composeImmediateRuntimeErrornull4784 internal fun composeImmediateRuntimeError(message: String) {
4785     throw ComposeRuntimeError(
4786         "Compose Runtime internal error. Unexpected or incorrect use of the Compose " +
4787             "internal runtime API ($message). Please report to Google or use " +
4788             "https://goo.gle/compose-feedback"
4789     )
4790 }
4791 
4792 private val InvalidationLocationAscending =
i1null4793     Comparator<Invalidation> { i1, i2 -> i1.location.compareTo(i2.location) }
4794 
4795 /**
4796  * Extract the state of movable content from the given writer. A new slot table is created and the
4797  * content is removed from [slots] (leaving a movable content group that, if composed over, will
4798  * create new content) and added to this new slot table. The invalidations that occur to recompose
4799  * scopes in the movable content state will be collected and forwarded to the new if the state is
4800  * used.
4801  */
extractMovableContentAtCurrentnull4802 internal fun extractMovableContentAtCurrent(
4803     composition: ControlledComposition,
4804     reference: MovableContentStateReference,
4805     slots: SlotWriter,
4806     applier: Applier<*>?,
4807 ): MovableContentState {
4808     val slotTable = SlotTable()
4809     if (slots.collectingSourceInformation) {
4810         slotTable.collectSourceInformation()
4811     }
4812     if (slots.collectingCalledInformation) {
4813         slotTable.collectCalledByInformation()
4814     }
4815 
4816     // If an applier is provided then we are extracting a state from the middle of an
4817     // already extracted state. If the group has nodes then the nodes need to be removed
4818     // from their parent so they can potentially be inserted into a destination.
4819     val currentGroup = slots.currentGroup
4820     if (applier != null && slots.nodeCount(currentGroup) > 0) {
4821         @Suppress("UNCHECKED_CAST")
4822         applier as Applier<Any?>
4823 
4824         // Find the parent node by going up until the first node group
4825         var parentNodeGroup = slots.parent
4826         while (parentNodeGroup > 0 && !slots.isNode(parentNodeGroup)) {
4827             parentNodeGroup = slots.parent(parentNodeGroup)
4828         }
4829 
4830         // If we don't find a node group the nodes in the state have already been removed
4831         // as they are the nodes that were removed when the state was removed from the original
4832         // table.
4833         if (parentNodeGroup >= 0 && slots.isNode(parentNodeGroup)) {
4834             val node = slots.node(parentNodeGroup)
4835             var currentChild = parentNodeGroup + 1
4836             val end = parentNodeGroup + slots.groupSize(parentNodeGroup)
4837 
4838             // Find the node index
4839             var nodeIndex = 0
4840             while (currentChild < end) {
4841                 val size = slots.groupSize(currentChild)
4842                 if (currentChild + size > currentGroup) {
4843                     break
4844                 }
4845                 nodeIndex += if (slots.isNode(currentChild)) 1 else slots.nodeCount(currentChild)
4846                 currentChild += size
4847             }
4848 
4849             // Remove the nodes
4850             val count = if (slots.isNode(currentGroup)) 1 else slots.nodeCount(currentGroup)
4851             applier.down(node)
4852             applier.remove(nodeIndex, count)
4853             applier.up()
4854         }
4855     }
4856 
4857     // Write a table that as if it was written by a calling invokeMovableContentLambda because this
4858     // might be removed from the composition before the new composition can be composed to receive
4859     // it. When the new composition receives the state it must recompose over the state by calling
4860     // invokeMovableContentLambda.
4861     val anchors =
4862         slotTable.write { writer ->
4863             writer.beginInsert()
4864 
4865             // This is the prefix created by invokeMovableContentLambda
4866             writer.startGroup(movableContentKey, reference.content)
4867             writer.markGroup()
4868             writer.update(reference.parameter)
4869 
4870             // Move the content into current location
4871             val anchors = slots.moveTo(reference.anchor, 1, writer)
4872 
4873             // skip the group that was just inserted.
4874             writer.skipGroup()
4875 
4876             // End the group that represents the call to invokeMovableContentLambda
4877             writer.endGroup()
4878 
4879             writer.endInsert()
4880 
4881             anchors
4882         }
4883 
4884     val state = MovableContentState(slotTable)
4885     if (RecomposeScopeImpl.hasAnchoredRecomposeScopes(slotTable, anchors)) {
4886         // If any recompose scopes are invalidated while the movable content is outside a
4887         // composition, ensure the reference is updated to contain the invalidation.
4888         val movableContentRecomposeScopeOwner =
4889             object : RecomposeScopeOwner {
4890                 override fun invalidate(
4891                     scope: RecomposeScopeImpl,
4892                     instance: Any?
4893                 ): InvalidationResult {
4894                     // Try sending this to the original owner first.
4895                     val result =
4896                         (composition as? RecomposeScopeOwner)?.invalidate(scope, instance)
4897                             ?: InvalidationResult.IGNORED
4898 
4899                     // If the original owner ignores this then we need to record it in the
4900                     // reference
4901                     if (result == InvalidationResult.IGNORED) {
4902                         reference.invalidations += scope to instance
4903                         return InvalidationResult.SCHEDULED
4904                     }
4905                     return result
4906                 }
4907 
4908                 // The only reason [recomposeScopeReleased] is called is when the recompose scope is
4909                 // removed from the table. First, this never happens for content that is moving, and
4910                 // 2) even if it did the only reason we tell the composer is to clear tracking
4911                 // tables that contain this information which is not relevant here.
4912                 override fun recomposeScopeReleased(scope: RecomposeScopeImpl) {
4913                     // Nothing to do
4914                 }
4915 
4916                 // [recordReadOf] this is also something that would happen only during active
4917                 // recomposition which doesn't happened to a slot table that is moving.
4918                 override fun recordReadOf(value: Any) {
4919                     // Nothing to do
4920                 }
4921             }
4922         slotTable.write { writer ->
4923             RecomposeScopeImpl.adoptAnchoredScopes(
4924                 slots = writer,
4925                 anchors = anchors,
4926                 newOwner = movableContentRecomposeScopeOwner
4927             )
4928         }
4929     }
4930     return state
4931 }
4932 
4933 internal class CompositionDataImpl(val composition: Composition) :
4934     CompositionData, CompositionInstance {
4935     private val slotTable
4936         get() = (composition as CompositionImpl).slotTable
4937 
4938     override val compositionGroups: Iterable<CompositionGroup>
4939         get() = slotTable.compositionGroups
4940 
4941     override val isEmpty: Boolean
4942         get() = slotTable.isEmpty
4943 
findnull4944     override fun find(identityToFind: Any): CompositionGroup? = slotTable.find(identityToFind)
4945 
4946     override fun hashCode(): Int = composition.hashCode() * 31
4947 
4948     override fun equals(other: Any?): Boolean =
4949         other is CompositionDataImpl && composition == other.composition
4950 
4951     override val parent: CompositionInstance?
4952         get() = composition.parent?.let { CompositionDataImpl(it) }
4953 
4954     override val data: CompositionData
4955         get() = this
4956 
findContextGroupnull4957     override fun findContextGroup(): CompositionGroup? {
4958         val parentSlotTable = composition.parent?.slotTable ?: return null
4959         val context = composition.context ?: return null
4960 
4961         return parentSlotTable.findSubcompositionContextGroup(context)?.let {
4962             parentSlotTable.compositionGroupOf(it)
4963         }
4964     }
4965 
4966     private val Composition.slotTable
4967         get() = (this as? CompositionImpl)?.slotTable
4968 
4969     private val Composition.context
4970         get() = (this as? CompositionImpl)?.parent
4971 
4972     private val Composition.parent
4973         get() = context?.composition
4974 }
4975