1 /*
<lambda>null2  * Copyright 2024 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.tooling
18 
19 import androidx.collection.ScatterSet
20 import androidx.compose.runtime.ExperimentalComposeRuntimeApi
21 import androidx.compose.runtime.collection.wrapIntoSet
22 import androidx.compose.runtime.external.kotlinx.collections.immutable.PersistentList
23 import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentListOf
24 import androidx.compose.runtime.snapshots.ObserverHandle
25 import androidx.compose.runtime.snapshots.Snapshot
26 import androidx.compose.runtime.snapshots.StateObject
27 import androidx.compose.runtime.snapshots.fastForEach
28 import androidx.compose.runtime.snapshots.sync
29 
30 /**
31  * An observer for the snapshot system that notifies an observer when a snapshot is created,
32  * applied, and/or disposed.
33  *
34  * All methods are called in the thread of the snapshot so all observers must be thread safe as they
35  * may be called from any thread.
36  *
37  * Calling any of the Snapshot API (including, reading or writing mutable state objects) is not
38  * supported and may produce inconsistent result or throw an exception.
39  */
40 @ExperimentalComposeRuntimeApi
41 @Suppress("CallbackName")
42 interface SnapshotObserver {
43     /**
44      * Called before a snapshot is created allowing reads and writes to the snapshot to be observed.
45      *
46      * This method is called in the same thread that creates the snapshot.
47      *
48      * @param parent the parent snapshot for the new snapshot if it is a nested snapshot or null
49      *   otherwise.
50      * @param readonly whether the snapshot being created will be read-only.
51      * @return optional read and write observers that will be added to the snapshot created.
52      */
53     fun onPreCreate(parent: Snapshot?, readonly: Boolean): SnapshotInstanceObservers? = null
54 
55     /**
56      * Called after snapshot is created.
57      *
58      * This is called prior to the instance being returned by [Snapshot.takeSnapshot] or
59      * [Snapshot.takeMutableSnapshot].
60      *
61      * This method is called in the same thread that creates the snapshot.
62      *
63      * @param snapshot the snapshot that was created.
64      * @param parent the parent snapshot for the new snapshot if it is a nested snapshot or null if
65      *   it is a root snapshot.
66      * @param observers the read and write observers that were installed by the value returned by
67      *   [onCreated]. This allows correlating which snapshot observers returned by [onPreCreate] to
68      *   the [snapshot] that was created.
69      */
70     fun onCreated(snapshot: Snapshot, parent: Snapshot?, observers: SnapshotInstanceObservers?) {}
71 
72     /**
73      * Called while a snapshot is being disposed.
74      *
75      * This method is called in the same thread that disposes the snapshot.
76      *
77      * @param snapshot information about the snapshot that was created.
78      */
79     fun onPreDispose(snapshot: Snapshot) {}
80 
81     /**
82      * Called after a snapshot is applied.
83      *
84      * For nested snapshots, the changes will only be visible to the parent snapshot, not globally.
85      * Snapshots do not have a parent will have changes that are visible globally and such
86      * notification are equivalent the notification sent to [Snapshot.registerApplyObserver] and
87      * will include all objects modified by any nested snapshots that have been applied to the
88      * parent snapshot.
89      *
90      * This method is called in the same thread that applies the snapshot.
91      *
92      * @param snapshot the snapshot that was applied.
93      * @param changed the set of objects that were modified during the snapshot.
94      */
95     fun onApplied(snapshot: Snapshot, changed: Set<Any>) {}
96 }
97 
98 /**
99  * The return result of [SnapshotObserver.onPreCreate] allowing the reads and writes performed in
100  * the newly created snapshot to be observed
101  */
102 @ExperimentalComposeRuntimeApi
103 class SnapshotInstanceObservers(
104     /**
105      * Called whenever a state is read in the snapshot. This is called before the read observer
106      * passed to [Snapshot.takeSnapshot] or [Snapshot.takeMutableSnapshot].
107      *
108      * This method is called in the same thread that reads snapshot state.
109      */
110     val readObserver: ((Any) -> Unit)? = null,
111 
112     /**
113      * Called just before a state object is written to the first time in the snapshot or a nested
114      * mutable snapshot. This might be called several times for the same object if nested mutable
115      * snapshots are created as the unmodified value may be needed by the nested snapshot so a new
116      * copy is created. This is not called for each write, only when the write results in the object
117      * be recorded as being modified requiring a copy to be made before the write completes. This is
118      * called before the write has been applied to the instance.
119      *
120      * This is called before the write observer passed to [Snapshot.takeMutableSnapshot].
121      *
122      * This method is called in the same thread that writes to the snapshot state.
123      */
124     val writeObserver: ((Any) -> Unit)? = null,
125 )
126 
127 /**
128  * This is a tooling API and is not intended to be used in a production application as it will
129  * introduce global overhead to creating, applying and disposing all snapshots and, potentially, to
130  * reading and writing all state objects.
131  *
132  * Observe when snapshots are created, applied, and/or disposed. The observer can also install read
133  * and write observers on the snapshot being created.
134  *
135  * This method is thread-safe and calling [ObserverHandle.dispose] on the [ObserverHandle] returned
136  * is also thread-safe.
137  *
138  * @param snapshotObserver the snapshot observer to install.
139  * @return [ObserverHandle] an instance to unregister the [snapshotObserver].
140  */
141 @ExperimentalComposeRuntimeApi
Snapshotnull142 fun Snapshot.Companion.observeSnapshots(snapshotObserver: SnapshotObserver): ObserverHandle {
143     sync { observers = (observers ?: persistentListOf()).add(snapshotObserver) }
144     return ObserverHandle {
145         sync {
146             val newObservers = observers?.remove(snapshotObserver)
147             observers = newObservers?.takeIf { it.isNotEmpty() }
148         }
149     }
150 }
151 
152 @ExperimentalComposeRuntimeApi private var observers: PersistentList<SnapshotObserver>? = null
153 
154 @ExperimentalComposeRuntimeApi
creatingSnapshotnull155 internal inline fun <R : Snapshot> creatingSnapshot(
156     parent: Snapshot?,
157     noinline readObserver: ((Any) -> Unit)?,
158     noinline writeObserver: ((Any) -> Unit)?,
159     readonly: Boolean,
160     crossinline block: (readObserver: ((Any) -> Unit)?, writeObserver: ((Any) -> Unit)?) -> R
161 ): R {
162     var observerMap: Map<SnapshotObserver, SnapshotInstanceObservers>? = null
163     val observers = observers
164     var actualReadObserver = readObserver
165     var actualWriteObserver = writeObserver
166     if (observers != null) {
167         val result = observers.mergeObservers(parent, readonly, readObserver, writeObserver)
168         val mappedObservers = result.first
169         actualReadObserver = mappedObservers.readObserver
170         actualWriteObserver = mappedObservers.writeObserver
171         observerMap = result.second
172     }
173     val result = block(actualReadObserver, actualWriteObserver)
174     observers?.dispatchCreatedObservers(parent, result, observerMap)
175     return result
176 }
177 
178 @ExperimentalComposeRuntimeApi
mergeObserversnull179 internal fun PersistentList<SnapshotObserver>.mergeObservers(
180     parent: Snapshot?,
181     readonly: Boolean,
182     readObserver: ((Any) -> Unit)?,
183     writeObserver: ((Any) -> Unit)?,
184 ): Pair<SnapshotInstanceObservers, Map<SnapshotObserver, SnapshotInstanceObservers>?> {
185     var currentReadObserver = readObserver
186     var currentWriteObserver = writeObserver
187     var observerMap: MutableMap<SnapshotObserver, SnapshotInstanceObservers>? = null
188     fastForEach { observer ->
189         val instance = observer.onPreCreate(parent, readonly)
190         if (instance != null) {
191             currentReadObserver = mergeObservers(instance.readObserver, currentReadObserver)
192             currentWriteObserver = mergeObservers(instance.writeObserver, currentWriteObserver)
193             (observerMap
194                 ?: run {
195                     val newMap = mutableMapOf<SnapshotObserver, SnapshotInstanceObservers>()
196                     observerMap = newMap
197                     newMap
198                 })[observer] = instance
199         }
200     }
201     return SnapshotInstanceObservers(currentReadObserver, currentWriteObserver) to observerMap
202 }
203 
mergeObserversnull204 private fun mergeObservers(a: ((Any) -> Unit)?, b: ((Any) -> Unit)?): ((Any) -> Unit)? {
205     return if (a != null && b != null) {
206         {
207             a(it)
208             b(it)
209         }
210     } else a ?: b
211 }
212 
213 @ExperimentalComposeRuntimeApi
dispatchCreatedObserversnull214 internal fun PersistentList<SnapshotObserver>.dispatchCreatedObservers(
215     parent: Snapshot?,
216     result: Snapshot,
217     observerMap: Map<SnapshotObserver, SnapshotInstanceObservers>?
218 ) {
219     fastForEach { observer ->
220         val instance = observerMap?.get(observer)
221         observer.onCreated(result, parent, instance)
222     }
223 }
224 
225 @OptIn(ExperimentalComposeRuntimeApi::class)
dispatchObserverOnPreDisposenull226 internal fun dispatchObserverOnPreDispose(snapshot: Snapshot) {
227     observers?.fastForEach { observer -> observer.onPreDispose(snapshot) }
228 }
229 
230 @OptIn(ExperimentalComposeRuntimeApi::class)
dispatchObserverOnAppliednull231 internal fun dispatchObserverOnApplied(snapshot: Snapshot, changes: ScatterSet<StateObject>?) {
232     val observers = observers
233     if (!observers.isNullOrEmpty()) {
234         val wrappedChanges = changes?.wrapIntoSet() ?: emptySet()
235         observers.fastForEach { observer -> observer.onApplied(snapshot, wrappedChanges) }
236     }
237 }
238