use crate::app::event::{Event, UserAction};
use crate::app::state::{Consumed, SessionState};
use crate::app::{AeonError, DynError};
use crate::debug;
use std::collections::VecDeque;
pub const DEFAULT_EVENT_LIMIT: usize = 1 << 16; pub const DEFAULT_PAYLOAD_LIMIT: usize = 1 << 28; #[derive(Clone, Eq, PartialEq)]
pub struct UndoStackEntry {
perform_action: UserAction,
reverse_action: UserAction,
}
impl UndoStackEntry {
pub fn payload_size(&self) -> usize {
self.perform_action.byte_size() + self.reverse_action.byte_size()
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct UndoStack {
event_limit: usize,
payload_limit: usize,
current_payload_size: usize,
undo_stack: VecDeque<UndoStackEntry>,
redo_stack: VecDeque<UndoStackEntry>,
}
impl UndoStack {
pub fn new(event_limit: usize, payload_limit: usize) -> UndoStack {
UndoStack {
event_limit,
payload_limit,
current_payload_size: 0,
undo_stack: VecDeque::with_capacity(event_limit),
redo_stack: VecDeque::with_capacity(event_limit),
}
}
pub fn can_undo(&self) -> bool {
!self.undo_stack.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.redo_stack.is_empty()
}
pub fn clear(&mut self) {
self.current_payload_size = 0;
self.undo_stack.clear();
self.redo_stack.clear();
}
pub fn undo_len(&self) -> usize {
self.undo_stack.len()
}
pub fn redo_len(&self) -> usize {
self.redo_stack.len()
}
#[must_use]
pub fn do_action(&mut self, perform: UserAction, reverse: UserAction) -> bool {
self.redo_stack.clear();
while self.undo_stack.len() >= self.event_limit {
let Some(event) = self.drop_undo_event() else {
break; };
debug!(
"Event count exceeded. Dropping action with {} events.",
event.perform_action.events.len(),
);
}
let additional_payload = perform.byte_size() + reverse.byte_size();
while self.current_payload_size + additional_payload >= self.payload_limit {
let Some(event) = self.drop_undo_event() else {
break; };
debug!(
"Payload size exceeded. Dropping action with {} events.",
event.perform_action.events.len()
);
}
if self.undo_stack.len() >= self.event_limit {
assert!(self.undo_stack.is_empty());
debug!("Cannot save new undo item. Event limit is likely zero.");
return false;
}
if self.current_payload_size + additional_payload >= self.payload_limit {
assert_eq!(self.current_payload_size, 0);
debug!(
"Cannot save new undo item. Event payload too large: {} > {}.",
additional_payload, self.payload_limit
);
return false;
}
self.undo_stack.push_back(UndoStackEntry {
perform_action: perform,
reverse_action: reverse,
});
self.current_payload_size += additional_payload;
true
}
#[must_use]
pub fn undo_action(&mut self) -> Option<UserAction> {
let entry = self.undo_stack.pop_back()?;
let result = Some(entry.reverse_action.clone());
self.current_payload_size -= entry.payload_size();
self.redo_stack.push_back(entry);
result
}
#[must_use]
pub fn redo_action(&mut self) -> Option<UserAction> {
let entry = self.redo_stack.pop_back()?;
let result = Some(entry.perform_action.clone());
self.current_payload_size += entry.payload_size();
self.undo_stack.push_back(entry);
result
}
fn drop_undo_event(&mut self) -> Option<UndoStackEntry> {
let entry = self.undo_stack.pop_front()?;
self.current_payload_size -= entry.payload_size();
assert!(self.current_payload_size < self.payload_limit);
Some(entry)
}
}
impl SessionState for UndoStack {
fn perform_event(&mut self, _event: &Event, _at_path: &[&str]) -> Result<Consumed, DynError> {
AeonError::throw("`UndoStack` cannot consume events.")
}
fn refresh(&self, full_path: &[String], at_path: &[&str]) -> Result<Event, DynError> {
match at_path {
["can_undo"] => Ok(Event {
path: full_path.to_vec(),
payload: serde_json::to_string(&self.can_undo()).ok(),
}),
["can_redo"] => Ok(Event {
path: full_path.to_vec(),
payload: serde_json::to_string(&self.can_redo()).ok(),
}),
_ => AeonError::throw(format!("`UndoStack` has no path `{:?}`.", at_path)),
}
}
}
impl Default for UndoStack {
fn default() -> Self {
UndoStack::new(DEFAULT_EVENT_LIMIT, DEFAULT_PAYLOAD_LIMIT)
}
}
#[cfg(test)]
mod tests {
use crate::app::event::{Event, UserAction};
use crate::app::state::_undo_stack::UndoStack;
#[test]
pub fn test_normal_behaviour() {
let mut stack = UndoStack::default();
let e1: UserAction = Event::build(&[], Some("payload 1")).into();
let e2: UserAction = Event::build(&[], Some("payload 2")).into();
assert!(stack.do_action(e1.clone(), e2.clone()));
assert!(stack.do_action(e2.clone(), e1.clone()));
assert_eq!(2, stack.undo_len());
assert_eq!(Some(e1.clone()), stack.undo_action());
assert_eq!(1, stack.undo_len());
assert_eq!(1, stack.redo_len());
assert_eq!(Some(e2.clone()), stack.undo_action());
assert_eq!(None, stack.undo_action());
assert_eq!(Some(e1.clone()), stack.redo_action());
assert_eq!(Some(e2.clone()), stack.redo_action());
assert_eq!(None, stack.redo_action());
assert_eq!(2, stack.undo_len());
assert_eq!(Some(e1.clone()), stack.undo_action());
assert_eq!(1, stack.redo_len());
assert!(stack.do_action(e1.clone(), e2.clone()));
assert_eq!(0, stack.redo_len());
assert_eq!(2, stack.undo_len());
assert_eq!(Some(e2.clone()), stack.undo_action());
}
#[test]
pub fn test_basic_limits() {
let e1: UserAction = Event::build(&[], None).into();
let e2: UserAction = Event::build(&[], None).into();
let e3: UserAction = Event::build(&["path"], Some("payload 3")).into();
let mut stack = UndoStack::new(4, 2 * e3.byte_size() + 1);
assert!(stack.do_action(e2.clone(), e1.clone()));
assert!(stack.do_action(e1.clone(), e2.clone()));
assert!(stack.do_action(e1.clone(), e2.clone()));
assert_eq!(3, stack.undo_len());
assert!(stack.do_action(e1.clone(), e2.clone()));
assert_eq!(4, stack.undo_len());
assert!(stack.do_action(e1.clone(), e2.clone()));
assert_eq!(4, stack.undo_len());
assert_eq!(Some(e2.clone()), stack.undo_action());
assert_eq!(Some(e2.clone()), stack.undo_action());
assert_eq!(Some(e2.clone()), stack.undo_action());
assert_eq!(Some(e2.clone()), stack.undo_action());
assert_eq!(None, stack.undo_action());
assert!(stack.do_action(e2.clone(), e1.clone()));
assert!(stack.do_action(e3.clone(), e1.clone()));
assert!(stack.do_action(e2.clone(), e3.clone()));
assert_eq!(3, stack.undo_len());
assert!(stack.do_action(e2.clone(), e3.clone()));
assert_eq!(2, stack.undo_len());
assert!(stack.do_action(e3.clone(), e3.clone()));
assert_eq!(1, stack.undo_len());
assert_eq!(Some(e3.clone()), stack.undo_action());
assert_eq!(Some(e3.clone()), stack.redo_action());
}
#[test]
pub fn test_extreme_limits() {
let e1: UserAction = Event::build(&[], None).into();
let e2: UserAction = Event::build(&[], None).into();
let e3: UserAction = Event::build(&["path"], Some("payload 3")).into();
let mut stack = UndoStack::new(0, 1024);
assert!(!stack.do_action(e1.clone(), e2.clone()));
let mut stack = UndoStack::new(8, 8);
assert!(stack.do_action(e1.clone(), e2.clone()));
assert!(!stack.do_action(e1.clone(), e3.clone()));
assert_eq!(0, stack.undo_len());
}
}