1#![allow(dead_code)]
5
6use std::collections::HashMap;
7
8#[derive(Debug, Clone)]
12pub struct CommandResult {
13 pub success: bool,
14 pub message: String,
15}
16
17#[derive(Debug, Clone)]
19pub struct CommandState {
20 pub params: HashMap<String, f64>,
21 pub flags: HashMap<String, bool>,
22 pub history: Vec<String>,
24}
25
26pub trait Command: std::fmt::Debug {
28 fn execute(&self, state: &mut CommandState) -> CommandResult;
29 fn undo(&self, state: &mut CommandState) -> CommandResult;
30 fn description(&self) -> &str;
31}
32
33pub struct CommandBus {
35 pub undo_stack: Vec<Box<dyn Command>>,
36 pub redo_stack: Vec<Box<dyn Command>>,
37 pub max_history: usize,
38 pub state: CommandState,
39}
40
41#[derive(Debug)]
45pub struct SetParamCommand {
46 pub key: String,
47 pub value: f64,
48 pub old_value: f64,
49}
50
51impl Command for SetParamCommand {
52 fn execute(&self, state: &mut CommandState) -> CommandResult {
53 state.params.insert(self.key.clone(), self.value);
54 CommandResult {
55 success: true,
56 message: format!("set {} = {}", self.key, self.value),
57 }
58 }
59
60 fn undo(&self, state: &mut CommandState) -> CommandResult {
61 state.params.insert(self.key.clone(), self.old_value);
62 CommandResult {
63 success: true,
64 message: format!("undo set {} -> {}", self.key, self.old_value),
65 }
66 }
67
68 fn description(&self) -> &str {
69 "SetParamCommand"
70 }
71}
72
73#[derive(Debug)]
75pub struct SetFlagCommand {
76 pub key: String,
77 pub value: bool,
78 pub old_value: bool,
79}
80
81impl Command for SetFlagCommand {
82 fn execute(&self, state: &mut CommandState) -> CommandResult {
83 state.flags.insert(self.key.clone(), self.value);
84 CommandResult {
85 success: true,
86 message: format!("set flag {} = {}", self.key, self.value),
87 }
88 }
89
90 fn undo(&self, state: &mut CommandState) -> CommandResult {
91 state.flags.insert(self.key.clone(), self.old_value);
92 CommandResult {
93 success: true,
94 message: format!("undo flag {} -> {}", self.key, self.old_value),
95 }
96 }
97
98 fn description(&self) -> &str {
99 "SetFlagCommand"
100 }
101}
102
103#[derive(Debug)]
105pub struct BatchCommand {
106 pub commands: Vec<Box<dyn Command>>,
107 pub name: String,
108}
109
110impl Command for BatchCommand {
111 fn execute(&self, state: &mut CommandState) -> CommandResult {
112 let mut all_ok = true;
113 let mut msgs = Vec::new();
114 for cmd in &self.commands {
115 let r = cmd.execute(state);
116 if !r.success {
117 all_ok = false;
118 }
119 msgs.push(r.message);
120 }
121 CommandResult {
122 success: all_ok,
123 message: msgs.join("; "),
124 }
125 }
126
127 fn undo(&self, state: &mut CommandState) -> CommandResult {
128 let mut all_ok = true;
129 let mut msgs = Vec::new();
130 for cmd in self.commands.iter().rev() {
131 let r = cmd.undo(state);
132 if !r.success {
133 all_ok = false;
134 }
135 msgs.push(r.message);
136 }
137 CommandResult {
138 success: all_ok,
139 message: msgs.join("; "),
140 }
141 }
142
143 fn description(&self) -> &str {
144 &self.name
145 }
146}
147
148pub fn new_command_state() -> CommandState {
151 CommandState {
152 params: HashMap::new(),
153 flags: HashMap::new(),
154 history: Vec::new(),
155 }
156}
157
158pub fn new_command_bus(max_history: usize) -> CommandBus {
159 CommandBus {
160 undo_stack: Vec::new(),
161 redo_stack: Vec::new(),
162 max_history,
163 state: new_command_state(),
164 }
165}
166
167pub fn execute_command(bus: &mut CommandBus, cmd: Box<dyn Command>) -> CommandResult {
172 let result = cmd.execute(&mut bus.state);
173 if result.success {
174 bus.state.history.push(cmd.description().to_string());
175 bus.redo_stack.clear();
176 bus.undo_stack.push(cmd);
177 if bus.undo_stack.len() > bus.max_history {
179 bus.undo_stack.remove(0);
180 }
181 }
182 result
183}
184
185pub fn undo_last(bus: &mut CommandBus) -> Option<CommandResult> {
187 let cmd = bus.undo_stack.pop()?;
188 let result = cmd.undo(&mut bus.state);
189 bus.redo_stack.push(cmd);
190 Some(result)
191}
192
193pub fn redo_last(bus: &mut CommandBus) -> Option<CommandResult> {
195 let cmd = bus.redo_stack.pop()?;
196 let result = cmd.execute(&mut bus.state);
197 bus.undo_stack.push(cmd);
198 Some(result)
199}
200
201pub fn undo_count(bus: &CommandBus) -> usize {
202 bus.undo_stack.len()
203}
204
205pub fn redo_count(bus: &CommandBus) -> usize {
206 bus.redo_stack.len()
207}
208
209pub fn clear_history(bus: &mut CommandBus) {
210 bus.undo_stack.clear();
211 bus.redo_stack.clear();
212}
213
214pub fn command_descriptions(bus: &CommandBus) -> Vec<&str> {
216 bus.undo_stack.iter().map(|c| c.description()).collect()
217}
218
219#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn test_new_bus_empty() {
227 let bus = new_command_bus(10);
228 assert_eq!(undo_count(&bus), 0);
229 assert_eq!(redo_count(&bus), 0);
230 }
231
232 #[test]
233 fn test_execute_set_param() {
234 let mut bus = new_command_bus(10);
235 let cmd = Box::new(SetParamCommand {
236 key: "height".to_string(),
237 value: 1.75,
238 old_value: 0.0,
239 });
240 let result = execute_command(&mut bus, cmd);
241 assert!(result.success);
242 assert_eq!(
243 *bus.state.params.get("height").expect("should succeed"),
244 1.75
245 );
246 }
247
248 #[test]
249 fn test_undo_set_param() {
250 let mut bus = new_command_bus(10);
251 let cmd = Box::new(SetParamCommand {
252 key: "age".to_string(),
253 value: 30.0,
254 old_value: 25.0,
255 });
256 execute_command(&mut bus, cmd);
257 let r = undo_last(&mut bus).expect("should succeed");
258 assert!(r.success);
259 assert_eq!(*bus.state.params.get("age").expect("should succeed"), 25.0);
260 }
261
262 #[test]
263 fn test_redo_after_undo() {
264 let mut bus = new_command_bus(10);
265 let cmd = Box::new(SetParamCommand {
266 key: "x".to_string(),
267 value: 5.0,
268 old_value: 0.0,
269 });
270 execute_command(&mut bus, cmd);
271 undo_last(&mut bus);
272 let r = redo_last(&mut bus).expect("should succeed");
273 assert!(r.success);
274 assert_eq!(*bus.state.params.get("x").expect("should succeed"), 5.0);
275 }
276
277 #[test]
278 fn test_redo_cleared_on_new_command() {
279 let mut bus = new_command_bus(10);
280 execute_command(
281 &mut bus,
282 Box::new(SetParamCommand {
283 key: "a".to_string(),
284 value: 1.0,
285 old_value: 0.0,
286 }),
287 );
288 undo_last(&mut bus);
289 assert_eq!(redo_count(&bus), 1);
290 execute_command(
291 &mut bus,
292 Box::new(SetParamCommand {
293 key: "b".to_string(),
294 value: 2.0,
295 old_value: 0.0,
296 }),
297 );
298 assert_eq!(redo_count(&bus), 0);
299 }
300
301 #[test]
302 fn test_set_flag_command() {
303 let mut bus = new_command_bus(10);
304 let cmd = Box::new(SetFlagCommand {
305 key: "visible".to_string(),
306 value: true,
307 old_value: false,
308 });
309 execute_command(&mut bus, cmd);
310 assert!(*bus.state.flags.get("visible").expect("should succeed"));
311 }
312
313 #[test]
314 fn test_undo_set_flag() {
315 let mut bus = new_command_bus(10);
316 execute_command(
317 &mut bus,
318 Box::new(SetFlagCommand {
319 key: "f".to_string(),
320 value: true,
321 old_value: false,
322 }),
323 );
324 undo_last(&mut bus);
325 assert!(!*bus.state.flags.get("f").expect("should succeed"));
326 }
327
328 #[test]
329 fn test_batch_command() {
330 let mut bus = new_command_bus(10);
331 let batch = Box::new(BatchCommand {
332 name: "set_both".to_string(),
333 commands: vec![
334 Box::new(SetParamCommand {
335 key: "p1".to_string(),
336 value: 1.0,
337 old_value: 0.0,
338 }),
339 Box::new(SetParamCommand {
340 key: "p2".to_string(),
341 value: 2.0,
342 old_value: 0.0,
343 }),
344 ],
345 });
346 let r = execute_command(&mut bus, batch);
347 assert!(r.success);
348 assert_eq!(*bus.state.params.get("p1").expect("should succeed"), 1.0);
349 assert_eq!(*bus.state.params.get("p2").expect("should succeed"), 2.0);
350 }
351
352 #[test]
353 fn test_batch_undo() {
354 let mut bus = new_command_bus(10);
355 execute_command(
356 &mut bus,
357 Box::new(BatchCommand {
358 name: "batch".to_string(),
359 commands: vec![Box::new(SetParamCommand {
360 key: "q".to_string(),
361 value: 9.0,
362 old_value: 1.0,
363 })],
364 }),
365 );
366 undo_last(&mut bus);
367 assert_eq!(*bus.state.params.get("q").expect("should succeed"), 1.0);
368 }
369
370 #[test]
371 fn test_clear_history() {
372 let mut bus = new_command_bus(10);
373 execute_command(
374 &mut bus,
375 Box::new(SetParamCommand {
376 key: "x".to_string(),
377 value: 1.0,
378 old_value: 0.0,
379 }),
380 );
381 clear_history(&mut bus);
382 assert_eq!(undo_count(&bus), 0);
383 assert_eq!(redo_count(&bus), 0);
384 }
385
386 #[test]
387 fn test_command_descriptions() {
388 let mut bus = new_command_bus(10);
389 execute_command(
390 &mut bus,
391 Box::new(SetParamCommand {
392 key: "x".to_string(),
393 value: 1.0,
394 old_value: 0.0,
395 }),
396 );
397 execute_command(
398 &mut bus,
399 Box::new(SetFlagCommand {
400 key: "f".to_string(),
401 value: true,
402 old_value: false,
403 }),
404 );
405 let descs = command_descriptions(&bus);
406 assert_eq!(descs.len(), 2);
407 assert!(descs.contains(&"SetParamCommand"));
408 assert!(descs.contains(&"SetFlagCommand"));
409 }
410
411 #[test]
412 fn test_undo_empty_returns_none() {
413 let mut bus = new_command_bus(10);
414 assert!(undo_last(&mut bus).is_none());
415 }
416
417 #[test]
418 fn test_redo_empty_returns_none() {
419 let mut bus = new_command_bus(10);
420 assert!(redo_last(&mut bus).is_none());
421 }
422
423 #[test]
424 fn test_max_history_trimmed() {
425 let mut bus = new_command_bus(3);
426 for i in 0..5 {
427 execute_command(
428 &mut bus,
429 Box::new(SetParamCommand {
430 key: format!("p{}", i),
431 value: i as f64,
432 old_value: 0.0,
433 }),
434 );
435 }
436 assert_eq!(undo_count(&bus), 3);
437 }
438
439 #[test]
440 fn test_state_history_log() {
441 let mut bus = new_command_bus(10);
442 execute_command(
443 &mut bus,
444 Box::new(SetParamCommand {
445 key: "k".to_string(),
446 value: 1.0,
447 old_value: 0.0,
448 }),
449 );
450 assert_eq!(bus.state.history.len(), 1);
451 assert_eq!(bus.state.history[0], "SetParamCommand");
452 }
453}