1use crate::widget::TypeId;
18use std::collections::HashMap;
19
20#[derive(Debug, Clone, PartialEq, Eq, Hash)]
22pub enum WidgetKey {
23 String(String),
25 Index(usize),
27}
28
29impl WidgetKey {
30 #[must_use]
32 pub fn string(s: impl Into<String>) -> Self {
33 Self::String(s.into())
34 }
35
36 #[must_use]
38 pub const fn index(i: usize) -> Self {
39 Self::Index(i)
40 }
41}
42
43#[derive(Debug, Clone)]
45pub struct DiffNode {
46 pub type_id: TypeId,
48 pub key: Option<String>,
50 pub props_hash: u64,
52 pub children: Vec<DiffNode>,
54 pub index: usize,
56}
57
58impl DiffNode {
59 #[must_use]
61 pub const fn new(type_id: TypeId, props_hash: u64) -> Self {
62 Self {
63 type_id,
64 key: None,
65 props_hash,
66 children: Vec::new(),
67 index: 0,
68 }
69 }
70
71 #[must_use]
73 pub fn with_key(mut self, key: impl Into<String>) -> Self {
74 self.key = Some(key.into());
75 self
76 }
77
78 #[must_use]
80 pub const fn with_index(mut self, index: usize) -> Self {
81 self.index = index;
82 self
83 }
84
85 pub fn add_child(&mut self, child: Self) {
87 self.children.push(child);
88 }
89
90 #[must_use]
92 pub fn with_child(mut self, child: Self) -> Self {
93 self.children.push(child);
94 self
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100pub enum DiffOp {
101 Insert {
103 path: Vec<usize>,
105 index: usize,
107 type_id: TypeId,
109 props_hash: u64,
111 },
112 Remove {
114 path: Vec<usize>,
116 },
117 Update {
119 path: Vec<usize>,
121 new_props_hash: u64,
123 },
124 Move {
126 from_path: Vec<usize>,
128 to_path: Vec<usize>,
130 },
131 Replace {
133 path: Vec<usize>,
135 new_type_id: TypeId,
137 new_props_hash: u64,
139 },
140}
141
142#[derive(Debug, Clone, Default)]
144pub struct DiffResult {
145 pub operations: Vec<DiffOp>,
147}
148
149impl DiffResult {
150 #[must_use]
152 pub fn new() -> Self {
153 Self::default()
154 }
155
156 #[must_use]
158 pub fn is_empty(&self) -> bool {
159 self.operations.is_empty()
160 }
161
162 #[must_use]
164 pub fn len(&self) -> usize {
165 self.operations.len()
166 }
167
168 pub fn push(&mut self, op: DiffOp) {
170 self.operations.push(op);
171 }
172}
173
174#[derive(Debug, Default)]
176pub struct TreeDiffer {
177 current_path: Vec<usize>,
179}
180
181impl TreeDiffer {
182 #[must_use]
184 pub fn new() -> Self {
185 Self::default()
186 }
187
188 #[must_use]
190 pub fn diff(&mut self, old: &DiffNode, new: &DiffNode) -> DiffResult {
191 let mut result = DiffResult::new();
192 self.current_path.clear();
193 self.diff_node(old, new, &mut result);
194 result
195 }
196
197 fn diff_node(&mut self, old: &DiffNode, new: &DiffNode, result: &mut DiffResult) {
198 if old.type_id != new.type_id {
200 result.push(DiffOp::Replace {
201 path: self.current_path.clone(),
202 new_type_id: new.type_id,
203 new_props_hash: new.props_hash,
204 });
205 return;
206 }
207
208 if old.props_hash != new.props_hash {
210 result.push(DiffOp::Update {
211 path: self.current_path.clone(),
212 new_props_hash: new.props_hash,
213 });
214 }
215
216 self.diff_children(&old.children, &new.children, result);
218 }
219
220 fn diff_children(
221 &mut self,
222 old_children: &[DiffNode],
223 new_children: &[DiffNode],
224 result: &mut DiffResult,
225 ) {
226 let old_keyed: HashMap<&str, usize> = old_children
228 .iter()
229 .enumerate()
230 .filter_map(|(i, c)| c.key.as_deref().map(|k| (k, i)))
231 .collect();
232
233 let _new_keyed: HashMap<&str, usize> = new_children
234 .iter()
235 .enumerate()
236 .filter_map(|(i, c)| c.key.as_deref().map(|k| (k, i)))
237 .collect();
238
239 let mut old_matched = vec![false; old_children.len()];
241 let mut new_matched = vec![false; new_children.len()];
242
243 for (new_idx, new_child) in new_children.iter().enumerate() {
245 if let Some(key) = &new_child.key {
246 if let Some(&old_idx) = old_keyed.get(key.as_str()) {
247 old_matched[old_idx] = true;
248 new_matched[new_idx] = true;
249
250 if old_idx != new_idx {
252 let mut from_path = self.current_path.clone();
253 from_path.push(old_idx);
254 let mut to_path = self.current_path.clone();
255 to_path.push(new_idx);
256 result.push(DiffOp::Move { from_path, to_path });
257 }
258
259 self.current_path.push(new_idx);
261 self.diff_node(&old_children[old_idx], new_child, result);
262 self.current_path.pop();
263 }
264 }
265 }
266
267 let old_unkeyed: Vec<usize> = old_children
269 .iter()
270 .enumerate()
271 .filter(|(i, c)| c.key.is_none() && !old_matched[*i])
272 .map(|(i, _)| i)
273 .collect();
274
275 let new_unkeyed: Vec<usize> = new_children
276 .iter()
277 .enumerate()
278 .filter(|(i, c)| c.key.is_none() && !new_matched[*i])
279 .map(|(i, _)| i)
280 .collect();
281
282 let mut old_unkeyed_used = vec![false; old_unkeyed.len()];
284 for new_pos in &new_unkeyed {
285 let new_child = &new_children[*new_pos];
286 let mut found = false;
287
288 for (old_pos_idx, old_pos) in old_unkeyed.iter().enumerate() {
289 if old_unkeyed_used[old_pos_idx] {
290 continue;
291 }
292 let old_child = &old_children[*old_pos];
293
294 if old_child.type_id == new_child.type_id {
295 old_unkeyed_used[old_pos_idx] = true;
296 old_matched[*old_pos] = true;
297 new_matched[*new_pos] = true;
298 found = true;
299
300 self.current_path.push(*new_pos);
302 self.diff_node(old_child, new_child, result);
303 self.current_path.pop();
304 break;
305 }
306 }
307
308 if !found {
309 new_matched[*new_pos] = true;
311 result.push(DiffOp::Insert {
312 path: self.current_path.clone(),
313 index: *new_pos,
314 type_id: new_child.type_id,
315 props_hash: new_child.props_hash,
316 });
317
318 self.current_path.push(*new_pos);
320 self.insert_subtree(new_child, result);
321 self.current_path.pop();
322 }
323 }
324
325 for (i, matched) in old_matched.iter().enumerate().rev() {
327 if !matched {
328 let mut path = self.current_path.clone();
329 path.push(i);
330 result.push(DiffOp::Remove { path });
331 }
332 }
333
334 for (i, matched) in new_matched.iter().enumerate() {
336 if !matched {
337 let new_child = &new_children[i];
338 result.push(DiffOp::Insert {
339 path: self.current_path.clone(),
340 index: i,
341 type_id: new_child.type_id,
342 props_hash: new_child.props_hash,
343 });
344
345 self.current_path.push(i);
347 self.insert_subtree(new_child, result);
348 self.current_path.pop();
349 }
350 }
351 }
352
353 fn insert_subtree(&mut self, node: &DiffNode, result: &mut DiffResult) {
354 for (i, child) in node.children.iter().enumerate() {
355 result.push(DiffOp::Insert {
356 path: self.current_path.clone(),
357 index: i,
358 type_id: child.type_id,
359 props_hash: child.props_hash,
360 });
361
362 self.current_path.push(i);
363 self.insert_subtree(child, result);
364 self.current_path.pop();
365 }
366 }
367}
368
369#[must_use]
371pub fn diff_trees(old: &DiffNode, new: &DiffNode) -> DiffResult {
372 let mut differ = TreeDiffer::new();
373 differ.diff(old, new)
374}
375
376#[cfg(test)]
377mod tests {
378 use super::*;
379
380 fn make_type_id<T: 'static>() -> TypeId {
381 TypeId::of::<T>()
382 }
383
384 #[test]
385 fn test_widget_key_string() {
386 let key = WidgetKey::string("test");
387 assert_eq!(key, WidgetKey::String("test".to_string()));
388 }
389
390 #[test]
391 fn test_widget_key_index() {
392 let key = WidgetKey::index(42);
393 assert_eq!(key, WidgetKey::Index(42));
394 }
395
396 #[test]
397 fn test_diff_node_new() {
398 let type_id = make_type_id::<u32>();
399 let node = DiffNode::new(type_id, 123);
400
401 assert_eq!(node.type_id, type_id);
402 assert_eq!(node.props_hash, 123);
403 assert!(node.key.is_none());
404 assert!(node.children.is_empty());
405 }
406
407 #[test]
408 fn test_diff_node_with_key() {
409 let type_id = make_type_id::<u32>();
410 let node = DiffNode::new(type_id, 123).with_key("my-key");
411
412 assert_eq!(node.key, Some("my-key".to_string()));
413 }
414
415 #[test]
416 fn test_diff_node_with_child() {
417 let type_id = make_type_id::<u32>();
418 let child = DiffNode::new(type_id, 456);
419 let parent = DiffNode::new(type_id, 123).with_child(child);
420
421 assert_eq!(parent.children.len(), 1);
422 assert_eq!(parent.children[0].props_hash, 456);
423 }
424
425 #[test]
426 fn test_diff_result_empty() {
427 let result = DiffResult::new();
428 assert!(result.is_empty());
429 assert_eq!(result.len(), 0);
430 }
431
432 #[test]
433 fn test_diff_identical_trees() {
434 let type_id = make_type_id::<u32>();
435 let old = DiffNode::new(type_id, 123);
436 let new = DiffNode::new(type_id, 123);
437
438 let result = diff_trees(&old, &new);
439 assert!(result.is_empty());
440 }
441
442 #[test]
443 fn test_diff_props_changed() {
444 let type_id = make_type_id::<u32>();
445 let old = DiffNode::new(type_id, 123);
446 let new = DiffNode::new(type_id, 456);
447
448 let result = diff_trees(&old, &new);
449 assert_eq!(result.len(), 1);
450 assert!(matches!(
451 &result.operations[0],
452 DiffOp::Update { path, new_props_hash: 456 } if path.is_empty()
453 ));
454 }
455
456 #[test]
457 fn test_diff_type_changed() {
458 let old_type = make_type_id::<u32>();
459 let new_type = make_type_id::<String>();
460 let old = DiffNode::new(old_type, 123);
461 let new = DiffNode::new(new_type, 123);
462
463 let result = diff_trees(&old, &new);
464 assert_eq!(result.len(), 1);
465 assert!(matches!(
466 &result.operations[0],
467 DiffOp::Replace { path, new_type_id, .. } if path.is_empty() && *new_type_id == new_type
468 ));
469 }
470
471 #[test]
472 fn test_diff_child_added() {
473 let type_id = make_type_id::<u32>();
474 let child_type = make_type_id::<String>();
475
476 let old = DiffNode::new(type_id, 123);
477 let new = DiffNode::new(type_id, 123).with_child(DiffNode::new(child_type, 456));
478
479 let result = diff_trees(&old, &new);
480 assert_eq!(result.len(), 1);
481 assert!(matches!(
482 &result.operations[0],
483 DiffOp::Insert { path, index: 0, type_id: t, .. } if path.is_empty() && *t == child_type
484 ));
485 }
486
487 #[test]
488 fn test_diff_child_removed() {
489 let type_id = make_type_id::<u32>();
490 let child_type = make_type_id::<String>();
491
492 let old = DiffNode::new(type_id, 123).with_child(DiffNode::new(child_type, 456));
493 let new = DiffNode::new(type_id, 123);
494
495 let result = diff_trees(&old, &new);
496 assert_eq!(result.len(), 1);
497 assert!(matches!(
498 &result.operations[0],
499 DiffOp::Remove { path } if *path == vec![0]
500 ));
501 }
502
503 #[test]
504 fn test_diff_keyed_children_reordered() {
505 let type_id = make_type_id::<u32>();
506
507 let old = DiffNode::new(type_id, 0)
508 .with_child(DiffNode::new(type_id, 1).with_key("a"))
509 .with_child(DiffNode::new(type_id, 2).with_key("b"));
510
511 let new = DiffNode::new(type_id, 0)
512 .with_child(DiffNode::new(type_id, 2).with_key("b"))
513 .with_child(DiffNode::new(type_id, 1).with_key("a"));
514
515 let result = diff_trees(&old, &new);
516
517 let move_ops: Vec<_> = result
519 .operations
520 .iter()
521 .filter(|op| matches!(op, DiffOp::Move { .. }))
522 .collect();
523 assert!(!move_ops.is_empty());
524 }
525
526 #[test]
527 fn test_diff_keyed_child_updated() {
528 let type_id = make_type_id::<u32>();
529
530 let old = DiffNode::new(type_id, 0).with_child(DiffNode::new(type_id, 1).with_key("item"));
531 let new = DiffNode::new(type_id, 0).with_child(DiffNode::new(type_id, 2).with_key("item"));
532
533 let result = diff_trees(&old, &new);
534
535 let update_ops: Vec<_> = result
536 .operations
537 .iter()
538 .filter(|op| matches!(op, DiffOp::Update { .. }))
539 .collect();
540 assert_eq!(update_ops.len(), 1);
541 }
542
543 #[test]
544 fn test_diff_nested_changes() {
545 let type_id = make_type_id::<u32>();
546
547 let old = DiffNode::new(type_id, 0)
548 .with_child(DiffNode::new(type_id, 1).with_child(DiffNode::new(type_id, 2)));
549
550 let new = DiffNode::new(type_id, 0)
551 .with_child(DiffNode::new(type_id, 1).with_child(DiffNode::new(type_id, 3)));
552
553 let result = diff_trees(&old, &new);
554
555 let update_ops: Vec<_> = result
557 .operations
558 .iter()
559 .filter(|op| matches!(op, DiffOp::Update { path, .. } if *path == vec![0, 0]))
560 .collect();
561 assert_eq!(update_ops.len(), 1);
562 }
563
564 #[test]
565 fn test_diff_multiple_children_mixed() {
566 let type_id = make_type_id::<u32>();
567 let string_type = make_type_id::<String>();
568
569 let old = DiffNode::new(type_id, 0)
570 .with_child(DiffNode::new(type_id, 1))
571 .with_child(DiffNode::new(string_type, 2))
572 .with_child(DiffNode::new(type_id, 3));
573
574 let new = DiffNode::new(type_id, 0)
575 .with_child(DiffNode::new(type_id, 1))
576 .with_child(DiffNode::new(type_id, 4)); let result = diff_trees(&old, &new);
579
580 let remove_ops: Vec<_> = result
582 .operations
583 .iter()
584 .filter(|op| matches!(op, DiffOp::Remove { .. }))
585 .collect();
586 assert!(!remove_ops.is_empty());
587 }
588
589 #[test]
590 fn test_tree_differ_reuse() {
591 let type_id = make_type_id::<u32>();
592 let mut differ = TreeDiffer::new();
593
594 let old1 = DiffNode::new(type_id, 1);
595 let new1 = DiffNode::new(type_id, 2);
596 let result1 = differ.diff(&old1, &new1);
597
598 let old2 = DiffNode::new(type_id, 3);
599 let new2 = DiffNode::new(type_id, 3);
600 let result2 = differ.diff(&old2, &new2);
601
602 assert_eq!(result1.len(), 1);
603 assert!(result2.is_empty());
604 }
605
606 #[test]
607 fn test_diff_empty_to_tree() {
608 let type_id = make_type_id::<u32>();
609
610 let old = DiffNode::new(type_id, 0);
611 let new = DiffNode::new(type_id, 0)
612 .with_child(DiffNode::new(type_id, 1))
613 .with_child(DiffNode::new(type_id, 2));
614
615 let result = diff_trees(&old, &new);
616
617 let insert_ops: Vec<_> = result
618 .operations
619 .iter()
620 .filter(|op| matches!(op, DiffOp::Insert { .. }))
621 .collect();
622 assert_eq!(insert_ops.len(), 2);
623 }
624
625 #[test]
626 fn test_diff_tree_to_empty() {
627 let type_id = make_type_id::<u32>();
628
629 let old = DiffNode::new(type_id, 0)
630 .with_child(DiffNode::new(type_id, 1))
631 .with_child(DiffNode::new(type_id, 2));
632 let new = DiffNode::new(type_id, 0);
633
634 let result = diff_trees(&old, &new);
635
636 let remove_ops: Vec<_> = result
637 .operations
638 .iter()
639 .filter(|op| matches!(op, DiffOp::Remove { .. }))
640 .collect();
641 assert_eq!(remove_ops.len(), 2);
642 }
643
644 #[test]
645 fn test_diff_deeply_nested() {
646 let type_id = make_type_id::<u32>();
647
648 let old = DiffNode::new(type_id, 0).with_child(
649 DiffNode::new(type_id, 1)
650 .with_child(DiffNode::new(type_id, 2).with_child(DiffNode::new(type_id, 3))),
651 );
652
653 let new = DiffNode::new(type_id, 0).with_child(
654 DiffNode::new(type_id, 1)
655 .with_child(DiffNode::new(type_id, 2).with_child(DiffNode::new(type_id, 99))),
656 );
657
658 let result = diff_trees(&old, &new);
659
660 let update_ops: Vec<_> = result
662 .operations
663 .iter()
664 .filter(|op| {
665 matches!(op, DiffOp::Update { path, new_props_hash: 99 } if *path == vec![0, 0, 0])
666 })
667 .collect();
668 assert_eq!(update_ops.len(), 1);
669 }
670
671 #[test]
672 fn test_diff_op_debug() {
673 let op = DiffOp::Insert {
674 path: vec![0, 1],
675 index: 2,
676 type_id: make_type_id::<u32>(),
677 props_hash: 123,
678 };
679 let debug_str = format!("{op:?}");
680 assert!(debug_str.contains("Insert"));
681 }
682
683 #[test]
684 fn test_diff_result_push() {
685 let mut result = DiffResult::new();
686 result.push(DiffOp::Remove { path: vec![0] });
687 assert_eq!(result.len(), 1);
688 assert!(!result.is_empty());
689 }
690}