1 /*
2  * 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.ComposeNodeLifecycleCallback
22 import androidx.compose.runtime.Composition
23 import androidx.compose.runtime.CompositionContext
24 import androidx.compose.runtime.ControlledComposition
25 import androidx.compose.runtime.InternalComposeApi
26 import androidx.compose.runtime.MovableContentState
27 import androidx.compose.runtime.MovableContentStateReference
28 import androidx.compose.runtime.RecomposeScopeImpl
29 import androidx.compose.runtime.RememberManager
30 import androidx.compose.runtime.RememberObserverHolder
31 import androidx.compose.runtime.SlotTable
32 import androidx.compose.runtime.SlotWriter
33 import androidx.compose.runtime.changelist.Operation.AdvanceSlotsBy
34 import androidx.compose.runtime.changelist.Operation.AppendValue
35 import androidx.compose.runtime.changelist.Operation.ApplyChangeList
36 import androidx.compose.runtime.changelist.Operation.CopyNodesToNewAnchorLocation
37 import androidx.compose.runtime.changelist.Operation.CopySlotTableToAnchorLocation
38 import androidx.compose.runtime.changelist.Operation.DeactivateCurrentGroup
39 import androidx.compose.runtime.changelist.Operation.DetermineMovableContentNodeIndex
40 import androidx.compose.runtime.changelist.Operation.Downs
41 import androidx.compose.runtime.changelist.Operation.EndCompositionScope
42 import androidx.compose.runtime.changelist.Operation.EndCurrentGroup
43 import androidx.compose.runtime.changelist.Operation.EndMovableContentPlacement
44 import androidx.compose.runtime.changelist.Operation.EndResumingScope
45 import androidx.compose.runtime.changelist.Operation.EnsureGroupStarted
46 import androidx.compose.runtime.changelist.Operation.EnsureRootGroupStarted
47 import androidx.compose.runtime.changelist.Operation.InsertSlots
48 import androidx.compose.runtime.changelist.Operation.InsertSlotsWithFixups
49 import androidx.compose.runtime.changelist.Operation.MoveCurrentGroup
50 import androidx.compose.runtime.changelist.Operation.MoveNode
51 import androidx.compose.runtime.changelist.Operation.ReleaseMovableGroupAtCurrent
52 import androidx.compose.runtime.changelist.Operation.Remember
53 import androidx.compose.runtime.changelist.Operation.RememberPausingScope
54 import androidx.compose.runtime.changelist.Operation.RemoveCurrentGroup
55 import androidx.compose.runtime.changelist.Operation.RemoveNode
56 import androidx.compose.runtime.changelist.Operation.ResetSlots
57 import androidx.compose.runtime.changelist.Operation.SideEffect
58 import androidx.compose.runtime.changelist.Operation.SkipToEndOfCurrentGroup
59 import androidx.compose.runtime.changelist.Operation.StartResumingScope
60 import androidx.compose.runtime.changelist.Operation.TrimParentValues
61 import androidx.compose.runtime.changelist.Operation.UpdateAnchoredValue
62 import androidx.compose.runtime.changelist.Operation.UpdateAuxData
63 import androidx.compose.runtime.changelist.Operation.UpdateNode
64 import androidx.compose.runtime.changelist.Operation.UpdateValue
65 import androidx.compose.runtime.changelist.Operation.Ups
66 import androidx.compose.runtime.changelist.Operation.UseCurrentNode
67 import androidx.compose.runtime.internal.IntRef
68 
69 internal class ChangeList : OperationsDebugStringFormattable() {
70 
71     private val operations = Operations()
72 
73     val size: Int
74         get() = operations.size
75 
isEmptynull76     fun isEmpty() = operations.isEmpty()
77 
78     fun isNotEmpty() = operations.isNotEmpty()
79 
80     fun clear() {
81         operations.clear()
82     }
83 
executeAndFlushAllPendingChangesnull84     fun executeAndFlushAllPendingChanges(
85         applier: Applier<*>,
86         slots: SlotWriter,
87         rememberManager: RememberManager,
88         errorContext: OperationErrorContext?
89     ) =
90         operations.executeAndFlushAllPendingOperations(
91             applier,
92             slots,
93             rememberManager,
94             errorContext
95         )
96 
97     fun pushRemember(value: RememberObserverHolder) {
98         operations.push(Remember) { setObject(Remember.Value, value) }
99     }
100 
pushRememberPausingScopenull101     fun pushRememberPausingScope(scope: RecomposeScopeImpl) {
102         operations.push(RememberPausingScope) { setObject(RememberPausingScope.Scope, scope) }
103     }
104 
pushStartResumingScopenull105     fun pushStartResumingScope(scope: RecomposeScopeImpl) {
106         operations.push(StartResumingScope) { setObject(StartResumingScope.Scope, scope) }
107     }
108 
pushEndResumingScopenull109     fun pushEndResumingScope(scope: RecomposeScopeImpl) {
110         operations.push(EndResumingScope) { setObject(EndResumingScope.Scope, scope) }
111     }
112 
pushUpdateValuenull113     fun pushUpdateValue(value: Any?, groupSlotIndex: Int) {
114         operations.push(UpdateValue) {
115             setObject(UpdateValue.Value, value)
116             setInt(UpdateValue.GroupSlotIndex, groupSlotIndex)
117         }
118     }
119 
pushUpdateAnchoredValuenull120     fun pushUpdateAnchoredValue(value: Any?, anchor: Anchor, groupSlotIndex: Int) {
121         operations.push(UpdateAnchoredValue) {
122             setObjects(UpdateAnchoredValue.Value, value, UpdateAnchoredValue.Anchor, anchor)
123             setInt(UpdateAnchoredValue.GroupSlotIndex, groupSlotIndex)
124         }
125     }
126 
pushAppendValuenull127     fun pushAppendValue(anchor: Anchor, value: Any?) {
128         operations.push(AppendValue) {
129             setObjects(AppendValue.Anchor, anchor, AppendValue.Value, value)
130         }
131     }
132 
pushTrimValuesnull133     fun pushTrimValues(count: Int) {
134         operations.push(TrimParentValues) { setInt(TrimParentValues.Count, count) }
135     }
136 
pushResetSlotsnull137     fun pushResetSlots() {
138         operations.push(ResetSlots)
139     }
140 
pushDeactivateCurrentGroupnull141     fun pushDeactivateCurrentGroup() {
142         operations.push(DeactivateCurrentGroup)
143     }
144 
pushUpdateAuxDatanull145     fun pushUpdateAuxData(data: Any?) {
146         operations.push(UpdateAuxData) { setObject(UpdateAuxData.Data, data) }
147     }
148 
pushEnsureRootStartednull149     fun pushEnsureRootStarted() {
150         operations.push(EnsureRootGroupStarted)
151     }
152 
pushEnsureGroupStartednull153     fun pushEnsureGroupStarted(anchor: Anchor) {
154         operations.push(EnsureGroupStarted) { setObject(EnsureGroupStarted.Anchor, anchor) }
155     }
156 
pushEndCurrentGroupnull157     fun pushEndCurrentGroup() {
158         operations.push(EndCurrentGroup)
159     }
160 
pushSkipToEndOfCurrentGroupnull161     fun pushSkipToEndOfCurrentGroup() {
162         operations.push(SkipToEndOfCurrentGroup)
163     }
164 
pushRemoveCurrentGroupnull165     fun pushRemoveCurrentGroup() {
166         operations.push(RemoveCurrentGroup)
167     }
168 
pushInsertSlotsnull169     fun pushInsertSlots(anchor: Anchor, from: SlotTable) {
170         operations.push(InsertSlots) {
171             setObjects(InsertSlots.Anchor, anchor, InsertSlots.FromSlotTable, from)
172         }
173     }
174 
pushInsertSlotsnull175     fun pushInsertSlots(anchor: Anchor, from: SlotTable, fixups: FixupList) {
176         operations.push(InsertSlotsWithFixups) {
177             setObjects(
178                 InsertSlotsWithFixups.Anchor,
179                 anchor,
180                 InsertSlotsWithFixups.FromSlotTable,
181                 from,
182                 InsertSlotsWithFixups.Fixups,
183                 fixups
184             )
185         }
186     }
187 
pushMoveCurrentGroupnull188     fun pushMoveCurrentGroup(offset: Int) {
189         operations.push(MoveCurrentGroup) { setInt(MoveCurrentGroup.Offset, offset) }
190     }
191 
pushEndCompositionScopenull192     fun pushEndCompositionScope(action: (Composition) -> Unit, composition: Composition) {
193         operations.push(EndCompositionScope) {
194             setObjects(
195                 EndCompositionScope.Action,
196                 action,
197                 EndCompositionScope.Composition,
198                 composition
199             )
200         }
201     }
202 
pushUseNodenull203     fun pushUseNode(node: Any?) {
204         if (node is ComposeNodeLifecycleCallback) {
205             operations.push(UseCurrentNode)
206         }
207     }
208 
pushUpdateNodenull209     fun <T, V> pushUpdateNode(value: V, block: T.(V) -> Unit) {
210         operations.push(UpdateNode) {
211             @Suppress("UNCHECKED_CAST")
212             setObjects(UpdateNode.Value, value, UpdateNode.Block, block as (Any?.(Any?) -> Unit))
213         }
214     }
215 
pushRemoveNodenull216     fun pushRemoveNode(removeFrom: Int, moveCount: Int) {
217         operations.push(RemoveNode) {
218             setInts(RemoveNode.RemoveIndex, removeFrom, RemoveNode.Count, moveCount)
219         }
220     }
221 
pushMoveNodenull222     fun pushMoveNode(to: Int, from: Int, count: Int) {
223         operations.push(MoveNode) {
224             setInts(MoveNode.To, to, MoveNode.From, from, MoveNode.Count, count)
225         }
226     }
227 
pushAdvanceSlotsBynull228     fun pushAdvanceSlotsBy(distance: Int) {
229         operations.push(AdvanceSlotsBy) { setInt(AdvanceSlotsBy.Distance, distance) }
230     }
231 
pushUpsnull232     fun pushUps(count: Int) {
233         operations.push(Ups) { setInt(Ups.Count, count) }
234     }
235 
pushDownsnull236     fun pushDowns(nodes: Array<Any?>) {
237         if (nodes.isNotEmpty()) {
238             operations.push(Downs) { setObject(Downs.Nodes, nodes) }
239         }
240     }
241 
pushSideEffectnull242     fun pushSideEffect(effect: () -> Unit) {
243         operations.push(SideEffect) { setObject(SideEffect.Effect, effect) }
244     }
245 
pushDetermineMovableContentNodeIndexnull246     fun pushDetermineMovableContentNodeIndex(effectiveNodeIndexOut: IntRef, anchor: Anchor) {
247         operations.push(DetermineMovableContentNodeIndex) {
248             setObjects(
249                 DetermineMovableContentNodeIndex.EffectiveNodeIndexOut,
250                 effectiveNodeIndexOut,
251                 DetermineMovableContentNodeIndex.Anchor,
252                 anchor
253             )
254         }
255     }
256 
pushCopyNodesToNewAnchorLocationnull257     fun pushCopyNodesToNewAnchorLocation(nodes: List<Any?>, effectiveNodeIndex: IntRef) {
258         if (nodes.isNotEmpty()) {
259             operations.push(CopyNodesToNewAnchorLocation) {
260                 setObjects(
261                     CopyNodesToNewAnchorLocation.Nodes,
262                     nodes,
263                     CopyNodesToNewAnchorLocation.EffectiveNodeIndex,
264                     effectiveNodeIndex
265                 )
266             }
267         }
268     }
269 
270     @OptIn(InternalComposeApi::class)
pushCopySlotTableToAnchorLocationnull271     fun pushCopySlotTableToAnchorLocation(
272         resolvedState: MovableContentState?,
273         parentContext: CompositionContext,
274         from: MovableContentStateReference,
275         to: MovableContentStateReference,
276     ) {
277         operations.push(CopySlotTableToAnchorLocation) {
278             setObjects(
279                 CopySlotTableToAnchorLocation.ResolvedState,
280                 resolvedState,
281                 CopySlotTableToAnchorLocation.ParentCompositionContext,
282                 parentContext,
283                 CopySlotTableToAnchorLocation.To,
284                 to,
285                 CopySlotTableToAnchorLocation.From,
286                 from
287             )
288         }
289     }
290 
291     @OptIn(InternalComposeApi::class)
pushReleaseMovableGroupAtCurrentnull292     fun pushReleaseMovableGroupAtCurrent(
293         composition: ControlledComposition,
294         parentContext: CompositionContext,
295         reference: MovableContentStateReference
296     ) {
297         operations.push(ReleaseMovableGroupAtCurrent) {
298             setObjects(
299                 ReleaseMovableGroupAtCurrent.Composition,
300                 composition,
301                 ReleaseMovableGroupAtCurrent.ParentCompositionContext,
302                 parentContext,
303                 ReleaseMovableGroupAtCurrent.Reference,
304                 reference
305             )
306         }
307     }
308 
pushEndMovableContentPlacementnull309     fun pushEndMovableContentPlacement() {
310         operations.push(EndMovableContentPlacement)
311     }
312 
pushExecuteOperationsInnull313     fun pushExecuteOperationsIn(changeList: ChangeList, effectiveNodeIndex: IntRef? = null) {
314         if (changeList.isNotEmpty()) {
315             operations.push(ApplyChangeList) {
316                 setObjects(
317                     ApplyChangeList.Changes,
318                     changeList,
319                     ApplyChangeList.EffectiveNodeIndex,
320                     effectiveNodeIndex
321                 )
322             }
323         }
324     }
325 
toDebugStringnull326     override fun toDebugString(linePrefix: String): String {
327         return buildString {
328             append("ChangeList instance containing ")
329             append(size)
330             append(" operations")
331             if (isNotEmpty()) {
332                 append(":\n")
333                 append(operations.toDebugString(linePrefix))
334             }
335         }
336     }
337 }
338