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 {
226 self.mark_dirty(node_id);
227 }
228 self.view_id_map.insert(view_id, node_id);
229
230 node_id
231 }
232 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 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 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 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 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 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 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 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 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 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 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 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 view.id != 0 {
403 return view.id;
404 }
405
406 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 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 fn mark_for_removal(&mut self, node_id: NodeId, ctx: &mut ReconcileContext) {
428 if let Some(node) = self.nodes.get(node_id) {
429 self.view_id_map.remove(&node.view_id);
431
432 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 if let Some(node) = self.nodes.get_mut(node_id) {
443 node.generation = 0; }
445 }
446
447 fn collect_garbage(&mut self) {
449 let current_gen = self.generation;
450
451 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 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 self.removed_ids.push(id);
467 }
468 }
469 }
470
471 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 pub fn iter(&self) -> impl Iterator<Item = &TreeNode> {
491 self.nodes.values()
492 }
493
494 pub fn iter_with_ids(&self) -> impl Iterator<Item = (NodeId, &TreeNode)> {
496 self.nodes.iter()
497 }
498
499 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 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); 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 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 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 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 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 assert_eq!(tree.len(), 4); }
634}