use gantz_ca::CommitAddr;
use std::collections::HashMap;
pub fn undo(
registry: &gantz_ca::Registry,
redo_stacks: &mut HashMap<gantz_ca::Head, Vec<CommitAddr>>,
head: &gantz_ca::Head,
) -> Option<CommitAddr> {
let commit_ca = registry.head_commit_ca(head)?;
let parent = registry.commits().get(&commit_ca)?.parent?;
redo_stacks.entry(head.clone()).or_default().push(commit_ca);
Some(parent)
}
pub fn redo(
redo_stacks: &mut HashMap<gantz_ca::Head, Vec<CommitAddr>>,
head: &gantz_ca::Head,
) -> Option<CommitAddr> {
redo_stacks.get_mut(head)?.pop()
}
pub(crate) fn revert_commit(
registry: &mut gantz_ca::Registry,
timestamp: gantz_ca::Timestamp,
tip: CommitAddr,
target: CommitAddr,
) -> Option<CommitAddr> {
registry.commits().get(&tip)?;
let target_graph = registry.commits().get(&target)?.graph;
Some(
registry.commit_graph(timestamp, Some(tip), target_graph, || {
unreachable!("revert reuses an existing graph")
}),
)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct RevertCursor {
pub minted: CommitAddr,
pub target: CommitAddr,
}
pub fn session_undo(
registry: &mut gantz_ca::Registry,
redo_stacks: &mut HashMap<gantz_ca::Head, Vec<CommitAddr>>,
undo_cursors: &mut HashMap<gantz_ca::Head, RevertCursor>,
timestamp: gantz_ca::Timestamp,
head: &gantz_ca::Head,
live_camera: Option<crate::Camera>,
) -> Option<CommitAddr> {
let tip = registry.head_commit_ca(head)?;
let base = undo_cursors
.get(head)
.filter(|c| c.minted == tip)
.map(|c| c.target)
.unwrap_or(tip);
let target = registry.commits().get(&base)?.parent?;
let minted = revert_commit(registry, timestamp, tip, target)?;
copy_view(registry, target, minted, live_camera);
redo_stacks.entry(head.clone()).or_default().push(base);
undo_cursors.insert(head.clone(), RevertCursor { minted, target });
Some(minted)
}
pub fn session_redo(
registry: &mut gantz_ca::Registry,
redo_stacks: &mut HashMap<gantz_ca::Head, Vec<CommitAddr>>,
undo_cursors: &mut HashMap<gantz_ca::Head, RevertCursor>,
timestamp: gantz_ca::Timestamp,
head: &gantz_ca::Head,
live_camera: Option<crate::Camera>,
) -> Option<CommitAddr> {
let tip = registry.head_commit_ca(head)?;
let target = redo_stacks.get_mut(head)?.pop()?;
let minted = revert_commit(registry, timestamp, tip, target)?;
copy_view(registry, target, minted, live_camera);
undo_cursors.insert(head.clone(), RevertCursor { minted, target });
Some(minted)
}
fn copy_view(
registry: &mut gantz_ca::Registry,
src: CommitAddr,
dst: CommitAddr,
live_camera: Option<crate::Camera>,
) {
let Some(mut view) = crate::section::view(registry, &src) else {
return;
};
if view.layout.is_empty() {
return;
}
if let Some(camera) = live_camera {
view.camera = camera;
}
crate::section::set_view(registry, dst, &view);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ops::node_id;
use crate::ops::test_util::*;
#[test]
fn revert_commit_mints_previous_graph_forward() {
let secs = |s| std::time::Duration::from_secs(s);
let mut reg = gantz_ca::Registry::default();
let g1 = test_graph(&[1]);
let g1_ca = gantz_ca::graph_addr(&g1);
let c1 = reg.commit_graph(secs(1), None, g1_ca, || g1);
let g2 = test_graph(&[1, 2]);
let g2_ca = gantz_ca::graph_addr(&g2);
let c2 = reg.commit_graph(secs(2), Some(c1), g2_ca, || g2);
let c3 = reg.commit_graph(secs(3), Some(c2), g2_ca, || unreachable!("graph exists"));
reg.set_head("alpha".parse().unwrap(), c3);
let reverted = revert_commit(&mut reg, secs(4), c3, c1).unwrap();
let commit = ®.commits()[&reverted];
assert_eq!(commit.parent, Some(c3));
assert_eq!(commit.graph, g1_ca);
assert_eq!(reg.head(&"alpha".parse().unwrap()), Some(c3));
let again = revert_commit(&mut reg, secs(5), c3, c2).unwrap();
assert_eq!(reg.commits()[&again].graph, g2_ca);
assert_eq!(reg.commits()[&again].parent, Some(c3));
}
#[test]
fn session_undo_redo_stepping() {
let secs = |s| std::time::Duration::from_secs(s);
let mut reg = gantz_ca::Registry::default();
let g1 = test_graph(&[1]);
let g1_ca = gantz_ca::graph_addr(&g1);
let e1 = reg.commit_graph(secs(1), None, g1_ca, || g1);
let l = reg.commit_graph(secs(2), Some(e1), g1_ca, || unreachable!("graph exists"));
let g2 = test_graph(&[1, 2]);
let g2_ca = gantz_ca::graph_addr(&g2);
let e2 = reg.commit_graph(secs(3), Some(l), g2_ca, || g2);
reg.set_head("alpha".parse().unwrap(), e2);
let head = gantz_ca::Head::Branch("alpha".parse().unwrap());
let view = |x: f32| {
let mut v = crate::SceneView::default();
v.layout.insert(node_id(0), egui::pos2(x, 0.0));
v
};
crate::section::set_view(&mut reg, e1, &view(1.0));
crate::section::set_view(&mut reg, l, &view(2.0));
crate::section::set_view(&mut reg, e2, &view(3.0));
let stored =
|reg: &gantz_ca::Registry, ca: CommitAddr| crate::section::view(reg, &ca).unwrap();
let mut redo = HashMap::new();
let mut cursors = HashMap::new();
let cam = crate::Camera {
center: egui::pos2(9.0, 9.0),
zoom: 2.0,
};
let navigate = |reg: &mut gantz_ca::Registry, minted| {
reg.set_head("alpha".parse().unwrap(), minted);
};
let r1 = session_undo(
&mut reg,
&mut redo,
&mut cursors,
secs(10),
&head,
Some(cam),
)
.unwrap();
navigate(&mut reg, r1);
assert_eq!(reg.commits()[&r1].graph, g1_ca);
assert_eq!(reg.commits()[&r1].parent, Some(e2));
assert_eq!(stored(®, r1).layout, stored(®, l).layout);
assert_eq!(stored(®, r1).camera, cam);
let r2 = session_undo(
&mut reg,
&mut redo,
&mut cursors,
secs(11),
&head,
Some(cam),
)
.unwrap();
navigate(&mut reg, r2);
assert_eq!(reg.commits()[&r2].graph, g1_ca);
assert_eq!(reg.commits()[&r2].parent, Some(r1));
assert_eq!(stored(®, r2).layout, stored(®, e1).layout);
assert_eq!(
session_undo(
&mut reg,
&mut redo,
&mut cursors,
secs(12),
&head,
Some(cam)
),
None,
);
let r3 = session_redo(
&mut reg,
&mut redo,
&mut cursors,
secs(13),
&head,
Some(cam),
)
.unwrap();
navigate(&mut reg, r3);
assert_eq!(reg.commits()[&r3].graph, g1_ca);
assert_eq!(reg.commits()[&r3].parent, Some(r2));
assert_eq!(stored(®, r3).layout, stored(®, l).layout);
let r4 = session_undo(
&mut reg,
&mut redo,
&mut cursors,
secs(14),
&head,
Some(cam),
)
.unwrap();
navigate(&mut reg, r4);
assert_eq!(stored(®, r4).layout, stored(®, e1).layout);
let r5 = session_redo(
&mut reg,
&mut redo,
&mut cursors,
secs(15),
&head,
Some(cam),
)
.unwrap();
navigate(&mut reg, r5);
assert_eq!(stored(®, r5).layout, stored(®, l).layout);
let r6 = session_redo(
&mut reg,
&mut redo,
&mut cursors,
secs(16),
&head,
Some(cam),
)
.unwrap();
navigate(&mut reg, r6);
assert_eq!(reg.commits()[&r6].graph, g2_ca);
assert_eq!(stored(®, r6).layout, stored(®, e2).layout);
assert!(redo.get(&head).is_none_or(|s| s.is_empty()));
}
#[test]
fn session_undo_cursor_invalidated_by_edit() {
let secs = |s| std::time::Duration::from_secs(s);
let mut reg = gantz_ca::Registry::default();
let g1 = test_graph(&[1]);
let e1 = reg.commit_graph(
secs(1),
None,
gantz_ca::graph_addr(&test_graph(&[1])),
|| g1,
);
let g2 = test_graph(&[1, 2]);
let g2_ca = gantz_ca::graph_addr(&g2);
let e2 = reg.commit_graph(secs(2), Some(e1), g2_ca, || g2);
reg.set_head("alpha".parse().unwrap(), e2);
let head = gantz_ca::Head::Branch("alpha".parse().unwrap());
let mut redo = HashMap::new();
let mut cursors = HashMap::new();
let r1 = session_undo(&mut reg, &mut redo, &mut cursors, secs(10), &head, None).unwrap();
reg.set_head("alpha".parse().unwrap(), r1);
let g3 = test_graph(&[1, 3]);
let g3_ca = gantz_ca::graph_addr(&g3);
let e3 = reg.commit_graph(secs(11), Some(r1), g3_ca, || g3);
reg.set_head("alpha".parse().unwrap(), e3);
redo.remove(&head);
let r2 = session_undo(&mut reg, &mut redo, &mut cursors, secs(12), &head, None).unwrap();
assert_eq!(reg.commits()[&r2].parent, Some(e3));
assert_eq!(reg.commits()[&r2].graph, reg.commits()[&r1].graph);
}
}