#![no_std]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
mod collections;
use core::hash::Hash;
use crate::collections::{
Box,
HashMap,
Vec,
VecDeque,
};
pub trait DiffNode: Sized {
type Key: Eq + Hash;
type Modifications;
#[cfg_attr(feature = "hotpath", hotpath::measure)]
fn diff(&self, previous: Option<&Self>, diff: &mut Diff<Self::Modifications>) {
diff_inner(self, previous, diff, false);
}
fn key(&self) -> &Self::Key;
fn path(&self) -> &[u32];
fn children(&self) -> &[Self];
fn compatible(&self, previous: &Self) -> bool;
fn modifications(&self, previous: &Self) -> Option<Self::Modifications>;
#[cfg(debug_assertions)]
fn validate_children(&self) {}
}
#[derive(Debug)]
pub struct Diff<Modifications> {
pub added: Vec<Box<[u32]>>,
pub modified: Vec<(Box<[u32]>, Modifications)>,
pub removed: Vec<Box<[u32]>>,
pub moved: HashMap<Box<[u32]>, Vec<(u32, u32)>>,
}
impl<Modifications> Default for Diff<Modifications> {
fn default() -> Self {
Self {
added: Vec::new(),
modified: Vec::new(),
removed: Vec::new(),
moved: HashMap::default(),
}
}
}
fn diff_inner<Node: DiffNode>(
current: &Node,
previous: Option<&Node>,
diff: &mut Diff<Node::Modifications>,
parent_removed: bool,
) {
let Some(previous) = previous else {
diff.added.push(current.path().into());
for child in current.children() {
diff_inner(child, None, diff, false);
}
return;
};
if !current.compatible(previous) {
diff.removed.push(previous.path().into());
diff_inner(current, None, diff, false);
return;
}
let removed_here = current.key() != previous.key() || parent_removed;
if removed_here {
diff.added.push(current.path().into());
diff.removed.push(previous.path().into());
} else {
if let Some(modifications) = current.modifications(previous) {
diff.modified.push((current.path().into(), modifications));
}
if let (Some((&new_index, parent)), Some(&old_index)) =
(current.path().split_last(), previous.path().last())
&& new_index != old_index
{
diff.moved
.entry(parent.into())
.or_default()
.push((old_index, new_index));
}
}
#[cfg(debug_assertions)]
current.validate_children();
let current_children = current.children();
let previous_children = previous.children();
let mut start = 0;
while start < current_children.len() && start < previous_children.len() {
if current_children[start].key() != previous_children[start].key() {
break;
}
diff_inner(
¤t_children[start],
Some(&previous_children[start]),
diff,
removed_here,
);
start += 1;
}
if start < current_children.len() || start < previous_children.len() {
let mut previous_keys = HashMap::<&Node::Key, VecDeque<usize>>::default();
for (index, child) in previous_children.iter().enumerate().skip(start) {
previous_keys
.entry(child.key())
.or_default()
.push_back(index);
}
for child in ¤t_children[start..] {
if let Some(old_index) = previous_keys
.get_mut(child.key())
.and_then(VecDeque::pop_front)
{
diff_inner(
child,
Some(&previous_children[old_index]),
diff,
removed_here,
);
} else {
diff_inner(child, None, diff, false);
}
}
for indices in previous_keys.values() {
for index in indices {
diff.removed.push(previous_children[*index].path().into());
}
}
}
}