1 /*
<lambda>null2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.runtime.snapshots
18 
19 import androidx.collection.MutableScatterSet
20 import androidx.collection.mutableScatterSetOf
21 import androidx.compose.runtime.Composable
22 import androidx.compose.runtime.DisallowComposableCalls
23 import androidx.compose.runtime.ExperimentalComposeRuntimeApi
24 import androidx.compose.runtime.InternalComposeApi
25 import androidx.compose.runtime.checkPrecondition
26 import androidx.compose.runtime.collection.wrapIntoSet
27 import androidx.compose.runtime.internal.AtomicInt
28 import androidx.compose.runtime.internal.JvmDefaultWithCompatibility
29 import androidx.compose.runtime.internal.SnapshotThreadLocal
30 import androidx.compose.runtime.internal.currentThreadId
31 import androidx.compose.runtime.platform.makeSynchronizedObject
32 import androidx.compose.runtime.platform.synchronized
33 import androidx.compose.runtime.requirePrecondition
34 import androidx.compose.runtime.snapshots.Snapshot.Companion.takeMutableSnapshot
35 import androidx.compose.runtime.snapshots.Snapshot.Companion.takeSnapshot
36 import androidx.compose.runtime.snapshots.tooling.creatingSnapshot
37 import androidx.compose.runtime.snapshots.tooling.dispatchObserverOnApplied
38 import androidx.compose.runtime.snapshots.tooling.dispatchObserverOnPreDispose
39 import kotlin.contracts.ExperimentalContracts
40 import kotlin.contracts.InvocationKind
41 import kotlin.contracts.contract
42 
43 /**
44  * A snapshot of the values return by mutable states and other state objects. All state object will
45  * have the same value in the snapshot as they had when the snapshot was created unless they are
46  * explicitly changed in the snapshot.
47  *
48  * To enter a snapshot call [enter]. The snapshot is the current snapshot as returned by
49  * [currentSnapshot] until the control returns from the lambda (or until a nested [enter] is
50  * called). All state objects will return the values associated with this snapshot, locally in the
51  * thread, until [enter] returns. All other threads are unaffected.
52  *
53  * Snapshots can be nested by calling [takeNestedSnapshot].
54  *
55  * @see takeSnapshot
56  * @see takeMutableSnapshot
57  * @see androidx.compose.runtime.mutableStateOf
58  * @see androidx.compose.runtime.mutableStateListOf
59  * @see androidx.compose.runtime.mutableStateMapOf
60  */
61 sealed class Snapshot(
62     snapshotId: SnapshotId,
63 
64     /** A set of all the snapshots that should be treated as invalid. */
65     internal open var invalid: SnapshotIdSet
66 ) {
67     @Deprecated("Use id: Long constructor instead", level = DeprecationLevel.HIDDEN)
68     constructor(id: Int, invalid: SnapshotIdSet) : this(id.toSnapshotId(), invalid)
69 
70     /**
71      * The snapshot id of the snapshot. This is a unique number from a monotonically increasing
72      * value for each snapshot taken.
73      *
74      * [id] will is identical to [snapshotId] if the value of [snapshotId] is less than or equal to
75      * [Int.MAX_VALUE]. For [snapshotId] value greater than [Int.MAX_VALUE], this value will return
76      * a negative value.
77      */
78     @Deprecated("Use snapshotId instead", replaceWith = ReplaceWith("snapshotId"))
79     open val id: Int
80         get() = snapshotId.toInt()
81 
82     /**
83      * The snapshot id of the snapshot. This is a unique number from a monotonically increasing
84      * value for each snapshot taken.
85      */
86     open var snapshotId: SnapshotId = snapshotId
87         internal set
88 
89     internal open var writeCount: Int
90         get() = 0
91         @Suppress("UNUSED_PARAMETER")
92         set(value) {
93             error("Updating write count is not supported for this snapshot")
94         }
95 
96     /**
97      * The root snapshot for this snapshot. For non-nested snapshots this is always `this`. For
98      * nested snapshot it is the parent's [root].
99      */
100     abstract val root: Snapshot
101 
102     /** True if any change to a state object in this snapshot will throw. */
103     abstract val readOnly: Boolean
104 
105     /**
106      * Dispose the snapshot. Neglecting to dispose a snapshot will result in difficult to diagnose
107      * memory leaks as it indirectly causes all state objects to maintain its value for the
108      * un-disposed snapshot.
109      */
110     open fun dispose() {
111         disposed = true
112         sync { releasePinnedSnapshotLocked() }
113     }
114 
115     /**
116      * Take a snapshot of the state values in this snapshot. The resulting [Snapshot] is read-only.
117      * All nested snapshots need to be disposed by calling [dispose] before resources associated
118      * with this snapshot can be collected. Nested snapshots are still valid after the parent has
119      * been disposed.
120      */
121     abstract fun takeNestedSnapshot(readObserver: ((Any) -> Unit)? = null): Snapshot
122 
123     /**
124      * Whether there are any pending changes in this snapshot. These changes are not visible until
125      * the snapshot is applied.
126      */
127     abstract fun hasPendingChanges(): Boolean
128 
129     /**
130      * Enter the snapshot. In [block] all state objects have the value associated with this
131      * snapshot. The value of [currentSnapshot] will be this snapshot until this [block] returns or
132      * a nested call to [enter] is called. When [block] returns, the previous current snapshot is
133      * restored if there was one.
134      *
135      * All changes to state objects inside [block] are isolated to this snapshot and are not visible
136      * to other snapshot or as global state. If this is a [readOnly] snapshot, any changes to state
137      * objects will throw an [IllegalStateException].
138      *
139      * For a [MutableSnapshot], changes made to a snapshot inside [block] can be applied atomically
140      * to the global state (or to its parent snapshot if it is a nested snapshot) by calling
141      * [MutableSnapshot.apply].
142      *
143      * @see androidx.compose.runtime.mutableStateOf
144      * @see androidx.compose.runtime.mutableStateListOf
145      * @see androidx.compose.runtime.mutableStateMapOf
146      */
147     inline fun <T> enter(block: () -> T): T {
148         val previous = makeCurrent()
149         try {
150             return block()
151         } finally {
152             restoreCurrent(previous)
153         }
154     }
155 
156     @PublishedApi
157     internal open fun makeCurrent(): Snapshot? {
158         val previous = threadSnapshot.get()
159         threadSnapshot.set(this)
160         return previous
161     }
162 
163     @PublishedApi
164     internal open fun restoreCurrent(snapshot: Snapshot?) {
165         threadSnapshot.set(snapshot)
166     }
167 
168     /**
169      * Enter the snapshot, returning the previous [Snapshot] for leaving this snapshot later using
170      * [unsafeLeave]. Prefer [enter] or [asContextElement] instead of using [unsafeEnter] directly
171      * to prevent mismatched [unsafeEnter]/[unsafeLeave] calls.
172      *
173      * After returning all state objects have the value associated with this snapshot. The value of
174      * [currentSnapshot] will be this snapshot until [unsafeLeave] is called with the returned
175      * [Snapshot] or another call to [unsafeEnter] or [enter] is made.
176      *
177      * All changes to state objects until another snapshot is entered or this snapshot is left are
178      * isolated to this snapshot and are not visible to other snapshot or as global state. If this
179      * is a [readOnly] snapshot, any changes to state objects will throw an [IllegalStateException].
180      *
181      * For a [MutableSnapshot], changes made to a snapshot can be applied atomically to the global
182      * state (or to its parent snapshot if it is a nested snapshot) by calling
183      * [MutableSnapshot.apply].
184      */
185     fun unsafeEnter(): Snapshot? = makeCurrent()
186 
187     /** Leave the snapshot, restoring the [oldSnapshot] before returning. See [unsafeEnter]. */
188     fun unsafeLeave(oldSnapshot: Snapshot?) {
189         checkPrecondition(threadSnapshot.get() === this) {
190             "Cannot leave snapshot; $this is not the current snapshot"
191         }
192         restoreCurrent(oldSnapshot)
193     }
194 
195     internal var disposed = false
196 
197     /*
198      * Handle to use when unpinning this snapshot. -1 if this snapshot has been unpinned.
199      */
200     @Suppress("LeakingThis")
201     private var pinningTrackingHandle =
202         if (snapshotId != INVALID_SNAPSHOT) trackPinning(snapshotId, invalid) else -1
203 
204     internal inline val isPinned
205         get() = pinningTrackingHandle >= 0
206 
207     /*
208      * The read observer for the snapshot if there is one.
209      */
210     @PublishedApi internal abstract val readObserver: ((Any) -> Unit)?
211 
212     /** The write observer for the snapshot if there is one. */
213     internal abstract val writeObserver: ((Any) -> Unit)?
214 
215     /** Called when a nested snapshot of this snapshot is activated */
216     internal abstract fun nestedActivated(snapshot: Snapshot)
217 
218     /** Called when a nested snapshot of this snapshot is deactivated */
219     internal abstract fun nestedDeactivated(snapshot: Snapshot)
220 
221     /** Record that state was modified in the snapshot. */
222     internal abstract fun recordModified(state: StateObject)
223 
224     /** The set of state objects that have been modified in this snapshot. */
225     internal abstract val modified: MutableScatterSet<StateObject>?
226 
227     /**
228      * Notify the snapshot that all objects created in this snapshot to this point should be
229      * considered initialized. If any state object is modified after this point it will appear as
230      * modified in the snapshot. Any applicable snapshot write observer will be called for the
231      * object and the object will be part of the a set of mutated objects sent to any applicable
232      * snapshot apply observer.
233      *
234      * Unless [notifyObjectsInitialized] is called, state objects created in a snapshot are not
235      * considered modified by the snapshot even if they are modified after construction.
236      */
237     internal abstract fun notifyObjectsInitialized()
238 
239     /**
240      * Closes the snapshot by removing the snapshot id (an any previous id's) from the list of open
241      * snapshots and unpinning snapshots that no longer are referenced by this snapshot.
242      */
243     internal fun closeAndReleasePinning() {
244         sync {
245             closeLocked()
246             releasePinnedSnapshotsForCloseLocked()
247         }
248     }
249 
250     /**
251      * Closes the snapshot by removing the snapshot id (and any previous ids) from the list of open
252      * snapshots. Does not release pinned snapshots. See [releasePinnedSnapshotsForCloseLocked] for
253      * the second half of [closeAndReleasePinning].
254      *
255      * Call while holding a `sync {}` lock.
256      */
257     internal open fun closeLocked() {
258         openSnapshots = openSnapshots.clear(snapshotId)
259     }
260 
261     /**
262      * Releases all pinned snapshots required to perform a clean [closeAndReleasePinning].
263      *
264      * Call while holding a `sync {}` lock.
265      *
266      * See [closeAndReleasePinning], [closeLocked].
267      */
268     internal open fun releasePinnedSnapshotsForCloseLocked() {
269         releasePinnedSnapshotLocked()
270     }
271 
272     internal fun validateNotDisposed() {
273         requirePrecondition(!disposed) { "Cannot use a disposed snapshot" }
274     }
275 
276     internal fun releasePinnedSnapshotLocked() {
277         if (pinningTrackingHandle >= 0) {
278             releasePinningLocked(pinningTrackingHandle)
279             pinningTrackingHandle = -1
280         }
281     }
282 
283     internal fun takeoverPinnedSnapshot(): Int =
284         pinningTrackingHandle.also { pinningTrackingHandle = -1 }
285 
286     companion object {
287         /**
288          * Return the thread's active snapshot. If no thread snapshot is active then the current
289          * global snapshot is used.
290          */
291         val current
292             get() = currentSnapshot()
293 
294         /** Return `true` if the thread is currently in the context of a snapshot. */
295         val isInSnapshot: Boolean
296             get() = threadSnapshot.get() != null
297 
298         /**
299          * Returns whether any threads are currently in the process of notifying observers about
300          * changes to the global snapshot.
301          */
302         val isApplyObserverNotificationPending: Boolean
303             get() = pendingApplyObserverCount.get() > 0
304 
305         /**
306          * All new state objects initial state records should be [PreexistingSnapshotId] which then
307          * allows snapshots outside the creating snapshot to access the object with its initial
308          * state.
309          */
310         @Suppress("ConstPropertyName") const val PreexistingSnapshotId = 1
311 
312         /**
313          * Take a snapshot of the current value of all state objects. The values are preserved until
314          * [Snapshot.dispose] is called on the result.
315          *
316          * The [readObserver] parameter can be used to track when all state objects are read when in
317          * [Snapshot.enter]. A snapshot apply observer can be registered using
318          * [Snapshot.registerApplyObserver] to observe modification of state objects.
319          *
320          * An active snapshot (after it is created but before [Snapshot.dispose] is called) requires
321          * resources to track the values in the snapshot. Once a snapshot is no longer needed it
322          * should disposed by calling [Snapshot.dispose].
323          *
324          * Leaving a snapshot active could cause hard to diagnose memory leaks values as are
325          * maintained by state objects for these unneeded snapshots. Take care to always call
326          * [Snapshot.dispose] on all snapshots when they are no longer needed.
327          *
328          * Composition uses both of these to implicitly subscribe to changes to state object and
329          * automatically update the composition when state objects read during composition change.
330          *
331          * A nested snapshot can be taken of a snapshot which is an independent read-only copy of
332          * the snapshot and can be disposed independently. This is used by [takeSnapshot] when in a
333          * read-only snapshot for API consistency allowing the result of [takeSnapshot] to be
334          * disposed leaving the parent snapshot active.
335          *
336          * @param readObserver called when any state object is read in the lambda passed to
337          *   [Snapshot.enter] or in the [Snapshot.enter] of any nested snapshot.
338          * @see Snapshot
339          * @see Snapshot.registerApplyObserver
340          */
341         fun takeSnapshot(readObserver: ((Any) -> Unit)? = null): Snapshot =
342             currentSnapshot().takeNestedSnapshot(readObserver)
343 
344         /**
345          * Take a snapshot of the current value of all state objects that also allows the state to
346          * be changed and later atomically applied when [MutableSnapshot.apply] is called. The
347          * values are preserved until [Snapshot.dispose] is called on the result. The global state
348          * will either see all the changes made as one atomic change, when [MutableSnapshot .apply]
349          * is called, or none of the changes if the mutable state object is disposed before being
350          * applied.
351          *
352          * The values in a snapshot can be modified by calling [Snapshot.enter] and then, in its
353          * lambda, modify any state object. The new values of the state objects will only become
354          * visible to the global state when [MutableSnapshot.apply] is called.
355          *
356          * An active snapshot (after it is created but before [Snapshot.dispose] is called) requires
357          * resources to track the values in the snapshot. Once a snapshot is no longer needed it
358          * should disposed by calling [Snapshot.dispose].
359          *
360          * Leaving a snapshot active could cause hard to diagnose memory leaks as values are
361          * maintained by state objects for these unneeded snapshots. Take care to always call
362          * [Snapshot.dispose] on all snapshots when they are no longer needed.
363          *
364          * A nested snapshot can be taken by calling [Snapshot.takeNestedSnapshot], for a read-only
365          * snapshot, or [MutableSnapshot.takeNestedMutableSnapshot] for a snapshot that can be
366          * changed. Nested mutable snapshots are applied to the this, the parent snapshot, when
367          * their [MutableSnapshot.apply] is called. Their applied changes will be visible to in this
368          * snapshot but will not be visible other snapshots (including other nested snapshots) or
369          * the global state until this snapshot is applied by calling [MutableSnapshot.apply].
370          *
371          * Once [MutableSnapshot.apply] is called on this, the parent snapshot, all calls to
372          * [MutableSnapshot.apply] on an active nested snapshot will fail.
373          *
374          * Changes to a mutable snapshot are isolated, using snapshot isolation, from all other
375          * snapshots. Their changes are only visible as global state or to new snapshots once
376          * [MutableSnapshot.apply] is called.
377          *
378          * Applying a snapshot can fail if currently visible changes to the state object conflicts
379          * with a change made in the snapshot.
380          *
381          * When in a mutable snapshot, [takeMutableSnapshot] creates a nested snapshot of the
382          * current mutable snapshot. If the current snapshot is read-only, an exception is thrown.
383          * The current snapshot is the result of calling [currentSnapshot] which is updated by
384          * calling [Snapshot.enter] which makes the [Snapshot] the current snapshot while in its
385          * lambda.
386          *
387          * Composition uses mutable snapshots to allow changes made in a [Composable] functions to
388          * be temporarily isolated from the global state and is later applied to the global state
389          * when the composition is applied. If [MutableSnapshot.apply] fails applying this snapshot,
390          * the snapshot and the changes calculated during composition are disposed and a new
391          * composition is scheduled to be calculated again.
392          *
393          * @param readObserver called when any state object is read in the lambda passed to
394          *   [Snapshot.enter] or in the [Snapshot.enter] of any nested snapshots.
395          *
396          * Composition, layout and draw use [readObserver] to implicitly subscribe to changes to
397          * state objects to know when to update.
398          *
399          * @param writeObserver called when a state object is created or just before it is written
400          *   to the first time in the snapshot or a nested mutable snapshot. This might be called
401          *   several times for the same object if nested mutable snapshots are created.
402          *
403          * Composition uses [writeObserver] to track when a state object is modified during
404          * composition in order to invalidate the reads that have not yet occurred. This allows a
405          * single pass of composition for state objects that are written to before they are read
406          * (such as modifying the value of a dynamic ambient provider).
407          *
408          * @see Snapshot.takeSnapshot
409          * @see Snapshot
410          * @see MutableSnapshot
411          */
412         fun takeMutableSnapshot(
413             readObserver: ((Any) -> Unit)? = null,
414             writeObserver: ((Any) -> Unit)? = null
415         ): MutableSnapshot =
416             (currentSnapshot() as? MutableSnapshot)?.takeNestedMutableSnapshot(
417                 readObserver,
418                 writeObserver
419             ) ?: error("Cannot create a mutable snapshot of an read-only snapshot")
420 
421         /**
422          * Escape the current snapshot, if there is one. All state objects will have the value
423          * associated with the global while the [block] lambda is executing.
424          *
425          * @return the result of [block]
426          */
427         inline fun <T> global(block: () -> T): T {
428             val previous = removeCurrent()
429             try {
430                 return block()
431             } finally {
432                 restoreCurrent(previous)
433             }
434         }
435 
436         /**
437          * Take a [MutableSnapshot] and run [block] within it. When [block] returns successfully,
438          * attempt to [MutableSnapshot.apply] the snapshot. Returns the result of [block] or throws
439          * [SnapshotApplyConflictException] if snapshot changes attempted by [block] could not be
440          * applied.
441          *
442          * Prior to returning, any changes made to snapshot state (e.g. state holders returned by
443          * [androidx.compose.runtime.mutableStateOf] are not visible to other threads. When
444          * [withMutableSnapshot] returns successfully those changes will be made visible to other
445          * threads and any snapshot observers (e.g. [androidx.compose.runtime.snapshotFlow]) will be
446          * notified of changes.
447          *
448          * [block] must not suspend if [withMutableSnapshot] is called from a suspend function.
449          */
450         // TODO: determine a good way to prevent/discourage suspending in an inlined [block]
451         inline fun <R> withMutableSnapshot(block: () -> R): R =
452             takeMutableSnapshot().run {
453                 var hasError = false
454                 try {
455                     enter(block)
456                 } catch (e: Throwable) {
457                     hasError = true
458                     throw e
459                 } finally {
460                     if (!hasError) {
461                         apply().check()
462                     }
463                     dispose()
464                 }
465             }
466 
467         /**
468          * Observe reads and or write of state objects in the current thread.
469          *
470          * This only affects the current snapshot (if any) and any new snapshots create from
471          * [Snapshot.takeSnapshot] and [takeMutableSnapshot]. It will not affect any snapshots
472          * previous created even if [Snapshot.enter] is called in [block].
473          *
474          * @param readObserver called when any state object is read.
475          * @param writeObserver called when a state object is created or just before it is written
476          *   to the first time in the snapshot or a nested mutable snapshot. This might be called
477          *   several times for the same object if nested mutable snapshots are created.
478          * @param block the code the [readObserver] and [writeObserver] will be observing. Once
479          *   [block] returns, the [readObserver] and [writeObserver] will no longer be called.
480          */
481         fun <T> observe(
482             readObserver: ((Any) -> Unit)? = null,
483             writeObserver: ((Any) -> Unit)? = null,
484             block: () -> T
485         ): T {
486             if (readObserver == null && writeObserver == null) {
487                 // No observer change, just execute the block
488                 return block()
489             }
490 
491             val previous = threadSnapshot.get()
492             if (previous is TransparentObserverMutableSnapshot && previous.canBeReused) {
493                 // Change observers in place without allocating new snapshots.
494                 val previousReadObserver = previous.readObserver
495                 val previousWriteObserver = previous.writeObserver
496 
497                 try {
498                     previous.readObserver = mergedReadObserver(readObserver, previousReadObserver)
499                     previous.writeObserver =
500                         mergedWriteObserver(writeObserver, previousWriteObserver)
501                     return block()
502                 } finally {
503                     previous.readObserver = previousReadObserver
504                     previous.writeObserver = previousWriteObserver
505                 }
506             } else {
507                 // The snapshot is not already transparent, observe in a new transparent snapshot
508                 val snapshot =
509                     when {
510                         previous == null || previous is MutableSnapshot -> {
511                             TransparentObserverMutableSnapshot(
512                                 parentSnapshot = previous as? MutableSnapshot,
513                                 specifiedReadObserver = readObserver,
514                                 specifiedWriteObserver = writeObserver,
515                                 mergeParentObservers = true,
516                                 ownsParentSnapshot = false
517                             )
518                         }
519                         readObserver == null -> {
520                             return block()
521                         }
522                         else -> {
523                             previous.takeNestedSnapshot(readObserver)
524                         }
525                     }
526                 try {
527                     return snapshot.enter(block)
528                 } finally {
529                     snapshot.dispose()
530                 }
531             }
532         }
533 
534         @Suppress("unused") // left here for binary compatibility
535         @PublishedApi
536         internal fun createNonObservableSnapshot(): Snapshot =
537             createTransparentSnapshotWithNoParentReadObserver(
538                 previousSnapshot = threadSnapshot.get()
539             )
540 
541         @PublishedApi
542         internal val currentThreadSnapshot: Snapshot?
543             get() = threadSnapshot.get()
544 
545         private inline val TransparentObserverMutableSnapshot.canBeReused: Boolean
546             get() = threadId == currentThreadId()
547 
548         private inline val TransparentObserverSnapshot.canBeReused: Boolean
549             get() = threadId == currentThreadId()
550 
551         @PublishedApi
552         internal fun makeCurrentNonObservable(previous: Snapshot?): Snapshot =
553             when {
554                 previous is TransparentObserverMutableSnapshot && previous.canBeReused -> {
555                     previous.readObserver = null
556                     previous
557                 }
558                 previous is TransparentObserverSnapshot && previous.canBeReused -> {
559                     previous.readObserver = null
560                     previous
561                 }
562                 else -> {
563                     val snapshot =
564                         createTransparentSnapshotWithNoParentReadObserver(
565                             previousSnapshot = previous
566                         )
567                     snapshot.makeCurrent()
568                     snapshot
569                 }
570             }
571 
572         @PublishedApi
573         internal fun restoreNonObservable(
574             previous: Snapshot?,
575             nonObservable: Snapshot,
576             observer: ((Any) -> Unit)?
577         ) {
578             if (previous === nonObservable) {
579                 when (previous) {
580                     is TransparentObserverMutableSnapshot -> {
581                         previous.readObserver = observer
582                     }
583                     is TransparentObserverSnapshot -> {
584                         previous.readObserver = observer
585                     }
586                     else -> {
587                         error("Non-transparent snapshot was reused: $previous")
588                     }
589                 }
590             } else {
591                 nonObservable.restoreCurrent(previous)
592                 nonObservable.dispose()
593             }
594         }
595 
596         /**
597          * Passed [block] will be run with all the currently set snapshot read observers disabled.
598          */
599         @Suppress("BanInlineOptIn") // Treat Kotlin Contracts as non-experimental.
600         @OptIn(ExperimentalContracts::class)
601         inline fun <T> withoutReadObservation(block: @DisallowComposableCalls () -> T): T {
602             contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
603             val previousSnapshot = currentThreadSnapshot
604             val observer = previousSnapshot?.readObserver
605             val newSnapshot = makeCurrentNonObservable(previousSnapshot)
606             try {
607                 return block()
608             } finally {
609                 restoreNonObservable(previousSnapshot, newSnapshot, observer)
610             }
611         }
612 
613         /**
614          * Register an apply listener that is called back when snapshots are applied to the global
615          * state.
616          *
617          * @return [ObserverHandle] to unregister [observer].
618          */
619         fun registerApplyObserver(observer: (Set<Any>, Snapshot) -> Unit): ObserverHandle {
620             // Ensure observer does not see changes before this call.
621             advanceGlobalSnapshot(emptyLambda)
622 
623             sync { applyObservers += observer }
624             return ObserverHandle { sync { applyObservers -= observer } }
625         }
626 
627         /**
628          * Register an observer of the first write to the global state of a global state object
629          * since the last call to [sendApplyNotifications].
630          *
631          * Composition uses this to schedule a new composition whenever a state object that was read
632          * in composition is modified.
633          *
634          * State objects can be sent to the apply observer that have not been sent to global write
635          * observers. This happens for state objects inside [MutableSnapshot] that is later applied
636          * by calling [MutableSnapshot.apply].
637          *
638          * This should only be used to determine if a call to [sendApplyNotifications] should be
639          * scheduled to be called.
640          *
641          * @return [ObserverHandle] to unregister [observer].
642          */
643         fun registerGlobalWriteObserver(observer: ((Any) -> Unit)): ObserverHandle {
644             sync { globalWriteObservers += observer }
645             advanceGlobalSnapshot()
646             return ObserverHandle {
647                 sync { globalWriteObservers -= observer }
648                 advanceGlobalSnapshot()
649             }
650         }
651 
652         /**
653          * Notify the snapshot that all objects created in this snapshot to this point should be
654          * considered initialized. If any state object is are modified passed this point it will
655          * appear as modified in the snapshot and any applicable snapshot write observer will be
656          * called for the object and the object will be part of the a set of mutated objects sent to
657          * any applicable snapshot apply observer.
658          *
659          * Unless [notifyObjectsInitialized] is called, state objects created in a snapshot are not
660          * considered modified by the snapshot even if they are modified after construction.
661          *
662          * Compose uses this between phases of composition to allow observing changes to state
663          * objects create in a previous phase.
664          */
665         fun notifyObjectsInitialized() = currentSnapshot().notifyObjectsInitialized()
666 
667         /**
668          * Send any pending apply notifications for state objects changed outside a snapshot.
669          *
670          * Apply notifications for state objects modified outside snapshot are deferred until method
671          * is called. This method is implicitly called whenever a non-nested [MutableSnapshot] is
672          * applied making its changes visible to all new, non-nested snapshots.
673          *
674          * Composition schedules this to be called after changes to state objects are detected an
675          * observer registered with [registerGlobalWriteObserver].
676          */
677         fun sendApplyNotifications() {
678             val changes = sync { globalSnapshot.hasPendingChanges() }
679             if (changes) advanceGlobalSnapshot()
680         }
681 
682         @InternalComposeApi fun openSnapshotCount() = openSnapshots.toList().size
683 
684         @PublishedApi
685         internal fun removeCurrent(): Snapshot? {
686             val previous = threadSnapshot.get()
687             if (previous != null) threadSnapshot.set(null)
688             return previous
689         }
690 
691         @PublishedApi
692         internal fun restoreCurrent(previous: Snapshot?) {
693             if (previous != null) threadSnapshot.set(previous)
694         }
695     }
696 }
697 
698 /**
699  * Pin the snapshot and invalid set.
700  *
701  * @return returns a handle that should be passed to [releasePinningLocked] when the snapshot closes
702  *   or is disposed.
703  */
trackPinningnull704 internal fun trackPinning(snapshotId: SnapshotId, invalid: SnapshotIdSet): Int {
705     val pinned = invalid.lowest(snapshotId)
706     return sync { pinningTable.add(pinned) }
707 }
708 
709 /** Release the [handle] returned by [trackPinning] */
releasePinningLockednull710 internal fun releasePinningLocked(handle: Int) {
711     pinningTable.remove(handle)
712 }
713 
714 /**
715  * A snapshot of the values return by mutable states and other state objects. All state object will
716  * have the same value in the snapshot as they had when the snapshot was created unless they are
717  * explicitly changed in the snapshot.
718  *
719  * To enter a snapshot call [enter]. The snapshot is the current snapshot as returned by
720  * [currentSnapshot] until the control returns from the lambda (or until a nested [enter] is called.
721  * All state objects will return the values associated with this snapshot, locally in the thread,
722  * until [enter] returns. All other threads are unaffected.
723  *
724  * All changes made in a [MutableSnapshot] are snapshot isolated from all other snapshots and their
725  * changes can only be seen globally, or by new shots, after [MutableSnapshot.apply] as been called.
726  *
727  * Snapshots can be nested by calling [takeNestedSnapshot] or
728  * [MutableSnapshot.takeNestedMutableSnapshot].
729  *
730  * @see Snapshot.takeMutableSnapshot
731  * @see androidx.compose.runtime.mutableStateOf
732  * @see androidx.compose.runtime.mutableStateListOf
733  * @see androidx.compose.runtime.mutableStateMapOf
734  */
735 open class MutableSnapshot
736 internal constructor(
737     snapshotId: SnapshotId,
738     invalid: SnapshotIdSet,
739     override val readObserver: ((Any) -> Unit)?,
740     override val writeObserver: ((Any) -> Unit)?
741 ) : Snapshot(snapshotId, invalid) {
742     /**
743      * Whether there are any pending changes in this snapshot. These changes are not visible until
744      * the snapshot is applied.
745      */
hasPendingChangesnull746     override fun hasPendingChanges(): Boolean = modified?.isNotEmpty() == true
747 
748     /**
749      * Take a mutable snapshot of the state values in this snapshot. Entering this snapshot by
750      * calling [enter] allows state objects to be modified that are not visible to the this, the
751      * parent snapshot, until the [apply] is called.
752      *
753      * Applying a nested snapshot, by calling [apply], applies its change to, this, the parent
754      * snapshot. For a change to be visible globally, all the parent snapshots need to be applied
755      * until the root snapshot is applied to the global state.
756      *
757      * All nested snapshots need to be disposed by calling [dispose] before resources associated
758      * with this snapshot can be collected. Nested active snapshots are still valid after the parent
759      * has been disposed but calling [apply] will fail.
760      */
761     @OptIn(ExperimentalComposeRuntimeApi::class)
762     open fun takeNestedMutableSnapshot(
763         readObserver: ((Any) -> Unit)? = null,
764         writeObserver: ((Any) -> Unit)? = null
765     ): MutableSnapshot {
766         validateNotDisposed()
767         validateNotAppliedOrPinned()
768         return creatingSnapshot(this, readObserver, writeObserver, readonly = false) {
769             actualReadObserver,
770             actualWriteObserver ->
771             advance {
772                 sync {
773                     val newId = nextSnapshotId
774                     nextSnapshotId += 1
775                     openSnapshots = openSnapshots.set(newId)
776                     val currentInvalid = invalid
777                     this.invalid = currentInvalid.set(newId)
778                     NestedMutableSnapshot(
779                         newId,
780                         currentInvalid.addRange(snapshotId + 1, newId),
781                         mergedReadObserver(actualReadObserver, this.readObserver),
782                         mergedWriteObserver(actualWriteObserver, this.writeObserver),
783                         this
784                     )
785                 }
786             }
787         }
788     }
789 
790     /**
791      * Apply the changes made to state objects in this snapshot to the global state, or to the
792      * parent snapshot if this is a nested mutable snapshot.
793      *
794      * Once this method returns all changes made to this snapshot are atomically visible as the
795      * global state of the state object or to the parent snapshot.
796      *
797      * While a snapshot is active (after it is created but before [apply] or [dispose] is called)
798      * requires resources to track the values in the snapshot. Once a snapshot is no longer needed
799      * it should be either applied by calling [apply] or disposed by calling [dispose]. A snapshot
800      * that has been had is [apply] called can also have [dispose] called on it. However, calling
801      * [apply] after calling [dispose] will throw an exception.
802      *
803      * Leaving a snapshot active could cause hard to diagnose memory leaks values are maintained by
804      * state objects for unneeded snapshots. Take care to always call [dispose] on any snapshot.
805      */
applynull806     open fun apply(): SnapshotApplyResult {
807         // NOTE: the this algorithm is currently does not guarantee serializable snapshots as it
808         // doesn't prevent crossing writes as described here https://arxiv.org/pdf/1412.2324.pdf
809 
810         // Just removing the snapshot from the active snapshot set is enough to make it part of the
811         // next snapshot, however, this should only be done after first determining that there are
812         // no
813         // colliding writes are being applied.
814 
815         // A write is considered colliding if any write occurred in a state object in a snapshot
816         // applied since the snapshot was taken.
817         val modified = modified
818         val optimisticMerges =
819             if (modified != null) {
820                 val globalSnapshot = globalSnapshot
821                 optimisticMerges(
822                     globalSnapshot.snapshotId,
823                     this,
824                     openSnapshots.clear(globalSnapshot.snapshotId)
825                 )
826             } else null
827 
828         var observers = emptyList<(Set<Any>, Snapshot) -> Unit>()
829         var globalModified: MutableScatterSet<StateObject>? = null
830         sync {
831             validateOpen(this)
832             if (modified == null || modified.size == 0) {
833                 closeLocked()
834                 val globalSnapshot = globalSnapshot
835                 val previousModified = globalSnapshot.modified
836                 resetGlobalSnapshotLocked(globalSnapshot, emptyLambda)
837                 if (previousModified != null && previousModified.isNotEmpty()) {
838                     observers = applyObservers
839                     globalModified = previousModified
840                 }
841             } else {
842                 val globalSnapshot = globalSnapshot
843                 val result =
844                     innerApplyLocked(
845                         nextSnapshotId,
846                         modified,
847                         optimisticMerges,
848                         openSnapshots.clear(globalSnapshot.snapshotId)
849                     )
850                 if (result != SnapshotApplyResult.Success) return result
851 
852                 closeLocked()
853 
854                 // Take a new global snapshot that includes this one.
855                 val previousModified = globalSnapshot.modified
856                 resetGlobalSnapshotLocked(globalSnapshot, emptyLambda)
857                 this.modified = null
858                 globalSnapshot.modified = null
859 
860                 observers = applyObservers
861                 globalModified = previousModified
862             }
863         }
864 
865         // Mark as applied
866         applied = true
867 
868         // Notify any apply observers that changes applied were seen
869         if (globalModified != null) {
870             val nonNullGlobalModified = globalModified!!.wrapIntoSet()
871             if (nonNullGlobalModified.isNotEmpty()) {
872                 observers.fastForEach { it(nonNullGlobalModified, this) }
873             }
874         }
875 
876         if (modified != null && modified.isNotEmpty()) {
877             val modifiedSet = modified.wrapIntoSet()
878             observers.fastForEach { it(modifiedSet, this) }
879         }
880 
881         dispatchObserverOnApplied(this, modified)
882 
883         // Wait to release pinned snapshots until after running observers.
884         // This permits observers to safely take a nested snapshot of the one that was just applied
885         // before unpinning records that need to be retained in this case.
886         sync {
887             releasePinnedSnapshotsForCloseLocked()
888             checkAndOverwriteUnusedRecordsLocked()
889             globalModified?.forEach { processForUnusedRecordsLocked(it) }
890             modified?.forEach { processForUnusedRecordsLocked(it) }
891             merged?.fastForEach { processForUnusedRecordsLocked(it) }
892             merged = null
893         }
894 
895         return SnapshotApplyResult.Success
896     }
897 
898     override val readOnly: Boolean
899         get() = false
900 
901     override val root: Snapshot
902         get() = this
903 
disposenull904     override fun dispose() {
905         if (!disposed) {
906             super.dispose()
907             nestedDeactivated(this)
908             dispatchObserverOnPreDispose(this)
909         }
910     }
911 
912     @OptIn(ExperimentalComposeRuntimeApi::class)
takeNestedSnapshotnull913     override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?): Snapshot {
914         validateNotDisposed()
915         validateNotAppliedOrPinned()
916         val previousId = snapshotId
917         return creatingSnapshot(
918             if (this is GlobalSnapshot) null else this,
919             readObserver = readObserver,
920             writeObserver = null,
921             readonly = true
922         ) { actualReadObserver, _ ->
923             advance {
924                 sync {
925                     val readonlyId = nextSnapshotId.also { nextSnapshotId += 1 }
926                     openSnapshots = openSnapshots.set(readonlyId)
927                     NestedReadonlySnapshot(
928                         snapshotId = readonlyId,
929                         invalid = invalid.addRange(previousId + 1, readonlyId),
930                         readObserver = mergedReadObserver(actualReadObserver, this.readObserver),
931                         parent = this
932                     )
933                 }
934             }
935         }
936     }
937 
nestedActivatednull938     override fun nestedActivated(snapshot: Snapshot) {
939         snapshots++
940     }
941 
nestedDeactivatednull942     override fun nestedDeactivated(snapshot: Snapshot) {
943         requirePrecondition(snapshots > 0) { "no pending nested snapshots" }
944         if (--snapshots == 0) {
945             if (!applied) {
946                 abandon()
947             }
948         }
949     }
950 
notifyObjectsInitializednull951     override fun notifyObjectsInitialized() {
952         if (applied || disposed) return
953         advance()
954     }
955 
closeLockednull956     override fun closeLocked() {
957         // Remove itself and previous ids from the open set.
958         openSnapshots = openSnapshots.clear(snapshotId).andNot(previousIds)
959     }
960 
releasePinnedSnapshotsForCloseLockednull961     override fun releasePinnedSnapshotsForCloseLocked() {
962         releasePreviouslyPinnedSnapshotsLocked()
963         super.releasePinnedSnapshotsForCloseLocked()
964     }
965 
validateNotAppliednull966     private fun validateNotApplied() {
967         checkPrecondition(!applied) { "Unsupported operation on a snapshot that has been applied" }
968     }
969 
validateNotAppliedOrPinnednull970     private fun validateNotAppliedOrPinned() {
971         checkPrecondition(!applied || isPinned) {
972             "Unsupported operation on a disposed or applied snapshot"
973         }
974     }
975 
976     /**
977      * Abandon the snapshot. This does NOT [closeAndReleasePinning], which must be done as an
978      * additional step by callers.
979      */
abandonnull980     private fun abandon() {
981         val modified = modified
982         if (modified != null) {
983             validateNotApplied()
984 
985             // Mark all state records created in this snapshot as invalid. This allows the snapshot
986             // id to be forgotten as no state records will refer to it.
987             this.modified = null
988             val id = snapshotId
989             modified.forEach { state ->
990                 var current: StateRecord? = state.firstStateRecord
991                 while (current != null) {
992                     if (current.snapshotId == id || current.snapshotId in previousIds) {
993                         current.snapshotId = INVALID_SNAPSHOT
994                     }
995                     current = current.next
996                 }
997             }
998         }
999 
1000         // The snapshot can now be closed.
1001         closeAndReleasePinning()
1002     }
1003 
innerApplyLockednull1004     internal fun innerApplyLocked(
1005         nextId: SnapshotId,
1006         modified: MutableScatterSet<StateObject>,
1007         optimisticMerges: Map<StateRecord, StateRecord>?,
1008         invalidSnapshots: SnapshotIdSet
1009     ): SnapshotApplyResult {
1010         // This must be called in a synchronized block
1011 
1012         // If there are modifications we need to ensure none of the modifications have
1013         // collisions.
1014 
1015         // A record is guaranteed not collide if no other write was performed to the record
1016         // by an applied snapshot since this snapshot was taken. No writes to a state object
1017         // occurred if, ignoring this snapshot, the readable records for the snapshots are
1018         // the same. If they are different then there is a potential collision and the state
1019         // object is asked if it can resolve the collision. If it can the updated state record
1020         // is for the apply.
1021         var mergedRecords: MutableList<Pair<StateObject, StateRecord>>? = null
1022         val start = this.invalid.set(this.snapshotId).or(this.previousIds)
1023         var statesToRemove: MutableList<StateObject>? = null
1024         modified.forEach { state ->
1025             val first = state.firstStateRecord
1026             // If either current or previous cannot be calculated the object was created
1027             // in a nested snapshot that was committed then changed.
1028             val current = readable(first, nextId, invalidSnapshots) ?: return@forEach
1029             val previous = readable(first, this.snapshotId, start) ?: return@forEach
1030             if (previous.snapshotId == PreexistingSnapshotId.toSnapshotId()) {
1031                 // A previous record might not be found if the state object was created in a
1032                 // nested snapshot that didn't have any other modifications. The `apply()` for
1033                 // a nested snapshot considers such snapshots no-op snapshots and just closes them
1034                 // which allows this object's previous record to be missing or be the record created
1035                 // during initial construction. In these cases taking applied is the right choice
1036                 // this indicates there was no conflicting writes.
1037                 return@forEach
1038             }
1039             if (current != previous) {
1040                 val applied = readable(first, this.snapshotId, this.invalid) ?: readError()
1041                 val merged =
1042                     optimisticMerges?.get(current)
1043                         ?: run { state.mergeRecords(previous, current, applied) }
1044                 when (merged) {
1045                     null -> return SnapshotApplyResult.Failure(this)
1046                     applied -> {
1047                         // Nothing to do the merge policy says that the current changes
1048                         // obscure the current value so ignore the conflict
1049                     }
1050                     current -> {
1051                         (mergedRecords
1052                                 ?: mutableListOf<Pair<StateObject, StateRecord>>().also {
1053                                     mergedRecords = it
1054                                 })
1055                             .add(state to current.create(snapshotId))
1056 
1057                         // If we revert to current then the state is no longer modified.
1058                         (statesToRemove
1059                                 ?: mutableListOf<StateObject>().also { statesToRemove = it })
1060                             .add(state)
1061                     }
1062                     else -> {
1063                         (mergedRecords
1064                                 ?: mutableListOf<Pair<StateObject, StateRecord>>().also {
1065                                     mergedRecords = it
1066                                 })
1067                             .add(
1068                                 if (merged != previous) state to merged
1069                                 else state to previous.create(snapshotId)
1070                             )
1071                     }
1072                 }
1073             }
1074         }
1075 
1076         mergedRecords?.let {
1077             // Ensure we have a new snapshot id
1078             advance()
1079 
1080             // Update all the merged records to have the new id.
1081             it.fastForEach { merged ->
1082                 val (state, stateRecord) = merged
1083                 stateRecord.snapshotId = nextId
1084                 sync {
1085                     stateRecord.next = state.firstStateRecord
1086                     state.prependStateRecord(stateRecord)
1087                 }
1088             }
1089         }
1090 
1091         statesToRemove?.let { list ->
1092             list.fastForEach { modified.remove(it) }
1093             val mergedList = merged
1094             merged = if (mergedList == null) list else mergedList + list
1095         }
1096 
1097         return SnapshotApplyResult.Success
1098     }
1099 
advancenull1100     internal inline fun <T> advance(block: () -> T): T {
1101         recordPrevious(snapshotId)
1102         return block().also {
1103             // Only advance this snapshot if it's possible for it to be applied later,
1104             // otherwise we don't need to bother.
1105             // This simplifies tracking of open snapshots when an apply observer takes
1106             // a nested snapshot of the snapshot that was just applied.
1107             if (!applied && !disposed) {
1108                 val previousId = snapshotId
1109                 sync {
1110                     snapshotId = nextSnapshotId.also { nextSnapshotId += 1 }
1111                     openSnapshots = openSnapshots.set(snapshotId)
1112                 }
1113                 invalid = invalid.addRange(previousId + 1, snapshotId)
1114             }
1115         }
1116     }
1117 
<lambda>null1118     internal fun advance(): Unit = advance {}
1119 
recordPreviousnull1120     internal fun recordPrevious(id: SnapshotId) {
1121         sync { previousIds = previousIds.set(id) }
1122     }
1123 
recordPreviousPinnedSnapshotnull1124     internal fun recordPreviousPinnedSnapshot(id: Int) {
1125         if (id >= 0) previousPinnedSnapshots += id
1126     }
1127 
recordPreviousPinnedSnapshotsnull1128     internal fun recordPreviousPinnedSnapshots(handles: IntArray) {
1129         // Avoid unnecessary copies implied by the `+` below.
1130         if (handles.isEmpty()) return
1131         val pinned = previousPinnedSnapshots
1132         previousPinnedSnapshots = if (pinned.isEmpty()) handles else pinned + handles
1133     }
1134 
releasePreviouslyPinnedSnapshotsLockednull1135     private fun releasePreviouslyPinnedSnapshotsLocked() {
1136         for (index in previousPinnedSnapshots.indices) {
1137             releasePinningLocked(previousPinnedSnapshots[index])
1138         }
1139     }
1140 
recordPreviousListnull1141     internal fun recordPreviousList(snapshots: SnapshotIdSet) {
1142         sync { previousIds = previousIds.or(snapshots) }
1143     }
1144 
recordModifiednull1145     override fun recordModified(state: StateObject) {
1146         (modified ?: mutableScatterSetOf<StateObject>().also { modified = it }).add(state)
1147     }
1148 
1149     override var writeCount: Int = 0
1150 
1151     override var modified: MutableScatterSet<StateObject>? = null
1152 
1153     internal var merged: List<StateObject>? = null
1154 
1155     /**
1156      * A set of the id's previously associated with this snapshot. When this snapshot closes then
1157      * these ids must be removed from the global as well.
1158      */
1159     internal var previousIds: SnapshotIdSet = SnapshotIdSet.EMPTY
1160 
1161     /** A list of the pinned snapshots handles that must be released by this snapshot */
1162     internal var previousPinnedSnapshots: IntArray = EmptyIntArray
1163 
1164     /**
1165      * The number of pending nested snapshots of this snapshot. To simplify the code, this snapshot
1166      * it, itself, counted as its own nested snapshot.
1167      */
1168     private var snapshots = 1
1169 
1170     /** Tracks whether the snapshot has been applied. */
1171     internal var applied = false
1172 
1173     private companion object {
1174         private val EmptyIntArray = IntArray(0)
1175     }
1176 }
1177 
1178 /**
1179  * The result of a applying a mutable snapshot. [Success] indicates that the snapshot was
1180  * successfully applied and is now visible as the global state of the state object (or visible in
1181  * the parent snapshot for a nested snapshot). [Failure] indicates one or more state objects were
1182  * modified by both this snapshot and in the global (or parent) snapshot, and the changes from this
1183  * snapshot are **not** visible in the global or parent snapshot.
1184  */
1185 sealed class SnapshotApplyResult {
1186     /**
1187      * Check the result of an apply. If the result is [Success] then this does does nothing. If the
1188      * result is [Failure] then a [SnapshotApplyConflictException] exception is thrown. Once [check]
1189      * as been called the snapshot is disposed.
1190      */
checknull1191     abstract fun check()
1192 
1193     /** True if the result is [Success]. */
1194     abstract val succeeded: Boolean
1195 
1196     object Success : SnapshotApplyResult() {
1197         /**
1198          * Check the result of a snapshot apply. Calling [check] on a [Success] result is a noop.
1199          */
1200         override fun check() {}
1201 
1202         override val succeeded: Boolean
1203             get() = true
1204     }
1205 
1206     class Failure(val snapshot: Snapshot) : SnapshotApplyResult() {
1207         /**
1208          * Check the result of a snapshot apply. Calling [check] on a [Failure] result throws a
1209          * [SnapshotApplyConflictException] exception.
1210          */
checknull1211         override fun check() {
1212             snapshot.dispose()
1213             throw SnapshotApplyConflictException(snapshot)
1214         }
1215 
1216         override val succeeded: Boolean
1217             get() = false
1218     }
1219 }
1220 
1221 /**
1222  * The type returned by observer registration methods that unregisters the observer when it is
1223  * disposed.
1224  */
1225 @Suppress("CallbackName")
interfacenull1226 fun interface ObserverHandle {
1227     /** Dispose the observer causing it to be unregistered from the snapshot system. */
1228     fun dispose()
1229 }
1230 
1231 /**
1232  * Return the thread's active snapshot. If no thread snapshot is active then the current global
1233  * snapshot is used.
1234  */
currentSnapshotnull1235 internal fun currentSnapshot(): Snapshot = threadSnapshot.get() ?: globalSnapshot
1236 
1237 /**
1238  * An exception that is thrown when [SnapshotApplyResult.check] is called on a result of a
1239  * [MutableSnapshot.apply] that fails to apply.
1240  */
1241 class SnapshotApplyConflictException(@Suppress("unused") val snapshot: Snapshot) : Exception()
1242 
1243 /** Snapshot local value of a state object. */
1244 abstract class StateRecord(
1245     /** The snapshot id of the snapshot in which the record was created. */
1246     internal var snapshotId: SnapshotId
1247 ) {
1248     constructor() : this(currentSnapshot().snapshotId)
1249 
1250     @Deprecated("Use snapshotId: Long constructor instead")
1251     constructor(id: Int) : this(id.toSnapshotId())
1252 
1253     /**
1254      * Reference of the next state record. State records are stored in a linked list.
1255      *
1256      * Changes to [next] must preserve all existing records to all threads even during
1257      * intermediately changes. For example, it is safe to add the beginning or end of the list but
1258      * adding to the middle requires care. First the new record must have its [next] updated then
1259      * the [next] of its new predecessor can then be set to point to it. This implies that records
1260      * that are already in the list cannot be moved in the list as this the change must be atomic to
1261      * all threads that cannot happen without a lock which this list cannot afford.
1262      *
1263      * It is unsafe to remove a record as it might be in the process of being reused (see
1264      * [usedLocked]). If a record is removed care must be taken to ensure that it is not being
1265      * claimed by some other thread. This would require changes to [usedLocked].
1266      */
1267     internal var next: StateRecord? = null
1268 
1269     /** Copy the value into this state record from another for the same state object. */
1270     abstract fun assign(value: StateRecord)
1271 
1272     /**
1273      * Create a new state record for the same state object. Consider also implementing the [create]
1274      * overload that provides snapshotId for faster record construction when snapshot id is known.
1275      */
1276     abstract fun create(): StateRecord
1277 
1278     /**
1279      * Create a new state record for the same state object and provided [snapshotId]. This allows to
1280      * implement an optimized version of [create] to avoid accessing [currentSnapshot] when snapshot
1281      * id is known. The default implementation provides a backwards compatible behavior, and should
1282      * be overridden if [StateRecord] subclass supports this optimization.
1283      */
1284     @Deprecated("Use snapshotId: Long version instead", level = DeprecationLevel.HIDDEN)
1285     open fun create(snapshotId: Int): StateRecord =
1286         create().also { it.snapshotId = snapshotId.toSnapshotId() }
1287 
1288     /**
1289      * Create a new state record for the same state object and provided [snapshotId]. This allows to
1290      * implement an optimized version of [create] to avoid accessing [currentSnapshot] when snapshot
1291      * id is known. The default implementation provides a backwards compatible behavior, and should
1292      * be overridden if [StateRecord] subclass supports this optimization.
1293      */
1294     open fun create(snapshotId: SnapshotId): StateRecord =
1295         create().also { it.snapshotId = snapshotId }
1296 }
1297 
1298 /**
1299  * Interface implemented by all snapshot aware state objects. Used by this module to maintain the
1300  * state records of a state object.
1301  */
1302 @JvmDefaultWithCompatibility
1303 interface StateObject {
1304     /** The first state record in a linked list of state records. */
1305     val firstStateRecord: StateRecord
1306 
1307     /**
1308      * Add a new state record to the beginning of a list. After this call [firstStateRecord] should
1309      * be [value].
1310      */
prependStateRecordnull1311     fun prependStateRecord(value: StateRecord)
1312 
1313     /**
1314      * Produce a merged state based on the conflicting state changes.
1315      *
1316      * This method must not modify any of the records received and should treat the state records as
1317      * immutable, even the [applied] record.
1318      *
1319      * @param previous the state record that was used to create the [applied] record and is a state
1320      *   that also (though indirectly) produced the [current] record.
1321      * @param current the state record of the parent snapshot or global state.
1322      * @param applied the state record that is being applied of the parent snapshot or global state.
1323      * @return the modified state or `null` if the values cannot be merged. If the states cannot be
1324      *   merged the current apply will fail. Any of the parameters can be returned as a result. If
1325      *   it is not one of the parameter values then it *must* be a new value that is created by
1326      *   calling [StateRecord.create] on one of the records passed and then can be modified to have
1327      *   the merged value before being returned. If a new record is returned [MutableSnapshot.apply]
1328      *   will update the internal snapshot id and call [prependStateRecord] if the record is used.
1329      */
1330     fun mergeRecords(
1331         previous: StateRecord,
1332         current: StateRecord,
1333         applied: StateRecord
1334     ): StateRecord? = null
1335 }
1336 
1337 /**
1338  * A snapshot whose state objects cannot be modified. If a state object is modified when in a
1339  * read-only snapshot a [IllegalStateException] is thrown.
1340  */
1341 internal class ReadonlySnapshot
1342 internal constructor(
1343     snapshotId: SnapshotId,
1344     invalid: SnapshotIdSet,
1345     override val readObserver: ((Any) -> Unit)?
1346 ) : Snapshot(snapshotId, invalid) {
1347     /**
1348      * The number of nested snapshots that are active. To simplify the code, this snapshot counts
1349      * itself as a nested snapshot.
1350      */
1351     private var snapshots = 1
1352     override val readOnly: Boolean
1353         get() = true
1354 
1355     override val root: Snapshot
1356         get() = this
1357 
1358     override fun hasPendingChanges(): Boolean = false
1359 
1360     override val writeObserver: ((Any) -> Unit)?
1361         get() = null
1362 
1363     override var modified: MutableScatterSet<StateObject>?
1364         get() = null
1365         @Suppress("UNUSED_PARAMETER") set(value) = unsupported()
1366 
1367     @OptIn(ExperimentalComposeRuntimeApi::class)
1368     override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?): Snapshot {
1369         validateOpen(this)
1370         return creatingSnapshot(
1371             parent = this,
1372             readObserver = readObserver,
1373             writeObserver = null,
1374             readonly = true,
1375         ) { actualReadObserver, _ ->
1376             NestedReadonlySnapshot(
1377                 snapshotId = snapshotId,
1378                 invalid = invalid,
1379                 readObserver = mergedReadObserver(actualReadObserver, this.readObserver),
1380                 parent = this
1381             )
1382         }
1383     }
1384 
1385     override fun notifyObjectsInitialized() {
1386         // Nothing to do for read-only snapshots
1387     }
1388 
1389     override fun dispose() {
1390         if (!disposed) {
1391             nestedDeactivated(this)
1392             super.dispose()
1393             dispatchObserverOnPreDispose(this)
1394         }
1395     }
1396 
1397     override fun nestedActivated(snapshot: Snapshot) {
1398         snapshots++
1399     }
1400 
1401     override fun nestedDeactivated(snapshot: Snapshot) {
1402         if (--snapshots == 0) {
1403             // A read-only snapshot can be just be closed as it has no modifications.
1404             closeAndReleasePinning()
1405         }
1406     }
1407 
1408     override fun recordModified(state: StateObject) {
1409         reportReadonlySnapshotWrite()
1410     }
1411 }
1412 
1413 internal class NestedReadonlySnapshot(
1414     snapshotId: SnapshotId,
1415     invalid: SnapshotIdSet,
1416     override val readObserver: ((Any) -> Unit)?,
1417     val parent: Snapshot
1418 ) : Snapshot(snapshotId, invalid) {
1419     init {
1420         parent.nestedActivated(this)
1421     }
1422 
1423     override val readOnly
1424         get() = true
1425 
1426     override val root: Snapshot
1427         get() = parent.root
1428 
1429     @OptIn(ExperimentalComposeRuntimeApi::class)
takeNestedSnapshotnull1430     override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?) =
1431         creatingSnapshot(
1432             parent = this,
1433             readObserver = readObserver,
1434             writeObserver = null,
1435             readonly = true,
1436         ) { actualReadObserver, _ ->
1437             NestedReadonlySnapshot(
1438                 snapshotId = snapshotId,
1439                 invalid = invalid,
1440                 readObserver = mergedReadObserver(actualReadObserver, this.readObserver),
1441                 parent = parent
1442             )
1443         }
1444 
notifyObjectsInitializednull1445     override fun notifyObjectsInitialized() {
1446         // Nothing to do for read-only snapshots
1447     }
1448 
hasPendingChangesnull1449     override fun hasPendingChanges(): Boolean = false
1450 
1451     override fun dispose() {
1452         if (!disposed) {
1453             if (snapshotId != parent.snapshotId) {
1454                 closeAndReleasePinning()
1455             }
1456             parent.nestedDeactivated(this)
1457             super.dispose()
1458             dispatchObserverOnPreDispose(this)
1459         }
1460     }
1461 
1462     override val modified: MutableScatterSet<StateObject>?
1463         get() = null
1464 
1465     override val writeObserver: ((Any) -> Unit)?
1466         get() = null
1467 
recordModifiednull1468     override fun recordModified(state: StateObject) = reportReadonlySnapshotWrite()
1469 
1470     override fun nestedDeactivated(snapshot: Snapshot) = unsupported()
1471 
1472     override fun nestedActivated(snapshot: Snapshot) = unsupported()
1473 }
1474 
1475 private val emptyLambda: (invalid: SnapshotIdSet) -> Unit = {}
1476 
1477 /**
1478  * A snapshot object that simplifies the code by treating the global state as a mutable snapshot.
1479  */
1480 internal class GlobalSnapshot(snapshotId: SnapshotId, invalid: SnapshotIdSet) :
1481     MutableSnapshot(
1482         snapshotId,
1483         invalid,
1484         null,
<lambda>null1485         { state -> sync { globalWriteObservers.fastForEach { it(state) } } }
1486     ) {
1487 
1488     @OptIn(ExperimentalComposeRuntimeApi::class)
takeNestedSnapshotnull1489     override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?): Snapshot =
1490         creatingSnapshot(
1491             parent = null,
1492             readonly = true,
1493             readObserver = readObserver,
1494             writeObserver = null,
1495         ) { actualReadObserver, _ ->
1496             takeNewSnapshot { invalid ->
1497                 ReadonlySnapshot(
1498                     snapshotId = sync { nextSnapshotId.also { nextSnapshotId += 1 } },
1499                     invalid = invalid,
1500                     readObserver = actualReadObserver
1501                 )
1502             }
1503         }
1504 
1505     @OptIn(ExperimentalComposeRuntimeApi::class)
takeNestedMutableSnapshotnull1506     override fun takeNestedMutableSnapshot(
1507         readObserver: ((Any) -> Unit)?,
1508         writeObserver: ((Any) -> Unit)?
1509     ): MutableSnapshot =
1510         creatingSnapshot(
1511             parent = null,
1512             readonly = false,
1513             readObserver = readObserver,
1514             writeObserver = writeObserver
1515         ) { actualReadObserver, actualWriteObserver ->
1516             takeNewSnapshot { invalid ->
1517                 MutableSnapshot(
1518                     snapshotId = sync { nextSnapshotId.also { nextSnapshotId += 1 } },
1519                     invalid = invalid,
1520 
1521                     // It is intentional that the global read observers are not merged with mutable
1522                     // snapshots read observers.
1523                     readObserver = actualReadObserver,
1524 
1525                     // It is intentional that global write observers are not merged with mutable
1526                     // snapshots write observers.
1527                     writeObserver = actualWriteObserver
1528                 )
1529             }
1530         }
1531 
notifyObjectsInitializednull1532     override fun notifyObjectsInitialized() {
1533         advanceGlobalSnapshot()
1534     }
1535 
nestedDeactivatednull1536     override fun nestedDeactivated(snapshot: Snapshot) = unsupported()
1537 
1538     override fun nestedActivated(snapshot: Snapshot) = unsupported()
1539 
1540     override fun apply(): SnapshotApplyResult =
1541         error("Cannot apply the global snapshot directly. Call Snapshot.advanceGlobalSnapshot")
1542 
1543     override fun dispose() {
1544         sync { releasePinnedSnapshotLocked() }
1545     }
1546 }
1547 
1548 /** A nested mutable snapshot created by [MutableSnapshot.takeNestedMutableSnapshot]. */
1549 internal class NestedMutableSnapshot(
1550     snapshotId: SnapshotId,
1551     invalid: SnapshotIdSet,
1552     readObserver: ((Any) -> Unit)?,
1553     writeObserver: ((Any) -> Unit)?,
1554     val parent: MutableSnapshot
1555 ) : MutableSnapshot(snapshotId, invalid, readObserver, writeObserver) {
1556     private var deactivated = false
1557 
1558     init {
1559         parent.nestedActivated(this)
1560     }
1561 
1562     override val root: Snapshot
1563         get() = parent.root
1564 
disposenull1565     override fun dispose() {
1566         if (!disposed) {
1567             super.dispose()
1568             deactivate()
1569         }
1570     }
1571 
applynull1572     override fun apply(): SnapshotApplyResult {
1573         if (parent.applied || parent.disposed) return SnapshotApplyResult.Failure(this)
1574 
1575         // Applying a nested mutable snapshot applies its changes to the parent snapshot.
1576 
1577         // See MutableSnapshot.apply() for implantation notes.
1578 
1579         // The apply observer notification are for applying to the global scope so it is elided
1580         // here making this code a bit simpler than MutableSnapshot.apply.
1581 
1582         val modified = modified
1583         val id = snapshotId
1584         val optimisticMerges =
1585             if (modified != null) optimisticMerges(parent.snapshotId, this, parent.invalid)
1586             else null
1587         sync {
1588             validateOpen(this)
1589             if (modified == null || modified.size == 0) {
1590                 closeAndReleasePinning()
1591             } else {
1592                 val result =
1593                     innerApplyLocked(parent.snapshotId, modified, optimisticMerges, parent.invalid)
1594                 if (result != SnapshotApplyResult.Success) return result
1595 
1596                 parent.modified?.apply { addAll(modified) }
1597                     ?: modified.also {
1598                         // Ensure modified reference is only used by one snapshot
1599                         parent.modified = it
1600                         this.modified = null
1601                     }
1602             }
1603 
1604             // Ensure the parent is newer than the current snapshot
1605             if (parent.snapshotId < id) {
1606                 parent.advance()
1607             }
1608 
1609             // Make the snapshot visible in the parent snapshot
1610             parent.invalid = parent.invalid.clear(id).andNot(previousIds)
1611 
1612             // Ensure the ids associated with this snapshot are also applied by the parent.
1613             parent.recordPrevious(id)
1614             parent.recordPreviousPinnedSnapshot(takeoverPinnedSnapshot())
1615             parent.recordPreviousList(previousIds)
1616             parent.recordPreviousPinnedSnapshots(previousPinnedSnapshots)
1617         }
1618 
1619         applied = true
1620         deactivate()
1621         dispatchObserverOnApplied(this, modified)
1622         return SnapshotApplyResult.Success
1623     }
1624 
deactivatenull1625     private fun deactivate() {
1626         if (!deactivated) {
1627             deactivated = true
1628             parent.nestedDeactivated(this)
1629         }
1630     }
1631 }
1632 
1633 /** A pseudo snapshot that doesn't introduce isolation but does introduce observers. */
1634 internal class TransparentObserverMutableSnapshot(
1635     private val parentSnapshot: MutableSnapshot?,
1636     specifiedReadObserver: ((Any) -> Unit)?,
1637     specifiedWriteObserver: ((Any) -> Unit)?,
1638     private val mergeParentObservers: Boolean,
1639     private val ownsParentSnapshot: Boolean
1640 ) :
1641     MutableSnapshot(
1642         INVALID_SNAPSHOT,
1643         SnapshotIdSet.EMPTY,
1644         mergedReadObserver(
1645             specifiedReadObserver,
1646             parentSnapshot?.readObserver ?: globalSnapshot.readObserver,
1647             mergeParentObservers
1648         ),
1649         mergedWriteObserver(
1650             specifiedWriteObserver,
1651             parentSnapshot?.writeObserver ?: globalSnapshot.writeObserver
1652         )
1653     ) {
1654     override var readObserver: ((Any) -> Unit)? = super.readObserver
1655     override var writeObserver: ((Any) -> Unit)? = super.writeObserver
1656 
1657     internal val threadId: Long = currentThreadId()
1658 
1659     private val currentSnapshot: MutableSnapshot
1660         get() = parentSnapshot ?: globalSnapshot
1661 
disposenull1662     override fun dispose() {
1663         // Explicitly don't call super.dispose()
1664         disposed = true
1665         if (ownsParentSnapshot) {
1666             parentSnapshot?.dispose()
1667         }
1668     }
1669 
1670     override var snapshotId: SnapshotId
1671         get() = currentSnapshot.snapshotId
1672         @Suppress("UNUSED_PARAMETER")
1673         set(value) {
1674             unsupported()
1675         }
1676 
1677     override var invalid
1678         get() = currentSnapshot.invalid
1679         @Suppress("UNUSED_PARAMETER") set(value) = unsupported()
1680 
hasPendingChangesnull1681     override fun hasPendingChanges(): Boolean = currentSnapshot.hasPendingChanges()
1682 
1683     override var modified: MutableScatterSet<StateObject>?
1684         get() = currentSnapshot.modified
1685         @Suppress("UNUSED_PARAMETER") set(value) = unsupported()
1686 
1687     override var writeCount: Int
1688         get() = currentSnapshot.writeCount
1689         set(value) {
1690             currentSnapshot.writeCount = value
1691         }
1692 
1693     override val readOnly: Boolean
1694         get() = currentSnapshot.readOnly
1695 
applynull1696     override fun apply(): SnapshotApplyResult = currentSnapshot.apply()
1697 
1698     override fun recordModified(state: StateObject) = currentSnapshot.recordModified(state)
1699 
1700     override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?): Snapshot {
1701         val mergedReadObserver = mergedReadObserver(readObserver, this.readObserver)
1702         return if (!mergeParentObservers) {
1703             createTransparentSnapshotWithNoParentReadObserver(
1704                 previousSnapshot = currentSnapshot.takeNestedSnapshot(null),
1705                 readObserver = mergedReadObserver,
1706                 ownsPreviousSnapshot = true
1707             )
1708         } else {
1709             currentSnapshot.takeNestedSnapshot(mergedReadObserver)
1710         }
1711     }
1712 
takeNestedMutableSnapshotnull1713     override fun takeNestedMutableSnapshot(
1714         readObserver: ((Any) -> Unit)?,
1715         writeObserver: ((Any) -> Unit)?
1716     ): MutableSnapshot {
1717         val mergedReadObserver = mergedReadObserver(readObserver, this.readObserver)
1718         val mergedWriteObserver = mergedWriteObserver(writeObserver, this.writeObserver)
1719         return if (!mergeParentObservers) {
1720             val nestedSnapshot =
1721                 currentSnapshot.takeNestedMutableSnapshot(
1722                     readObserver = null,
1723                     writeObserver = mergedWriteObserver
1724                 )
1725             TransparentObserverMutableSnapshot(
1726                 parentSnapshot = nestedSnapshot,
1727                 specifiedReadObserver = mergedReadObserver,
1728                 specifiedWriteObserver = mergedWriteObserver,
1729                 mergeParentObservers = false,
1730                 ownsParentSnapshot = true
1731             )
1732         } else {
1733             currentSnapshot.takeNestedMutableSnapshot(mergedReadObserver, mergedWriteObserver)
1734         }
1735     }
1736 
notifyObjectsInitializednull1737     override fun notifyObjectsInitialized() = currentSnapshot.notifyObjectsInitialized()
1738 
1739     /** Should never be called. */
1740     override fun nestedActivated(snapshot: Snapshot) = unsupported()
1741 
1742     override fun nestedDeactivated(snapshot: Snapshot) = unsupported()
1743 }
1744 
1745 /** A pseudo snapshot that doesn't introduce isolation but does introduce observers. */
1746 internal class TransparentObserverSnapshot(
1747     private val parentSnapshot: Snapshot?,
1748     specifiedReadObserver: ((Any) -> Unit)?,
1749     private val mergeParentObservers: Boolean,
1750     private val ownsParentSnapshot: Boolean
1751 ) :
1752     Snapshot(
1753         INVALID_SNAPSHOT,
1754         SnapshotIdSet.EMPTY,
1755     ) {
1756     override var readObserver: ((Any) -> Unit)? =
1757         mergedReadObserver(
1758             specifiedReadObserver,
1759             parentSnapshot?.readObserver ?: globalSnapshot.readObserver,
1760             mergeParentObservers
1761         )
1762     override val writeObserver: ((Any) -> Unit)? = null
1763 
1764     internal val threadId: Long = currentThreadId()
1765 
1766     override val root: Snapshot = this
1767 
1768     private val currentSnapshot: Snapshot
1769         get() = parentSnapshot ?: globalSnapshot
1770 
1771     override fun dispose() {
1772         // Explicitly don't call super.dispose()
1773         disposed = true
1774         if (ownsParentSnapshot) {
1775             parentSnapshot?.dispose()
1776         }
1777     }
1778 
1779     override var snapshotId: SnapshotId
1780         get() = currentSnapshot.snapshotId
1781         @Suppress("UNUSED_PARAMETER")
1782         set(value) {
1783             unsupported()
1784         }
1785 
1786     override var invalid
1787         get() = currentSnapshot.invalid
1788         @Suppress("UNUSED_PARAMETER") set(value) = unsupported()
1789 
1790     override fun hasPendingChanges(): Boolean = currentSnapshot.hasPendingChanges()
1791 
1792     override var modified: MutableScatterSet<StateObject>?
1793         get() = currentSnapshot.modified
1794         @Suppress("UNUSED_PARAMETER") set(value) = unsupported()
1795 
1796     override val readOnly: Boolean
1797         get() = currentSnapshot.readOnly
1798 
1799     override fun recordModified(state: StateObject) = currentSnapshot.recordModified(state)
1800 
1801     override fun takeNestedSnapshot(readObserver: ((Any) -> Unit)?): Snapshot {
1802         val mergedReadObserver = mergedReadObserver(readObserver, this.readObserver)
1803         return if (!mergeParentObservers) {
1804             createTransparentSnapshotWithNoParentReadObserver(
1805                 currentSnapshot.takeNestedSnapshot(null),
1806                 mergedReadObserver,
1807                 ownsPreviousSnapshot = true
1808             )
1809         } else {
1810             currentSnapshot.takeNestedSnapshot(mergedReadObserver)
1811         }
1812     }
1813 
1814     override fun notifyObjectsInitialized() = currentSnapshot.notifyObjectsInitialized()
1815 
1816     /** Should never be called. */
1817     override fun nestedActivated(snapshot: Snapshot) = unsupported()
1818 
1819     override fun nestedDeactivated(snapshot: Snapshot) = unsupported()
1820 }
1821 
createTransparentSnapshotWithNoParentReadObservernull1822 private fun createTransparentSnapshotWithNoParentReadObserver(
1823     previousSnapshot: Snapshot?,
1824     readObserver: ((Any) -> Unit)? = null,
1825     ownsPreviousSnapshot: Boolean = false
1826 ): Snapshot =
1827     if (previousSnapshot is MutableSnapshot || previousSnapshot == null) {
1828         TransparentObserverMutableSnapshot(
1829             parentSnapshot = previousSnapshot as? MutableSnapshot,
1830             specifiedReadObserver = readObserver,
1831             specifiedWriteObserver = null,
1832             mergeParentObservers = false,
1833             ownsParentSnapshot = ownsPreviousSnapshot
1834         )
1835     } else {
1836         TransparentObserverSnapshot(
1837             parentSnapshot = previousSnapshot,
1838             specifiedReadObserver = readObserver,
1839             mergeParentObservers = false,
1840             ownsParentSnapshot = ownsPreviousSnapshot
1841         )
1842     }
1843 
mergedReadObservernull1844 private fun mergedReadObserver(
1845     readObserver: ((Any) -> Unit)?,
1846     parentObserver: ((Any) -> Unit)?,
1847     mergeReadObserver: Boolean = true
1848 ): ((Any) -> Unit)? {
1849     @Suppress("NAME_SHADOWING") val parentObserver = if (mergeReadObserver) parentObserver else null
1850     return if (readObserver != null && parentObserver != null && readObserver !== parentObserver) {
1851         { state: Any ->
1852             readObserver(state)
1853             parentObserver(state)
1854         }
1855     } else readObserver ?: parentObserver
1856 }
1857 
mergedWriteObservernull1858 private fun mergedWriteObserver(
1859     writeObserver: ((Any) -> Unit)?,
1860     parentObserver: ((Any) -> Unit)?
1861 ): ((Any) -> Unit)? =
1862     if (writeObserver != null && parentObserver != null && writeObserver !== parentObserver) {
1863         { state: Any ->
1864             writeObserver(state)
1865             parentObserver(state)
1866         }
1867     } else writeObserver ?: parentObserver
1868 
1869 /**
1870  * Snapshot id of `0` is reserved as invalid and no state record with snapshot `0` is considered
1871  * valid.
1872  *
1873  * The value `0` was chosen as it is the default value of the Int snapshot id type and records
1874  * initially created will naturally have a snapshot id of 0. If this wasn't considered invalid
1875  * adding such a record to a state object will make the state record immediately visible to the
1876  * snapshots instead of being born invalid. Using `0` ensures all state records are created invalid
1877  * and must be explicitly marked as valid in to be visible in a snapshot.
1878  */
1879 private val INVALID_SNAPSHOT = SnapshotIdZero
1880 
1881 /** Current thread snapshot */
1882 private val threadSnapshot = SnapshotThreadLocal<Snapshot>()
1883 
1884 /**
1885  * A global synchronization object. This synchronization object should be taken before modifying any
1886  * of the fields below.
1887  */
1888 @PublishedApi internal val lock = makeSynchronizedObject()
1889 
1890 @Suppress("BanInlineOptIn", "LEAKED_IN_PLACE_LAMBDA", "WRONG_INVOCATION_KIND")
1891 @OptIn(ExperimentalContracts::class)
1892 @PublishedApi
syncnull1893 internal inline fun <T> sync(block: () -> T): T {
1894     contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
1895     return synchronized(lock, block)
1896 }
1897 
1898 // The following variables should only be written when sync is taken
1899 
1900 /**
1901  * A set of snapshots that are currently open and should be considered invalid for new snapshots.
1902  */
1903 private var openSnapshots = SnapshotIdSet.EMPTY
1904 
1905 /** The first snapshot created must be at least on more than the [Snapshot.PreexistingSnapshotId] */
1906 private var nextSnapshotId = Snapshot.PreexistingSnapshotId.toSnapshotId() + 1
1907 
1908 /**
1909  * A tracking table for pinned snapshots. A pinned snapshot is the lowest snapshot id that the
1910  * snapshot is ignoring by considering them invalid. This is used to calculate when a snapshot
1911  * record can be reused.
1912  */
1913 private val pinningTable = SnapshotDoubleIndexHeap()
1914 
1915 /**
1916  * The set of objects who have more than one active state record. These are traversed during apply
1917  * of mutable snapshots and when the global snapshot is advanced to determine if any of the records
1918  * can be cleared.
1919  */
1920 private val extraStateObjects = SnapshotWeakSet<StateObject>()
1921 
1922 /** A list of apply observers */
1923 private var applyObservers = emptyList<(Set<Any>, Snapshot) -> Unit>()
1924 
1925 /** A list of observers of writes to the global state. */
1926 private var globalWriteObservers = emptyList<(Any) -> Unit>()
1927 
1928 private val globalSnapshot =
1929     GlobalSnapshot(
<lambda>null1930             snapshotId = nextSnapshotId.also { nextSnapshotId += 1 },
1931             invalid = SnapshotIdSet.EMPTY
1932         )
<lambda>null1933         .also { openSnapshots = openSnapshots.set(it.snapshotId) }
1934 
1935 // Unused, kept for API compat
1936 @Suppress("unused") @PublishedApi internal val snapshotInitializer: Snapshot = globalSnapshot
1937 
resetGlobalSnapshotLockednull1938 private fun <T> resetGlobalSnapshotLocked(
1939     globalSnapshot: GlobalSnapshot,
1940     block: (invalid: SnapshotIdSet) -> T
1941 ): T {
1942     val snapshotId = globalSnapshot.snapshotId
1943     val result = block(openSnapshots.clear(snapshotId))
1944 
1945     val nextGlobalSnapshotId = nextSnapshotId
1946     nextSnapshotId += 1
1947 
1948     openSnapshots = openSnapshots.clear(snapshotId)
1949     globalSnapshot.snapshotId = nextGlobalSnapshotId
1950     globalSnapshot.invalid = openSnapshots
1951     globalSnapshot.writeCount = 0
1952     globalSnapshot.modified = null
1953     globalSnapshot.releasePinnedSnapshotLocked()
1954     openSnapshots = openSnapshots.set(nextGlobalSnapshotId)
1955 
1956     return result
1957 }
1958 
1959 /**
1960  * Counts the number of threads currently inside `advanceGlobalSnapshot`, notifying observers of
1961  * changes to the global snapshot.
1962  */
1963 private var pendingApplyObserverCount = AtomicInt(0)
1964 
advanceGlobalSnapshotnull1965 private fun <T> advanceGlobalSnapshot(block: (invalid: SnapshotIdSet) -> T): T {
1966     val globalSnapshot = globalSnapshot
1967 
1968     val modified: MutableScatterSet<StateObject>?
1969     val result = sync {
1970         modified = globalSnapshot.modified
1971         if (modified != null) {
1972             pendingApplyObserverCount.add(1)
1973         }
1974         resetGlobalSnapshotLocked(globalSnapshot, block)
1975     }
1976 
1977     // If the previous global snapshot had any modified states then notify the registered apply
1978     // observers.
1979     modified?.let {
1980         try {
1981             val observers = applyObservers
1982             observers.fastForEach { observer -> observer(it.wrapIntoSet(), globalSnapshot) }
1983         } finally {
1984             pendingApplyObserverCount.add(-1)
1985         }
1986     }
1987 
1988     sync {
1989         checkAndOverwriteUnusedRecordsLocked()
1990         modified?.forEach { processForUnusedRecordsLocked(it) }
1991     }
1992 
1993     return result
1994 }
1995 
advanceGlobalSnapshotnull1996 private fun advanceGlobalSnapshot() = advanceGlobalSnapshot(emptyLambda)
1997 
1998 private fun <T : Snapshot> takeNewSnapshot(block: (invalid: SnapshotIdSet) -> T): T =
1999     advanceGlobalSnapshot { invalid ->
2000         val result = block(invalid)
2001         sync { openSnapshots = openSnapshots.set(result.snapshotId) }
2002         result
2003     }
2004 
validateOpennull2005 private fun validateOpen(snapshot: Snapshot) {
2006     val openSnapshots = openSnapshots
2007     if (!openSnapshots.get(snapshot.snapshotId)) {
2008         error(
2009             "Snapshot is not open: snapshotId=${
2010                 snapshot.snapshotId
2011             }, disposed=${
2012                 snapshot.disposed
2013             }, applied=${
2014                 (snapshot as? MutableSnapshot)?.applied ?: "read-only"
2015             }, lowestPin=${
2016                 sync { pinningTable.lowestOrDefault(SnapshotIdInvalidValue) }
2017             }"
2018         )
2019     }
2020 }
2021 
2022 /**
2023  * A candidate snapshot is valid if the it is less than or equal to the current snapshot and it
2024  * wasn't specifically marked as invalid when the snapshot started.
2025  *
2026  * All snapshot active at when the snapshot was taken considered invalid for the snapshot (they have
2027  * not been applied and therefore are considered invalid).
2028  *
2029  * All snapshots taken after the current snapshot are considered invalid since they where taken
2030  * after the current snapshot was taken.
2031  *
2032  * INVALID_SNAPSHOT is reserved as an invalid snapshot id.
2033  */
validnull2034 private fun valid(
2035     currentSnapshot: SnapshotId,
2036     candidateSnapshot: SnapshotId,
2037     invalid: SnapshotIdSet
2038 ): Boolean {
2039     return candidateSnapshot != INVALID_SNAPSHOT &&
2040         candidateSnapshot <= currentSnapshot &&
2041         !invalid.get(candidateSnapshot)
2042 }
2043 
2044 // Determine if the given data is valid for the snapshot.
validnull2045 private fun valid(data: StateRecord, snapshot: SnapshotId, invalid: SnapshotIdSet): Boolean {
2046     return valid(snapshot, data.snapshotId, invalid)
2047 }
2048 
readablenull2049 private fun <T : StateRecord> readable(r: T, id: SnapshotId, invalid: SnapshotIdSet): T? {
2050     // The readable record is the valid record with the highest snapshotId
2051     var current: StateRecord? = r
2052     var candidate: StateRecord? = null
2053     while (current != null) {
2054         if (valid(current, id, invalid)) {
2055             candidate =
2056                 if (candidate == null) current
2057                 else if (candidate.snapshotId < current.snapshotId) current else candidate
2058         }
2059         current = current.next
2060     }
2061     if (candidate != null) {
2062         @Suppress("UNCHECKED_CAST") return candidate as T
2063     }
2064     return null
2065 }
2066 
2067 /**
2068  * Return the current readable state record for the current snapshot. It is assumed that [this] is
2069  * the first record of [state]
2070  */
readablenull2071 fun <T : StateRecord> T.readable(state: StateObject): T {
2072     val snapshot = Snapshot.current
2073     snapshot.readObserver?.invoke(state)
2074     return readable(this, snapshot.snapshotId, snapshot.invalid)
2075         ?: sync {
2076             // Readable can return null when the global snapshot has been advanced by another thread
2077             // and state written to the object was overwritten while this thread was paused.
2078             // Repeating the read is valid here as either this will return the same result as
2079             // the previous call or will find a valid record. Being in a sync block prevents other
2080             // threads from writing to this state object until the read completes.
2081             val syncSnapshot = Snapshot.current
2082             @Suppress("UNCHECKED_CAST")
2083             readable(state.firstStateRecord as T, syncSnapshot.snapshotId, syncSnapshot.invalid)
2084                 ?: readError()
2085         }
2086 }
2087 
2088 // unused, still here for API compat.
2089 /**
2090  * Return the current readable state record for the [snapshot]. It is assumed that [this] is the
2091  * first record of [state]
2092  */
readablenull2093 fun <T : StateRecord> T.readable(state: StateObject, snapshot: Snapshot): T {
2094     // invoke the observer associated with the current snapshot.
2095     snapshot.readObserver?.invoke(state)
2096     return readable(this, snapshot.snapshotId, snapshot.invalid)
2097         ?: sync {
2098             // Readable can return null when the global snapshot has been advanced by another thread
2099             // See T.readable(state: StateObject) for more info.
2100             val syncSnapshot = Snapshot.current
2101             @Suppress("UNCHECKED_CAST")
2102             readable(state.firstStateRecord as T, syncSnapshot.snapshotId, syncSnapshot.invalid)
2103                 ?: readError()
2104         }
2105 }
2106 
readErrornull2107 private fun readError(): Nothing {
2108     error(
2109         "Reading a state that was created after the snapshot was taken or in a snapshot that " +
2110             "has not yet been applied"
2111     )
2112 }
2113 
2114 /**
2115  * A record can be reused if no other snapshot will see it as valid. This is always true for a
2116  * record created in an abandoned snapshot. It is also true if the record is valid in the previous
2117  * snapshot and is obscured by another record also valid in the previous state record.
2118  */
usedLockednull2119 private fun usedLocked(state: StateObject): StateRecord? {
2120     var current: StateRecord? = state.firstStateRecord
2121     var validRecord: StateRecord? = null
2122     val reuseLimit = pinningTable.lowestOrDefault(nextSnapshotId) - 1
2123     val invalid = SnapshotIdSet.EMPTY
2124     while (current != null) {
2125         val currentId = current.snapshotId
2126         if (currentId == INVALID_SNAPSHOT) {
2127             // Any records that were marked invalid by an abandoned snapshot or is marked reachable
2128             // can be used immediately.
2129             return current
2130         }
2131         if (valid(current, reuseLimit, invalid)) {
2132             if (validRecord == null) {
2133                 validRecord = current
2134             } else {
2135                 // If we have two valid records one must obscure the other. Return the
2136                 // record with the lowest id
2137                 return if (current.snapshotId < validRecord.snapshotId) current else validRecord
2138             }
2139         }
2140         current = current.next
2141     }
2142     return null
2143 }
2144 
2145 /**
2146  * Clear records that cannot be selected in any currently open snapshot.
2147  *
2148  * This method uses the same technique as [usedLocked] which uses the [pinningTable] to determine
2149  * lowest id in the invalid set for all snapshots. Only the record with the greatest id of all
2150  * records less or equal to this lowest id can possibly be selected in any snapshot and all other
2151  * records below that number can be overwritten.
2152  *
2153  * However, this technique doesn't find all records that will not be selected by any open snapshot
2154  * as a record that has an id above that number could be reusable but will not be found.
2155  *
2156  * For example if snapshot 1 is open and 2 is created and modifies [state] then is applied, 3 is
2157  * open and then 4 is open, and then 1 is applied. When 3 modifies [state] and then applies, as 1 is
2158  * pinned by 4, it is uncertain whether the record for 2 is needed by 4 so it must be kept even if 4
2159  * also modified [state] and would not select 2. Accurately determine if a record is selectable
2160  * would require keeping a list of all open [Snapshot] instances which currently is not kept and
2161  * traversing that list for each record.
2162  *
2163  * If any such records are possible this method returns true. In other words, this method returns
2164  * true if any records might be reusable but this function could not prove there were or not.
2165  */
overwriteUnusedRecordsLockednull2166 private fun overwriteUnusedRecordsLocked(state: StateObject): Boolean {
2167     var current: StateRecord? = state.firstStateRecord
2168     var overwriteRecord: StateRecord? = null
2169     var validRecord: StateRecord? = null
2170     val reuseLimit = pinningTable.lowestOrDefault(nextSnapshotId)
2171     var retainedRecords = 0
2172 
2173     while (current != null) {
2174         val currentId = current.snapshotId
2175         if (currentId != INVALID_SNAPSHOT) {
2176             if (currentId < reuseLimit) {
2177                 if (validRecord == null) {
2178                     // If any records are below [reuseLimit] then we must keep the highest one
2179                     // so the lowest snapshot can select it.
2180                     validRecord = current
2181                     retainedRecords++
2182                 } else {
2183                     // If [validRecord] is from an earlier snapshot, overwrite it instead
2184                     val recordToOverwrite =
2185                         if (current.snapshotId < validRecord.snapshotId) {
2186                             current
2187                         } else {
2188                             // We cannot use `.also { }` here as it prevents smart casting of other
2189                             // uses of [validRecord].
2190                             val result = validRecord
2191                             validRecord = current
2192                             result
2193                         }
2194                     if (overwriteRecord == null) {
2195                         // Find a record we will definitely keep
2196                         overwriteRecord =
2197                             state.firstStateRecord.findYoungestOr { it.snapshotId >= reuseLimit }
2198                     }
2199                     recordToOverwrite.snapshotId = INVALID_SNAPSHOT
2200                     recordToOverwrite.assign(overwriteRecord)
2201                 }
2202             } else {
2203                 retainedRecords++
2204             }
2205         }
2206         current = current.next
2207     }
2208 
2209     return retainedRecords > 1
2210 }
2211 
findYoungestOrnull2212 private inline fun StateRecord.findYoungestOr(predicate: (StateRecord) -> Boolean): StateRecord {
2213     var current: StateRecord? = this
2214     var youngest = this
2215     while (current != null) {
2216         if (predicate(current)) return current
2217         if (youngest.snapshotId < current.snapshotId) youngest = current
2218         current = current.next
2219     }
2220     return youngest
2221 }
2222 
checkAndOverwriteUnusedRecordsLockednull2223 private fun checkAndOverwriteUnusedRecordsLocked() {
2224     extraStateObjects.removeIf { !overwriteUnusedRecordsLocked(it) }
2225 }
2226 
processForUnusedRecordsLockednull2227 private fun processForUnusedRecordsLocked(state: StateObject) {
2228     if (overwriteUnusedRecordsLocked(state)) {
2229         extraStateObjects.add(state)
2230     }
2231 }
2232 
2233 @PublishedApi
writableRecordnull2234 internal fun <T : StateRecord> T.writableRecord(state: StateObject, snapshot: Snapshot): T {
2235     if (snapshot.readOnly) {
2236         // If the snapshot is read-only, use the snapshot recordModified to report it.
2237         snapshot.recordModified(state)
2238     }
2239     val id = snapshot.snapshotId
2240     val readData = readable(this, id, snapshot.invalid) ?: readError()
2241 
2242     // If the readable data was born in this snapshot, it is writable.
2243     if (readData.snapshotId == snapshot.snapshotId) return readData
2244 
2245     // Otherwise, make a copy of the readable data and mark it as born in this snapshot, making it
2246     // writable.
2247     @Suppress("UNCHECKED_CAST")
2248     val newData =
2249         sync {
2250             // Verify that some other thread didn't already create this.
2251             val newReadData = readable(state.firstStateRecord, id, snapshot.invalid) ?: readError()
2252             if (newReadData.snapshotId == id) newReadData
2253             else newReadData.newWritableRecordLocked(state, snapshot)
2254         }
2255             as T
2256 
2257     if (readData.snapshotId != Snapshot.PreexistingSnapshotId.toSnapshotId()) {
2258         snapshot.recordModified(state)
2259     }
2260 
2261     return newData
2262 }
2263 
overwritableRecordnull2264 internal fun <T : StateRecord> T.overwritableRecord(
2265     state: StateObject,
2266     snapshot: Snapshot,
2267     candidate: T
2268 ): T {
2269     if (snapshot.readOnly) {
2270         // If the snapshot is read-only, use the snapshot recordModified to report it.
2271         snapshot.recordModified(state)
2272     }
2273     val id = snapshot.snapshotId
2274 
2275     if (candidate.snapshotId == id) return candidate
2276 
2277     val newData = sync { newOverwritableRecordLocked(state) }
2278     newData.snapshotId = id
2279 
2280     if (candidate.snapshotId != Snapshot.PreexistingSnapshotId.toSnapshotId()) {
2281         snapshot.recordModified(state)
2282     }
2283 
2284     return newData
2285 }
2286 
<lambda>null2287 internal fun <T : StateRecord> T.newWritableRecord(state: StateObject, snapshot: Snapshot) = sync {
2288     newWritableRecordLocked(state, snapshot)
2289 }
2290 
newWritableRecordLockednull2291 private fun <T : StateRecord> T.newWritableRecordLocked(state: StateObject, snapshot: Snapshot): T {
2292     // Calling used() on a state object might return the same record for each thread calling
2293     // used() therefore selecting the record to reuse should be guarded.
2294 
2295     // Note: setting the snapshotId to Int.MAX_VALUE will make it invalid for all snapshots.
2296     // This means the lock can be released as used() will no longer select it. Using id could
2297     // also be used but it puts the object into a state where the reused value appears to be
2298     // the current valid value for the snapshot. This is not an issue if the snapshot is only
2299     // being read from a single thread but using Int.MAX_VALUE allows multiple readers,
2300     // single writer, of a snapshot. Note that threads reading a mutating snapshot should not
2301     // cache the result of readable() as the mutating thread calls to writable() can change the
2302     // result of readable().
2303     val newData = newOverwritableRecordLocked(state)
2304     newData.assign(this)
2305     newData.snapshotId = snapshot.snapshotId
2306     return newData
2307 }
2308 
newOverwritableRecordLockednull2309 internal fun <T : StateRecord> T.newOverwritableRecordLocked(state: StateObject): T {
2310     // Calling used() on a state object might return the same record for each thread calling
2311     // used() therefore selecting the record to reuse should be guarded.
2312 
2313     // Note: setting the snapshotId to Int.MAX_VALUE will make it invalid for all snapshots.
2314     // This means the lock can be released as used() will no longer select it. Using id could
2315     // also be used but it puts the object into a state where the reused value appears to be
2316     // the current valid value for the snapshot. This is not an issue if the snapshot is only
2317     // being read from a single thread but using Int.MAX_VALUE allows multiple readers,
2318     // single writer, of a snapshot. Note that threads reading a mutating snapshot should not
2319     // cache the result of readable() as the mutating thread calls to writable() can change the
2320     // result of readable().
2321     @Suppress("UNCHECKED_CAST")
2322     return (usedLocked(state) as T?)?.apply { snapshotId = SnapshotIdMax }
2323         ?: create(SnapshotIdMax).apply {
2324             this.next = state.firstStateRecord
2325             state.prependStateRecord(this as T)
2326         } as T
2327 }
2328 
2329 @PublishedApi
notifyWritenull2330 internal fun notifyWrite(snapshot: Snapshot, state: StateObject) {
2331     snapshot.writeCount += 1
2332     snapshot.writeObserver?.invoke(state)
2333 }
2334 
2335 /**
2336  * Call [block] with a writable state record for [snapshot] of the given record. It is assumed that
2337  * this is called for the first state record in a state object. If the snapshot is read-only calling
2338  * this will throw.
2339  */
writablenull2340 inline fun <T : StateRecord, R> T.writable(
2341     state: StateObject,
2342     snapshot: Snapshot,
2343     block: T.() -> R
2344 ): R {
2345     // A writable record will always be the readable record (as all newer records are invalid it
2346     // must be the newest valid record). This means that if the readable record is not from the
2347     // current snapshot, a new record must be created. To create a new writable record, a record
2348     // can be reused, if possible, and the readable record is applied to it. If a record cannot
2349     // be reused, a new record is created and the readable record is applied to it. Once the
2350     // values are correct the record is made live by giving it the current snapshot id.
2351 
2352     // Writes need to be in a `sync` block as all writes in flight must be completed before a new
2353     // snapshot is take. Writing in a sync block ensures this is the case because new snapshots
2354     // are also in a sync block.
2355     return sync { this.writableRecord(state, snapshot).block() }
2356         .also { notifyWrite(snapshot, state) }
2357 }
2358 
2359 /**
2360  * Call [block] with a writable state record for the given record. It is assumed that this is called
2361  * for the first state record in a state object. A record is writable if it was created in the
2362  * current mutable snapshot.
2363  */
writablenull2364 inline fun <T : StateRecord, R> T.writable(state: StateObject, block: T.() -> R): R {
2365     val snapshot: Snapshot
2366     return sync {
2367             snapshot = Snapshot.current
2368             this.writableRecord(state, snapshot).block()
2369         }
2370         .also { notifyWrite(snapshot, state) }
2371 }
2372 
2373 /**
2374  * Call [block] with a writable state record for the given record. It is assumed that this is called
2375  * for the first state record in a state object. A record is writable if it was created in the
2376  * current mutable snapshot. This should only be used when the record will be overwritten in its
2377  * entirety (such as having only one field and that field is written to).
2378  *
2379  * WARNING: If the caller doesn't overwrite all the fields in the state record the object will be
2380  * inconsistent and the fields not written are almost guaranteed to be incorrect. If it is possible
2381  * that [block] will not write to all the fields use [writable] instead.
2382  *
2383  * @param state The object that has this record in its record list.
2384  * @param candidate The current for the snapshot record returned by [withCurrent]
2385  * @param block The block that will mutate all the field of the record.
2386  */
overwritablenull2387 internal inline fun <T : StateRecord, R> T.overwritable(
2388     state: StateObject,
2389     candidate: T,
2390     block: T.() -> R
2391 ): R {
2392     val snapshot: Snapshot
2393     return sync {
2394             snapshot = Snapshot.current
2395             this.overwritableRecord(state, snapshot, candidate).block()
2396         }
2397         .also { notifyWrite(snapshot, state) }
2398 }
2399 
2400 /**
2401  * Produce a set of optimistic merges of the state records, this is performed outside the a
2402  * synchronization block to reduce the amount of time taken in the synchronization block reducing
2403  * the thread contention of merging state values.
2404  */
optimisticMergesnull2405 private fun optimisticMerges(
2406     currentSnapshotId: SnapshotId,
2407     applyingSnapshot: MutableSnapshot,
2408     invalidSnapshots: SnapshotIdSet
2409 ): Map<StateRecord, StateRecord>? {
2410     val modified = applyingSnapshot.modified
2411     if (modified == null) return null
2412     val start =
2413         applyingSnapshot.invalid.set(applyingSnapshot.snapshotId).or(applyingSnapshot.previousIds)
2414     var result: MutableMap<StateRecord, StateRecord>? = null
2415     modified.forEach { state ->
2416         val first = state.firstStateRecord
2417         val current = readable(first, currentSnapshotId, invalidSnapshots) ?: return@forEach
2418         val previous = readable(first, currentSnapshotId, start) ?: return@forEach
2419         if (current != previous) {
2420             // Try to produce a merged state record
2421             val applied =
2422                 readable(first, applyingSnapshot.snapshotId, applyingSnapshot.invalid)
2423                     ?: readError()
2424             val merged = state.mergeRecords(previous, current, applied)
2425             if (merged != null) {
2426                 (result ?: hashMapOf<StateRecord, StateRecord>().also { result = it })[current] =
2427                     merged
2428             } else {
2429                 // If one fails don't bother calculating the others as they are likely not going
2430                 // to be used. There is an unlikely case that a optimistic merge cannot be
2431                 // produced but the snapshot will apply because, once the synchronization is taken,
2432                 // the current state can be merge. This routine errors on the side of reduced
2433                 // overall work by not performing work that is likely to be ignored.
2434                 return null
2435             }
2436         }
2437     }
2438     return result
2439 }
2440 
reportReadonlySnapshotWritenull2441 private fun reportReadonlySnapshotWrite(): Nothing {
2442     error("Cannot modify a state object in a read-only snapshot")
2443 }
2444 
2445 /** Returns the current record without notifying any read observers. */
2446 @PublishedApi
currentnull2447 internal fun <T : StateRecord> current(r: T, snapshot: Snapshot) =
2448     readable(r, snapshot.snapshotId, snapshot.invalid)
2449         ?: sync {
2450             // Global snapshot could have been advanced
2451             // see StateRecord.readable for more details
2452             readable(r, snapshot.snapshotId, snapshot.invalid)
2453         }
2454         ?: readError()
2455 
2456 @PublishedApi
currentnull2457 internal fun <T : StateRecord> current(r: T) =
2458     Snapshot.current.let { snapshot ->
2459         readable(r, snapshot.snapshotId, snapshot.invalid)
2460             ?: sync {
2461                 // Global snapshot could have been advanced
2462                 // see StateRecord.readable for more details
2463                 Snapshot.current.let { syncSnapshot ->
2464                     readable(r, syncSnapshot.snapshotId, syncSnapshot.invalid)
2465                 }
2466             }
2467             ?: readError()
2468     }
2469 
2470 /**
2471  * Provides a [block] with the current record, without notifying any read observers.
2472  *
2473  * @see readable
2474  */
withCurrentnull2475 inline fun <T : StateRecord, R> T.withCurrent(block: (r: T) -> R): R = block(current(this))
2476 
2477 /** Helper routine to add a range of values ot a snapshot set */
2478 internal fun SnapshotIdSet.addRange(from: SnapshotId, until: SnapshotId): SnapshotIdSet {
2479     var result = this
2480     var invalidId = from
2481     while (invalidId < until) {
2482         result = result.set(invalidId)
2483         invalidId += 1
2484     }
2485     return result
2486 }
2487