1 /*
<lambda>null2  * Copyright 2020 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.ui.node
18 
19 import androidx.compose.runtime.collection.mutableVectorOf
20 
21 /**
22  * Tracks the nodes being positioned and dispatches OnPositioned callbacks when we finished the
23  * measure/layout pass.
24  */
25 internal class OnPositionedDispatcher {
26     private val layoutNodes = mutableVectorOf<LayoutNode>()
27     private var cachedNodes: Array<LayoutNode?>? = null
28 
29     fun isNotEmpty() = layoutNodes.isNotEmpty()
30 
31     fun onNodePositioned(node: LayoutNode) {
32         layoutNodes += node
33         node.needsOnPositionedDispatch = true
34     }
35 
36     fun remove(node: LayoutNode) {
37         layoutNodes.remove(node)
38     }
39 
40     fun onRootNodePositioned(rootNode: LayoutNode) {
41         layoutNodes.clear()
42         layoutNodes += rootNode
43         rootNode.needsOnPositionedDispatch = true
44     }
45 
46     fun dispatch() {
47         // sort layoutNodes so that the root is at the end and leaves are at the front
48         layoutNodes.sortWith(DepthComparator)
49         val cache: Array<LayoutNode?>
50         val size = layoutNodes.size
51         val cachedNodes = this.cachedNodes
52         if (cachedNodes == null || cachedNodes.size < size) {
53             cache = arrayOfNulls(maxOf(MinArraySize, layoutNodes.size))
54         } else {
55             cache = cachedNodes
56         }
57         this.cachedNodes = null
58 
59         // copy to cache to prevent reentrancy being a problem
60         for (i in 0 until size) {
61             cache[i] = layoutNodes[i]
62         }
63         layoutNodes.clear()
64         for (i in size - 1 downTo 0) {
65             val layoutNode = cache[i]!!
66             if (layoutNode.needsOnPositionedDispatch) {
67                 dispatchHierarchy(layoutNode)
68             }
69         }
70         this.cachedNodes = cache
71     }
72 
73     private fun dispatchHierarchy(layoutNode: LayoutNode) {
74         // TODO(lmr): investigate a non-recursive version of this that leverages
75         //  node traversal
76         layoutNode.dispatchOnPositionedCallbacks()
77         layoutNode.needsOnPositionedDispatch = false
78 
79         layoutNode.forEachChild { child -> dispatchHierarchy(child) }
80     }
81 
82     internal companion object {
83         private const val MinArraySize = 16
84 
85         private object DepthComparator : Comparator<LayoutNode> {
86             override fun compare(a: LayoutNode, b: LayoutNode): Int {
87                 val depthDiff = b.depth.compareTo(a.depth)
88                 if (depthDiff != 0) {
89                     return depthDiff
90                 }
91                 return a.hashCode().compareTo(b.hashCode())
92             }
93         }
94     }
95 }
96