Closed jkelleyrtp closed 5 years ago
Not any other than the ones in the documentation, unfortunately. If the record is meant to modify the struct, the record needs to own the struct, not the other way.
For example:
struct Foo {
string: undo::Record<String>,
}
@evenorog Thanks for this crate. What's the recommended way to store the pervious state of an applied action (to allow redo of destructive actions)?
@Boscop it's not completely clear to me what you mean, do you want to take a copy of the "target" before applying the command and then copy it back when undoing?
For that to work as expected you might want to implement the redo
function in addition to apply
and undo
(apply
is only called once). Something like this i guess:
use undo::{Command, Record};
struct Add {
c: char,
old_string: String,
}
impl Command<String> for Add {
fn apply(&mut self, s: &mut String) -> undo::Result {
self.old_string = s.clone();
self.redo()
}
fn undo(&mut self, s: &mut String) -> undo::Result {
*s = self.old_string.clone();
Ok(())
}
fn redo(&mut self, s: &mut String) -> undo::Result {
s.push(self.c);
Ok(())
}
}
@evenorog sorry, I meant, what's the recommended way to implement undo of destructive actions. It feels weird to mutate the action. IMO the action should not be mutated when it's applied (it will be dropped afterwards anyway, so any change to it will get lost). But I also don't want to infect the evolving object with its own history of states.
@Boscop mutating the command/action is the intended way to do this. The command is not dropped afterwards, it is stored in the Record
or History
and lives as long as necessary. If you are familiar with Qt's undo framework then this works basically the same way.
Ah right! So each (destructive) command should store the state necessary for undoing it.
Btw, with the History, can emacs-like undoing be implemented easily? Such that when you do these actions:
A, B, undo, C, undo, undo
you end up at state A, B
?
It would mean:
A, B, undo(B), C, undo(C), undo(undo(B)) = A, B
So everything you ever did becomes part of the history. Branching off in the past doesn't overwrite the following history.
Hmm, History should store the necessary state for this to work, but the branching back is not done automatically when undoing so by default you would end up in the empty state.
Right now I can't say how easy it would be to implement this, but I can try to add an example that does this.
Thanks, I'm looking forward to it!
@evenorog In my project, I'd like to support undo/redo but also across sessions. How can I (de)serialize a Record to/from my own representation (sqlite)?
And when should I use the undo crate vs the redo crate? I have a huge application state, every command only changes a small part of it.
@Boscop For (de)serialization you need to use the redo crate with the serde
feature.
The commands in undo is stored as Box<dyn Command>
and the commands in redo is stored as a single generic type C
. So in undo you can store many different commands and certain features is easier to implement, like merging. When using redo you probably want to use an enum containing all the different commands, and certain features needs to be implemented manually.
Otherwise they are mostly the same and I try to keep the API similar so it is easy to switch from one to the other.
Having a hard time integrating this into my project. How would a struct own a
Record
?