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(InternalComposeApi::class)
18 
19 package androidx.compose.runtime
20 
21 import androidx.collection.MutableScatterSet
22 import androidx.compose.runtime.changelist.ChangeList
23 import androidx.compose.runtime.collection.ScopeMap
24 import androidx.compose.runtime.collection.fastForEach
25 import androidx.compose.runtime.internal.AtomicReference
26 import androidx.compose.runtime.internal.RememberEventDispatcher
27 import androidx.compose.runtime.internal.trace
28 import androidx.compose.runtime.platform.makeSynchronizedObject
29 import androidx.compose.runtime.platform.synchronized
30 import androidx.compose.runtime.snapshots.ReaderKind
31 import androidx.compose.runtime.snapshots.StateObjectImpl
32 import androidx.compose.runtime.snapshots.fastAll
33 import androidx.compose.runtime.snapshots.fastAny
34 import androidx.compose.runtime.snapshots.fastForEach
35 import androidx.compose.runtime.tooling.CompositionObserver
36 import androidx.compose.runtime.tooling.CompositionObserverHandle
37 import kotlin.coroutines.CoroutineContext
38 import kotlin.coroutines.EmptyCoroutineContext
39 
40 /**
41  * A composition object is usually constructed for you, and returned from an API that is used to
42  * initially compose a UI. For instance, [setContent] returns a Composition.
43  *
44  * The [dispose] method should be used when you would like to dispose of the UI and the Composition.
45  */
46 interface Composition {
47     /**
48      * Returns true if any pending invalidations have been scheduled. An invalidation is schedule if
49      * [RecomposeScope.invalidate] has been called on any composition scopes create for the
50      * composition.
51      *
52      * Modifying [MutableState.value] of a value produced by [mutableStateOf] will automatically
53      * call [RecomposeScope.invalidate] for any scope that read [State.value] of the mutable state
54      * instance during composition.
55      *
56      * @see RecomposeScope
57      * @see mutableStateOf
58      */
59     val hasInvalidations: Boolean
60 
61     /** True if [dispose] has been called. */
62     val isDisposed: Boolean
63 
64     /**
65      * Clear the hierarchy that was created from the composition and release resources allocated for
66      * composition. After calling [dispose] the composition will no longer be recomposed and calling
67      * [setContent] will throw an [IllegalStateException]. Calling [dispose] is idempotent, all
68      * calls after the first are a no-op.
69      */
70     fun dispose()
71 
72     /**
73      * Update the composition with the content described by the [content] composable. After this has
74      * been called the changes to produce the initial composition has been calculated and applied to
75      * the composition.
76      *
77      * Will throw an [IllegalStateException] if the composition has been disposed.
78      *
79      * @param content A composable function that describes the content of the composition.
80      * @exception IllegalStateException thrown in the composition has been [dispose]d.
81      */
82     fun setContent(content: @Composable () -> Unit)
83 }
84 
85 /**
86  * A [ReusableComposition] is a [Composition] that can be reused for different composable content.
87  *
88  * This interface is used by components that have to synchronize lifecycle of parent and child
89  * compositions and efficiently reuse the nodes emitted by [ReusableComposeNode].
90  */
91 sealed interface ReusableComposition : Composition {
92     /**
93      * Update the composition with the content described by the [content] composable. After this has
94      * been called the changes to produce the initial composition has been calculated and applied to
95      * the composition.
96      *
97      * This method forces this composition into "reusing" state before setting content. In reusing
98      * state, all remembered content is discarded, and nodes emitted by [ReusableComposeNode] are
99      * re-used for the new content. The nodes are only reused if the group structure containing the
100      * node matches new content.
101      *
102      * Will throw an [IllegalStateException] if the composition has been disposed.
103      *
104      * @param content A composable function that describes the content of the composition.
105      * @exception IllegalStateException thrown in the composition has been [dispose]d.
106      */
setContentWithReusenull107     fun setContentWithReuse(content: @Composable () -> Unit)
108 
109     /**
110      * Deactivate all observation scopes in composition and remove all remembered slots while
111      * preserving nodes in place. The composition can be re-activated by calling [setContent] with a
112      * new content.
113      */
114     fun deactivate()
115 }
116 
117 /**
118  * A key to locate a service using the [CompositionServices] interface optionally implemented by
119  * implementations of [Composition].
120  */
121 interface CompositionServiceKey<T>
122 
123 /**
124  * Allows finding composition services from the runtime. The services requested through this
125  * interface are internal to the runtime and cannot be provided directly.
126  *
127  * The [CompositionServices] interface is used by the runtime to provide optional and/or
128  * experimental services through public extension functions.
129  *
130  * Implementation of [Composition] that delegate to another [Composition] instance should implement
131  * this interface and delegate calls to [getCompositionService] to the original [Composition].
132  */
133 interface CompositionServices {
134     /** Find a service of class [T]. */
135     fun <T> getCompositionService(key: CompositionServiceKey<T>): T?
136 }
137 
138 /**
139  * Find a Composition service.
140  *
141  * Find services that implement optional and/or experimental services provided through public or
142  * experimental extension functions.
143  */
getCompositionServicenull144 internal fun <T> Composition.getCompositionService(key: CompositionServiceKey<T>) =
145     (this as? CompositionServices)?.getCompositionService(key)
146 
147 /**
148  * A controlled composition is a [Composition] that can be directly controlled by the caller.
149  *
150  * This is the interface used by the [Recomposer] to control how and when a composition is
151  * invalidated and subsequently recomposed.
152  *
153  * Normally a composition is controlled by the [Recomposer] but it is often more efficient for tests
154  * to take direct control over a composition by calling [ControlledComposition] instead of
155  * [Composition].
156  *
157  * @see ControlledComposition
158  */
159 sealed interface ControlledComposition : Composition {
160     /**
161      * True if the composition is actively compositing such as when actively in a call to
162      * [composeContent] or [recompose].
163      */
164     val isComposing: Boolean
165 
166     /**
167      * True after [composeContent] or [recompose] has been called and [applyChanges] is expected as
168      * the next call. An exception will be throw in [composeContent] or [recompose] is called while
169      * there are pending from the previous composition pending to be applied.
170      */
171     val hasPendingChanges: Boolean
172 
173     /**
174      * Called by the parent composition in response to calling [setContent]. After this method the
175      * changes should be calculated but not yet applied. DO NOT call this method directly if this is
176      * interface is controlled by a [Recomposer], either use [setContent] or
177      * [Recomposer.composeInitial] instead.
178      *
179      * @param content A composable function that describes the tree.
180      */
181     fun composeContent(content: @Composable () -> Unit)
182 
183     /**
184      * Record the values that were modified after the last call to [recompose] or from the initial
185      * call to [composeContent]. This should be called before [recompose] is called to record which
186      * parts of the composition need to be recomposed.
187      *
188      * @param values the set of values that have changed since the last composition.
189      */
190     fun recordModificationsOf(values: Set<Any>)
191 
192     /**
193      * Returns true if any of the object instances in [values] is observed by this composition. This
194      * allows detecting if values changed by a previous composition will potentially affect this
195      * composition.
196      */
197     fun observesAnyOf(values: Set<Any>): Boolean
198 
199     /**
200      * Execute [block] with [isComposing] set temporarily to `true`. This allows treating
201      * invalidations reported during [prepareCompose] as if they happened while composing to avoid
202      * double invalidations when propagating changes from a parent composition while before
203      * composing the child composition.
204      */
205     fun prepareCompose(block: () -> Unit)
206 
207     /**
208      * Record that [value] has been read. This is used primarily by the [Recomposer] to inform the
209      * composer when the a [MutableState] instance has been read implying it should be observed for
210      * changes.
211      *
212      * @param value the instance from which a property was read
213      */
214     fun recordReadOf(value: Any)
215 
216     /**
217      * Record that [value] has been modified. This is used primarily by the [Recomposer] to inform
218      * the composer when the a [MutableState] instance been change by a composable function.
219      */
220     fun recordWriteOf(value: Any)
221 
222     /**
223      * Recompose the composition to calculate any changes necessary to the composition state and the
224      * tree maintained by the applier. No changes have been made yet. Changes calculated will be
225      * applied when [applyChanges] is called.
226      *
227      * @return returns `true` if any changes are pending and [applyChanges] should be called.
228      */
229     fun recompose(): Boolean
230 
231     /**
232      * Insert the given list of movable content with their paired state in potentially a different
233      * composition. If the second part of the pair is null then the movable content should be
234      * inserted as new. If second part of the pair has a value then the state should be moved into
235      * the referenced location and then recomposed there.
236      */
237     @InternalComposeApi
238     fun insertMovableContent(
239         references: List<Pair<MovableContentStateReference, MovableContentStateReference?>>
240     )
241 
242     /** Dispose the value state that is no longer needed. */
243     @InternalComposeApi fun disposeUnusedMovableContent(state: MovableContentState)
244 
245     /**
246      * Apply the changes calculated during [setContent] or [recompose]. If an exception is thrown by
247      * [applyChanges] the composition is irreparably damaged and should be [dispose]d.
248      */
249     fun applyChanges()
250 
251     /**
252      * Apply change that must occur after the main bulk of changes have been applied. Late changes
253      * are the result of inserting movable content and it must be performed after [applyChanges]
254      * because, for content that have moved must be inserted only after it has been removed from the
255      * previous location. All deletes must be executed before inserts. To ensure this, all deletes
256      * are performed in [applyChanges] and all inserts are performed in [applyLateChanges].
257      */
258     fun applyLateChanges()
259 
260     /**
261      * Call when all changes, including late changes, have been applied. This signals to the
262      * composition that any transitory composition state can now be discarded. This is advisory only
263      * and a controlled composition will execute correctly when this is not called.
264      */
265     fun changesApplied()
266 
267     /**
268      * Abandon current changes and reset composition state. Called when recomposer cannot proceed
269      * with current recomposition loop and needs to reset composition.
270      */
271     fun abandonChanges()
272 
273     /**
274      * Invalidate all invalidation scopes. This is called, for example, by [Recomposer] when the
275      * Recomposer becomes active after a previous period of inactivity, potentially missing more
276      * granular invalidations.
277      */
278     fun invalidateAll()
279 
280     /**
281      * Throws an exception if the internal state of the composer has been corrupted and is no longer
282      * consistent. Used in testing the composer itself.
283      */
284     @InternalComposeApi fun verifyConsistent()
285 
286     /**
287      * Temporarily delegate all invalidations sent to this composition to the [to] composition. This
288      * is used when movable content moves between compositions. The recompose scopes are not
289      * redirected until after the move occurs during [applyChanges] and [applyLateChanges]. This is
290      * used to compose as if the scopes have already been changed.
291      */
292     fun <R> delegateInvalidations(to: ControlledComposition?, groupIndex: Int, block: () -> R): R
293 
294     /**
295      * Sets the [shouldPause] callback allowing a composition to be pausable if it is not `null`.
296      * Setting the callback to `null` disables pausing.
297      *
298      * @return the previous value of the callback which will be restored once the callback is no
299      *   longer needed.
300      * @see PausableComposition
301      */
302     @Suppress("ExecutorRegistration")
303     fun getAndSetShouldPauseCallback(shouldPause: ShouldPauseCallback?): ShouldPauseCallback?
304 }
305 
306 /** Utility function to set and restore a should pause callback. */
pausablenull307 internal inline fun <R> ControlledComposition.pausable(
308     shouldPause: ShouldPauseCallback,
309     block: () -> R
310 ): R {
311     val previous = getAndSetShouldPauseCallback(shouldPause)
312     return try {
313         block()
314     } finally {
315         getAndSetShouldPauseCallback(previous)
316     }
317 }
318 
319 /**
320  * The [CoroutineContext] that should be used to perform concurrent recompositions of this
321  * [ControlledComposition] when used in an environment supporting concurrent composition.
322  *
323  * See [Recomposer.runRecomposeConcurrentlyAndApplyChanges] as an example of configuring such an
324  * environment.
325  */
326 // Implementation note: as/if this method graduates it should become a real method of
327 // ControlledComposition with a default implementation.
328 @ExperimentalComposeApi
329 val ControlledComposition.recomposeCoroutineContext: CoroutineContext
330     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
331     @ExperimentalComposeApi
332     get() = (this as? CompositionImpl)?.recomposeContext ?: EmptyCoroutineContext
333 
334 /**
335  * This method is the way to initiate a composition. [parent] [CompositionContext] can be
336  * * provided to make the composition behave as a sub-composition of the parent. If composition does
337  * * not have a parent, [Recomposer] instance should be provided.
338  *
339  * It is important to call [Composition.dispose] when composition is no longer needed in order to
340  * release resources.
341  *
342  * @sample androidx.compose.runtime.samples.CustomTreeComposition
343  * @param applier The [Applier] instance to be used in the composition.
344  * @param parent The parent [CompositionContext].
345  * @see Applier
346  * @see Composition
347  * @see Recomposer
348  */
Compositionnull349 fun Composition(applier: Applier<*>, parent: CompositionContext): Composition =
350     CompositionImpl(parent, applier)
351 
352 /**
353  * This method is the way to initiate a reusable composition. [parent] [CompositionContext] can be
354  * provided to make the composition behave as a sub-composition of the parent. If composition does
355  * not have a parent, [Recomposer] instance should be provided.
356  *
357  * It is important to call [Composition.dispose] when composition is no longer needed in order to
358  * release resources.
359  *
360  * @param applier The [Applier] instance to be used in the composition.
361  * @param parent The parent [CompositionContext].
362  * @see Applier
363  * @see ReusableComposition
364  * @see rememberCompositionContext
365  */
366 fun ReusableComposition(applier: Applier<*>, parent: CompositionContext): ReusableComposition =
367     CompositionImpl(parent, applier)
368 
369 /**
370  * This method is a way to initiate a composition. Optionally, a [parent] [CompositionContext] can
371  * be provided to make the composition behave as a sub-composition of the parent or a [Recomposer]
372  * can be provided.
373  *
374  * A controlled composition allows direct control of the composition instead of it being controlled
375  * by the [Recomposer] passed ot the root composition.
376  *
377  * It is important to call [Composition.dispose] this composer is no longer needed in order to
378  * release resources.
379  *
380  * @sample androidx.compose.runtime.samples.CustomTreeComposition
381  * @param applier The [Applier] instance to be used in the composition.
382  * @param parent The parent [CompositionContext].
383  * @see Applier
384  * @see Composition
385  * @see Recomposer
386  */
387 @TestOnly
388 fun ControlledComposition(applier: Applier<*>, parent: CompositionContext): ControlledComposition =
389     CompositionImpl(parent, applier)
390 
391 /**
392  * Create a [Composition] using [applier] to manage the composition, as a child of [parent].
393  *
394  * When used in a configuration that supports concurrent recomposition, hint to the environment that
395  * [recomposeCoroutineContext] should be used to perform recomposition. Recompositions will be
396  * launched into the
397  */
398 @ExperimentalComposeApi
399 fun Composition(
400     applier: Applier<*>,
401     parent: CompositionContext,
402     recomposeCoroutineContext: CoroutineContext
403 ): Composition = CompositionImpl(parent, applier, recomposeContext = recomposeCoroutineContext)
404 
405 @TestOnly
406 @ExperimentalComposeApi
407 fun ControlledComposition(
408     applier: Applier<*>,
409     parent: CompositionContext,
410     recomposeCoroutineContext: CoroutineContext
411 ): ControlledComposition =
412     CompositionImpl(parent, applier, recomposeContext = recomposeCoroutineContext)
413 
414 private val PendingApplyNoModifications = Any()
415 
416 internal val CompositionImplServiceKey = object : CompositionServiceKey<CompositionImpl> {}
417 
418 /**
419  * The implementation of the [Composition] interface.
420  *
421  * @param parent An optional reference to the parent composition.
422  * @param applier The applier to use to manage the tree built by the composer.
423  * @param recomposeContext The coroutine context to use to recompose this composition. If left
424  *   `null` the controlling recomposer's default context is used.
425  */
426 @OptIn(ExperimentalComposeRuntimeApi::class)
427 internal class CompositionImpl(
428     /**
429      * The parent composition from [rememberCompositionContext], for sub-compositions, or the an
430      * instance of [Recomposer] for root compositions.
431      */
432     @get:TestOnly val parent: CompositionContext,
433 
434     /** The applier to use to update the tree managed by the composition. */
435     private val applier: Applier<*>,
436     recomposeContext: CoroutineContext? = null
437 ) :
438     ControlledComposition,
439     ReusableComposition,
440     RecomposeScopeOwner,
441     CompositionServices,
442     PausableComposition {
443 
444     /**
445      * `null` if a composition isn't pending to apply. `Set<Any>` or `Array<Set<Any>>` if there are
446      * modifications to record [PendingApplyNoModifications] if a composition is pending to apply,
447      * no modifications. any set contents will be sent to [recordModificationsOf] after applying
448      * changes before releasing [lock]
449      */
450     private val pendingModifications = AtomicReference<Any?>(null)
451 
452     // Held when making changes to self or composer
453     private val lock = makeSynchronizedObject()
454 
455     /**
456      * A set of remember observers that were potentially abandoned between [composeContent] or
457      * [recompose] and [applyChanges]. When inserting new content any newly remembered
458      * [RememberObserver]s are added to this set and then removed as [RememberObserver.onRemembered]
459      * is dispatched. If any are left in this when exiting [applyChanges] they have been abandoned
460      * and are sent an [RememberObserver.onAbandoned] notification.
461      */
462     @Suppress("AsCollectionCall") // Requires iterator API when dispatching abandons
463     private val abandonSet = MutableScatterSet<RememberObserver>().asMutableSet()
464 
465     /** The slot table is used to store the composition information required for recomposition. */
466     @Suppress("MemberVisibilityCanBePrivate") // published as internal
467     internal val slotTable =
<lambda>null468         SlotTable().also {
469             if (parent.collectingCallByInformation) it.collectCalledByInformation()
470             if (parent.collectingSourceInformation) it.collectSourceInformation()
471         }
472 
473     /**
474      * A map of observable objects to the [RecomposeScope]s that observe the object. If the key
475      * object is modified the associated scopes should be invalidated.
476      */
477     private val observations = ScopeMap<Any, RecomposeScopeImpl>()
478 
479     /** Used for testing. Returns the objects that are observed */
480     internal val observedObjects
481         @TestOnly @Suppress("AsCollectionCall") get() = observations.map.asMap().keys
482 
483     /**
484      * A set of scopes that were invalidated by a call from [recordModificationsOf]. This set is
485      * only used in [addPendingInvalidationsLocked], and is reused between invocations.
486      */
487     private val invalidatedScopes = MutableScatterSet<RecomposeScopeImpl>()
488 
489     /**
490      * A set of scopes that were invalidated conditionally (that is they were invalidated by a
491      * [derivedStateOf] object) by a call from [recordModificationsOf]. They need to be held in the
492      * [observations] map until invalidations are drained for composition as a later call to
493      * [recordModificationsOf] might later cause them to be unconditionally invalidated.
494      */
495     private val conditionallyInvalidatedScopes = MutableScatterSet<RecomposeScopeImpl>()
496 
497     /** A map of object read during derived states to the corresponding derived state. */
498     private val derivedStates = ScopeMap<Any, DerivedState<*>>()
499 
500     /** Used for testing. Returns dependencies of derived states that are currently observed. */
501     internal val derivedStateDependencies
502         @TestOnly @Suppress("AsCollectionCall") get() = derivedStates.map.asMap().keys
503 
504     /** Used for testing. Returns the conditional scopes being tracked by the composer */
505     internal val conditionalScopes: List<RecomposeScopeImpl>
506         @TestOnly
507         @Suppress("AsCollectionCall")
508         get() = conditionallyInvalidatedScopes.asSet().toList()
509 
510     /**
511      * A list of changes calculated by [Composer] to be applied to the [Applier] and the [SlotTable]
512      * to reflect the result of composition. This is a list of lambdas that need to be invoked in
513      * order to produce the desired effects.
514      */
515     private val changes = ChangeList()
516 
517     /**
518      * A list of changes calculated by [Composer] to be applied after all other compositions have
519      * had [applyChanges] called. These changes move [MovableContent] state from one composition to
520      * another and must be applied after [applyChanges] because [applyChanges] copies and removes
521      * the state out of the previous composition so it can be inserted into the new location. As
522      * inserts might be earlier in the composition than the position it is deleted, this move must
523      * be done in two phases.
524      */
525     private val lateChanges = ChangeList()
526 
527     /**
528      * When an observable object is modified during composition any recompose scopes that are
529      * observing that object are invalidated immediately. Since they have already been processed
530      * there is no need to process them again, so this set maintains a set of the recompose scopes
531      * that were already dismissed by composition and should be ignored in the next call to
532      * [recordModificationsOf].
533      */
534     private val observationsProcessed = ScopeMap<Any, RecomposeScopeImpl>()
535 
536     /**
537      * A map of the invalid [RecomposeScope]s. If this map is non-empty the current state of the
538      * composition does not reflect the current state of the objects it observes and should be
539      * recomposed by calling [recompose]. Tbe value is a map of values that invalidated the scope.
540      * The scope is checked with these instances to ensure the value has changed. This is used to
541      * only invalidate the scope if a [derivedStateOf] object changes.
542      */
543     private var invalidations = ScopeMap<RecomposeScopeImpl, Any>()
544 
545     /**
546      * As [RecomposeScope]s are removed the corresponding entries in the observations set must be
547      * removed as well. This process is expensive so should only be done if it is certain the
548      * [observations] set contains [RecomposeScope] that is no longer needed. [pendingInvalidScopes]
549      * is set to true whenever a [RecomposeScope] is removed from the [slotTable].
550      */
551     @Suppress("MemberVisibilityCanBePrivate") // published as internal
552     internal var pendingInvalidScopes = false
553 
554     /**
555      * If the [shouldPause] callback is set the composition is pausable and should pause whenever
556      * the [shouldPause] callback returns `true`.
557      */
558     private var shouldPause: ShouldPauseCallback? = null
559 
560     private var pendingPausedComposition: PausedCompositionImpl? = null
561 
562     private var invalidationDelegate: CompositionImpl? = null
563 
564     private var invalidationDelegateGroup: Int = 0
565 
566     internal val observerHolder = CompositionObserverHolder()
567 
568     private val rememberManager = RememberEventDispatcher()
569 
570     /** The [Composer] to use to create and update the tree managed by this composition. */
571     internal val composer: ComposerImpl =
572         ComposerImpl(
573                 applier = applier,
574                 parentContext = parent,
575                 slotTable = slotTable,
576                 abandonSet = abandonSet,
577                 changes = changes,
578                 lateChanges = lateChanges,
579                 composition = this
580             )
<lambda>null581             .also { parent.registerComposer(it) }
582 
583     /** The [CoroutineContext] override, if there is one, for this composition. */
584     private val _recomposeContext: CoroutineContext? = recomposeContext
585 
586     /** the [CoroutineContext] to use to [recompose] this composition. */
587     val recomposeContext: CoroutineContext
588         get() = _recomposeContext ?: parent.recomposeCoroutineContext
589 
590     /** Return true if this is a root (non-sub-) composition. */
591     val isRoot: Boolean = parent is Recomposer
592 
593     /** True if [dispose] has been called. */
594     private var disposed = false
595 
596     /** True if a sub-composition of this composition is current composing. */
597     private val areChildrenComposing
598         get() = composer.areChildrenComposing
599 
600     /**
601      * The [Composable] function used to define the tree managed by this composition. This is set by
602      * [setContent].
603      */
604     var composable: @Composable () -> Unit = {}
605 
606     override val isComposing: Boolean
607         get() = composer.isComposing
608 
609     override val isDisposed: Boolean
610         get() = disposed
611 
612     override val hasPendingChanges: Boolean
<lambda>null613         get() = synchronized(lock) { composer.hasPendingChanges }
614 
615     override fun setContent(content: @Composable () -> Unit) {
<lambda>null616         checkPrecondition(pendingPausedComposition == null) {
617             "A pausable composition is in progress"
618         }
619         composeInitial(content)
620     }
621 
622     override fun setContentWithReuse(content: @Composable () -> Unit) {
<lambda>null623         checkPrecondition(pendingPausedComposition == null) {
624             "A pausable composition is in progress"
625         }
626         composer.startReuseFromRoot()
627 
628         composeInitial(content)
629 
630         composer.endReuseFromRoot()
631     }
632 
633     override fun setPausableContent(content: @Composable () -> Unit): PausedComposition {
<lambda>null634         checkPrecondition(!disposed) { "The composition is disposed" }
<lambda>null635         checkPrecondition(pendingPausedComposition == null) {
636             "A pausable composition is in progress"
637         }
638         val pausedComposition =
639             PausedCompositionImpl(
640                 composition = this,
641                 context = parent,
642                 composer = composer,
643                 content = content,
644                 reusable = false,
645                 abandonSet = abandonSet,
646                 applier = applier,
647                 lock = lock,
648             )
649         pendingPausedComposition = pausedComposition
650         return pausedComposition
651     }
652 
653     override fun setPausableContentWithReuse(content: @Composable () -> Unit): PausedComposition {
<lambda>null654         checkPrecondition(!disposed) { "The composition is disposed" }
<lambda>null655         checkPrecondition(pendingPausedComposition == null) {
656             "A pausable composition is in progress"
657         }
658         val pausedComposition =
659             PausedCompositionImpl(
660                 composition = this,
661                 context = parent,
662                 composer = composer,
663                 content = content,
664                 reusable = true,
665                 abandonSet = abandonSet,
666                 applier = applier,
667                 lock = lock,
668             )
669         pendingPausedComposition = pausedComposition
670         return pausedComposition
671     }
672 
pausedCompositionFinishednull673     internal fun pausedCompositionFinished() {
674         pendingPausedComposition = null
675     }
676 
composeInitialnull677     private fun composeInitial(content: @Composable () -> Unit) {
678         checkPrecondition(!disposed) { "The composition is disposed" }
679         this.composable = content
680         parent.composeInitial(this, composable)
681     }
682 
683     @OptIn(ExperimentalComposeRuntimeApi::class)
observenull684     internal fun observe(observer: CompositionObserver): CompositionObserverHandle {
685         synchronized(lock) {
686             observerHolder.observer = observer
687             observerHolder.root = true
688         }
689         return object : CompositionObserverHandle {
690             override fun dispose() {
691                 synchronized(lock) {
692                     if (observerHolder.observer == observer) {
693                         observerHolder.observer = null
694                         observerHolder.root = false
695                     }
696                 }
697             }
698         }
699     }
700 
invalidateGroupsWithKeynull701     fun invalidateGroupsWithKey(key: Int) {
702         val scopesToInvalidate = synchronized(lock) { slotTable.invalidateGroupsWithKey(key) }
703         // Calls to invalidate must be performed without the lock as the they may cause the
704         // recomposer to take its lock to respond to the invalidation and that takes the locks
705         // in the opposite order of composition so if composition begins in another thread taking
706         // the recomposer lock with the composer lock held will deadlock.
707         val forceComposition =
708             scopesToInvalidate == null ||
709                 scopesToInvalidate.fastAny {
710                     it.invalidateForResult(null) == InvalidationResult.IGNORED
711                 }
712         if (forceComposition && composer.forceRecomposeScopes()) {
713             parent.invalidate(this)
714         }
715     }
716 
717     @Suppress("UNCHECKED_CAST")
drainPendingModificationsForCompositionLockednull718     private fun drainPendingModificationsForCompositionLocked() {
719         // Recording modifications may race for lock. If there are pending modifications
720         // and we won the lock race, drain them before composing.
721         when (val toRecord = pendingModifications.getAndSet(PendingApplyNoModifications)) {
722             null -> {
723                 // Do nothing, just start composing.
724             }
725             PendingApplyNoModifications -> {
726                 composeRuntimeError("pending composition has not been applied")
727             }
728             is Set<*> -> {
729                 addPendingInvalidationsLocked(toRecord as Set<Any>, forgetConditionalScopes = true)
730             }
731             is Array<*> ->
732                 for (changed in toRecord as Array<Set<Any>>) {
733                     addPendingInvalidationsLocked(changed, forgetConditionalScopes = true)
734                 }
735             else -> composeRuntimeError("corrupt pendingModifications drain: $pendingModifications")
736         }
737     }
738 
739     @Suppress("UNCHECKED_CAST")
drainPendingModificationsLockednull740     private fun drainPendingModificationsLocked() {
741         when (val toRecord = pendingModifications.getAndSet(null)) {
742             PendingApplyNoModifications -> {
743                 // No work to do
744             }
745             is Set<*> -> {
746                 addPendingInvalidationsLocked(toRecord as Set<Any>, forgetConditionalScopes = false)
747             }
748             is Array<*> ->
749                 for (changed in toRecord as Array<Set<Any>>) {
750                     addPendingInvalidationsLocked(changed, forgetConditionalScopes = false)
751                 }
752             null ->
753                 composeRuntimeError(
754                     "calling recordModificationsOf and applyChanges concurrently is not supported"
755                 )
756             else -> composeRuntimeError("corrupt pendingModifications drain: $pendingModifications")
757         }
758     }
759 
760     // Drain the modification out of the normal recordModificationsOf(), composition() cycle.
761     // This avoids the checks to make sure the two calls are called in order.
762     @Suppress("UNCHECKED_CAST")
drainPendingModificationsOutOfBandLockednull763     private fun drainPendingModificationsOutOfBandLocked() {
764         when (val toRecord = pendingModifications.getAndSet(emptySet<Any>())) {
765             PendingApplyNoModifications,
766             null -> {
767                 // No work to do
768             }
769             is Set<*> -> {
770                 addPendingInvalidationsLocked(toRecord as Set<Any>, forgetConditionalScopes = false)
771             }
772             is Array<*> ->
773                 for (changed in toRecord as Array<Set<Any>>) {
774                     addPendingInvalidationsLocked(changed, forgetConditionalScopes = false)
775                 }
776             else -> composeRuntimeError("corrupt pendingModifications drain: $pendingModifications")
777         }
778     }
779 
780     override fun composeContent(content: @Composable () -> Unit) {
781         // TODO: This should raise a signal to any currently running recompose calls
782         //   to halt and return
<lambda>null783         guardChanges {
784             synchronized(lock) {
785                 drainPendingModificationsForCompositionLocked()
786                 guardInvalidationsLocked { invalidations ->
787                     val observer = observer()
788                     if (observer != null) {
789                         @Suppress("UNCHECKED_CAST")
790                         observer.onBeginComposition(
791                             this,
792                             invalidations.asMap() as Map<RecomposeScope, Set<Any>>
793                         )
794                     }
795                     composer.composeContent(invalidations, content, shouldPause)
796                     observer?.onEndComposition(this)
797                 }
798             }
799         }
800     }
801 
updateMovingInvalidationsnull802     internal fun updateMovingInvalidations() {
803         synchronized(lock) {
804             drainPendingModificationsOutOfBandLocked()
805             guardInvalidationsLocked { invalidations ->
806                 composer.updateComposerInvalidations(invalidations)
807             }
808         }
809     }
810 
disposenull811     override fun dispose() {
812         synchronized(lock) {
813             checkPrecondition(!composer.isComposing) {
814                 "Composition is disposed while composing. If dispose is triggered by a call in " +
815                     "@Composable function, consider wrapping it with SideEffect block."
816             }
817             if (!disposed) {
818                 disposed = true
819                 composable = {}
820 
821                 // Changes are deferred if the composition contains movable content that needs
822                 // to be released. NOTE: Applying these changes leaves the slot table in
823                 // potentially invalid state. The routine use to produce this change list reuses
824                 // code that extracts movable content from groups that are being deleted. This code
825                 // does not bother to correctly maintain the node counts of a group nested groups
826                 // that are going to be removed anyway so the node counts of the groups affected
827                 // are might be incorrect after the changes have been applied.
828                 val deferredChanges = composer.deferredChanges
829                 if (deferredChanges != null) {
830                     applyChangesInLocked(deferredChanges)
831                 }
832 
833                 // Dispatch all the `onForgotten` events for object that are no longer part of a
834                 // composition because this composition is being discarded. It is important that
835                 // this is done after applying deferred changes above to avoid sending `
836                 // onForgotten` notification to objects that are still part of movable content that
837                 // will be moved to a new location.
838                 val nonEmptySlotTable = slotTable.groupsSize > 0
839                 if (nonEmptySlotTable || abandonSet.isNotEmpty()) {
840                     rememberManager.use(abandonSet, composer.errorContext) {
841                         if (nonEmptySlotTable) {
842                             applier.onBeginChanges()
843                             slotTable.write { writer -> writer.removeCurrentGroup(rememberManager) }
844                             applier.clear()
845                             applier.onEndChanges()
846                             dispatchRememberObservers()
847                         }
848                         dispatchAbandons()
849                     }
850                 }
851                 composer.dispose()
852             }
853         }
854         parent.unregisterComposition(this)
855     }
856 
857     override val hasInvalidations
<lambda>null858         get() = synchronized(lock) { invalidations.size > 0 }
859 
860     /**
861      * To bootstrap multithreading handling, recording modifications is now deferred between
862      * recomposition with changes to apply and the application of those changes.
863      * [pendingModifications] will contain a queue of changes to apply once all current changes have
864      * been successfully processed. Draining this queue is the responsibility of [recompose] if it
865      * would return `false` (changes do not need to be applied) or [applyChanges].
866      */
867     @Suppress("UNCHECKED_CAST")
recordModificationsOfnull868     override fun recordModificationsOf(values: Set<Any>) {
869         while (true) {
870             val old = pendingModifications.get()
871             val new: Any =
872                 when (old) {
873                     null,
874                     PendingApplyNoModifications -> values
875                     is Set<*> -> arrayOf(old, values)
876                     is Array<*> -> (old as Array<Set<Any>>) + values
877                     else -> error("corrupt pendingModifications: $pendingModifications")
878                 }
879             if (pendingModifications.compareAndSet(old, new)) {
880                 if (old == null) {
881                     synchronized(lock) { drainPendingModificationsLocked() }
882                 }
883                 break
884             }
885         }
886     }
887 
observesAnyOfnull888     override fun observesAnyOf(values: Set<Any>): Boolean {
889         values.fastForEach { value ->
890             if (value in observations || value in derivedStates) return true
891         }
892         return false
893     }
894 
prepareComposenull895     override fun prepareCompose(block: () -> Unit) = composer.prepareCompose(block)
896 
897     private fun addPendingInvalidationsLocked(value: Any, forgetConditionalScopes: Boolean) {
898         observations.forEachScopeOf(value) { scope ->
899             if (
900                 !observationsProcessed.remove(value, scope) &&
901                     scope.invalidateForResult(value) != InvalidationResult.IGNORED
902             ) {
903                 if (scope.isConditional && !forgetConditionalScopes) {
904                     conditionallyInvalidatedScopes.add(scope)
905                 } else {
906                     invalidatedScopes.add(scope)
907                 }
908             }
909         }
910     }
911 
addPendingInvalidationsLockednull912     private fun addPendingInvalidationsLocked(values: Set<Any>, forgetConditionalScopes: Boolean) {
913         values.fastForEach { value ->
914             if (value is RecomposeScopeImpl) {
915                 value.invalidateForResult(null)
916             } else {
917                 addPendingInvalidationsLocked(value, forgetConditionalScopes)
918                 derivedStates.forEachScopeOf(value) {
919                     addPendingInvalidationsLocked(it, forgetConditionalScopes)
920                 }
921             }
922         }
923 
924         val conditionallyInvalidatedScopes = conditionallyInvalidatedScopes
925         val invalidatedScopes = invalidatedScopes
926         if (forgetConditionalScopes && conditionallyInvalidatedScopes.isNotEmpty()) {
927             observations.removeScopeIf { scope ->
928                 scope in conditionallyInvalidatedScopes || scope in invalidatedScopes
929             }
930             conditionallyInvalidatedScopes.clear()
931             cleanUpDerivedStateObservations()
932         } else if (invalidatedScopes.isNotEmpty()) {
933             observations.removeScopeIf { scope -> scope in invalidatedScopes }
934             cleanUpDerivedStateObservations()
935             invalidatedScopes.clear()
936         }
937     }
938 
cleanUpDerivedStateObservationsnull939     private fun cleanUpDerivedStateObservations() {
940         derivedStates.removeScopeIf { derivedState -> derivedState !in observations }
941         if (conditionallyInvalidatedScopes.isNotEmpty()) {
942             conditionallyInvalidatedScopes.removeIf { scope -> !scope.isConditional }
943         }
944     }
945 
recordReadOfnull946     override fun recordReadOf(value: Any) {
947         // Not acquiring lock since this happens during composition with it already held
948         if (!areChildrenComposing) {
949             composer.currentRecomposeScope?.let {
950                 it.used = true
951                 val alreadyRead = it.recordRead(value)
952                 if (!alreadyRead) {
953                     if (value is StateObjectImpl) {
954                         value.recordReadIn(ReaderKind.Composition)
955                     }
956 
957                     observations.add(value, it)
958 
959                     // Record derived state dependency mapping
960                     if (value is DerivedState<*>) {
961                         val record = value.currentRecord
962                         derivedStates.removeScope(value)
963                         record.dependencies.forEachKey { dependency ->
964                             if (dependency is StateObjectImpl) {
965                                 dependency.recordReadIn(ReaderKind.Composition)
966                             }
967                             derivedStates.add(dependency, value)
968                         }
969                         it.recordDerivedStateValue(value, record.currentValue)
970                     }
971                 }
972             }
973         }
974     }
975 
invalidateScopeOfLockednull976     private fun invalidateScopeOfLocked(value: Any) {
977         // Invalidate any recompose scopes that read this value.
978         observations.forEachScopeOf(value) { scope ->
979             if (scope.invalidateForResult(value) == InvalidationResult.IMMINENT) {
980                 // If we process this during recordWriteOf, ignore it when recording modifications
981                 observationsProcessed.add(value, scope)
982             }
983         }
984     }
985 
recordWriteOfnull986     override fun recordWriteOf(value: Any) =
987         synchronized(lock) {
988             invalidateScopeOfLocked(value)
989 
990             // If writing to dependency of a derived value and the value is changed, invalidate the
991             // scopes that read the derived value.
992             derivedStates.forEachScopeOf(value) { invalidateScopeOfLocked(it) }
993         }
994 
recomposenull995     override fun recompose(): Boolean =
996         synchronized(lock) {
997             drainPendingModificationsForCompositionLocked()
998             val pendingPausedComposition = pendingPausedComposition
999             if (pendingPausedComposition != null && !pendingPausedComposition.isRecomposing) {
1000                 // If the composition is pending do not recompose it now as the recomposition
1001                 // is in the control of the pausable composition and is supposed to happen when
1002                 // the resume is called. However, this may cause the pausable composition to go
1003                 // revert to an incomplete state. If isRecomposing is true then this is being
1004                 // called in resume()
1005                 pendingPausedComposition.markIncomplete()
1006                 return false
1007             }
1008             guardChanges {
1009                 guardInvalidationsLocked { invalidations ->
1010                     val observer = observer()
1011                     @Suppress("UNCHECKED_CAST")
1012                     observer?.onBeginComposition(
1013                         this,
1014                         invalidations.asMap() as Map<RecomposeScope, Set<Any>>
1015                     )
1016                     composer.recompose(invalidations, shouldPause).also { shouldDrain ->
1017                         // Apply would normally do this for us; do it now if apply shouldn't happen.
1018                         if (!shouldDrain) drainPendingModificationsLocked()
1019                         observer?.onEndComposition(this)
1020                     }
1021                 }
1022             }
1023         }
1024 
insertMovableContentnull1025     override fun insertMovableContent(
1026         references: List<Pair<MovableContentStateReference, MovableContentStateReference?>>
1027     ) {
1028         runtimeCheck(references.fastAll { it.first.composition == this })
1029         guardChanges { composer.insertMovableContentReferences(references) }
1030     }
1031 
disposeUnusedMovableContentnull1032     override fun disposeUnusedMovableContent(state: MovableContentState) {
1033         rememberManager.use(abandonSet, composer.errorContext) {
1034             val slotTable = state.slotTable
1035             slotTable.write { writer -> writer.removeCurrentGroup(rememberManager) }
1036             dispatchRememberObservers()
1037         }
1038     }
1039 
applyChangesInLockednull1040     private fun applyChangesInLocked(changes: ChangeList) {
1041         rememberManager.prepare(abandonSet, composer.errorContext)
1042         try {
1043             if (changes.isEmpty()) return
1044             trace("Compose:applyChanges") {
1045                 val applier = pendingPausedComposition?.pausableApplier ?: applier
1046                 val rememberManager = pendingPausedComposition?.rememberManager ?: rememberManager
1047                 applier.onBeginChanges()
1048 
1049                 // Apply all changes
1050                 slotTable.write { slots ->
1051                     changes.executeAndFlushAllPendingChanges(
1052                         applier,
1053                         slots,
1054                         rememberManager,
1055                         composer.errorContext
1056                     )
1057                 }
1058                 applier.onEndChanges()
1059             }
1060 
1061             // Side effects run after lifecycle observers so that any remembered objects
1062             // that implement RememberObserver receive onRemembered before a side effect
1063             // that captured it and operates on it can run.
1064             rememberManager.dispatchRememberObservers()
1065             rememberManager.dispatchSideEffects()
1066 
1067             if (pendingInvalidScopes) {
1068                 trace("Compose:unobserve") {
1069                     pendingInvalidScopes = false
1070                     observations.removeScopeIf { scope -> !scope.valid }
1071                     cleanUpDerivedStateObservations()
1072                 }
1073             }
1074         } finally {
1075             // Only dispatch abandons if we do not have any late changes or pending paused
1076             // compositions. The instances in the abandon set can be remembered in the late changes
1077             // or when the paused composition is applied.
1078             try {
1079                 if (this.lateChanges.isEmpty() && pendingPausedComposition == null) {
1080                     rememberManager.dispatchAbandons()
1081                 }
1082             } finally {
1083                 rememberManager.clear()
1084             }
1085         }
1086     }
1087 
applyChangesnull1088     override fun applyChanges() {
1089         synchronized(lock) {
1090             guardChanges {
1091                 applyChangesInLocked(changes)
1092                 drainPendingModificationsLocked()
1093             }
1094         }
1095     }
1096 
applyLateChangesnull1097     override fun applyLateChanges() {
1098         synchronized(lock) {
1099             guardChanges {
1100                 if (lateChanges.isNotEmpty()) {
1101                     applyChangesInLocked(lateChanges)
1102                 }
1103             }
1104         }
1105     }
1106 
changesAppliednull1107     override fun changesApplied() {
1108         synchronized(lock) {
1109             guardChanges {
1110                 composer.changesApplied()
1111 
1112                 // By this time all abandon objects should be notified that they have been
1113                 // abandoned.
1114                 if (this.abandonSet.isNotEmpty()) {
1115                     rememberManager.use(abandonSet, traceContext = composer.errorContext) {
1116                         dispatchAbandons()
1117                     }
1118                 }
1119             }
1120         }
1121     }
1122 
guardInvalidationsLockednull1123     private inline fun <T> guardInvalidationsLocked(
1124         block: (changes: ScopeMap<RecomposeScopeImpl, Any>) -> T
1125     ): T {
1126         val invalidations = takeInvalidations()
1127         return try {
1128             block(invalidations)
1129         } catch (e: Throwable) {
1130             this.invalidations = invalidations
1131             throw e
1132         }
1133     }
1134 
guardChangesnull1135     private inline fun <T> guardChanges(block: () -> T): T =
1136         try {
1137             trackAbandonedValues(block)
1138         } catch (e: Throwable) {
1139             abandonChanges()
1140             throw e
1141         }
1142 
abandonChangesnull1143     override fun abandonChanges() {
1144         pendingModifications.set(null)
1145         changes.clear()
1146         lateChanges.clear()
1147 
1148         if (abandonSet.isNotEmpty()) {
1149             rememberManager.use(abandonSet, composer.errorContext) { dispatchAbandons() }
1150         }
1151     }
1152 
invalidateAllnull1153     override fun invalidateAll() {
1154         synchronized(lock) { slotTable.slots.forEach { (it as? RecomposeScopeImpl)?.invalidate() } }
1155     }
1156 
verifyConsistentnull1157     override fun verifyConsistent() {
1158         synchronized(lock) {
1159             if (!isComposing) {
1160                 composer.verifyConsistent()
1161                 slotTable.verifyWellFormed()
1162                 validateRecomposeScopeAnchors(slotTable)
1163             }
1164         }
1165     }
1166 
delegateInvalidationsnull1167     override fun <R> delegateInvalidations(
1168         to: ControlledComposition?,
1169         groupIndex: Int,
1170         block: () -> R
1171     ): R {
1172         return if (to != null && to != this && groupIndex >= 0) {
1173             invalidationDelegate = to as CompositionImpl
1174             invalidationDelegateGroup = groupIndex
1175             try {
1176                 block()
1177             } finally {
1178                 invalidationDelegate = null
1179                 invalidationDelegateGroup = 0
1180             }
1181         } else block()
1182     }
1183 
getAndSetShouldPauseCallbacknull1184     override fun getAndSetShouldPauseCallback(
1185         shouldPause: ShouldPauseCallback?
1186     ): ShouldPauseCallback? {
1187         val previous = this.shouldPause
1188         this.shouldPause = shouldPause
1189         return previous
1190     }
1191 
invalidatenull1192     override fun invalidate(scope: RecomposeScopeImpl, instance: Any?): InvalidationResult {
1193         if (scope.defaultsInScope) {
1194             scope.defaultsInvalid = true
1195         }
1196         val anchor = scope.anchor
1197         if (anchor == null || !anchor.valid)
1198             return InvalidationResult.IGNORED // The scope was removed from the composition
1199         if (!slotTable.ownsAnchor(anchor)) {
1200             // The scope might be owned by the delegate
1201             val delegate = synchronized(lock) { invalidationDelegate }
1202             if (delegate?.tryImminentInvalidation(scope, instance) == true)
1203                 return InvalidationResult.IMMINENT // The scope was owned by the delegate
1204 
1205             return InvalidationResult.IGNORED // The scope has not yet entered the composition
1206         }
1207         if (!scope.canRecompose)
1208             return InvalidationResult.IGNORED // The scope isn't able to be recomposed/invalidated
1209         return invalidateChecked(scope, anchor, instance)
1210     }
1211 
recomposeScopeReleasednull1212     override fun recomposeScopeReleased(scope: RecomposeScopeImpl) {
1213         pendingInvalidScopes = true
1214     }
1215 
1216     @Suppress("UNCHECKED_CAST")
getCompositionServicenull1217     override fun <T> getCompositionService(key: CompositionServiceKey<T>): T? =
1218         if (key == CompositionImplServiceKey) this as T else null
1219 
1220     private fun tryImminentInvalidation(scope: RecomposeScopeImpl, instance: Any?): Boolean =
1221         isComposing && composer.tryImminentInvalidation(scope, instance)
1222 
1223     private fun invalidateChecked(
1224         scope: RecomposeScopeImpl,
1225         anchor: Anchor,
1226         instance: Any?
1227     ): InvalidationResult {
1228         val delegate =
1229             synchronized(lock) {
1230                 val delegate =
1231                     invalidationDelegate?.let { changeDelegate ->
1232                         // Invalidations are delegated when recomposing changes to movable content
1233                         // that
1234                         // is destined to be moved. The movable content is composed in the
1235                         // destination
1236                         // composer but all the recompose scopes point the current composer and will
1237                         // arrive
1238                         // here. this redirects the invalidations that will be moved to the
1239                         // destination
1240                         // composer instead of recording an invalid invalidation in the from
1241                         // composer.
1242                         if (slotTable.groupContainsAnchor(invalidationDelegateGroup, anchor)) {
1243                             changeDelegate
1244                         } else null
1245                     }
1246                 if (delegate == null) {
1247                     if (tryImminentInvalidation(scope, instance)) {
1248                         // The invalidation was redirected to the composer.
1249                         return InvalidationResult.IMMINENT
1250                     }
1251 
1252                     // Observer requires a map of scope -> states, so we have to fill it if observer
1253                     // is set.
1254                     val observer = observer()
1255                     if (instance == null) {
1256                         // invalidations[scope] containing ScopeInvalidated means it was invalidated
1257                         // unconditionally.
1258                         invalidations.set(scope, ScopeInvalidated)
1259                     } else if (observer == null && instance !is DerivedState<*>) {
1260                         // If observer is not set, we only need to add derived states to
1261                         // invalidation,
1262                         // as regular states are always going to invalidate.
1263                         invalidations.set(scope, ScopeInvalidated)
1264                     } else {
1265                         if (!invalidations.anyScopeOf(scope) { it === ScopeInvalidated }) {
1266                             invalidations.add(scope, instance)
1267                         }
1268                     }
1269                 }
1270                 delegate
1271             }
1272 
1273         // We call through the delegate here to ensure we don't nest synchronization scopes.
1274         if (delegate != null) {
1275             return delegate.invalidateChecked(scope, anchor, instance)
1276         }
1277         parent.invalidate(this)
1278         return if (isComposing) InvalidationResult.DEFERRED else InvalidationResult.SCHEDULED
1279     }
1280 
removeObservationnull1281     internal fun removeObservation(instance: Any, scope: RecomposeScopeImpl) {
1282         observations.remove(instance, scope)
1283     }
1284 
removeDerivedStateObservationnull1285     internal fun removeDerivedStateObservation(state: DerivedState<*>) {
1286         // remove derived state if it is not observed in other scopes
1287         if (state !in observations) {
1288             derivedStates.removeScope(state)
1289         }
1290     }
1291 
1292     /**
1293      * This takes ownership of the current invalidations and sets up a new array map to hold the new
1294      * invalidations.
1295      */
takeInvalidationsnull1296     private fun takeInvalidations(): ScopeMap<RecomposeScopeImpl, Any> {
1297         val invalidations = invalidations
1298         this.invalidations = ScopeMap()
1299         return invalidations
1300     }
1301 
1302     /**
1303      * Helper for [verifyConsistent] to ensure the anchor match there respective invalidation
1304      * scopes.
1305      */
validateRecomposeScopeAnchorsnull1306     private fun validateRecomposeScopeAnchors(slotTable: SlotTable) {
1307         val scopes = slotTable.slots.mapNotNull { it as? RecomposeScopeImpl }
1308         scopes.fastForEach { scope ->
1309             scope.anchor?.let { anchor ->
1310                 checkPrecondition(scope in slotTable.slotsOf(anchor.toIndexFor(slotTable))) {
1311                     val dataIndex = slotTable.slots.indexOf(scope)
1312                     "Misaligned anchor $anchor in scope $scope encountered, scope found at " +
1313                         "$dataIndex"
1314                 }
1315             }
1316         }
1317     }
1318 
trackAbandonedValuesnull1319     private inline fun <T> trackAbandonedValues(block: () -> T): T {
1320         var success = false
1321         return try {
1322             block().also { success = true }
1323         } finally {
1324             if (!success && abandonSet.isNotEmpty()) {
1325                 rememberManager.use(abandonSet, composer.errorContext) { dispatchAbandons() }
1326             }
1327         }
1328     }
1329 
observernull1330     private fun observer(): CompositionObserver? {
1331         val holder = observerHolder
1332 
1333         return if (holder.root) {
1334             holder.observer
1335         } else {
1336             val parentHolder = parent.observerHolder
1337             val parentObserver = parentHolder?.observer
1338             if (parentObserver != holder.observer) {
1339                 holder.observer = parentObserver
1340             }
1341             parentObserver
1342         }
1343     }
1344 
deactivatenull1345     override fun deactivate() {
1346         synchronized(lock) {
1347             val nonEmptySlotTable = slotTable.groupsSize > 0
1348             if (nonEmptySlotTable || abandonSet.isNotEmpty()) {
1349                 trace("Compose:deactivate") {
1350                     rememberManager.use(abandonSet, composer.errorContext) {
1351                         if (nonEmptySlotTable) {
1352                             applier.onBeginChanges()
1353                             slotTable.write { writer ->
1354                                 writer.deactivateCurrentGroup(rememberManager)
1355                             }
1356                             applier.onEndChanges()
1357                             dispatchRememberObservers()
1358                         }
1359                         dispatchAbandons()
1360                     }
1361                 }
1362             }
1363             observations.clear()
1364             derivedStates.clear()
1365             invalidations.clear()
1366             changes.clear()
1367             lateChanges.clear()
1368             composer.deactivate()
1369         }
1370     }
1371 
1372     // This is only used in tests to ensure the stacks do not silently leak.
composerStacksSizesnull1373     internal fun composerStacksSizes(): Int = composer.stacksSize()
1374 }
1375 
1376 internal object ScopeInvalidated
1377 
1378 @ExperimentalComposeRuntimeApi
1379 internal class CompositionObserverHolder(
1380     var observer: CompositionObserver? = null,
1381     var root: Boolean = false,
1382 )
1383