1 /*
<lambda>null2 * Copyright 2024 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.xr.compose.subspace.node
18
19 import androidx.xr.compose.subspace.layout.CombinedSubspaceModifier
20 import androidx.xr.compose.subspace.layout.Placeable
21 import androidx.xr.compose.subspace.layout.SubspaceModifier
22 import androidx.xr.compose.subspace.layout.findInstance
23 import androidx.xr.compose.subspace.layout.traverseSelfThenAncestors
24 import androidx.xr.compose.subspace.layout.traverseSelfThenDescendants
25 import androidx.xr.compose.unit.VolumeConstraints
26
27 private val SentinelHead =
28 object : SubspaceModifier.Node() {
29 override fun toString() = "<Head>"
30 }
31
32 /** See [androidx.compose.ui.node.NodeChain] */
33 internal class SubspaceModifierNodeChain(private val subspaceLayoutNode: SubspaceLayoutNode) {
34 private var current: MutableList<SubspaceModifier>? = null
35 private var buffer: MutableList<SubspaceModifier>? = null
36 internal val tail: SubspaceModifier.Node = subspaceLayoutNode.measurableLayout.tail
37 internal var head: SubspaceModifier.Node = tail
38 private var inMeasurePass: Boolean = false
39
padChainnull40 private fun padChain(): SubspaceModifier.Node {
41 val currentHead = head
42 currentHead.parent = SentinelHead
43 SentinelHead.child = currentHead
44 return SentinelHead
45 }
46
trimChainnull47 private fun trimChain(): SubspaceModifier.Node {
48 val result = SentinelHead.child ?: tail
49 result.parent = null
50 SentinelHead.child = null
51 return result
52 }
53
updateFromnull54 internal fun updateFrom(modifier: SubspaceModifier) {
55 val paddedHead = padChain()
56 val before = current
57 val beforeSize = before?.size ?: 0
58 val after = modifier.fillVector(buffer ?: mutableListOf())
59 var i = 0
60
61 if (beforeSize == 0) {
62 // Common case where we are initializing the chain or the previous size is zero.
63 var node = paddedHead
64 while (i < after.size) {
65 val next = after[i]
66 val parent = node
67 node = createAndInsertNodeAsChild(next, parent)
68 i++
69 }
70 } else if (after.size == 0) {
71 // Common case where we are removing all the modifiers.
72 var node = paddedHead.child
73 while (node != null && i < beforeSize) {
74 node = removeNode(node).child
75 i++
76 }
77 } else {
78 // Find the diffs between before and after sets. This is not as complex as base Compose
79 // which
80 // does a full diff. Revisit this if we see any performance issues with dynamic
81 // modifiers.
82 var node = paddedHead
83
84 // First match as many same-type modifiers at the beginning of the lists.
85 checkNotNull(before) { "prior modifier list should be non-empty" }
86 while (i < beforeSize && i < after.size && before[i]::class == after[i]::class) {
87 node = checkNotNull(node.child) { "child should not be null" }
88 if (before[i] != after[i]) {
89 updateNode(node, after[i])
90 }
91 i++
92 }
93
94 // Then remove the remaining existing modifiers when we have a structural change.
95 var nodeToDelete = node.child
96 var beforeIndex = i
97 while (nodeToDelete != null && beforeIndex < beforeSize) {
98 nodeToDelete = removeNode(nodeToDelete).child
99 beforeIndex++
100 }
101
102 // Finally add the remaining new modifiers.
103 while (i < after.size) {
104 val next = after[i]
105 val parent = node
106 node = createAndInsertNodeAsChild(next, parent)
107 i++
108 }
109 }
110
111 current = after
112 // Clear the before vector to allow old modifiers to be Garbage Collected.
113 buffer = before?.also { it.clear() }
114 head = trimChain()
115 }
116
117 /**
118 * Marks all nodes in the chain as attached to a [SubspaceLayout].
119 *
120 * This should be called *before* [runOnAttach] is called. We check that the node is not already
121 * attached as this method may be called more than necessary to ensure proper state.
122 */
<lambda>null123 internal fun markAsAttached() = headToTail { if (!it.isAttached) it.markAsAttached() }
124
<lambda>null125 internal fun runOnAttach() = headToTail { if (it.isAttached) it.onAttach() }
126
127 /**
128 * Marks all nodes in the chain as detached from a [SubspaceLayout].
129 *
130 * This should be called *after* [runOnDetach] is called. We check that the node is attached as
131 * this method may be called more than necessary to ensure proper state.
132 */
<lambda>null133 internal fun markAsDetached() = tailToHead { if (it.isAttached) it.markAsDetached() }
134
<lambda>null135 internal fun runOnDetach() = tailToHead { if (it.isAttached) it.onDetach() }
136
measureChainnull137 internal fun measureChain(
138 constraints: VolumeConstraints,
139 wrappedMeasureBlock: (VolumeConstraints) -> Placeable,
140 ): Placeable {
141 val layoutNode = getAll<SubspaceLayoutModifierNode>().firstOrNull()
142 if (layoutNode == null || inMeasurePass) {
143 inMeasurePass = false
144 return wrappedMeasureBlock(constraints)
145 }
146 inMeasurePass = true
147 val placeable = layoutNode.requireCoordinator().measure(constraints)
148 inMeasurePass = false
149 return placeable
150 }
151
152 /** Executes [block] for each modifier in the chain starting at the head. */
headToTailnull153 internal inline fun headToTail(block: (SubspaceModifier.Node) -> Unit) =
154 head.traverseSelfThenDescendants().forEach(block)
155
156 /** Executes [block] for each modifier in the chain starting at the tail. */
157 internal inline fun tailToHead(block: (SubspaceModifier.Node) -> Unit) =
158 tail.traverseSelfThenAncestors().forEach(block)
159
160 /** Returns all nodes of the given type in the chain in the declared modifier order. */
161 internal inline fun <reified T> getAll(): Sequence<T> =
162 head.traverseSelfThenDescendants().filterIsInstance<T>()
163
164 /**
165 * Returns the last node of the given type in the chain if it exists, null otherwise.
166 *
167 * When considering only one instance of a modifier type, prefer the last instance.
168 */
169 internal inline fun <reified T> getLast(): T? =
170 tail.traverseSelfThenAncestors().findInstance<T>()
171
172 private fun createAndInsertNodeAsChild(
173 element: SubspaceModifier,
174 parent: SubspaceModifier.Node,
175 ): SubspaceModifier.Node {
176 val node = (element as SubspaceModifierNodeElement<*>).create()
177 node.layoutNode = subspaceLayoutNode
178 return insertChild(node, parent)
179 }
180
insertChildnull181 private fun insertChild(
182 node: SubspaceModifier.Node,
183 parent: SubspaceModifier.Node,
184 ): SubspaceModifier.Node {
185 val theChild = parent.child
186 if (theChild != null) {
187 theChild.parent = node
188 node.child = theChild
189 }
190 parent.child = node
191 node.parent = parent
192 if (!node.isAttached) {
193 node.markAsAttached()
194 node.onAttach()
195 }
196 return node
197 }
198
updateNodenull199 private fun updateNode(node: SubspaceModifier.Node, modifier: SubspaceModifier) {
200 (modifier as SubspaceModifierNodeElement<*>).updateUnsafe(node)
201 }
202
removeNodenull203 private fun removeNode(node: SubspaceModifier.Node): SubspaceModifier.Node {
204 val child = node.child
205 val parent = node.parent
206 if (child != null) {
207 child.parent = parent
208 node.child = null
209 }
210 if (parent != null) {
211 parent.child = child
212 node.parent = null
213 }
214 if (node.isAttached) {
215 node.onDetach()
216 node.markAsDetached()
217 }
218 return parent!!
219 }
220 }
221
updateUnsafenull222 private fun <T : SubspaceModifier.Node> SubspaceModifierNodeElement<T>.updateUnsafe(
223 node: SubspaceModifier.Node
224 ) {
225 @Suppress("UNCHECKED_CAST") update(node as T)
226 }
227
fillVectornull228 private fun SubspaceModifier.fillVector(
229 result: MutableList<SubspaceModifier>
230 ): MutableList<SubspaceModifier> {
231 val capacity = result.size.coerceAtLeast(16)
232 val stack = ArrayList<SubspaceModifier>(capacity).also { it.add(this) }
233 var predicate: ((SubspaceModifier) -> Boolean)? = null
234 while (stack.isNotEmpty()) {
235 when (val next = stack.removeAt(stack.size - 1)) {
236 is CombinedSubspaceModifier -> {
237 stack.add(next.inner)
238 stack.add(next.outer)
239 }
240 is SubspaceModifierNodeElement<*> -> result.add(next as SubspaceModifier)
241
242 // some other [androidx.compose.ui.node.Modifier] implementation that we don't know
243 // about...
244 // late-allocate the predicate only once for the entire stack
245 else ->
246 next.all(
247 predicate
248 ?: { element: SubspaceModifier ->
249 result.add(element)
250 true
251 }
252 .also { predicate = it }
253 )
254 }
255 }
256 return result
257 }
258