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