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