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