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