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.internal
18
19 import androidx.collection.MutableIntList
20 import androidx.collection.MutableScatterMap
21 import androidx.collection.MutableScatterSet
22 import androidx.collection.mutableScatterMapOf
23 import androidx.collection.mutableScatterSetOf
24 import androidx.compose.runtime.ComposeNodeLifecycleCallback
25 import androidx.compose.runtime.RecomposeScopeImpl
26 import androidx.compose.runtime.RememberManager
27 import androidx.compose.runtime.RememberObserver
28 import androidx.compose.runtime.RememberObserverHolder
29 import androidx.compose.runtime.Stack
30 import androidx.compose.runtime.collection.MutableVector
31 import androidx.compose.runtime.collection.mutableVectorOf
32 import androidx.compose.runtime.tooling.CompositionErrorContext
33
34 /**
35 * Used as a placeholder for paused compositions to ensure the remembers are dispatch in the correct
36 * order. While the paused composition is resuming all remembered objects are placed into the this
37 * classes list instead of the main list. As remembers are dispatched, this will dispatch remembers
38 * to the object remembered in the paused composition's content in the order that they would have
39 * been dispatched had the composition not been paused.
40 */
41 internal class PausedCompositionRemembers(private val abandoning: MutableSet<RememberObserver>) :
42 RememberObserver {
43 val pausedRemembers = mutableVectorOf<RememberObserverHolder>()
44
45 override fun onRemembered() {
46 pausedRemembers.forEach {
47 val wrapped = it.wrapped
48 abandoning.remove(wrapped)
49 wrapped.onRemembered()
50 }
51 }
52
53 // These are never called
54 override fun onForgotten() {}
55
56 override fun onAbandoned() {}
57 }
58
59 /** Helper for collecting remember observers for later strictly ordered dispatch. */
60 internal class RememberEventDispatcher() : RememberManager {
61 private var abandoning: MutableSet<RememberObserver>? = null
62 private var traceContext: CompositionErrorContext? = null
63 private val remembering = mutableVectorOf<RememberObserverHolder>()
64 private val rememberSet = mutableScatterSetOf<RememberObserverHolder>()
65 private var currentRememberingList = remembering
66 private var currentRememberSet = rememberSet
67 private val leaving = mutableVectorOf<Any>()
68 private val sideEffects = mutableVectorOf<() -> Unit>()
69 private var releasing: MutableScatterSet<ComposeNodeLifecycleCallback>? = null
70 private var pausedPlaceholders:
71 MutableScatterMap<RecomposeScopeImpl, PausedCompositionRemembers>? =
72 null
73 private val pending = mutableListOf<Any>()
74 private val priorities = MutableIntList()
75 private val afters = MutableIntList()
76 private var nestedRemembersLists: Stack<MutableVector<RememberObserverHolder>>? = null
77 private val toAdd: MutableList<Any> = mutableListOf()
78 private val toAddAfter: MutableIntList = MutableIntList()
79 private val toAddPriority: MutableIntList = MutableIntList()
80
preparenull81 fun prepare(
82 abandoning: MutableSet<RememberObserver>,
83 traceContext: CompositionErrorContext?,
84 ) {
85 clear()
86 this.abandoning = abandoning
87 this.traceContext = traceContext
88 }
89
usenull90 inline fun use(
91 abandoning: MutableSet<RememberObserver>,
92 traceContext: CompositionErrorContext?,
93 block: RememberEventDispatcher.() -> Unit
94 ) {
95 try {
96 prepare(abandoning, traceContext)
97 this.block()
98 } finally {
99 clear()
100 }
101 }
102
clearnull103 fun clear() {
104 this.abandoning = null
105 this.traceContext = null
106 this.remembering.clear()
107 this.rememberSet.clear()
108 this.currentRememberingList = remembering
109 this.currentRememberSet = rememberSet
110 this.leaving.clear()
111 this.sideEffects.clear()
112 this.releasing = null
113 this.pausedPlaceholders = null
114 this.pending.clear()
115 this.priorities.clear()
116 this.afters.clear()
117 this.nestedRemembersLists = null
118 }
119
rememberingnull120 override fun remembering(instance: RememberObserverHolder) {
121 currentRememberingList.add(instance)
122 currentRememberSet.add(instance)
123 }
124
forgettingnull125 override fun forgetting(
126 instance: RememberObserverHolder,
127 endRelativeOrder: Int,
128 priority: Int,
129 endRelativeAfter: Int
130 ) {
131 if (instance in currentRememberSet) {
132 currentRememberSet.remove(instance)
133 currentRememberingList.remove(instance)
134 val abandoning = abandoning ?: return
135 abandoning.add(instance.wrapped)
136 }
137 recordLeaving(instance, endRelativeOrder, priority, endRelativeAfter)
138 }
139
sideEffectnull140 override fun sideEffect(effect: () -> Unit) {
141 sideEffects += effect
142 }
143
deactivatingnull144 override fun deactivating(
145 instance: ComposeNodeLifecycleCallback,
146 endRelativeOrder: Int,
147 priority: Int,
148 endRelativeAfter: Int
149 ) {
150 recordLeaving(instance, endRelativeOrder, priority, endRelativeAfter)
151 }
152
releasingnull153 override fun releasing(
154 instance: ComposeNodeLifecycleCallback,
155 endRelativeOrder: Int,
156 priority: Int,
157 endRelativeAfter: Int
158 ) {
159 val releasing =
160 releasing ?: mutableScatterSetOf<ComposeNodeLifecycleCallback>().also { releasing = it }
161
162 releasing += instance
163 recordLeaving(instance, endRelativeOrder, priority, endRelativeAfter)
164 }
165
rememberPausingScopenull166 override fun rememberPausingScope(scope: RecomposeScopeImpl) {
167 val abandoning = abandoning ?: return
168 val pausedPlaceholder = PausedCompositionRemembers(abandoning)
169 (pausedPlaceholders
170 ?: mutableScatterMapOf<RecomposeScopeImpl, PausedCompositionRemembers>().also {
171 pausedPlaceholders = it
172 })[scope] = pausedPlaceholder
173 this.currentRememberingList.add(RememberObserverHolder(pausedPlaceholder, after = null))
174 }
175
startResumingScopenull176 override fun startResumingScope(scope: RecomposeScopeImpl) {
177 val placeholder = pausedPlaceholders?.get(scope)
178 if (placeholder != null) {
179 (nestedRemembersLists
180 ?: Stack<MutableVector<RememberObserverHolder>>().also {
181 nestedRemembersLists = it
182 })
183 .push(currentRememberingList)
184 currentRememberingList = placeholder.pausedRemembers
185 }
186 }
187
endResumingScopenull188 override fun endResumingScope(scope: RecomposeScopeImpl) {
189 val pausedPlaceholders = pausedPlaceholders
190 if (pausedPlaceholders != null) {
191 val placeholder = pausedPlaceholders[scope]
192 if (placeholder != null) {
193 nestedRemembersLists?.pop()?.let { currentRememberingList = it }
194 pausedPlaceholders.remove(scope)
195 }
196 }
197 }
198
dispatchRememberObserversnull199 fun dispatchRememberObservers() {
200 val abandoning = abandoning ?: return
201 // Add any pending out-of-order forgotten objects
202 processPendingLeaving(Int.MIN_VALUE)
203
204 // Send forgets and node callbacks
205 if (leaving.isNotEmpty()) {
206 trace("Compose:onForgotten") {
207 val releasing = releasing
208 for (i in leaving.size - 1 downTo 0) {
209 val instance = leaving[i]
210 withComposeStackTrace(instance) {
211 if (instance is RememberObserverHolder) {
212 val wrapped = instance.wrapped
213 abandoning.remove(wrapped)
214 wrapped.onForgotten()
215 }
216 if (instance is ComposeNodeLifecycleCallback) {
217 // node callbacks are in the same queue as forgets to ensure ordering
218 if (releasing != null && instance in releasing) {
219 instance.onRelease()
220 } else {
221 instance.onDeactivate()
222 }
223 }
224 }
225 }
226 }
227 }
228
229 // Send remembers
230 if (remembering.isNotEmpty()) {
231 trace("Compose:onRemembered") { dispatchRememberList(remembering) }
232 }
233 }
234
dispatchRememberListnull235 private fun dispatchRememberList(list: MutableVector<RememberObserverHolder>) {
236 val abandoning = abandoning ?: return
237 list.forEach { instance ->
238 val wrapped = instance.wrapped
239 abandoning.remove(wrapped)
240 withComposeStackTrace(instance) { wrapped.onRemembered() }
241 }
242 }
243
dispatchSideEffectsnull244 fun dispatchSideEffects() {
245 if (sideEffects.isNotEmpty()) {
246 trace("Compose:sideeffects") {
247 sideEffects.forEach { sideEffect -> sideEffect() }
248 sideEffects.clear()
249 }
250 }
251 }
252
dispatchAbandonsnull253 fun dispatchAbandons() {
254 val abandoning = abandoning ?: return
255 if (abandoning.isNotEmpty()) {
256 trace("Compose:abandons") {
257 val iterator = abandoning.iterator()
258 // remove elements one by one to ensure that abandons will not be dispatched
259 // second time in case [onAbandoned] throws.
260 while (iterator.hasNext()) {
261 val instance = iterator.next()
262 iterator.remove()
263 instance.onAbandoned()
264 }
265 }
266 }
267 }
268
recordLeavingnull269 private fun recordLeaving(
270 instance: Any,
271 endRelativeOrder: Int,
272 priority: Int,
273 endRelativeAfter: Int
274 ) {
275 processPendingLeaving(endRelativeOrder)
276 if (endRelativeAfter in 0 until endRelativeOrder) {
277 pending.add(instance)
278 priorities.add(priority)
279 afters.add(endRelativeAfter)
280 } else {
281 leaving.add(instance)
282 }
283 }
284
processPendingLeavingnull285 private fun processPendingLeaving(endRelativeOrder: Int) {
286 if (pending.isNotEmpty()) {
287 var index = 0
288 val toAdd = toAdd
289 val toAddAfter = toAddAfter
290 val toAddPriority = toAddPriority
291 try {
292 while (index < afters.size) {
293 if (endRelativeOrder <= afters[index]) {
294 val instance = pending.removeAt(index)
295 val endRelativeAfter = afters.removeAt(index)
296 val priority = priorities.removeAt(index)
297 toAdd.add(instance)
298 toAddAfter.add(endRelativeAfter)
299 toAddPriority.add(priority)
300 } else {
301 index++
302 }
303 }
304 if (toAdd.isNotEmpty()) {
305 // Sort the list into [after, -priority] order where it is ordered by after
306 // in ascending order as the primary key and priority in descending order as
307 // secondary key.
308
309 // For example if remember occurs after a child group it must be added after
310 // all the remembers of the child. This is reported with an after which is the
311 // slot index of the child's last slot. As this slot might be at the same
312 // location as where its parents ends this would be ambiguous which should
313 // first if both the two groups request a slot to be after the same slot.
314 // Priority is used to break the tie here which is the group index of the group
315 // which is leaving. Groups that are lower must be added before the parent's
316 // remember when they have the same after.
317
318 // The sort must be stable as as consecutive remembers in the same group after
319 // the same child will have the same after and priority.
320
321 // A selection sort is used here because it is stable and the groups are
322 // typically very short so this quickly exit list of one and not loop for
323 // for sizes of 2. As the information is split between three lists, to
324 // reduce allocations, [MutableList.sort] cannot be used as it doesn't have
325 // an option to supply a custom swap.
326 for (i in 0 until toAdd.size - 1) {
327 for (j in i + 1 until toAdd.size) {
328 val iAfter = toAddAfter[i]
329 val jAfter = toAddAfter[j]
330 if (
331 iAfter < jAfter ||
332 (jAfter == iAfter && toAddPriority[i] < toAddPriority[j])
333 ) {
334 toAdd.swap(i, j)
335 toAddPriority.swap(i, j)
336 toAddAfter.swap(i, j)
337 }
338 }
339 }
340 leaving.addAll(toAdd)
341 }
342 } finally {
343 toAdd.clear()
344 toAddAfter.clear()
345 toAddPriority.clear()
346 }
347 }
348 }
349
withComposeStackTracenull350 private inline fun <T> withComposeStackTrace(instance: Any, block: () -> T): T =
351 try {
352 block()
353 } catch (e: Throwable) {
<lambda>null354 throw e.also { traceContext?.apply { e.attachComposeStackTrace(instance) } }
355 }
356 }
357
swapnull358 private fun <T> MutableList<T>.swap(a: Int, b: Int) {
359 val item = this[a]
360 this[a] = this[b]
361 this[b] = item
362 }
363
MutableIntListnull364 private fun MutableIntList.swap(a: Int, b: Int) {
365 val item = this[a]
366 this[a] = this[b]
367 this[b] = item
368 }
369