1 /* 2 * Copyright 2019 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 18 19 import androidx.compose.runtime.internal.JvmDefaultWithCompatibility 20 21 /** 22 * An Applier is responsible for applying the tree-based operations that get emitted during a 23 * composition. Every [Composer] has an [Applier] which it uses to emit a [ComposeNode]. 24 * 25 * A custom [Applier] implementation will be needed in order to utilize Compose to build and 26 * maintain a tree of a novel type. 27 * 28 * @sample androidx.compose.runtime.samples.CustomTreeComposition 29 * @see AbstractApplier 30 * @see Composition 31 * @see Composer 32 * @see ComposeNode 33 */ 34 @JvmDefaultWithCompatibility 35 interface Applier<N> { 36 /** 37 * The node that operations will be applied on at any given time. It is expected that the value 38 * of this property will change as [down] and [up] are called. 39 */ 40 val current: N 41 42 /** 43 * Called when the [Composer] is about to begin applying changes using this applier. 44 * [onEndChanges] will be called when changes are complete. 45 */ onBeginChangesnull46 fun onBeginChanges() {} 47 48 /** 49 * Called when the [Composer] is finished applying changes using this applier. A call to 50 * [onBeginChanges] will always precede a call to [onEndChanges]. 51 */ onEndChangesnull52 fun onEndChanges() {} 53 54 /** 55 * Indicates that the applier is getting traversed "down" the tree. When this gets called, 56 * [node] is expected to be a child of [current], and after this operation, [node] is expected 57 * to be the new [current]. 58 */ downnull59 fun down(node: N) 60 61 /** 62 * Indicates that the applier is getting traversed "up" the tree. After this operation 63 * completes, the [current] should return the "parent" of the [current] node at the beginning of 64 * this operation. 65 */ 66 fun up() 67 68 /** 69 * Indicates that [instance] should be inserted as a child to [current] at [index]. An applier 70 * should insert the node into the tree either in [insertTopDown] or [insertBottomUp], not both. 71 * 72 * The [insertTopDown] method is called before the children of [instance] have been created and 73 * inserted into it. [insertBottomUp] is called after all children have been created and 74 * inserted. 75 * 76 * Some trees are faster to build top-down, in which case the [insertTopDown] method should be 77 * used to insert the [instance]. Other trees are faster to build bottom-up in which case 78 * [insertBottomUp] should be used. 79 * 80 * To give example of building a tree top-down vs. bottom-up consider the following tree, 81 * ``` 82 * R 83 * | 84 * B 85 * / \ 86 * A C 87 * ``` 88 * 89 * where the node `B` is being inserted into the tree at `R`. Top-down building of the tree 90 * first inserts `B` into `R`, then inserts `A` into `B` followed by inserting `C` into B`. For 91 * example, 92 * 93 * ``` 94 * 1 2 3 95 * R R R 96 * | | | 97 * B B B 98 * / / \ 99 * A A C 100 * ``` 101 * 102 * A bottom-up building of the tree starts with inserting `A` and `C` into `B` then inserts `B` 103 * tree into `R`. 104 * 105 * ``` 106 * 1 2 3 107 * B B R 108 * | / \ | 109 * A A C B 110 * / \ 111 * A C 112 * ``` 113 * 114 * To see how building top-down vs. bottom-up can differ significantly in performance consider a 115 * tree where whenever a child is added to the tree all parent nodes, up to the root, are 116 * notified of the new child entering the tree. If the tree is built top-down, 117 * 1. `R` is notified of `B` entering. 118 * 2. `B` is notified of `A` entering, `R` is notified of `A` entering. 119 * 3. `B` is notified of `C` entering, `R` is notified of `C` entering. 120 * 121 * for a total of 5 notifications. The number of notifications grows exponentially with the 122 * number of inserts. 123 * 124 * For bottom-up, the notifications are, 125 * 1. `B` is notified `A` entering. 126 * 2. `B` is notified `C` entering. 127 * 3. `R` is notified `B` entering. 128 * 129 * The notifications are linear to the number of nodes inserted. 130 * 131 * If, on the other hand, all children are notified when the parent enters a tree, then the 132 * notifications are, for top-down, 133 * 1. `B` is notified it is entering `R`. 134 * 2. `A` is notified it is entering `B`. 135 * 3. `C` is notified it is entering `B`. 136 * 137 * which is linear to the number of nodes inserted. 138 * 139 * For bottom-up, the notifications look like, 140 * 1. `A` is notified it is entering `B`. 141 * 2. `C` is notified it is entering `B`. 142 * 3. `B` is notified it is entering `R`, `A` is notified it is entering `R`, `C` is notified it 143 * is entering `R`. 144 * 145 * which exponential to the number of nodes inserted. 146 */ 147 fun insertTopDown(index: Int, instance: N) 148 149 /** 150 * Indicates that [instance] should be inserted as a child of [current] at [index]. An applier 151 * should insert the node into the tree either in [insertTopDown] or [insertBottomUp], not both. 152 * See the description of [insertTopDown] to which describes when to implement [insertTopDown] 153 * and when to use [insertBottomUp]. 154 */ 155 fun insertBottomUp(index: Int, instance: N) 156 157 /** 158 * Indicates that the children of [current] from [index] to [index] + [count] should be removed. 159 */ 160 fun remove(index: Int, count: Int) 161 162 /** 163 * Indicates that [count] children of [current] should be moved from index [from] to index [to]. 164 * 165 * The [to] index is relative to the position before the change, so, for example, to move an 166 * element at position 1 to after the element at position 2, [from] should be `1` and [to] 167 * should be `3`. If the elements were A B C D E, calling `move(1, 3, 1)` would result in the 168 * elements being reordered to A C B D E. 169 */ 170 fun move(from: Int, to: Int, count: Int) 171 172 /** 173 * Move to the root and remove all nodes from the root, preparing both this [Applier] and its 174 * root to be used as the target of a new composition in the future. 175 */ 176 fun clear() 177 178 /** Apply a change to the current node. */ 179 fun apply(block: N.(Any?) -> Unit, value: Any?) { 180 current.block(value) 181 } 182 183 /** Notify [current] is is being reused in reusable content. */ reusenull184 fun reuse() { 185 (current as? ComposeNodeLifecycleCallback)?.onReuse() 186 } 187 } 188 189 /** 190 * An abstract [Applier] implementation. 191 * 192 * @sample androidx.compose.runtime.samples.CustomTreeComposition 193 * @see Applier 194 * @see Composition 195 * @see Composer 196 * @see ComposeNode 197 */ 198 abstract class AbstractApplier<T>(val root: T) : Applier<T> { 199 private val stack = Stack<T>() 200 201 override var current: T = root 202 protected set 203 downnull204 override fun down(node: T) { 205 stack.push(current) 206 current = node 207 } 208 upnull209 override fun up() { 210 current = stack.pop() 211 } 212 clearnull213 final override fun clear() { 214 stack.clear() 215 current = root 216 onClear() 217 } 218 219 /** Called to perform clearing of the [root] when [clear] is called. */ onClearnull220 protected abstract fun onClear() 221 222 protected fun MutableList<T>.remove(index: Int, count: Int) { 223 if (count == 1) { 224 removeAt(index) 225 } else { 226 subList(index, index + count).clear() 227 } 228 } 229 movenull230 protected fun MutableList<T>.move(from: Int, to: Int, count: Int) { 231 val dest = if (from > to) to else to - count 232 if (count == 1) { 233 if (from == to + 1 || from == to - 1) { 234 // Adjacent elements, perform swap to avoid backing array manipulations. 235 val fromEl = get(from) 236 val toEl = set(to, fromEl) 237 set(from, toEl) 238 } else { 239 val fromEl = removeAt(from) 240 add(dest, fromEl) 241 } 242 } else { 243 val subView = subList(from, from + count) 244 val subCopy = subView.toMutableList() 245 subView.clear() 246 addAll(dest, subCopy) 247 } 248 } 249 } 250 251 internal class OffsetApplier<N>(private val applier: Applier<N>, private val offset: Int) : 252 Applier<N> { 253 private var nesting = 0 254 override val current: N 255 get() = applier.current 256 downnull257 override fun down(node: N) { 258 nesting++ 259 applier.down(node) 260 } 261 upnull262 override fun up() { 263 runtimeCheck(nesting > 0) { "OffsetApplier up called with no corresponding down" } 264 nesting-- 265 applier.up() 266 } 267 insertTopDownnull268 override fun insertTopDown(index: Int, instance: N) { 269 applier.insertTopDown(index + if (nesting == 0) offset else 0, instance) 270 } 271 insertBottomUpnull272 override fun insertBottomUp(index: Int, instance: N) { 273 applier.insertBottomUp(index + if (nesting == 0) offset else 0, instance) 274 } 275 removenull276 override fun remove(index: Int, count: Int) { 277 applier.remove(index + if (nesting == 0) offset else 0, count) 278 } 279 movenull280 override fun move(from: Int, to: Int, count: Int) { 281 val effectiveOffset = if (nesting == 0) offset else 0 282 applier.move(from + effectiveOffset, to + effectiveOffset, count) 283 } 284 clearnull285 override fun clear() { 286 composeImmediateRuntimeError("Clear is not valid on OffsetApplier") 287 } 288 } 289