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