Rundo can help your data struct has undo redo ability painless. Thanks Rust Procedural Macros, you only need add a attr before your struct!
Assume you have a struct Point
, as below.
struct Point {
x: f32,
y: f32,
}
After add #[rundo]
before Point
, undo redo was supported. that all! A full demo:
#![feature(proc_macro)]
#![feature(decl_macro)]
extern crate rundo;
use rundo::prelude::*;
#[rundo]
struct Point {
x: f32,
y: f32,
}
fn main(){
let mut space = Workspace::new(Point! {x: 2.0, y: 2.0,});
// x will chgange to 3.0
*space.get_mut().x = 3.0;
assert_eq!(*space.data.x, 3.0);
// x will chgange to 4.0
*space.get_mut().x = 4.0;
assert_eq!(*space.data.x, 4.0);
// x will undo to 3.0
space.undo();
assert_eq!(*space.data.x, 3.0);
// x will undo to 2.0
space.undo();
assert_eq!(*space.data.x, 2.0);
// x will redo to 3.0
space.redo();
assert_eq!(*space.data.x, 3.0);
}
Note the macro Point!
at the first line of main function, why not use Point {...}
? That because Rundo redefined origin Point type with the same shape, but support undo redo. You can use it as same as before, but to literal construct must use a same name macro replace. And remenber, to use undo redo, must place struct in Rundo Workspace.
In some case we want merge mulit change to one action, of cause:
#![feature(proc_macro)]
#![feature(decl_macro)]
extern crate rundo;
use rundo::prelude::*;
#[rundo]
struct Point {
x: f32,
y: f32,
}
fn main(){
let mut space = Workspace::new(Point! {x: 2.0, y: 2.0,});
{
let point = &mut space.get_mut(); // <----+
// x change many times // |
*point.x = 5.0; // |
*point.x = 6.0; // |
*point.x = 7.0; // |
*point.x = 8.0; // |
}//--------- point life time -----------------+
// undo will direct back to 2.0
space.undo();
assert_eq!(*space.data.x, 2.0);
}
Like what your see, all change in point life time
will merge together.
Multi get_mut()
life time can also be batched:
#![feature(proc_macro)]
#![feature(decl_macro)]
extern crate rundo;
use rundo::prelude::*;
#[rundo]
struct Point {
x: f32,
y: f32,
}
fn main(){
let mut space = Workspace::new(Point! {x: 2.0, y: 2.0,});
space.begin_op();
{
let point = &mut space.get_mut();
*point.x = 3.0;
}
{
let point = &mut space.get_mut();
*point.x = 4.0;
}
space.end_op();
// undo will direct back to 2.0
space.undo();
assert_eq!(*space.data.x, 2.0);
}
begin_op
and end_op
can be nested pair call.
You can access and modify Point
across Workspace.data
directly, like:
#![feature(proc_macro)]
#![feature(decl_macro)]
extern crate rundo;
use rundo::prelude::*;
#[rundo]
struct Point {
x: f32,
y: f32,
}
fn main(){
let mut space = Workspace::new(Point! {x: 2.0, y: 2.0,});
{
let point = &mut space.data;
// modify also allowed
*point.x = 3.0;
}
// but undo not work, x not rollback to 2.0
space.undo();
assert_eq!(*space.data.x, 3.0);
}
if you directly modify value, the change isn't captured automatic. You can manaul capture by begin_op
and end_op
.
If some field you don't want use undo redo, #[rundo(skip)]
can skip it.
#![feature(proc_macro)]
#![feature(decl_macro)]
extern crate rundo;
use rundo::prelude::*;
#[rundo]
struct Point {
#[rundo(skip)]
x: f32,
y: f32,
}
fn main() {
let mut space = Workspace::new(Point! {x: 2.0, y: 2.0,});
space.get_mut().x = 5.0;
*space.get_mut().y = 6.0;
space.undo();
// x change will be not capture, undo will not affect it.
assert_eq!(space.data.x, 5.0);
// but y is undo to 2.0
assert_eq!(*space.data.y, 2.0);
}
You have a special struct, and want to implement undo redo by yourself, that easy, just implement the Rundo Trait.
.... Sorry, It's time to sleep, I will write this section in this weekend.