Skip to main content

repose_tree/
tree.rs

1//! The main ViewTree structure.
2
3use crate::{
4    hash::{hash_subtree, hash_view_content},
5    node::{LayoutCache, LayoutConstraints, NodeId, TreeNode, TreeStats},
6    reconcile::ReconcileContext,
7};
8use repose_core::{Rect, View, ViewId};
9use rustc_hash::{FxHashMap, FxHashSet};
10use slotmap::SlotMap;
11use smallvec::SmallVec;
12
13/// A persistent view tree that supports incremental updates.
14pub struct ViewTree {
15    /// All nodes in the tree.
16    nodes: SlotMap<NodeId, TreeNode>,
17
18    /// The root node.
19    root: Option<NodeId>,
20
21    /// Nodes that need re-layout.
22    dirty: FxHashSet<NodeId>,
23
24    /// Nodes that need re-paint.
25    paint_dirty: FxHashSet<NodeId>,
26
27    /// Current generation (frame counter).
28    generation: u64,
29
30    /// Map from user-facing ViewId to internal NodeId.
31    view_id_map: FxHashMap<ViewId, NodeId>,
32
33    /// Map from user key to NodeId (for stable identity in dynamic lists).
34    key_map: FxHashMap<(NodeId, u64), NodeId>, // (parent, key) -> child
35
36    /// Statistics for the last reconcile operation.
37    pub stats: TreeStats,
38
39    /// Nodes removed during the last update (needed to sync external systems like Taffy).
40    pub removed_ids: Vec<NodeId>,
41}
42
43impl Default for ViewTree {
44    fn default() -> Self {
45        Self::new()
46    }
47}
48
49impl ViewTree {
50    /// Create a new empty tree.
51    pub fn new() -> Self {
52        Self {
53            nodes: SlotMap::with_key(),
54            root: None,
55            dirty: FxHashSet::default(),
56            paint_dirty: FxHashSet::default(),
57            generation: 0,
58            view_id_map: FxHashMap::default(),
59            key_map: FxHashMap::default(),
60            stats: TreeStats::default(),
61            removed_ids: Vec::new(),
62        }
63    }
64
65    /// Get the current generation.
66    pub fn generation(&self) -> u64 {
67        self.generation
68    }
69
70    /// Get the root node ID.
71    pub fn root(&self) -> Option<NodeId> {
72        self.root
73    }
74
75    /// Get a node by ID.
76    pub fn get(&self, id: NodeId) -> Option<&TreeNode> {
77        self.nodes.get(id)
78    }
79
80    /// Get a mutable node by ID.
81    pub fn get_mut(&mut self, id: NodeId) -> Option<&mut TreeNode> {
82        self.nodes.get_mut(id)
83    }
84
85    /// Get a node by ViewId.
86    pub fn get_by_view_id(&self, view_id: ViewId) -> Option<&TreeNode> {
87        self.view_id_map
88            .get(&view_id)
89            .and_then(|id| self.nodes.get(*id))
90    }
91
92    /// Get the number of nodes in the tree.
93    pub fn len(&self) -> usize {
94        self.nodes.len()
95    }
96
97    /// Check if the tree is empty.
98    pub fn is_empty(&self) -> bool {
99        self.nodes.is_empty()
100    }
101
102    /// Check if a node is marked dirty.
103    pub fn is_dirty(&self, id: NodeId) -> bool {
104        self.dirty.contains(&id)
105    }
106
107    /// Get the set of dirty nodes.
108    pub fn dirty_nodes(&self) -> &FxHashSet<NodeId> {
109        &self.dirty
110    }
111
112    /// Clear the dirty set (after layout).
113    pub fn clear_dirty(&mut self) {
114        self.dirty.clear();
115    }
116
117    /// Mark a node as needing re-layout.
118    pub fn mark_dirty(&mut self, id: NodeId) {
119        self.dirty.insert(id);
120
121        // Also mark ancestors dirty (layout flows down from root)
122        let mut current = id;
123        while let Some(node) = self.nodes.get(current) {
124            if let Some(parent) = node.parent {
125                self.dirty.insert(parent);
126                current = parent;
127            } else {
128                break;
129            }
130        }
131    }
132
133    /// Update the tree from a new View, performing incremental reconciliation.
134    /// Returns the root NodeId.
135    pub fn update(&mut self, new_root: &View) -> NodeId {
136        self.removed_ids.clear(); // Clear previous frame's removals
137
138        self.generation += 1;
139        self.stats = TreeStats::default();
140
141        let mut ctx = ReconcileContext::new(self.generation);
142
143        let root_id = if let Some(existing_root) = self.root {
144            self.reconcile_node(existing_root, new_root, None, 0, 0, &mut ctx)
145        } else {
146            self.create_node(new_root, None, 0, 0, &mut ctx)
147        };
148
149        self.root = Some(root_id);
150
151        // Remove orphaned nodes (nodes not updated this generation)
152        self.collect_garbage();
153
154        // Update stats
155        self.stats.total_nodes = self.nodes.len();
156        self.stats.dirty_nodes = self.dirty.len();
157        self.stats.reconciled_nodes = ctx.reconciled;
158        self.stats.skipped_nodes = ctx.skipped;
159        self.stats.created_nodes = ctx.created;
160        self.stats.removed_nodes = ctx.removed;
161
162        root_id
163    }
164
165    /// Reconcile an existing node with a new View.
166    fn reconcile_node(
167        &mut self,
168        node_id: NodeId,
169        view: &View,
170        parent: Option<NodeId>,
171        depth: u32,
172        index_in_parent: u32,
173        ctx: &mut ReconcileContext,
174    ) -> NodeId {
175        let content_hash = hash_view_content(view);
176
177        let old_hash = self
178            .nodes
179            .get(node_id)
180            .expect("reconcile_node: node not found")
181            .content_hash;
182        let content_changed = old_hash != content_hash;
183
184        let new_children_hashes = self.reconcile_children(node_id, &view.children, depth, ctx);
185
186        let new_subtree_hash = hash_subtree(content_hash, &new_children_hashes);
187
188        let view_id = self.compute_view_id(view, node_id, parent, index_in_parent);
189
190        let subtree_changed;
191        {
192            let node = self
193                .nodes
194                .get_mut(node_id)
195                .expect("reconcile_node: node not found");
196
197            // Update parent, depth, generation
198            node.parent = parent;
199            node.depth = depth;
200            node.generation = self.generation;
201
202            // NOTE: fields like on_pointer_down aren't part of the content hash, so can't rely on content_changed to keep them in sync.
203            node.kind = view.kind.clone();
204            node.modifier = view.modifier.clone();
205            node.content_hash = content_hash;
206            node.user_key = view.modifier.key;
207
208            if content_changed {
209                node.invalidate_layout();
210                ctx.reconciled += 1;
211            }
212
213            // Update subtree hash
214            subtree_changed = node.subtree_hash != new_subtree_hash;
215            if subtree_changed {
216                node.subtree_hash = new_subtree_hash;
217            } else if !content_changed {
218                ctx.skipped += 1;
219            }
220
221            // Update view_id
222            node.view_id = view_id;
223        } // Mutable borrow of node ends here
224
225        if subtree_changed {
226            self.mark_dirty(node_id);
227        }
228        self.view_id_map.insert(view_id, node_id);
229
230        node_id
231    }
232    /// Reconcile children of a node.
233    /// Returns the subtree hashes of all children (for computing parent's subtree hash).
234    fn reconcile_children(
235        &mut self,
236        parent_id: NodeId,
237        new_children: &[View],
238        parent_depth: u32,
239        ctx: &mut ReconcileContext,
240    ) -> Vec<u64> {
241        let child_depth = parent_depth + 1;
242
243        // Get current children
244        let old_children: SmallVec<[NodeId; 4]> = self
245            .nodes
246            .get(parent_id)
247            .map(|n| n.children.clone())
248            .unwrap_or_default();
249
250        // Build a map of keyed children for efficient lookup
251        let mut keyed_children: FxHashMap<u64, NodeId> = FxHashMap::default();
252        let mut unkeyed_children: Vec<NodeId> = Vec::new();
253
254        for &child_id in &old_children {
255            if let Some(node) = self.nodes.get(child_id) {
256                if let Some(key) = node.user_key {
257                    keyed_children.insert(key, child_id);
258                } else {
259                    unkeyed_children.push(child_id);
260                }
261            }
262        }
263
264        let mut new_child_ids: SmallVec<[NodeId; 4]> = SmallVec::new();
265        let mut new_subtree_hashes: Vec<u64> = Vec::with_capacity(new_children.len());
266        let mut unkeyed_index = 0;
267        let mut used_nodes: FxHashSet<NodeId> = FxHashSet::default();
268
269        for (i, new_child) in new_children.iter().enumerate() {
270            let idx = i as u32;
271            let child_id = if let Some(key) = new_child.modifier.key {
272                // Keyed child: look up by key
273                if let Some(&existing_id) = keyed_children.get(&key) {
274                    used_nodes.insert(existing_id);
275                    self.reconcile_node(
276                        existing_id,
277                        new_child,
278                        Some(parent_id),
279                        child_depth,
280                        idx,
281                        ctx,
282                    )
283                } else {
284                    self.create_node(new_child, Some(parent_id), child_depth, idx, ctx)
285                }
286            } else {
287                // Unkeyed child: match by position
288                if unkeyed_index < unkeyed_children.len() {
289                    let existing_id = unkeyed_children[unkeyed_index];
290                    unkeyed_index += 1;
291                    used_nodes.insert(existing_id);
292                    self.reconcile_node(
293                        existing_id,
294                        new_child,
295                        Some(parent_id),
296                        child_depth,
297                        idx,
298                        ctx,
299                    )
300                } else {
301                    self.create_node(new_child, Some(parent_id), child_depth, idx, ctx)
302                }
303            };
304
305            new_child_ids.push(child_id);
306
307            if let Some(node) = self.nodes.get(child_id) {
308                new_subtree_hashes.push(node.subtree_hash);
309            }
310        }
311
312        // Mark unused old children for removal
313        for &old_child in &old_children {
314            if !used_nodes.contains(&old_child) {
315                self.mark_for_removal(old_child, ctx);
316            }
317        }
318
319        // Update parent's children list
320        if let Some(parent) = self.nodes.get_mut(parent_id) {
321            parent.children = new_child_ids;
322        }
323
324        new_subtree_hashes
325    }
326
327    /// Create a new node from a View.
328    fn create_node(
329        &mut self,
330        view: &View,
331        parent: Option<NodeId>,
332        depth: u32,
333        index_in_parent: u32,
334        ctx: &mut ReconcileContext,
335    ) -> NodeId {
336        let content_hash = hash_view_content(view);
337
338        // Insert a partial node first
339        let node_id = self.nodes.insert_with_key(|id| {
340            TreeNode::new(
341                id,
342                0,
343                view.kind.clone(),
344                view.modifier.clone(),
345                self.generation,
346            )
347        });
348        ctx.created += 1;
349
350        {
351            let node = self
352                .nodes
353                .get_mut(node_id)
354                .expect("create_node: node just inserted");
355            node.parent = parent;
356            node.depth = depth;
357            node.content_hash = content_hash;
358            node.user_key = view.modifier.key;
359        }
360
361        // Now, recursively create children
362        let child_depth = depth + 1;
363        let mut child_ids: SmallVec<[NodeId; 4]> = SmallVec::new();
364        let mut child_hashes: Vec<u64> = Vec::with_capacity(view.children.len());
365        for (i, child_view) in view.children.iter().enumerate() {
366            let child_id = self.create_node(child_view, Some(node_id), child_depth, i as u32, ctx);
367            child_ids.push(child_id);
368            child_hashes.push(
369                self.nodes
370                    .get(child_id)
371                    .expect("create_node: child just created")
372                    .subtree_hash,
373            );
374        }
375
376        // Now compute the view_id and subtree_hash, and update the node
377        let view_id = self.compute_view_id(view, node_id, parent, index_in_parent);
378        let subtree_hash = hash_subtree(content_hash, &child_hashes);
379
380        let node = self
381            .nodes
382            .get_mut(node_id)
383            .expect("create_node: node just inserted");
384        node.children = child_ids;
385        node.subtree_hash = subtree_hash;
386        node.view_id = view_id;
387
388        self.view_id_map.insert(view_id, node_id);
389        self.dirty.insert(node_id);
390
391        node_id
392    }
393    /// Compute a stable ViewId for a node.
394    fn compute_view_id(
395        &self,
396        view: &View,
397        node_id: NodeId,
398        parent: Option<NodeId>,
399        index_in_parent: u32,
400    ) -> ViewId {
401        // If the view already has an ID assigned, use it
402        if view.id != 0 {
403            return view.id;
404        }
405
406        // Otherwise compute from parent + index/key
407        let parent_id = parent
408            .and_then(|p| self.nodes.get(p))
409            .map(|n| n.view_id)
410            .unwrap_or(0);
411
412        let salt = view.modifier.key.unwrap_or(index_in_parent as u64);
413
414        // Simple hash combination
415        let mut id = parent_id.wrapping_mul(31).wrapping_add(salt);
416        id = id.wrapping_mul(0x9E3779B97F4A7C15);
417        id ^= id >> 30;
418
419        if id == 0 {
420            id = 1;
421        }
422
423        id
424    }
425
426    /// Mark a node and its descendants for removal.
427    fn mark_for_removal(&mut self, node_id: NodeId, ctx: &mut ReconcileContext) {
428        if let Some(node) = self.nodes.get(node_id) {
429            // Remove from view_id map
430            self.view_id_map.remove(&node.view_id);
431
432            // Recursively mark children
433            let children: SmallVec<[NodeId; 4]> = node.children.clone();
434            for child_id in children {
435                self.mark_for_removal(child_id, ctx);
436            }
437
438            ctx.removed += 1;
439        }
440
441        // Mark the node's generation as old so it gets collected
442        if let Some(node) = self.nodes.get_mut(node_id) {
443            node.generation = 0; // Will be collected
444        }
445    }
446
447    /// Remove nodes that weren't updated this generation.
448    fn collect_garbage(&mut self) {
449        let current_gen = self.generation;
450
451        // Find nodes to remove
452        let to_remove: Vec<NodeId> = self
453            .nodes
454            .iter()
455            .filter(|(_, node)| node.generation != current_gen)
456            .map(|(id, _)| id)
457            .collect();
458
459        // Remove them
460        for id in to_remove {
461            if let Some(node) = self.nodes.remove(id) {
462                self.view_id_map.remove(&node.view_id);
463                self.dirty.remove(&id);
464
465                // Track removal for external sync
466                self.removed_ids.push(id);
467            }
468        }
469    }
470
471    /// Set cached layout for a node.
472    pub fn set_layout(
473        &mut self,
474        id: NodeId,
475        rect: Rect,
476        screen_rect: Rect,
477        constraints: LayoutConstraints,
478    ) {
479        if let Some(node) = self.nodes.get_mut(id) {
480            node.layout_cache = Some(LayoutCache {
481                rect,
482                screen_rect,
483                constraints,
484                generation: self.generation,
485            });
486        }
487    }
488
489    /// Iterate over all nodes (parent before children).
490    pub fn iter(&self) -> impl Iterator<Item = &TreeNode> {
491        self.nodes.values()
492    }
493
494    /// Iterate over all nodes with their IDs.
495    pub fn iter_with_ids(&self) -> impl Iterator<Item = (NodeId, &TreeNode)> {
496        self.nodes.iter()
497    }
498
499    /// Walk the tree from root, calling `f` for each node.
500    /// Returns early if `f` returns false.
501    pub fn walk<F>(&self, mut f: F)
502    where
503        F: FnMut(&TreeNode, u32) -> bool,
504    {
505        if let Some(root_id) = self.root {
506            self.walk_node(root_id, 0, &mut f);
507        }
508    }
509
510    fn walk_node<F>(&self, id: NodeId, depth: u32, f: &mut F)
511    where
512        F: FnMut(&TreeNode, u32) -> bool,
513    {
514        if let Some(node) = self.nodes.get(id) {
515            if !f(node, depth) {
516                return;
517            }
518
519            for &child_id in &node.children {
520                self.walk_node(child_id, depth + 1, f);
521            }
522        }
523    }
524
525    /// Get children of a node.
526    pub fn children(&self, id: NodeId) -> Option<&[NodeId]> {
527        self.nodes.get(id).map(|n| n.children.as_slice())
528    }
529}
530
531#[cfg(test)]
532mod tests {
533    use super::*;
534    use repose_core::{Color, Modifier, View, ViewKind};
535
536    fn text_view(text: &str) -> View {
537        View::new(
538            0,
539            ViewKind::Text {
540                text: text.to_string(),
541                color: Color::WHITE,
542                font_size: 16.0,
543                soft_wrap: true,
544                max_lines: None,
545                overflow: repose_core::TextOverflow::Visible,
546                font_family: None,
547                annotations: None,
548            },
549        )
550    }
551
552    fn box_view() -> View {
553        View::new(0, ViewKind::Box)
554    }
555
556    #[test]
557    fn test_create_tree() {
558        let mut tree = ViewTree::new();
559
560        let root = box_view().with_children(vec![text_view("Hello"), text_view("World")]);
561
562        tree.update(&root);
563
564        assert_eq!(tree.len(), 3); // box + 2 text
565        assert!(tree.root().is_some());
566    }
567
568    #[test]
569    fn test_unchanged_tree_skips() {
570        let mut tree = ViewTree::new();
571
572        let root = box_view().with_children(vec![text_view("Hello")]);
573
574        tree.update(&root);
575        let gen1 = tree.generation();
576
577        // Same tree
578        tree.update(&root);
579        let gen2 = tree.generation();
580
581        assert_eq!(gen2, gen1 + 1);
582        assert!(tree.stats.skipped_nodes > 0);
583    }
584
585    #[test]
586    fn test_changed_content_reconciles() {
587        let mut tree = ViewTree::new();
588
589        let root1 = box_view().with_children(vec![text_view("Hello")]);
590
591        tree.update(&root1);
592
593        let root2 = box_view().with_children(vec![text_view("Changed")]);
594
595        tree.update(&root2);
596
597        assert!(tree.stats.reconciled_nodes > 0);
598    }
599
600    #[test]
601    fn test_keyed_children_stable() {
602        let mut tree = ViewTree::new();
603
604        // Initial: A, B, C
605        let root1 = box_view().with_children(vec![
606            text_view("A").modifier(Modifier::new().key(1)),
607            text_view("B").modifier(Modifier::new().key(2)),
608            text_view("C").modifier(Modifier::new().key(3)),
609        ]);
610
611        tree.update(&root1);
612
613        // Get B's NodeId
614        let b_view_id = tree
615            .root()
616            .and_then(|r| tree.children(r))
617            .and_then(|c| c.get(1).copied())
618            .and_then(|id| tree.get(id))
619            .map(|n| n.view_id);
620
621        // Reorder: C, A, B
622        let root2 = box_view().with_children(vec![
623            text_view("C").modifier(Modifier::new().key(3)),
624            text_view("A").modifier(Modifier::new().key(1)),
625            text_view("B").modifier(Modifier::new().key(2)),
626        ]);
627
628        tree.update(&root2);
629
630        // B should have same view_id (key-based stability)
631        // Note: Implementation detail - the node may be reused
632        assert_eq!(tree.len(), 4); // Still 4 nodes (box + 3 text)
633    }
634}