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