use std::collections::VecDeque;
pub trait Command: std::any::Any + Send + Sync {
fn execute(&mut self) -> Result<(), String>;
fn undo(&mut self) -> Result<(), String>;
fn description(&self) -> String;
fn try_merge(&mut self, _other: &dyn Command) -> bool {
false
}
}
#[derive(Clone, Debug)]
pub struct TransformCommand {
pub object_id: String,
pub old_transform: [f32; 9], pub new_transform: [f32; 9],
}
impl TransformCommand {
pub fn new(object_id: String, old_transform: [f32; 9], new_transform: [f32; 9]) -> Self {
Self {
object_id,
old_transform,
new_transform,
}
}
}
impl Command for TransformCommand {
fn execute(&mut self) -> Result<(), String> {
Ok(())
}
fn undo(&mut self) -> Result<(), String> {
Ok(())
}
fn description(&self) -> String {
format!("Transform {}", self.object_id)
}
fn try_merge(&mut self, other: &dyn Command) -> bool {
if let Some(other_transform) =
(other as &dyn std::any::Any).downcast_ref::<TransformCommand>()
{
if self.object_id == other_transform.object_id {
self.new_transform = other_transform.new_transform;
return true;
}
}
false
}
}
#[derive(Clone, Debug)]
pub struct FileEditCommand {
pub file_path: String,
pub old_content: String,
pub new_content: String,
}
impl FileEditCommand {
pub fn new(file_path: String, old_content: String, new_content: String) -> Self {
Self {
file_path,
old_content,
new_content,
}
}
}
impl Command for FileEditCommand {
fn execute(&mut self) -> Result<(), String> {
Ok(())
}
fn undo(&mut self) -> Result<(), String> {
Ok(())
}
fn description(&self) -> String {
format!("Edit {}", self.file_path)
}
}
#[derive(Clone, Debug)]
pub struct CreateObjectCommand {
pub object_id: String,
pub object_data: String, }
impl CreateObjectCommand {
pub fn new(object_id: String, object_data: String) -> Self {
Self {
object_id,
object_data,
}
}
}
impl Command for CreateObjectCommand {
fn execute(&mut self) -> Result<(), String> {
Ok(())
}
fn undo(&mut self) -> Result<(), String> {
Ok(())
}
fn description(&self) -> String {
format!("Create {}", self.object_id)
}
}
#[derive(Clone, Debug)]
pub struct DeleteObjectCommand {
pub object_id: String,
pub object_data: String, }
impl DeleteObjectCommand {
pub fn new(object_id: String, object_data: String) -> Self {
Self {
object_id,
object_data,
}
}
}
impl Command for DeleteObjectCommand {
fn execute(&mut self) -> Result<(), String> {
Ok(())
}
fn undo(&mut self) -> Result<(), String> {
Ok(())
}
fn description(&self) -> String {
format!("Delete {}", self.object_id)
}
}
#[derive(Clone, Debug)]
pub struct PropertyChangeCommand {
pub object_id: String,
pub property_name: String,
pub old_value: String,
pub new_value: String,
}
impl PropertyChangeCommand {
pub fn new(
object_id: String,
property_name: String,
old_value: String,
new_value: String,
) -> Self {
Self {
object_id,
property_name,
old_value,
new_value,
}
}
}
impl Command for PropertyChangeCommand {
fn execute(&mut self) -> Result<(), String> {
Ok(())
}
fn undo(&mut self) -> Result<(), String> {
Ok(())
}
fn description(&self) -> String {
format!("Change {}.{}", self.object_id, self.property_name)
}
}
pub struct UndoRedoManager {
undo_stack: VecDeque<Box<dyn Command>>,
redo_stack: VecDeque<Box<dyn Command>>,
max_history: usize,
merge_time_window_ms: u128, last_command_time: Option<std::time::Instant>,
}
impl UndoRedoManager {
pub fn new() -> Self {
Self {
undo_stack: VecDeque::new(),
redo_stack: VecDeque::new(),
max_history: 100,
merge_time_window_ms: 500, last_command_time: None,
}
}
pub fn with_max_history(max_history: usize) -> Self {
Self {
max_history,
..Self::new()
}
}
pub fn execute(&mut self, mut command: Box<dyn Command>) -> Result<(), String> {
command.execute()?;
let now = std::time::Instant::now();
let should_merge = if let Some(last_time) = self.last_command_time {
now.duration_since(last_time).as_millis() < self.merge_time_window_ms
} else {
false
};
if should_merge {
if let Some(last_cmd) = self.undo_stack.back_mut() {
if last_cmd.try_merge(&*command) {
self.last_command_time = Some(now);
return Ok(());
}
}
}
self.undo_stack.push_back(command);
self.last_command_time = Some(now);
self.redo_stack.clear();
while self.undo_stack.len() > self.max_history {
self.undo_stack.pop_front();
}
Ok(())
}
pub fn undo(&mut self) -> Result<(), String> {
if let Some(mut command) = self.undo_stack.pop_back() {
command.undo()?;
self.redo_stack.push_back(command);
self.last_command_time = None; Ok(())
} else {
Err("Nothing to undo".to_string())
}
}
pub fn redo(&mut self) -> Result<(), String> {
if let Some(mut command) = self.redo_stack.pop_back() {
command.execute()?;
self.undo_stack.push_back(command);
self.last_command_time = None; Ok(())
} else {
Err("Nothing to redo".to_string())
}
}
pub fn can_undo(&self) -> bool {
!self.undo_stack.is_empty()
}
pub fn can_redo(&self) -> bool {
!self.redo_stack.is_empty()
}
pub fn get_undo_description(&self) -> Option<String> {
self.undo_stack.back().map(|cmd| cmd.description())
}
pub fn get_redo_description(&self) -> Option<String> {
self.redo_stack.back().map(|cmd| cmd.description())
}
pub fn undo_count(&self) -> usize {
self.undo_stack.len()
}
pub fn redo_count(&self) -> usize {
self.redo_stack.len()
}
pub fn clear(&mut self) {
self.undo_stack.clear();
self.redo_stack.clear();
self.last_command_time = None;
}
pub fn get_undo_history(&self) -> Vec<String> {
self.undo_stack
.iter()
.map(|cmd| cmd.description())
.collect()
}
pub fn get_redo_history(&self) -> Vec<String> {
self.redo_stack
.iter()
.map(|cmd| cmd.description())
.collect()
}
pub fn set_max_history(&mut self, max: usize) {
self.max_history = max;
while self.undo_stack.len() > self.max_history {
self.undo_stack.pop_front();
}
}
pub fn set_merge_time_window(&mut self, ms: u128) {
self.merge_time_window_ms = ms;
}
}
impl Default for UndoRedoManager {
fn default() -> Self {
Self::new()
}
}
pub struct CommandBuilder;
impl CommandBuilder {
pub fn transform(object_id: &str) -> TransformCommandBuilder {
TransformCommandBuilder {
object_id: object_id.to_string(),
old_transform: [0.0; 9],
new_transform: [0.0; 9],
}
}
pub fn file_edit(file_path: &str) -> FileEditCommandBuilder {
FileEditCommandBuilder {
file_path: file_path.to_string(),
old_content: String::new(),
new_content: String::new(),
}
}
pub fn create_object(object_id: &str, object_data: String) -> Box<dyn Command> {
Box::new(CreateObjectCommand::new(object_id.to_string(), object_data))
}
pub fn delete_object(object_id: &str, object_data: String) -> Box<dyn Command> {
Box::new(DeleteObjectCommand::new(object_id.to_string(), object_data))
}
pub fn property_change(
object_id: &str,
property_name: &str,
old_value: String,
new_value: String,
) -> Box<dyn Command> {
Box::new(PropertyChangeCommand::new(
object_id.to_string(),
property_name.to_string(),
old_value,
new_value,
))
}
}
pub struct TransformCommandBuilder {
object_id: String,
old_transform: [f32; 9],
new_transform: [f32; 9],
}
impl TransformCommandBuilder {
pub fn old_position(mut self, x: f32, y: f32, z: f32) -> Self {
self.old_transform[0] = x;
self.old_transform[1] = y;
self.old_transform[2] = z;
self
}
pub fn new_position(mut self, x: f32, y: f32, z: f32) -> Self {
self.new_transform[0] = x;
self.new_transform[1] = y;
self.new_transform[2] = z;
self
}
pub fn old_rotation(mut self, x: f32, y: f32, z: f32) -> Self {
self.old_transform[3] = x;
self.old_transform[4] = y;
self.old_transform[5] = z;
self
}
pub fn new_rotation(mut self, x: f32, y: f32, z: f32) -> Self {
self.new_transform[3] = x;
self.new_transform[4] = y;
self.new_transform[5] = z;
self
}
pub fn old_scale(mut self, x: f32, y: f32, z: f32) -> Self {
self.old_transform[6] = x;
self.old_transform[7] = y;
self.old_transform[8] = z;
self
}
pub fn new_scale(mut self, x: f32, y: f32, z: f32) -> Self {
self.new_transform[6] = x;
self.new_transform[7] = y;
self.new_transform[8] = z;
self
}
pub fn build(self) -> Box<dyn Command> {
Box::new(TransformCommand::new(
self.object_id,
self.old_transform,
self.new_transform,
))
}
}
pub struct FileEditCommandBuilder {
file_path: String,
old_content: String,
new_content: String,
}
impl FileEditCommandBuilder {
pub fn old_content(mut self, content: String) -> Self {
self.old_content = content;
self
}
pub fn new_content(mut self, content: String) -> Self {
self.new_content = content;
self
}
pub fn build(self) -> Box<dyn Command> {
Box::new(FileEditCommand::new(
self.file_path,
self.old_content,
self.new_content,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_undo_redo_basic() {
let mut manager = UndoRedoManager::new();
let cmd = Box::new(TransformCommand::new(
"test".to_string(),
[0.0; 9],
[1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0],
));
manager.execute(cmd).unwrap();
assert!(manager.can_undo());
assert!(!manager.can_redo());
manager.undo().unwrap();
assert!(!manager.can_undo());
assert!(manager.can_redo());
manager.redo().unwrap();
assert!(manager.can_undo());
assert!(!manager.can_redo());
}
#[test]
fn test_command_builder() {
let cmd = CommandBuilder::transform("Player")
.old_position(0.0, 0.0, 0.0)
.new_position(1.0, 2.0, 3.0)
.build();
assert_eq!(cmd.description(), "Transform Player");
}
#[test]
fn test_history_limit() {
let mut manager = UndoRedoManager::with_max_history(3);
for i in 0..5 {
let cmd = Box::new(TransformCommand::new(
format!("obj{}", i),
[0.0; 9],
[1.0; 9],
));
manager.execute(cmd).unwrap();
}
assert_eq!(manager.undo_count(), 3);
}
}