fret_ui/tree/ui_tree_mutation/
core.rs1use super::super::*;
2use crate::tree::node_storage::TextWrapNoneMeasureCache;
3
4impl<H: UiHost> UiTree<H> {
5 pub(crate) fn create_node(&mut self, widget: impl Widget<H> + 'static) -> NodeId {
6 let node = Node::new(widget);
7 let inv = node.invalidation;
8 let id = self.nodes.insert(node);
9 self.update_invalidation_counters(InvalidationFlags::default(), inv);
10 if inv.layout {
11 self.layout_invalidations_count = self.layout_invalidations_count.saturating_add(1);
12 }
13 id
14 }
15
16 pub(crate) fn set_node_text_wrap_none_measure_cache(
17 &mut self,
18 node: NodeId,
19 fingerprint: u64,
20 size: Size,
21 ) {
22 let Some(n) = self.nodes.get_mut(node) else {
23 return;
24 };
25 n.text_wrap_none_measure_cache = Some(TextWrapNoneMeasureCache { fingerprint, size });
26 }
27
28 pub(crate) fn clear_node_text_wrap_none_measure_cache(&mut self, node: NodeId) {
29 let Some(n) = self.nodes.get_mut(node) else {
30 return;
31 };
32 n.text_wrap_none_measure_cache = None;
33 }
34
35 #[cfg(test)]
36 pub(crate) fn create_node_for_element(
37 &mut self,
38 element: GlobalElementId,
39 widget: impl Widget<H> + 'static,
40 ) -> NodeId {
41 let node = Node::new_for_element(element, widget);
42 let inv = node.invalidation;
43 let id = self.nodes.insert(node);
44 self.update_invalidation_counters(InvalidationFlags::default(), inv);
45 if inv.layout {
46 self.layout_invalidations_count = self.layout_invalidations_count.saturating_add(1);
47 }
48 id
49 }
50
51 #[cfg(test)]
52 pub(crate) fn test_clear_node_invalidations(&mut self, node: NodeId) {
53 let Some((layout_before, layout_after)) = self.nodes.get_mut(node).map(|n| {
54 let layout_before = n.invalidation.layout;
55 n.invalidation.clear();
56 n.paint_invalidated_by_hit_test_only = false;
57 let layout_after = n.invalidation.layout;
58 (layout_before, layout_after)
59 }) else {
60 return;
61 };
62 record_layout_invalidation_transition(
63 &mut self.layout_invalidations_count,
64 layout_before,
65 layout_after,
66 );
67 self.note_layout_invalidation_transition_for_subtree_aggregation(
68 node,
69 layout_before,
70 layout_after,
71 );
72 }
73
74 #[cfg(test)]
75 pub(crate) fn test_node_invalidations(&self, node: NodeId) -> Option<InvalidationFlags> {
76 self.nodes.get(node).map(|n| n.invalidation)
77 }
78
79 #[cfg(test)]
80 pub(crate) fn test_invalidation_counters(&self) -> (u32, u32, u32) {
81 (
82 self.layout_invalidations_count,
83 self.invalidated_layout_nodes,
84 self.invalidated_paint_nodes,
85 )
86 }
87
88 #[cfg(test)]
89 pub(crate) fn test_set_layout_invalidation(&mut self, node: NodeId, value: bool) {
90 let view_cache_active = self.view_cache_active();
91 let Some((layout_before, layout_after, should_mark_contained_cache_root_dirty)) =
92 self.nodes.get_mut(node).map(|n| {
93 let layout_before = n.invalidation.layout;
94 n.invalidation.layout = value;
95 if value {
96 n.invalidation.paint = true;
97 }
98 let should_mark_contained_cache_root_dirty = value
99 && view_cache_active
100 && n.view_cache.enabled
101 && n.view_cache.contained_layout;
102 let layout_after = n.invalidation.layout;
103 (
104 layout_before,
105 layout_after,
106 should_mark_contained_cache_root_dirty,
107 )
108 })
109 else {
110 return;
111 };
112 record_layout_invalidation_transition(
113 &mut self.layout_invalidations_count,
114 layout_before,
115 layout_after,
116 );
117 self.note_layout_invalidation_transition_for_subtree_aggregation(
118 node,
119 layout_before,
120 layout_after,
121 );
122
123 if should_mark_contained_cache_root_dirty {
124 self.mark_cache_root_dirty(
125 node,
126 UiDebugInvalidationSource::Other,
127 UiDebugInvalidationDetail::Unknown,
128 );
129 } else if !value {
130 self.dirty_cache_roots.remove(&node);
131 self.dirty_cache_root_reasons.remove(&node);
132 }
133 }
134
135 #[cfg(test)]
136 pub(crate) fn test_set_node_parent(&mut self, node: NodeId, parent: Option<NodeId>) {
137 let Some(n) = self.nodes.get_mut(node) else {
138 return;
139 };
140 n.parent = parent;
141 }
142
143 pub(in crate::tree) fn set_node_children_write_policy(
144 &mut self,
145 node: NodeId,
146 policy: ChildrenWritePolicy,
147 ) {
148 let Some(entry) = self.nodes.get_mut(node) else {
149 return;
150 };
151 entry.children_write_policy = policy;
152 }
153
154 pub(in crate::tree) fn detach_reparented_children_from_old_parents(
155 &mut self,
156 parent: NodeId,
157 children: &[NodeId],
158 ) {
159 let mut removals: HashMap<NodeId, HashSet<NodeId>> = HashMap::new();
160 for &child in children {
161 let Some(old_parent) = self.nodes.get(child).and_then(|node| node.parent) else {
162 continue;
163 };
164 if old_parent == parent {
165 continue;
166 }
167 removals.entry(old_parent).or_default().insert(child);
168 }
169
170 for (old_parent, removing) in removals {
171 let Some(old_children) = self.nodes.get(old_parent).map(|node| node.children.clone())
172 else {
173 continue;
174 };
175 if !old_children.iter().any(|child| removing.contains(child)) {
176 continue;
177 }
178 let filtered: Vec<NodeId> = old_children
179 .into_iter()
180 .filter(|child| !removing.contains(child))
181 .collect();
182 let policy = self
183 .nodes
184 .get(old_parent)
185 .map(|node| node.children_write_policy)
186 .unwrap_or_default();
187 match policy {
188 ChildrenWritePolicy::Standard => self.set_children(old_parent, filtered),
189 ChildrenWritePolicy::Barrier => self.set_children_barrier(old_parent, filtered),
190 }
191 }
192 }
193
194 pub fn set_root(&mut self, root: NodeId) {
195 let _ = self.set_base_root(root);
196 }
197
198 pub fn add_child(&mut self, parent: NodeId, child: NodeId) {
199 let Some(mut parent_children) = self.nodes.get(parent).map(|node| node.children.clone())
200 else {
201 return;
202 };
203 if !self.nodes.contains_key(child) {
204 return;
205 }
206
207 let old_parent = self.nodes.get(child).and_then(|node| node.parent);
208 let occurrences_in_parent = parent_children.iter().filter(|&&id| id == child).count();
209
210 if old_parent == Some(parent) && occurrences_in_parent == 1 {
211 return;
212 }
213
214 if let Some(old_parent) = old_parent
215 && old_parent != parent
216 && let Some(old_children) = self.nodes.get(old_parent).map(|node| node.children.clone())
217 && old_children.contains(&child)
218 {
219 let filtered_old_children: Vec<NodeId> =
220 old_children.into_iter().filter(|&id| id != child).collect();
221 self.set_children(old_parent, filtered_old_children);
222 }
223
224 parent_children.retain(|&id| id != child);
225 parent_children.push(child);
226 self.set_children(parent, parent_children);
227 }
228
229 #[track_caller]
230 pub fn set_children(&mut self, parent: NodeId, children: Vec<NodeId>) {
231 if self.nodes.get(parent).is_none() {
232 return;
233 }
234
235 self.set_node_children_write_policy(parent, ChildrenWritePolicy::Standard);
236 self.detach_reparented_children_from_old_parents(parent, &children);
237
238 let Some(_old_len) = self.nodes.get(parent).map(|n| n.children.len()) else {
239 return;
240 };
241
242 let same_children = self
249 .nodes
250 .get(parent)
251 .is_some_and(|n| n.children.as_slice() == children.as_slice());
252 if same_children {
253 self.repair_same_children_parent_pointers_and_reconnect_layout(parent, &children);
254 return;
255 }
256
257 #[cfg(feature = "diagnostics")]
258 if self.debug_enabled {
259 let location = std::panic::Location::caller();
260 let old_elements_head = self
261 .nodes
262 .get(parent)
263 .map(|n| self.debug_sample_child_elements_head(&n.children))
264 .unwrap_or([None; 4]);
265 let new_elements_head = self.debug_sample_child_elements_head(&children);
266 self.debug_set_children_writes.insert(
267 parent,
268 UiDebugSetChildrenWrite {
269 parent,
270 frame_id: self.debug_stats.frame_id,
271 old_len: _old_len.min(u32::MAX as usize) as u32,
272 new_len: children.len().min(u32::MAX as usize) as u32,
273 old_elements_head,
274 new_elements_head,
275 file: location.file(),
276 line: location.line(),
277 column: location.column(),
278 },
279 );
280 }
281
282 let Some(old_children) = self
283 .nodes
284 .get_mut(parent)
285 .map(|n| std::mem::take(&mut n.children))
286 else {
287 return;
288 };
289
290 for old in old_children {
291 if let Some(n) = self.nodes.get_mut(old)
292 && n.parent == Some(parent)
293 {
294 #[cfg(feature = "diagnostics")]
295 if self.debug_enabled {
296 let location = std::panic::Location::caller();
297 self.debug_parent_sever_writes.insert(
298 old,
299 UiDebugParentSeverWrite {
300 child: old,
301 parent,
302 frame_id: self.debug_stats.frame_id,
303 file: location.file(),
304 line: location.line(),
305 column: location.column(),
306 },
307 );
308 }
309 n.parent = None;
310 }
311 }
312
313 for &child in &children {
314 if let Some(n) = self.nodes.get_mut(child) {
315 n.parent = Some(parent);
316 }
317 }
318
319 let mut propagate = false;
320 let mut counter_update: Option<(InvalidationFlags, InvalidationFlags)> = None;
321 if let Some(n) = self.nodes.get_mut(parent) {
322 let prev = n.invalidation;
323 n.children = children;
324 let layout_before = n.invalidation.layout;
325 n.invalidation.mark(Invalidation::HitTest);
326 record_layout_invalidation_transition(
327 &mut self.layout_invalidations_count,
328 layout_before,
329 n.invalidation.layout,
330 );
331 counter_update = Some((prev, n.invalidation));
332 propagate = true;
333 }
334 if let Some((prev, next)) = counter_update {
335 self.update_invalidation_counters(prev, next);
336 }
337
338 self.recompute_node_subtree_layout_dirty_count_and_propagate(parent);
339
340 if propagate {
341 self.mark_invalidation_with_source(
345 parent,
346 Invalidation::HitTest,
347 UiDebugInvalidationSource::Other,
348 );
349 }
350 }
351
352 pub(in crate::tree) fn repair_same_children_parent_pointers_and_reconnect_layout(
353 &mut self,
354 parent: NodeId,
355 children: &[NodeId],
356 ) {
357 let mut repaired_parent_pointer = false;
358 for &child in children {
359 if let Some(n) = self.nodes.get_mut(child) {
360 repaired_parent_pointer |= n.parent != Some(parent);
361 n.parent = Some(parent);
362 }
363 }
364
365 self.recompute_node_subtree_layout_dirty_count_and_propagate(parent);
366
367 if repaired_parent_pointer
368 && self.subtree_has_pending_layout_work(parent)
369 && self
370 .nodes
371 .get(parent)
372 .is_some_and(|node| !node.invalidation.layout)
373 {
374 self.mark_invalidation_with_source(
379 parent,
380 Invalidation::Layout,
381 UiDebugInvalidationSource::Other,
382 );
383 }
384 }
385
386 pub(in crate::tree) fn subtree_has_pending_layout_work(&self, root: NodeId) -> bool {
387 if self.subtree_layout_dirty_aggregation_enabled() {
388 return self.node_subtree_layout_dirty(root);
389 }
390
391 let mut stack: Vec<NodeId> = vec![root];
392 while let Some(node) = stack.pop() {
393 let Some(entry) = self.nodes.get(node) else {
394 continue;
395 };
396 if entry.invalidation.layout {
397 return true;
398 }
399 stack.extend(entry.children.iter().copied());
400 }
401 false
402 }
403}