1#![no_std]
2
3extern crate alloc;
11#[cfg(feature = "std")]
12extern crate std;
13
14mod collections;
15
16use core::hash::Hash;
17
18use crate::collections::{
19 Box,
20 HashMap,
21 Vec,
22 VecDeque,
23};
24
25pub trait DiffNode: Sized {
28 type Key: Eq + Hash;
29 type Modifications;
30
31 #[cfg_attr(feature = "hotpath", hotpath::measure)]
34 fn diff(&self, previous: Option<&Self>, diff: &mut Diff<Self::Modifications>) {
35 diff_inner(self, previous, diff, false);
36 }
37
38 fn key(&self) -> &Self::Key;
39 fn path(&self) -> &[u32];
40 fn children(&self) -> &[Self];
41
42 fn compatible(&self, previous: &Self) -> bool;
44
45 fn modifications(&self, previous: &Self) -> Option<Self::Modifications>;
47
48 #[cfg(debug_assertions)]
50 fn validate_children(&self) {}
51}
52
53#[derive(Debug)]
55pub struct Diff<Modifications> {
56 pub added: Vec<Box<[u32]>>,
57 pub modified: Vec<(Box<[u32]>, Modifications)>,
58 pub removed: Vec<Box<[u32]>>,
59 pub moved: HashMap<Box<[u32]>, Vec<(u32, u32)>>,
60}
61
62impl<Modifications> Default for Diff<Modifications> {
63 fn default() -> Self {
64 Self {
65 added: Vec::new(),
66 modified: Vec::new(),
67 removed: Vec::new(),
68 moved: HashMap::default(),
69 }
70 }
71}
72
73fn diff_inner<Node: DiffNode>(
74 current: &Node,
75 previous: Option<&Node>,
76 diff: &mut Diff<Node::Modifications>,
77 parent_removed: bool,
78) {
79 let Some(previous) = previous else {
81 diff.added.push(current.path().into());
82
83 for child in current.children() {
84 diff_inner(child, None, diff, false);
85 }
86
87 return;
88 };
89
90 if !current.compatible(previous) {
92 diff.removed.push(previous.path().into());
93 diff_inner(current, None, diff, false);
94 return;
95 }
96
97 let removed_here = current.key() != previous.key() || parent_removed;
99 if removed_here {
100 diff.added.push(current.path().into());
101 diff.removed.push(previous.path().into());
102 } else {
103 if let Some(modifications) = current.modifications(previous) {
104 diff.modified.push((current.path().into(), modifications));
105 }
106
107 if let (Some((&new_index, parent)), Some(&old_index)) =
109 (current.path().split_last(), previous.path().last())
110 && new_index != old_index
111 {
112 diff.moved
113 .entry(parent.into())
114 .or_default()
115 .push((old_index, new_index));
116 }
117 }
118
119 #[cfg(debug_assertions)]
120 current.validate_children();
121
122 let current_children = current.children();
123 let previous_children = previous.children();
124
125 let mut start = 0;
127 while start < current_children.len() && start < previous_children.len() {
128 if current_children[start].key() != previous_children[start].key() {
129 break;
130 }
131
132 diff_inner(
133 ¤t_children[start],
134 Some(&previous_children[start]),
135 diff,
136 removed_here,
137 );
138 start += 1;
139 }
140
141 if start < current_children.len() || start < previous_children.len() {
142 let mut previous_keys = HashMap::<&Node::Key, VecDeque<usize>>::default();
144 for (index, child) in previous_children.iter().enumerate().skip(start) {
145 previous_keys
146 .entry(child.key())
147 .or_default()
148 .push_back(index);
149 }
150
151 for child in ¤t_children[start..] {
153 if let Some(old_index) = previous_keys
154 .get_mut(child.key())
155 .and_then(VecDeque::pop_front)
156 {
157 diff_inner(
158 child,
159 Some(&previous_children[old_index]),
160 diff,
161 removed_here,
162 );
163 } else {
164 diff_inner(child, None, diff, false);
165 }
166 }
167
168 for indices in previous_keys.values() {
170 for index in indices {
171 diff.removed.push(previous_children[*index].path().into());
172 }
173 }
174 }
175}