⎇ 9e53f03adea2b770d68954e404d2fa407dfc7d40
src/compute/mod.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
Layout algorithm decoupling, Sizing Constraints & Perf Improvements (#246)
1//! The layout algorithms themselves
2
3pub(crate) mod flexbox;
4pub(crate) mod leaf;
5
6use crate::error::TaffyError;
7use crate::geometry::{Point, Size};
8use crate::layout::{AvailableSpace, Cache, Layout, RunMode, SizingMode};
9use crate::node::Node;
10use crate::style::Display;
11use crate::sys::round;
12use crate::tree::LayoutTree;
13
14#[cfg(feature = "debug")]
15use crate::debug::NODE_LOGGER;
16
17/// Updates the stored layout of the provided `node` and its children
18pub fn compute_layout(
19 tree: &mut impl LayoutTree,
20 root: Node,
21 available_space: Size<AvailableSpace>,
22) -> Result<(), TaffyError> {
23 // Recursively compute node layout
24 let size =
25 compute_node_layout(tree, root, Size::NONE, available_space, RunMode::PeformLayout, SizingMode::InherentSize);
26
27 let layout = Layout { order: 0, size, location: Point::ZERO };
28 *tree.layout_mut(root) = layout;
29
30 // Recursively round the layout's of this node and all children
31 round_layout(tree, root, 0.0, 0.0);
32
33 Ok(())
34}
35
36/// Updates the stored layout of the provided `node` and its children
37fn compute_node_layout(
38 tree: &mut impl LayoutTree,
39 node: Node,
40 known_dimensions: Size<Option<f32>>,
41 available_space: Size<AvailableSpace>,
42 run_mode: RunMode,
43 sizing_mode: SizingMode,
44) -> Size<f32> {
45 // clear the dirtiness of the node now that we've computed it
46 tree.mark_dirty(node, false);
47
48 #[cfg(feature = "debug")]
49 NODE_LOGGER.push_node(node);
50 #[cfg(feature = "debug")]
51 println!();
52
53 // First we check if we have a cached result for the given input
54 let cache_run_mode = if tree.children(node).is_empty() { RunMode::PeformLayout } else { run_mode };
55 if let Some(cached_size) =
56 compute_from_cache(tree, node, known_dimensions, available_space, cache_run_mode, sizing_mode)
57 {
58 #[cfg(feature = "debug")]
59 NODE_LOGGER.labelled_debug_log("CACHE", cached_size);
60 #[cfg(feature = "debug")]
61 NODE_LOGGER.labelled_debug_log("run_mode", run_mode);
62 #[cfg(feature = "debug")]
63 NODE_LOGGER.labelled_debug_log("sizing_mode", sizing_mode);
64 #[cfg(feature = "debug")]
65 NODE_LOGGER.labelled_debug_log("known_dimensions", known_dimensions);
66 #[cfg(feature = "debug")]
67 NODE_LOGGER.labelled_debug_log("available_space", available_space);
68 #[cfg(feature = "debug")]
69 NODE_LOGGER.pop_node();
70 return cached_size;
71 }
72
73 #[cfg(feature = "debug")]
74 NODE_LOGGER.log("COMPUTE");
75 #[cfg(feature = "debug")]
76 NODE_LOGGER.labelled_debug_log("run_mode", run_mode);
77 #[cfg(feature = "debug")]
78 NODE_LOGGER.labelled_debug_log("sizing_mode", sizing_mode);
79 #[cfg(feature = "debug")]
80 NODE_LOGGER.labelled_debug_log("known_dimensions", known_dimensions);
81 #[cfg(feature = "debug")]
82 NODE_LOGGER.labelled_debug_log("available_space", available_space);
83
84 // Attempt to shortcut size computation based on
85 // - KnownSize sizing constraints
86 // - The node's preferred sizes (width/heights) styles and AvailableSpace sizing constraints
87 // (percentages resolve to pixel values if there is a definite AvailableSpace sizing constraint)
88 // let style = tree.style(node);
89 // // let known_node_size = available_space.
90 // // .zip_map(style.size, |constraint, style_size| style_size.maybe_resolve(constraint.as_option()));
91 // let known_node_size = style
92 // .size
93 // .maybe_resolve(available_space.as_options())
94 // .zip_map(known_dimensions, |style_size, known_dimensions| known_dimensions.or(style_size));
95 // if run_mode == RunMode::ComputeSize && known_node_size.width.is_some() && known_node_size.height.is_some() {
96 // let node_min_size = style.min_size.maybe_resolve(available_space.as_options());
97 // let node_max_size = style.max_size.maybe_resolve(available_space.as_options());
98 // return Size {
99 // width: known_node_size.width.maybe_max(node_min_size.width).maybe_min(node_max_size.width).unwrap_or(0.0),
100 // height: known_node_size
101 // .height
102 // .maybe_max(node_min_size.height)
103 // .maybe_min(node_max_size.height)
104 // .unwrap_or(0.0),
105 // };
106 // }
107
108 // If this is a leaf node we can skip a lot of this function in some cases
109 let computed_size = if tree.children(node).is_empty() {
110 #[cfg(feature = "debug")]
111 NODE_LOGGER.log("Algo: leaf");
112 self::leaf::compute(tree, node, known_dimensions, available_space, run_mode, sizing_mode)
113 } else {
114 // println!("match {:?}", tree.style(node).display);
115 match tree.style(node).display {
116 Display::Flex => {
117 #[cfg(feature = "debug")]
118 NODE_LOGGER.log("Algo: flexbox");
119 self::flexbox::compute(tree, node, known_dimensions, available_space, run_mode)
120 }
121 Display::None => {
122 #[cfg(feature = "debug")]
123 NODE_LOGGER.log("Algo: none");
124 Size { width: 0.0, height: 0.0 }
125 }
126 }
127 };
128
129 // Cache result
130 let cache_slot = (known_dimensions.width.is_some() as usize) + (known_dimensions.height.is_some() as usize * 2);
131 *tree.cache_mut(node, cache_slot) =
132 Some(Cache { known_dimensions, available_space, run_mode: cache_run_mode, cached_size: computed_size });
133
134 #[cfg(feature = "debug")]
135 NODE_LOGGER.labelled_debug_log("RESULT", computed_size);
136 #[cfg(feature = "debug")]
137 NODE_LOGGER.pop_node();
138
139 computed_size
140}
141
142/// Try to get the computation result from the cache.
143#[inline]
144fn compute_from_cache(
145 tree: &mut impl LayoutTree,
146 node: Node,
147 known_dimensions: Size<Option<f32>>,
148 available_space: Size<AvailableSpace>,
149 run_mode: RunMode,
150 sizing_mode: SizingMode,
151) -> Option<Size<f32>> {
152 for idx in 0..4 {
153 let entry = tree.cache_mut(node, idx);
154 #[cfg(feature = "debug")]
155 NODE_LOGGER.labelled_debug_log("cache_entry", &entry);
156 if let Some(entry) = entry {
157 // Cached ComputeSize results are not valid if we are running in PerformLayout mode
158 if entry.run_mode == RunMode::ComputeSize && run_mode == RunMode::PeformLayout {
159 return None;
160 }
161
162 // if known_dimensions.width == entry.known_dimensions.width
163 // && known_dimensions.height == entry.known_dimensions.height
164 if (known_dimensions.width == entry.known_dimensions.width || known_dimensions.width == Some(entry.cached_size.width))
165 && (known_dimensions.height == entry.known_dimensions.height || known_dimensions.height == Some(entry.cached_size.height))
166 // && entry.available_space.width.is_roughly_equal(available_space.width)
167 // && entry.available_space.height.is_roughly_equal(available_space.height)
168 && (
169 known_dimensions.width.is_some()
170 || entry.available_space.width.is_roughly_equal(available_space.width)
171 || (sizing_mode == SizingMode::ContentSize && available_space.width.is_definite() && available_space.width.unwrap() >= entry.cached_size.width)
172 )
173 && (
174 known_dimensions.height.is_some()
175 || entry.available_space.height.is_roughly_equal(available_space.height)
176 || (sizing_mode == SizingMode::ContentSize && available_space.height.is_definite() && available_space.height.unwrap() >= entry.cached_size.height)
177 )
178 // && (entry.available_space.width.is_roughly_equal(available_space.width) || (available_space.width.is_definite() && available_space.width.unwrap() >= entry.cached_size.width))
179 // && (entry.available_space.height.is_roughly_equal(available_space.height) || (available_space.height.is_definite() && available_space.height.unwrap() >= entry.cached_size.height))
180 {
181 return Some(entry.cached_size);
182 }
183 }
184 }
185
186 None
187}
188
189/// Rounds the calculated [`NodeData`] according to the spec
190fn round_layout(tree: &mut impl LayoutTree, root: Node, abs_x: f32, abs_y: f32) {
191 let layout = tree.layout_mut(root);
192 let abs_x = abs_x + layout.location.x;
193 let abs_y = abs_y + layout.location.y;
194
195 layout.location.x = round(layout.location.x);
196 layout.location.y = round(layout.location.y);
197
198 layout.size.width = round(layout.size.width);
199 layout.size.height = round(layout.size.height);
200
201 // Satisfy the borrow checker here by re-indexing to shorten the lifetime to the loop scope
202 for x in 0..tree.children(root).len() {
203 let child = tree.child(root, x);
204 round_layout(tree, child, abs_x, abs_y);
205 }
206}