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