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, &mut ctx)
145        } else {
146            self.create_node(new_root, None, 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        ctx: &mut ReconcileContext,
173    ) -> NodeId {
174        let content_hash = hash_view_content(view);
175
176        let old_hash = self.nodes[node_id].content_hash;
177        let content_changed = old_hash != content_hash;
178
179        let new_children_hashes = self.reconcile_children(node_id, &view.children, depth, ctx);
180
181        let new_subtree_hash = hash_subtree(content_hash, &new_children_hashes);
182
183        let view_id = self.compute_view_id(view, node_id, parent, depth);
184
185        let subtree_changed;
186        {
187            let node = self.nodes.get_mut(node_id).unwrap();
188
189            // Update parent, depth, generation
190            node.parent = parent;
191            node.depth = depth;
192            node.generation = self.generation;
193
194            if content_changed {
195                node.kind = view.kind.clone();
196                node.modifier = view.modifier.clone();
197                node.content_hash = content_hash;
198                node.user_key = view.modifier.key;
199                node.invalidate_layout();
200                ctx.reconciled += 1;
201            }
202
203            // Update subtree hash
204            subtree_changed = node.subtree_hash != new_subtree_hash;
205            if subtree_changed {
206                node.subtree_hash = new_subtree_hash;
207            } else if !content_changed {
208                ctx.skipped += 1;
209            }
210
211            // Update view_id
212            node.view_id = view_id;
213        } // Mutable borrow of node ends here
214
215        // --- MUTABLE SELF CALLS ---
216        if subtree_changed {
217            self.mark_dirty(node_id);
218        }
219        self.view_id_map.insert(view_id, node_id);
220
221        node_id
222    }
223    /// Reconcile children of a node.
224    /// Returns the subtree hashes of all children (for computing parent's subtree hash).
225    fn reconcile_children(
226        &mut self,
227        parent_id: NodeId,
228        new_children: &[View],
229        parent_depth: u32,
230        ctx: &mut ReconcileContext,
231    ) -> Vec<u64> {
232        let child_depth = parent_depth + 1;
233
234        // Get current children
235        let old_children: SmallVec<[NodeId; 4]> = self
236            .nodes
237            .get(parent_id)
238            .map(|n| n.children.clone())
239            .unwrap_or_default();
240
241        // Build a map of keyed children for efficient lookup
242        let mut keyed_children: FxHashMap<u64, NodeId> = FxHashMap::default();
243        let mut unkeyed_children: Vec<NodeId> = Vec::new();
244
245        for &child_id in &old_children {
246            if let Some(node) = self.nodes.get(child_id) {
247                if let Some(key) = node.user_key {
248                    keyed_children.insert(key, child_id);
249                } else {
250                    unkeyed_children.push(child_id);
251                }
252            }
253        }
254
255        let mut new_child_ids: SmallVec<[NodeId; 4]> = SmallVec::new();
256        let mut new_subtree_hashes: Vec<u64> = Vec::with_capacity(new_children.len());
257        let mut unkeyed_index = 0;
258        let mut used_nodes: FxHashSet<NodeId> = FxHashSet::default();
259
260        for new_child in new_children {
261            let child_id = if let Some(key) = new_child.modifier.key {
262                // Keyed child: look up by key
263                if let Some(&existing_id) = keyed_children.get(&key) {
264                    used_nodes.insert(existing_id);
265                    self.reconcile_node(existing_id, new_child, Some(parent_id), child_depth, ctx)
266                } else {
267                    self.create_node(new_child, Some(parent_id), child_depth, ctx)
268                }
269            } else {
270                // Unkeyed child: match by position
271                if unkeyed_index < unkeyed_children.len() {
272                    let existing_id = unkeyed_children[unkeyed_index];
273                    unkeyed_index += 1;
274                    used_nodes.insert(existing_id);
275                    self.reconcile_node(existing_id, new_child, Some(parent_id), child_depth, ctx)
276                } else {
277                    self.create_node(new_child, Some(parent_id), child_depth, ctx)
278                }
279            };
280
281            new_child_ids.push(child_id);
282
283            if let Some(node) = self.nodes.get(child_id) {
284                new_subtree_hashes.push(node.subtree_hash);
285            }
286        }
287
288        // Mark unused old children for removal
289        for &old_child in &old_children {
290            if !used_nodes.contains(&old_child) {
291                self.mark_for_removal(old_child, ctx);
292            }
293        }
294
295        // Update parent's children list
296        if let Some(parent) = self.nodes.get_mut(parent_id) {
297            parent.children = new_child_ids;
298        }
299
300        new_subtree_hashes
301    }
302
303    /// Create a new node from a View.
304    fn create_node(
305        &mut self,
306        view: &View,
307        parent: Option<NodeId>,
308        depth: u32,
309        ctx: &mut ReconcileContext,
310    ) -> NodeId {
311        let content_hash = hash_view_content(view);
312
313        // Insert a partial node first
314        let node_id = self.nodes.insert_with_key(|id| {
315            TreeNode::new(
316                id,
317                0,
318                view.kind.clone(),
319                view.modifier.clone(),
320                self.generation,
321            )
322        });
323        ctx.created += 1;
324
325        {
326            let node = self.nodes.get_mut(node_id).unwrap();
327            node.parent = parent;
328            node.depth = depth;
329            node.content_hash = content_hash;
330            node.user_key = view.modifier.key;
331        }
332
333        // Now, recursively create children
334        let child_depth = depth + 1;
335        let mut child_ids: SmallVec<[NodeId; 4]> = SmallVec::new();
336        let mut child_hashes: Vec<u64> = Vec::with_capacity(view.children.len());
337        for child_view in &view.children {
338            let child_id = self.create_node(child_view, Some(node_id), child_depth, ctx);
339            child_ids.push(child_id);
340            child_hashes.push(self.nodes[child_id].subtree_hash);
341        }
342
343        // Now compute the view_id and subtree_hash, and update the node
344        let view_id = self.compute_view_id(view, node_id, parent, depth);
345        let subtree_hash = hash_subtree(content_hash, &child_hashes);
346
347        let node = self.nodes.get_mut(node_id).unwrap();
348        node.children = child_ids;
349        node.subtree_hash = subtree_hash;
350        node.view_id = view_id;
351
352        self.view_id_map.insert(view_id, node_id);
353        self.dirty.insert(node_id);
354
355        node_id
356    }
357    /// Compute a stable ViewId for a node.
358    fn compute_view_id(
359        &self,
360        view: &View,
361        node_id: NodeId,
362        parent: Option<NodeId>,
363        index_in_parent: u32,
364    ) -> ViewId {
365        // If the view already has an ID assigned, use it
366        if view.id != 0 {
367            return view.id;
368        }
369
370        // Otherwise compute from parent + index/key
371        let parent_id = parent
372            .and_then(|p| self.nodes.get(p))
373            .map(|n| n.view_id)
374            .unwrap_or(0);
375
376        let salt = view.modifier.key.unwrap_or(index_in_parent as u64);
377
378        // Simple hash combination
379        let mut id = parent_id.wrapping_mul(31).wrapping_add(salt);
380        id = id.wrapping_mul(0x9E3779B97F4A7C15);
381        id ^= id >> 30;
382
383        if id == 0 {
384            id = 1;
385        }
386
387        id
388    }
389
390    /// Mark a node and its descendants for removal.
391    fn mark_for_removal(&mut self, node_id: NodeId, ctx: &mut ReconcileContext) {
392        if let Some(node) = self.nodes.get(node_id) {
393            // Remove from view_id map
394            self.view_id_map.remove(&node.view_id);
395
396            // Recursively mark children
397            let children: SmallVec<[NodeId; 4]> = node.children.clone();
398            for child_id in children {
399                self.mark_for_removal(child_id, ctx);
400            }
401
402            ctx.removed += 1;
403        }
404
405        // Mark the node's generation as old so it gets collected
406        if let Some(node) = self.nodes.get_mut(node_id) {
407            node.generation = 0; // Will be collected
408        }
409    }
410
411    /// Remove nodes that weren't updated this generation.
412    fn collect_garbage(&mut self) {
413        let current_gen = self.generation;
414
415        // Find nodes to remove
416        let to_remove: Vec<NodeId> = self
417            .nodes
418            .iter()
419            .filter(|(_, node)| node.generation != current_gen)
420            .map(|(id, _)| id)
421            .collect();
422
423        // Remove them
424        for id in to_remove {
425            if let Some(node) = self.nodes.remove(id) {
426                self.view_id_map.remove(&node.view_id);
427                self.dirty.remove(&id);
428
429                // Track removal for external sync
430                self.removed_ids.push(id);
431            }
432        }
433    }
434
435    /// Set cached layout for a node.
436    pub fn set_layout(
437        &mut self,
438        id: NodeId,
439        rect: Rect,
440        screen_rect: Rect,
441        constraints: LayoutConstraints,
442    ) {
443        if let Some(node) = self.nodes.get_mut(id) {
444            node.layout_cache = Some(LayoutCache {
445                rect,
446                screen_rect,
447                constraints,
448                generation: self.generation,
449            });
450        }
451    }
452
453    /// Iterate over all nodes (parent before children).
454    pub fn iter(&self) -> impl Iterator<Item = &TreeNode> {
455        self.nodes.values()
456    }
457
458    /// Iterate over all nodes with their IDs.
459    pub fn iter_with_ids(&self) -> impl Iterator<Item = (NodeId, &TreeNode)> {
460        self.nodes.iter()
461    }
462
463    /// Walk the tree from root, calling `f` for each node.
464    /// Returns early if `f` returns false.
465    pub fn walk<F>(&self, mut f: F)
466    where
467        F: FnMut(&TreeNode, u32) -> bool,
468    {
469        if let Some(root_id) = self.root {
470            self.walk_node(root_id, 0, &mut f);
471        }
472    }
473
474    fn walk_node<F>(&self, id: NodeId, depth: u32, f: &mut F)
475    where
476        F: FnMut(&TreeNode, u32) -> bool,
477    {
478        if let Some(node) = self.nodes.get(id) {
479            if !f(node, depth) {
480                return;
481            }
482
483            for &child_id in &node.children {
484                self.walk_node(child_id, depth + 1, f);
485            }
486        }
487    }
488
489    /// Get children of a node.
490    pub fn children(&self, id: NodeId) -> Option<&[NodeId]> {
491        self.nodes.get(id).map(|n| n.children.as_slice())
492    }
493}
494
495#[cfg(test)]
496mod tests {
497    use super::*;
498    use repose_core::{Color, Modifier, View, ViewKind};
499
500    fn text_view(text: &str) -> View {
501        View::new(
502            0,
503            ViewKind::Text {
504                text: text.to_string(),
505                color: Color::WHITE,
506                font_size: 16.0,
507                soft_wrap: true,
508                max_lines: None,
509                overflow: repose_core::TextOverflow::Visible,
510            },
511        )
512    }
513
514    fn box_view() -> View {
515        View::new(0, ViewKind::Box)
516    }
517
518    #[test]
519    fn test_create_tree() {
520        let mut tree = ViewTree::new();
521
522        let root = box_view().with_children(vec![text_view("Hello"), text_view("World")]);
523
524        tree.update(&root);
525
526        assert_eq!(tree.len(), 3); // box + 2 text
527        assert!(tree.root().is_some());
528    }
529
530    #[test]
531    fn test_unchanged_tree_skips() {
532        let mut tree = ViewTree::new();
533
534        let root = box_view().with_children(vec![text_view("Hello")]);
535
536        tree.update(&root);
537        let gen1 = tree.generation();
538
539        // Same tree
540        tree.update(&root);
541        let gen2 = tree.generation();
542
543        assert_eq!(gen2, gen1 + 1);
544        assert!(tree.stats.skipped_nodes > 0);
545    }
546
547    #[test]
548    fn test_changed_content_reconciles() {
549        let mut tree = ViewTree::new();
550
551        let root1 = box_view().with_children(vec![text_view("Hello")]);
552
553        tree.update(&root1);
554
555        let root2 = box_view().with_children(vec![text_view("Changed")]);
556
557        tree.update(&root2);
558
559        assert!(tree.stats.reconciled_nodes > 0);
560    }
561
562    #[test]
563    fn test_keyed_children_stable() {
564        let mut tree = ViewTree::new();
565
566        // Initial: A, B, C
567        let root1 = box_view().with_children(vec![
568            text_view("A").modifier(Modifier::new().key(1)),
569            text_view("B").modifier(Modifier::new().key(2)),
570            text_view("C").modifier(Modifier::new().key(3)),
571        ]);
572
573        tree.update(&root1);
574
575        // Get B's NodeId
576        let b_view_id = tree
577            .root()
578            .and_then(|r| tree.children(r))
579            .and_then(|c| c.get(1).copied())
580            .and_then(|id| tree.get(id))
581            .map(|n| n.view_id);
582
583        // Reorder: C, A, B
584        let root2 = box_view().with_children(vec![
585            text_view("C").modifier(Modifier::new().key(3)),
586            text_view("A").modifier(Modifier::new().key(1)),
587            text_view("B").modifier(Modifier::new().key(2)),
588        ]);
589
590        tree.update(&root2);
591
592        // B should have same view_id (key-based stability)
593        // Note: Implementation detail - the node may be reused
594        assert_eq!(tree.len(), 4); // Still 4 nodes (box + 3 text)
595    }
596}