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, &mut ctx)
145 } else {
146 self.create_node(new_root, None, 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 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 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 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 node.view_id = view_id;
213 } 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 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 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 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 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 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 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 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 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 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 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 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 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 view.id != 0 {
367 return view.id;
368 }
369
370 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 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 fn mark_for_removal(&mut self, node_id: NodeId, ctx: &mut ReconcileContext) {
392 if let Some(node) = self.nodes.get(node_id) {
393 self.view_id_map.remove(&node.view_id);
395
396 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 if let Some(node) = self.nodes.get_mut(node_id) {
407 node.generation = 0; }
409 }
410
411 fn collect_garbage(&mut self) {
413 let current_gen = self.generation;
414
415 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 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 self.removed_ids.push(id);
431 }
432 }
433 }
434
435 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 pub fn iter(&self) -> impl Iterator<Item = &TreeNode> {
455 self.nodes.values()
456 }
457
458 pub fn iter_with_ids(&self) -> impl Iterator<Item = (NodeId, &TreeNode)> {
460 self.nodes.iter()
461 }
462
463 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 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); 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 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 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 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 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 assert_eq!(tree.len(), 4); }
596}