Skip to main content

fret_ui/tree/ui_tree_mutation/
core.rs

1use 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        // Keep parent pointers consistent even when the child list is unchanged.
243        //
244        // This matters for view-cache reuse and GC/repair flows where a node may be temporarily
245        // detached and then re-attached without changing the parent's child list. Invalidation
246        // propagation relies on `parent` pointers even when semantics/debug traversals use the
247        // child lists.
248        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            // Structural changes must invalidate ancestors so the next layout pass walks far
342            // enough to place newly mounted subtrees, even when view-cache invalidation
343            // truncation is enabled.
344            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            // Same-children writes are also used as a parent-pointer repair path during retained /
375            // GC flows. If a descendant became layout-dirty while detached, reconnect the parent to
376            // the authoritative layout invalidation walk so the next frame descends back into the
377            // repaired subtree.
378            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}