1 /*
<lambda>null2  * Copyright 2023 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.changelist
18 
19 import androidx.compose.runtime.Anchor
20 import androidx.compose.runtime.Applier
21 import androidx.compose.runtime.Composition
22 import androidx.compose.runtime.CompositionContext
23 import androidx.compose.runtime.ControlledComposition
24 import androidx.compose.runtime.InternalComposeApi
25 import androidx.compose.runtime.MovableContentState
26 import androidx.compose.runtime.MovableContentStateReference
27 import androidx.compose.runtime.OffsetApplier
28 import androidx.compose.runtime.RecomposeScopeImpl
29 import androidx.compose.runtime.RecomposeScopeOwner
30 import androidx.compose.runtime.RememberManager
31 import androidx.compose.runtime.RememberObserverHolder
32 import androidx.compose.runtime.SlotTable
33 import androidx.compose.runtime.SlotWriter
34 import androidx.compose.runtime.TestOnly
35 import androidx.compose.runtime.composeRuntimeError
36 import androidx.compose.runtime.deactivateCurrentGroup
37 import androidx.compose.runtime.extractMovableContentAtCurrent
38 import androidx.compose.runtime.internal.IntRef
39 import androidx.compose.runtime.internal.identityHashCode
40 import androidx.compose.runtime.removeCurrentGroup
41 import androidx.compose.runtime.runtimeCheck
42 import androidx.compose.runtime.snapshots.fastForEachIndexed
43 import androidx.compose.runtime.tooling.ComposeStackTraceFrame
44 import androidx.compose.runtime.tooling.attachComposeStackTrace
45 import androidx.compose.runtime.tooling.buildTrace
46 import androidx.compose.runtime.withAfterAnchorInfo
47 import kotlin.jvm.JvmInline
48 
49 internal typealias IntParameter = Int
50 
51 internal sealed class Operation(val ints: Int = 0, val objects: Int = 0) {
52     val name: String
53         get() = this::class.simpleName.orEmpty()
54 
55     fun OperationArgContainer.executeWithComposeStackTrace(
56         applier: Applier<*>,
57         slots: SlotWriter,
58         rememberManager: RememberManager,
59         errorContext: OperationErrorContext?
60     ) {
61         withCurrentStackTrace(errorContext, slots, getGroupAnchor(slots)) {
62             execute(applier, slots, rememberManager, errorContext)
63         }
64     }
65 
66     protected open fun OperationArgContainer.getGroupAnchor(slots: SlotWriter): Anchor? = null
67 
68     protected abstract fun OperationArgContainer.execute(
69         applier: Applier<*>,
70         slots: SlotWriter,
71         rememberManager: RememberManager,
72         errorContext: OperationErrorContext?
73     )
74 
75     open fun intParamName(parameter: IntParameter): String = "IntParameter(${parameter})"
76 
77     open fun objectParamName(parameter: ObjectParameter<*>): String =
78         "ObjectParameter(${parameter.offset})"
79 
80     override fun toString() = name
81 
82     @JvmInline value class ObjectParameter<T>(val offset: Int)
83 
84     // region traversal operations
85     object Ups : Operation(ints = 1) {
86         inline val Count
87             get() = 0
88 
89         override fun intParamName(parameter: IntParameter) =
90             when (parameter) {
91                 Count -> "count"
92                 else -> super.intParamName(parameter)
93             }
94 
95         override fun OperationArgContainer.execute(
96             applier: Applier<*>,
97             slots: SlotWriter,
98             rememberManager: RememberManager,
99             errorContext: OperationErrorContext?
100         ) {
101             repeat(getInt(Count)) { applier.up() }
102         }
103     }
104 
105     object Downs : Operation(objects = 1) {
106         inline val Nodes
107             get() = ObjectParameter<Array<Any?>>(0)
108 
109         override fun objectParamName(parameter: ObjectParameter<*>) =
110             when (parameter) {
111                 Nodes -> "nodes"
112                 else -> super.objectParamName(parameter)
113             }
114 
115         override fun OperationArgContainer.execute(
116             applier: Applier<*>,
117             slots: SlotWriter,
118             rememberManager: RememberManager,
119             errorContext: OperationErrorContext?
120         ) {
121             @Suppress("UNCHECKED_CAST") val nodeApplier = applier as Applier<Any?>
122             val nodes = getObject(Nodes)
123             for (index in nodes.indices) {
124                 nodeApplier.down(nodes[index])
125             }
126         }
127     }
128 
129     object AdvanceSlotsBy : Operation(ints = 1) {
130         inline val Distance
131             get() = 0
132 
133         override fun intParamName(parameter: IntParameter) =
134             when (parameter) {
135                 Distance -> "distance"
136                 else -> super.intParamName(parameter)
137             }
138 
139         override fun OperationArgContainer.execute(
140             applier: Applier<*>,
141             slots: SlotWriter,
142             rememberManager: RememberManager,
143             errorContext: OperationErrorContext?
144         ) {
145             slots.advanceBy(getInt(Distance))
146         }
147     }
148 
149     // endregion traversal operations
150 
151     // region operations for Remember and SideEffects
152     object SideEffect : Operation(objects = 1) {
153         inline val Effect
154             get() = ObjectParameter<() -> Unit>(0)
155 
156         override fun objectParamName(parameter: ObjectParameter<*>) =
157             when (parameter) {
158                 Effect -> "effect"
159                 else -> super.objectParamName(parameter)
160             }
161 
162         override fun OperationArgContainer.execute(
163             applier: Applier<*>,
164             slots: SlotWriter,
165             rememberManager: RememberManager,
166             errorContext: OperationErrorContext?
167         ) {
168             rememberManager.sideEffect(getObject(Effect))
169         }
170     }
171 
172     object Remember : Operation(objects = 1) {
173         inline val Value
174             get() = ObjectParameter<RememberObserverHolder>(0)
175 
176         override fun objectParamName(parameter: ObjectParameter<*>) =
177             when (parameter) {
178                 Value -> "value"
179                 else -> super.objectParamName(parameter)
180             }
181 
182         override fun OperationArgContainer.execute(
183             applier: Applier<*>,
184             slots: SlotWriter,
185             rememberManager: RememberManager,
186             errorContext: OperationErrorContext?
187         ) {
188             rememberManager.remembering(getObject(Value))
189         }
190     }
191 
192     object RememberPausingScope : Operation(objects = 1) {
193         inline val Scope
194             get() = ObjectParameter<RecomposeScopeImpl>(0)
195 
196         override fun objectParamName(parameter: ObjectParameter<*>): String =
197             when (parameter) {
198                 Scope -> "scope"
199                 else -> super.objectParamName(parameter)
200             }
201 
202         override fun OperationArgContainer.execute(
203             applier: Applier<*>,
204             slots: SlotWriter,
205             rememberManager: RememberManager,
206             errorContext: OperationErrorContext?
207         ) {
208             val scope = getObject(Scope)
209             rememberManager.rememberPausingScope(scope)
210         }
211     }
212 
213     object StartResumingScope : Operation(objects = 1) {
214         inline val Scope
215             get() = ObjectParameter<RecomposeScopeImpl>(0)
216 
217         override fun objectParamName(parameter: ObjectParameter<*>): String =
218             when (parameter) {
219                 Scope -> "scope"
220                 else -> super.objectParamName(parameter)
221             }
222 
223         override fun OperationArgContainer.execute(
224             applier: Applier<*>,
225             slots: SlotWriter,
226             rememberManager: RememberManager,
227             errorContext: OperationErrorContext?
228         ) {
229             val scope = getObject(Scope)
230             rememberManager.startResumingScope(scope)
231         }
232     }
233 
234     object EndResumingScope : Operation(objects = 1) {
235         inline val Scope
236             get() = ObjectParameter<RecomposeScopeImpl>(0)
237 
238         override fun objectParamName(parameter: ObjectParameter<*>): String =
239             when (parameter) {
240                 Scope -> "scope"
241                 else -> super.objectParamName(parameter)
242             }
243 
244         override fun OperationArgContainer.execute(
245             applier: Applier<*>,
246             slots: SlotWriter,
247             rememberManager: RememberManager,
248             errorContext: OperationErrorContext?
249         ) {
250             val scope = getObject(Scope)
251             rememberManager.endResumingScope(scope)
252         }
253     }
254 
255     object AppendValue : Operation(objects = 2) {
256         inline val Anchor
257             get() = ObjectParameter<Anchor>(0)
258 
259         inline val Value
260             get() = ObjectParameter<Any?>(1)
261 
262         override fun objectParamName(parameter: ObjectParameter<*>): String =
263             when (parameter) {
264                 Anchor -> "anchor"
265                 Value -> "value"
266                 else -> super.objectParamName(parameter)
267             }
268 
269         override fun OperationArgContainer.execute(
270             applier: Applier<*>,
271             slots: SlotWriter,
272             rememberManager: RememberManager,
273             errorContext: OperationErrorContext?
274         ) {
275             val anchor = getObject(Anchor)
276             val value = getObject(Value)
277             if (value is RememberObserverHolder) {
278                 rememberManager.remembering(value)
279             }
280             slots.appendSlot(anchor, value)
281         }
282     }
283 
284     object TrimParentValues : Operation(ints = 1) {
285         inline val Count
286             get() = 0
287 
288         override fun intParamName(parameter: IntParameter): String =
289             when (parameter) {
290                 Count -> "count"
291                 else -> super.intParamName(parameter)
292             }
293 
294         override fun OperationArgContainer.execute(
295             applier: Applier<*>,
296             slots: SlotWriter,
297             rememberManager: RememberManager,
298             errorContext: OperationErrorContext?
299         ) {
300             val count = getInt(Count)
301             val slotsSize = slots.slotsSize
302             slots.forEachTailSlot(slots.parent, count) { slotIndex, value ->
303                 when (value) {
304                     is RememberObserverHolder -> {
305                         // Values are always updated in the composition order (not slot table order)
306                         // so there is no need to reorder these.
307                         val endRelativeOrder = slotsSize - slotIndex
308                         slots.withAfterAnchorInfo(value.after) { priority, endRelativeAfter ->
309                             rememberManager.forgetting(
310                                 instance = value,
311                                 endRelativeOrder = endRelativeOrder,
312                                 priority = priority,
313                                 endRelativeAfter = endRelativeAfter
314                             )
315                         }
316                     }
317                     is RecomposeScopeImpl -> value.release()
318                 }
319             }
320             slots.trimTailSlots(count)
321         }
322     }
323 
324     object UpdateValue : Operation(ints = 1, objects = 1) {
325         inline val Value
326             get() = ObjectParameter<Any?>(0)
327 
328         inline val GroupSlotIndex
329             get() = 0
330 
331         override fun intParamName(parameter: IntParameter) =
332             when (parameter) {
333                 GroupSlotIndex -> "groupSlotIndex"
334                 else -> super.intParamName(parameter)
335             }
336 
337         override fun objectParamName(parameter: ObjectParameter<*>) =
338             when (parameter) {
339                 Value -> "value"
340                 else -> super.objectParamName(parameter)
341             }
342 
343         override fun OperationArgContainer.execute(
344             applier: Applier<*>,
345             slots: SlotWriter,
346             rememberManager: RememberManager,
347             errorContext: OperationErrorContext?
348         ) {
349             val value = getObject(Value)
350             val groupSlotIndex = getInt(GroupSlotIndex)
351             if (value is RememberObserverHolder) {
352                 rememberManager.remembering(value)
353             }
354             when (val previous = slots.set(groupSlotIndex, value)) {
355                 is RememberObserverHolder -> {
356                     val endRelativeOrder =
357                         slots.slotsSize -
358                             slots.slotIndexOfGroupSlotIndex(slots.currentGroup, groupSlotIndex)
359                     // Values are always updated in the composition order (not slot table order)
360                     // so there is no need to reorder these.
361                     rememberManager.forgetting(previous, endRelativeOrder, -1, -1)
362                 }
363                 is RecomposeScopeImpl -> previous.release()
364             }
365         }
366     }
367 
368     object UpdateAnchoredValue : Operation(objects = 2, ints = 1) {
369         inline val Value
370             get() = ObjectParameter<Any?>(0)
371 
372         inline val Anchor
373             get() = ObjectParameter<Anchor>(1)
374 
375         inline val GroupSlotIndex
376             get() = 0
377 
378         override fun intParamName(parameter: IntParameter) =
379             when (parameter) {
380                 GroupSlotIndex -> "groupSlotIndex"
381                 else -> super.intParamName(parameter)
382             }
383 
384         override fun objectParamName(parameter: ObjectParameter<*>) =
385             when (parameter) {
386                 Value -> "value"
387                 Anchor -> "anchor"
388                 else -> super.objectParamName(parameter)
389             }
390 
391         override fun OperationArgContainer.execute(
392             applier: Applier<*>,
393             slots: SlotWriter,
394             rememberManager: RememberManager,
395             errorContext: OperationErrorContext?
396         ) {
397             val value = getObject(Value)
398             val anchor = getObject(Anchor)
399             val groupSlotIndex = getInt(GroupSlotIndex)
400             if (value is RememberObserverHolder) {
401                 rememberManager.remembering(value)
402             }
403             val groupIndex = slots.anchorIndex(anchor)
404             when (val previous = slots.set(groupIndex, groupSlotIndex, value)) {
405                 is RememberObserverHolder -> {
406                     val endRelativeSlotOrder =
407                         slots.slotsSize -
408                             slots.slotIndexOfGroupSlotIndex(groupIndex, groupSlotIndex)
409                     slots.withAfterAnchorInfo(previous.after) { priority, endRelativeAfter ->
410                         rememberManager.forgetting(
411                             previous,
412                             endRelativeSlotOrder,
413                             priority,
414                             endRelativeAfter
415                         )
416                     }
417                 }
418                 is RecomposeScopeImpl -> previous.release()
419             }
420         }
421     }
422 
423     // endregion operations for Remember and SideEffects
424 
425     // region operations for Nodes and Groups
426     object UpdateAuxData : Operation(objects = 1) {
427         inline val Data
428             get() = ObjectParameter<Any?>(0)
429 
430         override fun objectParamName(parameter: ObjectParameter<*>) =
431             when (parameter) {
432                 Data -> "data"
433                 else -> super.objectParamName(parameter)
434             }
435 
436         override fun OperationArgContainer.execute(
437             applier: Applier<*>,
438             slots: SlotWriter,
439             rememberManager: RememberManager,
440             errorContext: OperationErrorContext?
441         ) {
442             slots.updateAux(getObject(Data))
443         }
444     }
445 
446     object EnsureRootGroupStarted : Operation() {
447         override fun OperationArgContainer.execute(
448             applier: Applier<*>,
449             slots: SlotWriter,
450             rememberManager: RememberManager,
451             errorContext: OperationErrorContext?
452         ) {
453             slots.ensureStarted(0)
454         }
455     }
456 
457     object EnsureGroupStarted : Operation(objects = 1) {
458         inline val Anchor
459             get() = ObjectParameter<Anchor>(0)
460 
461         override fun objectParamName(parameter: ObjectParameter<*>) =
462             when (parameter) {
463                 Anchor -> "anchor"
464                 else -> super.objectParamName(parameter)
465             }
466 
467         override fun OperationArgContainer.execute(
468             applier: Applier<*>,
469             slots: SlotWriter,
470             rememberManager: RememberManager,
471             errorContext: OperationErrorContext?
472         ) {
473             slots.ensureStarted(getObject(Anchor))
474         }
475     }
476 
477     object RemoveCurrentGroup : Operation() {
478         override fun OperationArgContainer.execute(
479             applier: Applier<*>,
480             slots: SlotWriter,
481             rememberManager: RememberManager,
482             errorContext: OperationErrorContext?
483         ) {
484             slots.removeCurrentGroup(rememberManager)
485         }
486     }
487 
488     object MoveCurrentGroup : Operation(ints = 1) {
489         inline val Offset
490             get() = 0
491 
492         override fun intParamName(parameter: IntParameter) =
493             when (parameter) {
494                 Offset -> "offset"
495                 else -> super.intParamName(parameter)
496             }
497 
498         override fun OperationArgContainer.execute(
499             applier: Applier<*>,
500             slots: SlotWriter,
501             rememberManager: RememberManager,
502             errorContext: OperationErrorContext?
503         ) {
504             slots.moveGroup(getInt(Offset))
505         }
506     }
507 
508     object EndCurrentGroup : Operation() {
509         override fun OperationArgContainer.execute(
510             applier: Applier<*>,
511             slots: SlotWriter,
512             rememberManager: RememberManager,
513             errorContext: OperationErrorContext?
514         ) {
515             slots.endGroup()
516         }
517     }
518 
519     object SkipToEndOfCurrentGroup : Operation() {
520         override fun OperationArgContainer.execute(
521             applier: Applier<*>,
522             slots: SlotWriter,
523             rememberManager: RememberManager,
524             errorContext: OperationErrorContext?
525         ) {
526             slots.skipToGroupEnd()
527         }
528     }
529 
530     object EndCompositionScope : Operation(objects = 2) {
531         inline val Action
532             get() = ObjectParameter<(Composition) -> Unit>(0)
533 
534         inline val Composition
535             get() = ObjectParameter<Composition>(1)
536 
537         override fun objectParamName(parameter: ObjectParameter<*>) =
538             when (parameter) {
539                 Action -> "anchor"
540                 Composition -> "composition"
541                 else -> super.objectParamName(parameter)
542             }
543 
544         override fun OperationArgContainer.execute(
545             applier: Applier<*>,
546             slots: SlotWriter,
547             rememberManager: RememberManager,
548             errorContext: OperationErrorContext?
549         ) {
550             val action = getObject(Action)
551             val composition = getObject(Composition)
552 
553             action.invoke(composition)
554         }
555     }
556 
557     object UseCurrentNode : Operation() {
558         override fun OperationArgContainer.execute(
559             applier: Applier<*>,
560             slots: SlotWriter,
561             rememberManager: RememberManager,
562             errorContext: OperationErrorContext?
563         ) {
564             applier.reuse()
565         }
566     }
567 
568     object UpdateNode : Operation(objects = 2) {
569         inline val Value
570             get() = ObjectParameter<Any?>(0)
571 
572         inline val Block
573             get() = ObjectParameter<Any?.(Any?) -> Unit /* Node?.(Value) -> Unit */>(1)
574 
575         override fun objectParamName(parameter: ObjectParameter<*>) =
576             when (parameter) {
577                 Value -> "value"
578                 Block -> "block"
579                 else -> super.objectParamName(parameter)
580             }
581 
582         override fun OperationArgContainer.execute(
583             applier: Applier<*>,
584             slots: SlotWriter,
585             rememberManager: RememberManager,
586             errorContext: OperationErrorContext?
587         ) {
588             val value = getObject(Value)
589             val block = getObject(Block)
590             applier.apply(block, value)
591         }
592     }
593 
594     object RemoveNode : Operation(ints = 2) {
595         inline val RemoveIndex
596             get() = 0
597 
598         inline val Count
599             get() = 1
600 
601         override fun intParamName(parameter: IntParameter) =
602             when (parameter) {
603                 RemoveIndex -> "removeIndex"
604                 Count -> "count"
605                 else -> super.intParamName(parameter)
606             }
607 
608         override fun OperationArgContainer.execute(
609             applier: Applier<*>,
610             slots: SlotWriter,
611             rememberManager: RememberManager,
612             errorContext: OperationErrorContext?
613         ) {
614             applier.remove(index = getInt(RemoveIndex), count = getInt(Count))
615         }
616     }
617 
618     object MoveNode : Operation(ints = 3) {
619         inline val From
620             get() = 0
621 
622         inline val To
623             get() = 1
624 
625         inline val Count
626             get() = 2
627 
628         override fun intParamName(parameter: IntParameter) =
629             when (parameter) {
630                 From -> "from"
631                 To -> "to"
632                 Count -> "count"
633                 else -> super.intParamName(parameter)
634             }
635 
636         override fun OperationArgContainer.execute(
637             applier: Applier<*>,
638             slots: SlotWriter,
639             rememberManager: RememberManager,
640             errorContext: OperationErrorContext?
641         ) {
642             applier.move(from = getInt(From), to = getInt(To), count = getInt(Count))
643         }
644     }
645 
646     object InsertSlots : Operation(objects = 2) {
647         inline val Anchor
648             get() = ObjectParameter<Anchor>(0)
649 
650         inline val FromSlotTable
651             get() = ObjectParameter<SlotTable>(1)
652 
653         override fun objectParamName(parameter: ObjectParameter<*>) =
654             when (parameter) {
655                 Anchor -> "anchor"
656                 FromSlotTable -> "from"
657                 else -> super.objectParamName(parameter)
658             }
659 
660         override fun OperationArgContainer.execute(
661             applier: Applier<*>,
662             slots: SlotWriter,
663             rememberManager: RememberManager,
664             errorContext: OperationErrorContext?
665         ) {
666             val insertTable = getObject(FromSlotTable)
667             val anchor = getObject(Anchor)
668 
669             slots.beginInsert()
670             slots.moveFrom(
671                 table = insertTable,
672                 index = anchor.toIndexFor(insertTable),
673                 removeSourceGroup = false
674             )
675             slots.endInsert()
676         }
677     }
678 
679     object InsertSlotsWithFixups : Operation(objects = 3) {
680         inline val Anchor
681             get() = ObjectParameter<Anchor>(0)
682 
683         inline val FromSlotTable
684             get() = ObjectParameter<SlotTable>(1)
685 
686         inline val Fixups
687             get() = ObjectParameter<FixupList>(2)
688 
689         override fun objectParamName(parameter: ObjectParameter<*>) =
690             when (parameter) {
691                 Anchor -> "anchor"
692                 FromSlotTable -> "from"
693                 Fixups -> "fixups"
694                 else -> super.objectParamName(parameter)
695             }
696 
697         override fun OperationArgContainer.execute(
698             applier: Applier<*>,
699             slots: SlotWriter,
700             rememberManager: RememberManager,
701             errorContext: OperationErrorContext?
702         ) {
703             val insertTable = getObject(FromSlotTable)
704             val anchor = getObject(Anchor)
705             val fixups = getObject(Fixups)
706 
707             insertTable.write { writer ->
708                 fixups.executeAndFlushAllPendingFixups(
709                     applier,
710                     writer,
711                     rememberManager,
712                     errorContext?.withCurrentStackTrace(slots)
713                 )
714             }
715             slots.beginInsert()
716             slots.moveFrom(
717                 table = insertTable,
718                 index = anchor.toIndexFor(insertTable),
719                 removeSourceGroup = false
720             )
721             slots.endInsert()
722         }
723     }
724 
725     object InsertNodeFixup : Operation(ints = 1, objects = 2) {
726         inline val Factory
727             get() = ObjectParameter<() -> Any?>(0)
728 
729         inline val InsertIndex
730             get() = 0
731 
732         inline val GroupAnchor
733             get() = ObjectParameter<Anchor>(1)
734 
735         override fun intParamName(parameter: IntParameter) =
736             when (parameter) {
737                 InsertIndex -> "insertIndex"
738                 else -> super.intParamName(parameter)
739             }
740 
741         override fun objectParamName(parameter: ObjectParameter<*>) =
742             when (parameter) {
743                 Factory -> "factory"
744                 GroupAnchor -> "groupAnchor"
745                 else -> super.objectParamName(parameter)
746             }
747 
748         override fun OperationArgContainer.getGroupAnchor(slots: SlotWriter): Anchor? =
749             getObject(GroupAnchor)
750 
751         override fun OperationArgContainer.execute(
752             applier: Applier<*>,
753             slots: SlotWriter,
754             rememberManager: RememberManager,
755             errorContext: OperationErrorContext?
756         ) {
757             val node = getObject(Factory).invoke()
758             val groupAnchor = getObject(GroupAnchor)
759             val insertIndex = getInt(InsertIndex)
760 
761             val nodeApplier = @Suppress("UNCHECKED_CAST") (applier as Applier<Any?>)
762             slots.updateNode(groupAnchor, node)
763             nodeApplier.insertTopDown(insertIndex, node)
764             nodeApplier.down(node)
765         }
766     }
767 
768     object PostInsertNodeFixup : Operation(ints = 1, objects = 1) {
769         inline val InsertIndex
770             get() = 0
771 
772         inline val GroupAnchor
773             get() = ObjectParameter<Anchor>(0)
774 
775         override fun intParamName(parameter: IntParameter) =
776             when (parameter) {
777                 InsertIndex -> "insertIndex"
778                 else -> super.intParamName(parameter)
779             }
780 
781         override fun objectParamName(parameter: ObjectParameter<*>) =
782             when (parameter) {
783                 GroupAnchor -> "groupAnchor"
784                 else -> super.objectParamName(parameter)
785             }
786 
787         override fun OperationArgContainer.getGroupAnchor(slots: SlotWriter): Anchor? =
788             getObject(GroupAnchor)
789 
790         override fun OperationArgContainer.execute(
791             applier: Applier<*>,
792             slots: SlotWriter,
793             rememberManager: RememberManager,
794             errorContext: OperationErrorContext?
795         ) {
796             val groupAnchor = getObject(GroupAnchor)
797             val insertIndex = getInt(InsertIndex)
798 
799             applier.up()
800             val nodeApplier = @Suppress("UNCHECKED_CAST") (applier as Applier<Any?>)
801             val nodeToInsert = slots.node(groupAnchor)
802             nodeApplier.insertBottomUp(insertIndex, nodeToInsert)
803         }
804     }
805 
806     object DeactivateCurrentGroup : Operation() {
807         override fun OperationArgContainer.execute(
808             applier: Applier<*>,
809             slots: SlotWriter,
810             rememberManager: RememberManager,
811             errorContext: OperationErrorContext?,
812         ) {
813             slots.deactivateCurrentGroup(rememberManager)
814         }
815     }
816 
817     // endregion operations for Nodes and Groups
818 
819     // region operations for MovableContent
820     object ResetSlots : Operation() {
821         override fun OperationArgContainer.execute(
822             applier: Applier<*>,
823             slots: SlotWriter,
824             rememberManager: RememberManager,
825             errorContext: OperationErrorContext?,
826         ) {
827             slots.reset()
828         }
829     }
830 
831     object DetermineMovableContentNodeIndex : Operation(objects = 2) {
832         inline val EffectiveNodeIndexOut
833             get() = ObjectParameter<IntRef>(0)
834 
835         inline val Anchor
836             get() = ObjectParameter<Anchor>(1)
837 
838         override fun objectParamName(parameter: ObjectParameter<*>) =
839             when (parameter) {
840                 EffectiveNodeIndexOut -> "effectiveNodeIndexOut"
841                 Anchor -> "anchor"
842                 else -> super.objectParamName(parameter)
843             }
844 
845         override fun OperationArgContainer.execute(
846             applier: Applier<*>,
847             slots: SlotWriter,
848             rememberManager: RememberManager,
849             errorContext: OperationErrorContext?,
850         ) {
851             val effectiveNodeIndexOut = getObject(EffectiveNodeIndexOut)
852 
853             effectiveNodeIndexOut.element =
854                 positionToInsert(
855                     slots = slots,
856                     anchor = getObject(Anchor),
857                     applier = @Suppress("UNCHECKED_CAST") (applier as Applier<Any?>)
858                 )
859         }
860     }
861 
862     object CopyNodesToNewAnchorLocation : Operation(objects = 2) {
863         // IntRef because the index calculated after the operation is queued as part of
864         // `DetermineMovableContentNodeIndex`
865         inline val EffectiveNodeIndex
866             get() = ObjectParameter<IntRef>(0)
867 
868         inline val Nodes
869             get() = ObjectParameter<List<Any?>>(1)
870 
871         override fun objectParamName(parameter: ObjectParameter<*>) =
872             when (parameter) {
873                 EffectiveNodeIndex -> "effectiveNodeIndex"
874                 Nodes -> "nodes"
875                 else -> super.objectParamName(parameter)
876             }
877 
878         override fun OperationArgContainer.execute(
879             applier: Applier<*>,
880             slots: SlotWriter,
881             rememberManager: RememberManager,
882             errorContext: OperationErrorContext?,
883         ) {
884             val effectiveNodeIndex = getObject(EffectiveNodeIndex).element
885             val nodesToInsert = getObject(Nodes)
886             @Suppress("UNCHECKED_CAST")
887             nodesToInsert.fastForEachIndexed { i, node ->
888                 applier as Applier<Any?>
889                 applier.insertBottomUp(effectiveNodeIndex + i, node)
890                 applier.insertTopDown(effectiveNodeIndex + i, node)
891             }
892         }
893     }
894 
895     @OptIn(InternalComposeApi::class)
896     object CopySlotTableToAnchorLocation : Operation(objects = 4) {
897         inline val ResolvedState
898             get() = ObjectParameter<MovableContentState?>(0)
899 
900         inline val ParentCompositionContext
901             get() = ObjectParameter<CompositionContext>(1)
902 
903         inline val From
904             get() = ObjectParameter<MovableContentStateReference>(2)
905 
906         inline val To
907             get() = ObjectParameter<MovableContentStateReference>(3)
908 
909         override fun objectParamName(parameter: ObjectParameter<*>) =
910             when (parameter) {
911                 ResolvedState -> "resolvedState"
912                 ParentCompositionContext -> "resolvedCompositionContext"
913                 From -> "from"
914                 To -> "to"
915                 else -> super.objectParamName(parameter)
916             }
917 
918         override fun OperationArgContainer.execute(
919             applier: Applier<*>,
920             slots: SlotWriter,
921             rememberManager: RememberManager,
922             errorContext: OperationErrorContext?,
923         ) {
924             val from = getObject(From)
925             val to = getObject(To)
926             val parentCompositionContext = getObject(ParentCompositionContext)
927 
928             val resolvedState =
929                 getObject(ResolvedState)
930                     ?: parentCompositionContext.movableContentStateResolve(from)
931                     ?: composeRuntimeError("Could not resolve state for movable content")
932 
933             // The slot table contains the movable content group plus the group
934             // containing the movable content's table which then contains the actual
935             // state to be inserted. The state is at index 2 in the table (for the
936             // two groups) and is inserted into the provider group at offset 1 from the
937             // current location.
938             val anchors = slots.moveIntoGroupFrom(1, resolvedState.slotTable, 2)
939 
940             // For all the anchors that moved, if the anchor is tracking a recompose
941             // scope, update it to reference its new composer.
942             RecomposeScopeImpl.adoptAnchoredScopes(
943                 slots = slots,
944                 anchors = anchors,
945                 newOwner = to.composition as RecomposeScopeOwner
946             )
947         }
948     }
949 
950     object EndMovableContentPlacement : Operation() {
951         override fun OperationArgContainer.execute(
952             applier: Applier<*>,
953             slots: SlotWriter,
954             rememberManager: RememberManager,
955             errorContext: OperationErrorContext?,
956         ) {
957             positionToParentOf(
958                 slots = slots,
959                 applier = @Suppress("UNCHECKED_CAST") (applier as Applier<Any?>),
960                 index = 0
961             )
962             slots.endGroup()
963         }
964     }
965 
966     @OptIn(InternalComposeApi::class)
967     object ReleaseMovableGroupAtCurrent : Operation(objects = 3) {
968         inline val Composition
969             get() = ObjectParameter<ControlledComposition>(0)
970 
971         inline val ParentCompositionContext
972             get() = ObjectParameter<CompositionContext>(1)
973 
974         inline val Reference
975             get() = ObjectParameter<MovableContentStateReference>(2)
976 
977         override fun objectParamName(parameter: ObjectParameter<*>) =
978             when (parameter) {
979                 Composition -> "composition"
980                 ParentCompositionContext -> "parentCompositionContext"
981                 Reference -> "reference"
982                 else -> super.objectParamName(parameter)
983             }
984 
985         override fun OperationArgContainer.execute(
986             applier: Applier<*>,
987             slots: SlotWriter,
988             rememberManager: RememberManager,
989             errorContext: OperationErrorContext?,
990         ) {
991             val composition = getObject(Composition)
992             val reference = getObject(Reference)
993             val parentContext = getObject(ParentCompositionContext)
994             val state =
995                 extractMovableContentAtCurrent(
996                     composition = composition,
997                     reference = reference,
998                     slots = slots,
999                     applier = null,
1000                 )
1001             parentContext.movableContentStateReleased(reference, state, applier)
1002         }
1003     }
1004 
1005     object ApplyChangeList : Operation(objects = 2) {
1006         inline val Changes
1007             get() = ObjectParameter<ChangeList>(0)
1008 
1009         inline val EffectiveNodeIndex
1010             get() = ObjectParameter<IntRef?>(1)
1011 
1012         override fun objectParamName(parameter: ObjectParameter<*>) =
1013             when (parameter) {
1014                 Changes -> "changes"
1015                 EffectiveNodeIndex -> "effectiveNodeIndex"
1016                 else -> super.objectParamName(parameter)
1017             }
1018 
1019         override fun OperationArgContainer.execute(
1020             applier: Applier<*>,
1021             slots: SlotWriter,
1022             rememberManager: RememberManager,
1023             errorContext: OperationErrorContext?
1024         ) {
1025             val effectiveNodeIndex = getObject(EffectiveNodeIndex)?.element ?: 0
1026 
1027             getObject(Changes)
1028                 .executeAndFlushAllPendingChanges(
1029                     applier =
1030                         if (effectiveNodeIndex > 0) {
1031                             OffsetApplier(applier, effectiveNodeIndex)
1032                         } else {
1033                             applier
1034                         },
1035                     slots = slots,
1036                     rememberManager = rememberManager,
1037                     errorContext = errorContext?.withCurrentStackTrace(slots)
1038                 )
1039         }
1040     }
1041 
1042     // endregion operations for MovableContent
1043 
1044     /**
1045      * Operation type used for tests. Operations can be created with arbitrary int and object
1046      * params, which lets us test [Operations] without relying on the implementation details of any
1047      * particular operation we use in production.
1048      */
1049     class TestOperation
1050     @TestOnly
1051     constructor(
1052         ints: Int = 0,
1053         objects: Int = 0,
1054         val block: (Applier<*>, SlotWriter, RememberManager) -> Unit = { _, _, _ -> }
1055     ) : Operation(ints, objects) {
1056         @Suppress("PrimitiveInCollection") val intParams = List(ints) { it }
1057         val objParams = List(objects) { index -> ObjectParameter<Any?>(index) }
1058 
1059         override fun OperationArgContainer.execute(
1060             applier: Applier<*>,
1061             slots: SlotWriter,
1062             rememberManager: RememberManager,
1063             errorContext: OperationErrorContext?
1064         ): Unit = block(applier, slots, rememberManager)
1065 
1066         override fun toString() =
1067             "TestOperation(ints = $ints, objects = $objects)@${identityHashCode(this)}"
1068     }
1069 }
1070 
positionToParentOfnull1071 private fun positionToParentOf(slots: SlotWriter, applier: Applier<Any?>, index: Int) {
1072     while (!slots.indexInParent(index)) {
1073         slots.skipToGroupEnd()
1074         if (slots.isNode(slots.parent)) applier.up()
1075         slots.endGroup()
1076     }
1077 }
1078 
currentNodeIndexnull1079 private fun currentNodeIndex(slots: SlotWriter): Int {
1080     val original = slots.currentGroup
1081 
1082     // Find parent node
1083     var current = slots.parent
1084     while (current >= 0 && !slots.isNode(current)) {
1085         current = slots.parent(current)
1086     }
1087 
1088     var index = 0
1089     current++
1090     while (current < original) {
1091         if (slots.indexInGroup(original, current)) {
1092             if (slots.isNode(current)) index = 0
1093             current++
1094         } else {
1095             index += if (slots.isNode(current)) 1 else slots.nodeCount(current)
1096             current += slots.groupSize(current)
1097         }
1098     }
1099     return index
1100 }
1101 
positionToInsertnull1102 private fun positionToInsert(slots: SlotWriter, anchor: Anchor, applier: Applier<Any?>): Int {
1103     val destination = slots.anchorIndex(anchor)
1104     runtimeCheck(slots.currentGroup < destination)
1105     positionToParentOf(slots, applier, destination)
1106     var nodeIndex = currentNodeIndex(slots)
1107     while (slots.currentGroup < destination) {
1108         when {
1109             slots.indexInCurrentGroup(destination) -> {
1110                 if (slots.isNode) {
1111                     applier.down(slots.node(slots.currentGroup))
1112                     nodeIndex = 0
1113                 }
1114                 slots.startGroup()
1115             }
1116             else -> nodeIndex += slots.skipGroup()
1117         }
1118     }
1119 
1120     runtimeCheck(slots.currentGroup == destination)
1121     return nodeIndex
1122 }
1123 
withCurrentStackTracenull1124 private inline fun withCurrentStackTrace(
1125     errorContext: OperationErrorContext?,
1126     writer: SlotWriter,
1127     location: Anchor?,
1128     block: () -> Unit
1129 ) {
1130     try {
1131         block()
1132     } catch (e: Throwable) {
1133         throw e.attachComposeStackTrace(errorContext, writer, location)
1134     }
1135 }
1136 
Throwablenull1137 private fun Throwable.attachComposeStackTrace(
1138     errorContext: OperationErrorContext?,
1139     writer: SlotWriter,
1140     anchor: Anchor?
1141 ): Throwable {
1142     if (errorContext == null) return this
1143     return attachComposeStackTrace {
1144         if (anchor != null) {
1145             writer.seek(anchor)
1146         }
1147         val trace = writer.buildTrace()
1148         val offset = trace.lastOrNull()?.groupOffset
1149         val parentTrace =
1150             errorContext.buildStackTrace(offset).let {
1151                 if (offset == null || it.isEmpty()) {
1152                     it
1153                 } else {
1154                     val head = it.first()
1155                     val tail = it.drop(1)
1156                     listOf(head.copy(groupOffset = offset)) + tail
1157                 }
1158             }
1159         trace + parentTrace
1160     }
1161 }
1162 
OperationErrorContextnull1163 private fun OperationErrorContext.withCurrentStackTrace(slots: SlotWriter): OperationErrorContext {
1164     val parent = this
1165     return object : OperationErrorContext {
1166         override fun buildStackTrace(currentOffset: Int?): List<ComposeStackTraceFrame> {
1167             val parentTrace = parent.buildStackTrace(null)
1168             // Slots are positioned at the start of the next group when insertion happens
1169             val currentGroup = slots.parent
1170             if (currentGroup < 0) return parentTrace
1171             return slots.buildTrace(currentOffset, currentGroup, slots.parent(currentGroup)) +
1172                 parentTrace
1173         }
1174     }
1175 }
1176