Out of curiosity, how many of the developers here actually bother to define undo/redo nodes for their custom stuff?
Reid
Undo Nodes
-
- Posts: 625
- Joined: Mon Nov 15, 2021 9:23 pm
Undo Nodes
Cyberwerks Heavy Industries -- viewforum.php?f=76
-
- Posts: 146
- Joined: Sun Jan 22, 2023 5:18 am
- Location: Melbourne
- Contact:
Re: Undo Nodes
I must admit that I don't. I haven't looked at that part of the API myself but this is only my third month releasing modules. I think I've ignored it up until now because I don't recall seeing any user comments on the forums about undo behaviour and, as a VM user, I've rarely ever felt the need to use undo/redo.
I'm interested to hear what others think though, particularly those with more extended experience with VM and VMD.
I'm interested to hear what others think though, particularly those with more extended experience with VM and VMD.
Re: Undo Nodes
It's not something I've even considered as a developer, I have to admit.
Certainly, all Dome Music Technologies modules are pretty much driven only by front panel controls and CV inputs. (i.e. there's little in the way of internal state which requires an explicit snapshot). I'm assuming that standard control changes are already enabled for tracking of Undo/Redo operations, though I haven't tested it explicitly for myself.
However, I dimly recall making tweaks to the wrong knob, hitting CTRL-Z by instinct, and having the previous setting restored in the expected way. When I get a chance, I'll give that a more formal test.
Certainly, all Dome Music Technologies modules are pretty much driven only by front panel controls and CV inputs. (i.e. there's little in the way of internal state which requires an explicit snapshot). I'm assuming that standard control changes are already enabled for tracking of Undo/Redo operations, though I haven't tested it explicitly for myself.
However, I dimly recall making tweaks to the wrong knob, hitting CTRL-Z by instinct, and having the previous setting restored in the expected way. When I get a chance, I'll give that a more formal test.
______________________
Dome Music Technologies
Dome Music Technologies
-
- Posts: 625
- Joined: Mon Nov 15, 2021 9:23 pm
Re: Undo Nodes
The kind of things I use it for is first, custom controls that have their own state that wouldn't be correctly reset by VM, and second, more involved sequences of instructions like those I put on some menus. Anything other than that, and VM will correctly (I think) handle it.
Reid
Reid
Cyberwerks Heavy Industries -- viewforum.php?f=76
Re: Undo Nodes
For simple modules with no hidden state it works automatically but if you do have hidden state and want to reach professional levels then you should support undo/redo at least to a modest degree.
Sometimes it's a bit tricky from a design POV to decide whether some operations ought to support undo - for instance loading a sample file is destructive in GS but most users wouldn't expect to be able to undo a load. Also some API calls do things that can't be undone so one's stuffed there obviously.
I generally add undo/redo as one of the final things in a project as it's relatively easy to do en masse by cut and paste sweeps through the code.
You can reuse the same mechanism that you use to save and restore hidden state in presets. I handle this mostly as strings but in some modules there's a binary element too but that is generally the kind of data that one wouldn't undo.
So with strings I have global asString() and fromString() methods. In complex modules there are asStrings for sub-components that get concatenated in the global asString and matching scanValues methods that pass a Scanner object.
The general model for adding undo to an operation then becomes nothing more complicated than...
Then the undoredo handler just uses fromString to reinstate the state. Here's an example...
The undoType test probably isn't required but is handy for debugging and tracking what is and isn't handled.
If you don't have any binary to handle then GetStateInformation() is simply...
...and SetStateInformation is just...
So as you can see it's all dead simple so there's no real excuse for not doing it!
One more thing - you need to handle . and , correctly in strings to support international transit of presets but I covered that previously.
Sometimes it's a bit tricky from a design POV to decide whether some operations ought to support undo - for instance loading a sample file is destructive in GS but most users wouldn't expect to be able to undo a load. Also some API calls do things that can't be undone so one's stuffed there obviously.
I generally add undo/redo as one of the final things in a project as it's relatively easy to do en masse by cut and paste sweeps through the code.
You can reuse the same mechanism that you use to save and restore hidden state in presets. I handle this mostly as strings but in some modules there's a binary element too but that is generally the kind of data that one wouldn't undo.
So with strings I have global asString() and fromString() methods. In complex modules there are asStrings for sub-components that get concatenated in the global asString and matching scanValues methods that pass a Scanner object.
The general model for adding undo to an operation then becomes nothing more complicated than...
Code: Select all
String previous = asString();
<do the operation>
CreateUndoNode( undoDescriptionString, undoDescriptionString, (Object) previous, (Object) asString() );
Code: Select all
@Override
public void OnUndoRedo( String undoType, double newValue, Object optionalObject )
{
// add your own code here
if( undoType == "Randomize"
|| undoType == "Mouse Edit" )
{
fromString( (String) optionalObject );
}
}
If you don't have any binary to handle then GetStateInformation() is simply...
Code: Select all
return asString().getBytes( StandardCharsets.UTF_8 );
Code: Select all
fromString( new String( stateInfo, StandardCharsets.UTF_8 ) );
One more thing - you need to handle . and , correctly in strings to support international transit of presets but I covered that previously.
Re: Undo Nodes
A more heavyweight approach, which might be helpful if there are multiple undoable operations, is to implement each hidden-state-affecting change as a Command object:
Each possible operation must know how to make a change, and how to change it back (typically, when creating an instance of a Command object, you'll capture the "restore" state at that point and store it in the object). When performing the action, create the Command object and call `doCommand` on it; then add it to a Stack. To undo, pop the last object off the stack and call `undoCommand` on it.
Code: Select all
interface Command {
void doCommand();
void undoCommand();
}
Re: Undo Nodes
I still haven't released anything yet (send help LOL), however I've been using undo nodes extensively. I prefer it when undo/redo works as expected, but that's just my opinion.
For the more tricky undo/redo cases that may change a lot of settings at once, I find it simpler to just use my serialisation code to generate and restore from undo nodes. The same serialisation functions get used for state save/load, undo/redo operations, and custom copy/paste operations.
For the more tricky undo/redo cases that may change a lot of settings at once, I find it simpler to just use my serialisation code to generate and restore from undo nodes. The same serialisation functions get used for state save/load, undo/redo operations, and custom copy/paste operations.