| Crates.io | stratagem |
| lib.rs | stratagem |
| version | 0.1.0 |
| created_at | 2025-03-28 23:02:27.236055+00 |
| updated_at | 2025-03-28 23:02:27.236055+00 |
| description | A trait library that enables state management and command execution with built-in undo support, inspired by the Command design pattern. |
| homepage | |
| repository | https://github.com/ezrasingh/stratagem |
| max_upload_size | |
| id | 1610654 |
| size | 29,860 |
Stratagem is a Rust trait library that enables state management and command execution with built-in undo support, compatible with no_std environments. Inspired by the Command design pattern, it provides a flexible framework for modifying state through actions while tracking and undoing them efficiently.
This makes Stratagem ideal for use cases requiring undo/redo functionality, such as interactive applications, game state management, and embedded systems where heap allocation is limited.
The Commander trait defines how the state executes commands and undos (optional).
use stratagem::Commander;
#[derive(Default)]
struct State {
value: i32
}
impl Commander for State {
fn execute(&mut self, mut cmd: impl Command<Self> + 'static) {
cmd.execute(self);
}
}
impl State {
pub fn value(&self) -> &i32 {
&self.value
}
}
We can achieve the same result by using the Commander derive macro.
use stratagem::Commander;
#[derive(Commander)]
struct State {
value: i32
}
impl State {
pub fn value(&self) -> &i32 {
&self.value
}
}
The Command trait defines how commands interact with the state during execution and undo.
use stratagem::Command;
// Define a command to translate (add/sub) a value to the state
#[derive(Clone, Copy)]
struct Translate(i32);
impl Translate {
pub fn new(value: i32) -> Self {
Self(value)
}
}
impl Command<State> for Translate {
fn execute(&mut self, ctx: &mut State) {
ctx.value = ctx.value().saturating_add(self.0);
}
fn undo(&mut self, ctx: &mut State) {
ctx.value = ctx.value().saturating_sub(self.0);
}
}
// Define a command to scale (mult/div) a value to the state
#[derive(Clone, Copy)]
struct Scale {
factor: i32,
previous_value: Option<i32>,
};
impl Scale {
pub fn new(value: i32) -> Self {
Self{ value, previous_value: None }
}
}
impl Command<State> for Scale {
fn execute(&mut self, ctx: &mut State) {
// copy current value and store it
self.previous_value.replace(ctx.value());
ctx.value = ctx.value().saturating_mul(self.factor);
}
fn undo(&mut self, ctx: &mut State) {
// ctx.value = ctx.value().saturating_div(self.factor); // could panic
if let Some(last_value) = self.previous_value.take() {
ctx.value = last_value;
};
}
}
Dynamically dispatch commands at runtime
use stratagem::*;
let mut state = State::default();
let mut cmd = (
Translate::new(5),
Scale::new(0)
);
state.execute(cmd.0);
assert_eq!(state.value(), 5);
state.execute(cmd.1);
assert_eq!(state.value(), 0);
cmd1.undo(&mut state);
assert_eq!(state.value(), 5);
By enabling the time-machine feature (requires std due to Vec and Box depencies) we can convert anything implementing the Commander trait into a TimeMachine which wraps the state and provides a history to store past commands. The underlying state is accessible from the machine property.
use stratagem::time_machine::TimeMachine;
let mut state = TimeMachine::<State>::default();;
let mut cmd = Scale::from(10);
state.execute(cmd);
println!("{}", state.machine.value());
state.undo();
println!("{}", state.machine.value());
Contributions are welcome! Please see the contributing guidelines for more information.
This project is licensed under the Apache 2.0 or MIT License (your choice).