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.RememberManager
22 import androidx.compose.runtime.SlotWriter
23 import androidx.compose.runtime.changelist.Operation.InsertNodeFixup
24 import androidx.compose.runtime.changelist.Operation.PostInsertNodeFixup
25 import androidx.compose.runtime.changelist.Operation.UpdateNode
26 import androidx.compose.runtime.runtimeCheck
27 
28 internal class FixupList : OperationsDebugStringFormattable() {
29     private val operations = Operations()
30     private val pendingOperations = Operations()
31 
32     val size: Int
33         get() = operations.size
34 
isEmptynull35     fun isEmpty() = operations.isEmpty()
36 
37     fun isNotEmpty() = operations.isNotEmpty()
38 
39     fun clear() {
40         pendingOperations.clear()
41         operations.clear()
42     }
43 
executeAndFlushAllPendingFixupsnull44     fun executeAndFlushAllPendingFixups(
45         applier: Applier<*>,
46         slots: SlotWriter,
47         rememberManager: RememberManager,
48         errorContext: OperationErrorContext?
49     ) {
50         runtimeCheck(pendingOperations.isEmpty()) {
51             "FixupList has pending fixup operations that were not realized. " +
52                 "Were there mismatched insertNode() and endNodeInsert() calls?"
53         }
54         operations.executeAndFlushAllPendingOperations(
55             applier,
56             slots,
57             rememberManager,
58             errorContext
59         )
60     }
61 
createAndInsertNodenull62     fun createAndInsertNode(factory: () -> Any?, insertIndex: Int, groupAnchor: Anchor) {
63         operations.push(InsertNodeFixup) {
64             setObject(InsertNodeFixup.Factory, factory)
65             setInt(InsertNodeFixup.InsertIndex, insertIndex)
66             setObject(InsertNodeFixup.GroupAnchor, groupAnchor)
67         }
68 
69         pendingOperations.push(PostInsertNodeFixup) {
70             setInt(PostInsertNodeFixup.InsertIndex, insertIndex)
71             setObject(PostInsertNodeFixup.GroupAnchor, groupAnchor)
72         }
73     }
74 
endNodeInsertnull75     fun endNodeInsert() {
76         runtimeCheck(pendingOperations.isNotEmpty()) {
77             "Cannot end node insertion, there are no pending operations that can be realized."
78         }
79         pendingOperations.popInto(operations)
80     }
81 
updateNodenull82     fun <V, T> updateNode(value: V, block: T.(V) -> Unit) {
83         operations.push(UpdateNode) {
84             setObject(UpdateNode.Value, value)
85             setObject(UpdateNode.Block, @Suppress("UNCHECKED_CAST") (block as Any?.(Any?) -> Unit))
86         }
87     }
88 
toDebugStringnull89     override fun toDebugString(linePrefix: String): String {
90         return buildString {
91             append("FixupList instance containing $size operations")
92             if (isNotEmpty()) append(":\n${operations.toDebugString(linePrefix)}")
93         }
94     }
95 }
96