Skip to main content

fret_ui/tree/ui_tree_mutation/
remove.rs

1use super::super::*;
2
3impl<H: UiHost> UiTree<H> {
4    #[track_caller]
5    pub fn remove_subtree(&mut self, services: &mut dyn UiServices, root: NodeId) -> Vec<NodeId> {
6        #[cfg(feature = "diagnostics")]
7        let remove_record = if self.debug_enabled {
8            let location = std::panic::Location::caller();
9            let pre_exists = self.nodes.contains_key(root);
10            let root_element = self.nodes.get(root).and_then(|n| n.element);
11            let root_parent = self.nodes.get(root).and_then(|n| n.parent);
12            let root_parent_element =
13                root_parent.and_then(|p| self.nodes.get(p).and_then(|n| n.element));
14            let root_root = self.node_root(root);
15            let root_layer = self.node_layer(root);
16            let root_layer_visible =
17                root_layer.and_then(|layer| self.layers.get(layer).map(|l| l.visible));
18            let root_children_len = self
19                .nodes
20                .get(root)
21                .map(|n| n.children.len().min(u32::MAX as usize) as u32)
22                .unwrap_or(0);
23            let root_parent_children_len = root_parent.and_then(|p| {
24                self.nodes
25                    .get(p)
26                    .map(|n| n.children.len().min(u32::MAX as usize) as u32)
27            });
28            let root_parent_children_contains_root =
29                root_parent.and_then(|p| self.nodes.get(p).map(|n| n.children.contains(&root)));
30            let frame_context = self.debug_remove_subtree_frame_context.remove(&root);
31            let reachable_from_layer_roots = pre_exists
32                && frame_context
33                    .as_ref()
34                    .map(|ctx| ctx.root_reachable_from_layer_roots)
35                    .unwrap_or_else(|| self.debug_is_reachable_from_layer_roots(root));
36            let mut root_path: [u64; 16] = [0u64; 16];
37            let mut root_path_nodes: [Option<NodeId>; 16] = [None; 16];
38            let mut root_path_len: u8 = 0;
39            let mut root_path_truncated = false;
40            let mut current = Some(root);
41            while let Some(id) = current {
42                if (root_path_len as usize) >= root_path.len() {
43                    root_path_truncated = true;
44                    break;
45                }
46                root_path_nodes[root_path_len as usize] = Some(id);
47                root_path[root_path_len as usize] = id.data().as_ffi();
48                root_path_len = root_path_len.saturating_add(1);
49                current = self.nodes.get(id).and_then(|n| n.parent);
50            }
51            let root_path_edge_len = root_path_len.saturating_sub(1);
52            let mut root_path_edge_ui_contains_child: [u8; 16] = [2u8; 16];
53            for idx in 0..(root_path_edge_len as usize).min(root_path_edge_ui_contains_child.len())
54            {
55                let Some(child) = root_path_nodes[idx] else {
56                    continue;
57                };
58                let Some(parent) = root_path_nodes[idx + 1] else {
59                    continue;
60                };
61                let contains = self.nodes.get(parent).map(|n| n.children.contains(&child));
62                root_path_edge_ui_contains_child[idx] = match contains {
63                    Some(true) => 1,
64                    Some(false) => 0,
65                    None => 2,
66                };
67            }
68            Some((
69                location.file(),
70                location.line(),
71                location.column(),
72                pre_exists,
73                root_element,
74                root_parent,
75                root_parent_element,
76                root_root,
77                root_layer,
78                root_layer_visible,
79                reachable_from_layer_roots,
80                root_children_len,
81                root_parent_children_len,
82                root_parent_children_contains_root,
83                frame_context,
84                root_path_len,
85                root_path,
86                root_path_truncated,
87                root_path_edge_len,
88                root_path_edge_ui_contains_child,
89            ))
90        } else {
91            None
92        };
93
94        if self.root_to_layer.contains_key(&root) {
95            #[cfg(feature = "diagnostics")]
96            if let Some((
97                file,
98                line,
99                column,
100                _pre_exists,
101                root_element,
102                root_parent,
103                root_parent_element,
104                root_root,
105                root_layer,
106                root_layer_visible,
107                reachable_from_layer_roots,
108                root_children_len,
109                root_parent_children_len,
110                root_parent_children_contains_root,
111                frame_context,
112                root_path_len,
113                root_path,
114                root_path_truncated,
115                root_path_edge_len,
116                root_path_edge_ui_contains_child,
117            )) = remove_record
118            {
119                let root_path_edge_frame_contains_child = frame_context
120                    .map(|ctx| ctx.path_edge_frame_contains_child)
121                    .unwrap_or([2u8; 16]);
122                let reachable_from_view_cache_roots =
123                    frame_context.and_then(|ctx| ctx.root_reachable_from_view_cache_roots);
124                let unreachable_from_liveness_roots = !reachable_from_layer_roots
125                    && !matches!(reachable_from_view_cache_roots, Some(true));
126                let trigger_element = frame_context.and_then(|ctx| ctx.trigger_element);
127                let trigger_element_root = frame_context.and_then(|ctx| ctx.trigger_element_root);
128                let trigger_element_in_view_cache_keep_alive =
129                    frame_context.and_then(|ctx| ctx.trigger_element_in_view_cache_keep_alive);
130                let trigger_element_listed_under_reuse_root =
131                    frame_context.and_then(|ctx| ctx.trigger_element_listed_under_reuse_root);
132                let liveness_layer_roots_len =
133                    frame_context.map(|ctx| ctx.liveness_layer_roots_len);
134                let view_cache_reuse_roots_len =
135                    frame_context.map(|ctx| ctx.view_cache_reuse_roots_len);
136                let view_cache_reuse_root_nodes_len =
137                    frame_context.map(|ctx| ctx.view_cache_reuse_root_nodes_len);
138                let (root_parent_frame_children_len, root_parent_frame_children_contains_root) =
139                    frame_context
140                        .map(|ctx| {
141                            (
142                                ctx.parent_frame_children_len,
143                                ctx.parent_frame_children_contains_root,
144                            )
145                        })
146                        .unwrap_or((None, None));
147                let (root_frame_instance_present, root_frame_children_len) = frame_context
148                    .map(|ctx| {
149                        (
150                            Some(ctx.root_frame_instance_present),
151                            ctx.root_frame_children_len,
152                        )
153                    })
154                    .unwrap_or((None, None));
155                self.debug_removed_subtrees
156                    .push(UiDebugRemoveSubtreeRecord {
157                        outcome: UiDebugRemoveSubtreeOutcome::SkippedLayerRoot,
158                        frame_id: self.debug_stats.frame_id,
159                        root,
160                        root_element,
161                        root_parent,
162                        root_parent_element,
163                        root_root,
164                        root_layer,
165                        root_layer_visible,
166                        reachable_from_layer_roots,
167                        reachable_from_view_cache_roots,
168                        unreachable_from_liveness_roots,
169                        liveness_layer_roots_len,
170                        view_cache_reuse_roots_len,
171                        view_cache_reuse_root_nodes_len,
172                        trigger_element,
173                        trigger_element_root,
174                        trigger_element_in_view_cache_keep_alive,
175                        trigger_element_listed_under_reuse_root,
176                        root_children_len,
177                        root_parent_children_len,
178                        root_parent_children_contains_root,
179                        root_parent_frame_children_len,
180                        root_parent_frame_children_contains_root,
181                        root_frame_instance_present,
182                        root_frame_children_len,
183                        root_path_len,
184                        root_path,
185                        root_path_truncated,
186                        root_path_edge_len,
187                        root_path_edge_ui_contains_child,
188                        root_path_edge_frame_contains_child,
189                        removed_nodes: 0,
190                        removed_head_len: 0,
191                        removed_head: [0u64; 16],
192                        removed_tail_len: 0,
193                        removed_tail: [0u64; 16],
194                        file,
195                        line,
196                        column,
197                    });
198            }
199            return Vec::new();
200        }
201        let mut removed: Vec<NodeId> = Vec::new();
202        self.remove_subtree_inner(services, root, &mut removed);
203
204        #[cfg(feature = "diagnostics")]
205        if let Some((
206            file,
207            line,
208            column,
209            pre_exists,
210            root_element,
211            root_parent,
212            root_parent_element,
213            root_root,
214            root_layer,
215            root_layer_visible,
216            reachable_from_layer_roots,
217            root_children_len,
218            root_parent_children_len,
219            root_parent_children_contains_root,
220            frame_context,
221            root_path_len,
222            root_path,
223            root_path_truncated,
224            root_path_edge_len,
225            root_path_edge_ui_contains_child,
226        )) = remove_record
227        {
228            let root_path_edge_frame_contains_child = frame_context
229                .map(|ctx| ctx.path_edge_frame_contains_child)
230                .unwrap_or([2u8; 16]);
231            let reachable_from_view_cache_roots =
232                frame_context.and_then(|ctx| ctx.root_reachable_from_view_cache_roots);
233            let unreachable_from_liveness_roots = !reachable_from_layer_roots
234                && !matches!(reachable_from_view_cache_roots, Some(true));
235            let trigger_element = frame_context.and_then(|ctx| ctx.trigger_element);
236            let trigger_element_root = frame_context.and_then(|ctx| ctx.trigger_element_root);
237            let trigger_element_in_view_cache_keep_alive =
238                frame_context.and_then(|ctx| ctx.trigger_element_in_view_cache_keep_alive);
239            let trigger_element_listed_under_reuse_root =
240                frame_context.and_then(|ctx| ctx.trigger_element_listed_under_reuse_root);
241            let liveness_layer_roots_len = frame_context.map(|ctx| ctx.liveness_layer_roots_len);
242            let view_cache_reuse_roots_len =
243                frame_context.map(|ctx| ctx.view_cache_reuse_roots_len);
244            let view_cache_reuse_root_nodes_len =
245                frame_context.map(|ctx| ctx.view_cache_reuse_root_nodes_len);
246            let (root_parent_frame_children_len, root_parent_frame_children_contains_root) =
247                frame_context
248                    .map(|ctx| {
249                        (
250                            ctx.parent_frame_children_len,
251                            ctx.parent_frame_children_contains_root,
252                        )
253                    })
254                    .unwrap_or((None, None));
255            let (root_frame_instance_present, root_frame_children_len) = frame_context
256                .map(|ctx| {
257                    (
258                        Some(ctx.root_frame_instance_present),
259                        ctx.root_frame_children_len,
260                    )
261                })
262                .unwrap_or((None, None));
263            let outcome = if pre_exists {
264                UiDebugRemoveSubtreeOutcome::Removed
265            } else {
266                UiDebugRemoveSubtreeOutcome::RootMissing
267            };
268
269            let mut removed_head: [u64; 16] = [0u64; 16];
270            let mut removed_head_len: u8 = 0;
271            for (idx, node) in removed.iter().take(16).enumerate() {
272                removed_head[idx] = node.data().as_ffi();
273                removed_head_len = removed_head_len.saturating_add(1);
274            }
275
276            let mut removed_tail: [u64; 16] = [0u64; 16];
277            let mut removed_tail_len: u8 = 0;
278            for (idx, node) in removed.iter().rev().take(16).enumerate() {
279                removed_tail[idx] = node.data().as_ffi();
280                removed_tail_len = removed_tail_len.saturating_add(1);
281            }
282
283            self.debug_removed_subtrees
284                .push(UiDebugRemoveSubtreeRecord {
285                    outcome,
286                    frame_id: self.debug_stats.frame_id,
287                    root,
288                    root_element,
289                    root_parent,
290                    root_parent_element,
291                    root_root,
292                    root_layer,
293                    root_layer_visible,
294                    reachable_from_layer_roots,
295                    reachable_from_view_cache_roots,
296                    unreachable_from_liveness_roots,
297                    liveness_layer_roots_len,
298                    view_cache_reuse_roots_len,
299                    view_cache_reuse_root_nodes_len,
300                    trigger_element,
301                    trigger_element_root,
302                    trigger_element_in_view_cache_keep_alive,
303                    trigger_element_listed_under_reuse_root,
304                    root_children_len,
305                    root_parent_children_len,
306                    root_parent_children_contains_root,
307                    root_parent_frame_children_len,
308                    root_parent_frame_children_contains_root,
309                    root_frame_instance_present,
310                    root_frame_children_len,
311                    root_path_len,
312                    root_path,
313                    root_path_truncated,
314                    root_path_edge_len,
315                    root_path_edge_ui_contains_child,
316                    root_path_edge_frame_contains_child,
317                    removed_nodes: removed.len().min(u32::MAX as usize) as u32,
318                    removed_head_len,
319                    removed_head,
320                    removed_tail_len,
321                    removed_tail,
322                    file,
323                    line,
324                    column,
325                });
326        }
327
328        removed
329    }
330
331    pub(in crate::tree) fn remove_subtree_inner(
332        &mut self,
333        services: &mut dyn UiServices,
334        root: NodeId,
335        removed: &mut Vec<NodeId>,
336    ) {
337        // Avoid recursion: removing or cleaning up deep trees can overflow the stack.
338        //
339        // We remove nodes in a post-order traversal so children are removed before their parent.
340        let mut stack: Vec<(NodeId, bool)> = Vec::new();
341        stack.push((root, false));
342
343        while let Some((node, children_pushed)) = stack.pop() {
344            if self.root_to_layer.contains_key(&node) {
345                continue;
346            }
347            let Some(n) = self.nodes.get(node) else {
348                continue;
349            };
350            let layout_invalidated = n.invalidation.layout;
351            let subtree_layout_dirty_count = n.subtree_layout_dirty_count;
352
353            if !children_pushed {
354                let children = n.children.clone();
355                stack.push((node, true));
356                for child in children {
357                    stack.push((child, false));
358                }
359                continue;
360            }
361
362            let parent = self.nodes.get(node).and_then(|n| n.parent);
363            if let Some(parent) = parent
364                && let Some(p) = self.nodes.get_mut(parent)
365            {
366                p.children.retain(|&c| c != node);
367                if self.subtree_layout_dirty_aggregation_enabled() && subtree_layout_dirty_count > 0
368                {
369                    let delta = -(subtree_layout_dirty_count.min(i32::MAX as u32) as i32);
370                    self.apply_subtree_layout_dirty_delta_to_node_and_ancestors(parent, delta);
371                }
372            }
373
374            if self.focus == Some(node) {
375                self.set_focus_unchecked(None, "mutation/remove: removed focused node");
376            }
377            self.captured.retain(|_, n| *n != node);
378
379            self.cleanup_node_resources(services, node);
380            if let Some(n) = self.nodes.get(node) {
381                self.update_invalidation_counters(n.invalidation, InvalidationFlags::default());
382            }
383            if layout_invalidated {
384                record_layout_invalidation_transition(
385                    &mut self.layout_invalidations_count,
386                    true,
387                    false,
388                );
389            }
390            self.nodes.remove(node);
391            self.observed_in_layout.remove_node(node);
392            self.observed_in_paint.remove_node(node);
393            self.observed_globals_in_layout.remove_node(node);
394            self.observed_globals_in_paint.remove_node(node);
395            removed.push(node);
396        }
397    }
398}