#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Replay {
#[default]
Forward,
Undo,
Redo,
}
use super::geom::Cell;
use super::identity::Vid;
use rustc_hash::FxHashMap;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
struct IdHistory {
past: Vec<Vid>,
future: Vec<Vid>,
}
#[derive(Clone, Debug, Default)]
pub struct IdJournal {
mode: Replay,
hist: FxHashMap<Cell, IdHistory>,
}
impl IdJournal {
pub fn mode(&self) -> Replay {
self.mode
}
pub fn set_mode(&mut self, mode: Replay) {
self.mode = mode;
}
pub fn retired(&mut self, cell: Cell, id: Vid) {
let h = self.hist.entry(cell).or_default();
match self.mode {
Replay::Forward | Replay::Redo => h.past.push(id),
Replay::Undo => h.future.push(id),
}
}
pub fn created(&mut self, cell: Cell) -> Option<Vid> {
if self.hist.is_empty() {
return None;
}
let h = self.hist.get_mut(&cell)?;
let id = match self.mode {
Replay::Forward => {
h.future.clear();
None
}
Replay::Undo => h.past.pop(),
Replay::Redo => h.future.pop(),
};
if h.past.is_empty() && h.future.is_empty() {
self.hist.remove(&cell);
}
id
}
pub fn forget(&mut self, cell: Cell, id: Vid) {
let Some(h) = self.hist.get_mut(&cell) else {
return;
};
let stack = match self.mode {
Replay::Undo => &mut h.past,
Replay::Forward | Replay::Redo => &mut h.future,
};
if stack.last() == Some(&id) {
stack.pop();
}
if h.past.is_empty() && h.future.is_empty() {
self.hist.remove(&cell);
}
}
pub fn pending(&self) -> usize {
self.hist
.values()
.map(|h| h.past.len() + h.future.len())
.sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn undo_redo_chain_restores_ids_per_timeline() {
let c = (0, 4, 2);
let mut j = IdJournal::default();
j.retired(c, 1);
assert_eq!(j.created(c), None);
j.set_mode(Replay::Undo);
j.retired(c, 2);
assert_eq!(j.created(c), Some(1));
j.set_mode(Replay::Redo);
j.retired(c, 1);
assert_eq!(j.created(c), Some(2));
j.set_mode(Replay::Undo);
j.retired(c, 2);
j.set_mode(Replay::Forward);
assert_eq!(j.created(c), None);
j.set_mode(Replay::Redo);
assert_eq!(j.created(c), None);
assert_eq!(j.pending(), 1);
}
}