[go: nahoru, domu]

blob: 7db86325732a6a70a8042caaf10c1e6ca95be284 [file] [log] [blame]
Andrey Kulikov8f43a952020-02-12 16:01:29 +00001/*
2 * 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
17package androidx.ui.core
18
Andrey Kulikovb33a3802020-05-21 20:02:53 +010019import androidx.ui.core.LayoutNode.LayoutState
George Mount17d6fe52020-05-15 08:08:23 -070020import androidx.ui.util.fastForEach
Andrey Kulikov8f43a952020-02-12 16:01:29 +000021
22/**
23 * There are some contracts between the tree of LayoutNodes and the state of AndroidComposeView
24 * which is hard to enforce but important to maintain. This method is intended to do the
25 * work only during our tests and will iterate through the tree to validate the states consistency.
26 */
George Mount5469e252020-06-17 10:01:42 -070027@OptIn(ExperimentalLayoutNodeApi::class)
Andrey Kulikov8f43a952020-02-12 16:01:29 +000028internal class LayoutTreeConsistencyChecker(
29 private val root: LayoutNode,
George Mount17d6fe52020-05-15 08:08:23 -070030 private val relayoutNodes: DepthSortedSet,
Andrey Kulikov8f43a952020-02-12 16:01:29 +000031 private val postponedMeasureRequests: List<LayoutNode>
32) {
33 fun assertConsistent() {
34 val inconsistencyFound = !isTreeConsistent(root)
35 if (inconsistencyFound) {
Andrey Kulikov387c76d2020-06-09 17:02:08 +010036 throw IllegalStateException("Inconsistency found! ${logTree()}")
Andrey Kulikov8f43a952020-02-12 16:01:29 +000037 }
38 }
39
George Mount17d6fe52020-05-15 08:08:23 -070040 private fun isTreeConsistent(node: LayoutNode): Boolean {
41 if (!node.consistentLayoutState()) {
Andrey Kulikov8f43a952020-02-12 16:01:29 +000042 return false
43 }
George Mount17d6fe52020-05-15 08:08:23 -070044 node.children.fastForEach {
Andrey Kulikov8f43a952020-02-12 16:01:29 +000045 if (!isTreeConsistent(it)) {
46 return@isTreeConsistent false
47 }
48 }
49 return true
50 }
51
52 private fun LayoutNode.consistentLayoutState(): Boolean {
Andrey Kulikovb33a3802020-05-21 20:02:53 +010053 if (isPlaced) {
54 if (layoutState == LayoutState.NeedsRemeasure &&
55 postponedMeasureRequests.contains(this)
56 ) {
57 // this node is waiting to be measured by parent or if this will not happen
58 // `onRequestMeasure` will be called for all items in `postponedMeasureRequests`
59 return true
Andrey Kulikov8f43a952020-02-12 16:01:29 +000060 }
Andrey Kulikov42d66a02020-06-22 17:38:53 +010061 // remeasure or relayout is scheduled
62 val parentLayoutState = parent?.layoutState
63 if (layoutState == LayoutState.NeedsRemeasure) {
64 return relayoutNodes.contains(this) ||
65 parentLayoutState == LayoutState.NeedsRemeasure
66 }
67 if (layoutState == LayoutState.NeedsRelayout) {
68 return relayoutNodes.contains(this) ||
69 parentLayoutState == LayoutState.NeedsRemeasure ||
70 parentLayoutState == LayoutState.NeedsRelayout
Andrey Kulikov8f43a952020-02-12 16:01:29 +000071 }
72 }
73 return true
74 }
75
George Mount17d6fe52020-05-15 08:08:23 -070076 private fun nodeToString(node: LayoutNode): String {
Andrey Kulikov8f43a952020-02-12 16:01:29 +000077 return with(StringBuilder()) {
George Mount17d6fe52020-05-15 08:08:23 -070078 append(node)
Andrey Kulikovb33a3802020-05-21 20:02:53 +010079 append("[${node.layoutState}]")
George Mount17d6fe52020-05-15 08:08:23 -070080 if (!node.isPlaced) append("[!isPlaced]")
Andrey Kulikovb33a3802020-05-21 20:02:53 +010081 append("[measuredByParent=${node.measuredByParent}]")
George Mount17d6fe52020-05-15 08:08:23 -070082 if (!node.consistentLayoutState()) {
83 append("[INCONSISTENT]")
Andrey Kulikov8f43a952020-02-12 16:01:29 +000084 }
85 toString()
86 }
87 }
88
89 /** Prints the nodes tree into the logs. */
Andrey Kulikov387c76d2020-06-09 17:02:08 +010090 private fun logTree(): String {
91 val stringBuilder = StringBuilder()
George Mount17d6fe52020-05-15 08:08:23 -070092 fun printSubTree(node: LayoutNode, depth: Int) {
Andrey Kulikov8f43a952020-02-12 16:01:29 +000093 var childrenDepth = depth
94 val nodeRepresentation = nodeToString(node)
95 if (nodeRepresentation.isNotEmpty()) {
Andrey Kulikov8f43a952020-02-12 16:01:29 +000096 for (i in 0 until depth) {
97 stringBuilder.append("..")
98 }
Jim Sprocha88c07a2020-06-25 13:00:03 -070099 stringBuilder.appendLine(nodeRepresentation)
Andrey Kulikov8f43a952020-02-12 16:01:29 +0000100 childrenDepth += 1
101 }
George Mount17d6fe52020-05-15 08:08:23 -0700102 node.children.fastForEach { printSubTree(it, childrenDepth) }
Andrey Kulikov8f43a952020-02-12 16:01:29 +0000103 }
Jim Sprocha88c07a2020-06-25 13:00:03 -0700104 stringBuilder.appendLine("Tree state:")
Andrey Kulikov8f43a952020-02-12 16:01:29 +0000105 printSubTree(root, 0)
Andrey Kulikov387c76d2020-06-09 17:02:08 +0100106 return stringBuilder.toString()
Andrey Kulikov8f43a952020-02-12 16:01:29 +0000107 }
108}