1use 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
13pub struct ViewTree {
15 nodes: SlotMap<NodeId, TreeNode>,
17
18 root: Option<NodeId>,
20
21 dirty: FxHashSet<NodeId>,
23
24 paint_dirty: FxHashSet<NodeId>,
26
27 generation: u64,
29
30 view_id_map: FxHashMap<ViewId, NodeId>,
32
33 key_map: FxHashMap<(NodeId, u64), NodeId>, pub stats: TreeStats,
38
39 pub removed_ids: Vec<NodeId>,
41}
42
43impl Default for ViewTree {
44 fn default() -> Self {
45 Self::new()
46 }
47}
48
49impl ViewTree {
50 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 pub fn generation(&self) -> u64 {
67 self.generation
68 }
69
70 pub fn root(&self) -> Option<NodeId> {
72 self.root
73 }
74
75 pub fn get(&self, id: NodeId) -> Option<&TreeNode> {
77 self.nodes.get(id)
78 }
79
80 pub fn get_mut(&mut self, id: NodeId) -> Option<&mut TreeNode> {
82 self.nodes.get_mut(id)
83 }
84
85 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 pub fn len(&self) -> usize {
94 self.nodes.len()
95 }
96
97 pub fn is_empty(&self) -> bool {
99 self.nodes.is_empty()
100 }
101
102 pub fn is_dirty(&self, id: NodeId) -> bool {
104 self.dirty.contains(&id)
105 }
106
107 pub fn dirty_nodes(&self) -> &FxHashSet<NodeId> {
109 &self.dirty
110 }
111
112 pub fn clear_dirty(&mut self) {
114 self.dirty.clear();
115 }
116
117 pub fn mark_dirty(&mut self, id: NodeId) {
119 self.dirty.insert(id);
120
121 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 pub fn update(&mut self, new_root: &View) -> NodeId {
136 self.removed_ids.clear(); 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 self.collect_garbage();
153
154 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 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 node.parent = parent;
199 node.depth = depth;
200 node.generation = self.generation;
201
202 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 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 node.view_id = view_id;
223 } 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 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 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 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 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 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 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 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 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 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 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 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 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 view.id != 0 {
404 return view.id;
405 }
406
407 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 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 fn mark_for_removal(&mut self, node_id: NodeId, ctx: &mut ReconcileContext) {
429 if let Some(node) = self.nodes.get(node_id) {
430 self.view_id_map.remove(&node.view_id);
432
433 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 if let Some(node) = self.nodes.get_mut(node_id) {
444 node.generation = 0; }
446 }
447
448 fn collect_garbage(&mut self) {
450 let current_gen = self.generation;
451
452 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 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 self.removed_ids.push(id);
468 }
469 }
470 }
471
472 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 pub fn iter(&self) -> impl Iterator<Item = &TreeNode> {
492 self.nodes.values()
493 }
494
495 pub fn iter_with_ids(&self) -> impl Iterator<Item = (NodeId, &TreeNode)> {
497 self.nodes.iter()
498 }
499
500 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 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); 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 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 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 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 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 assert_eq!(tree.len(), 4); }
635}