Page 1 of 1

Undo Nodes

Posted: Thu Jun 01, 2023 7:47 am
by UrbanCyborg
Out of curiosity, how many of the developers here actually bother to define undo/redo nodes for their custom stuff?

Reid

Re: Undo Nodes

Posted: Thu Jun 01, 2023 9:31 am
by Centripidity
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.

Re: Undo Nodes

Posted: Thu Jun 01, 2023 10:14 am
by utdgrant
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.

Re: Undo Nodes

Posted: Thu Jun 01, 2023 1:26 pm
by UrbanCyborg
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

Re: Undo Nodes

Posted: Thu Jun 01, 2023 4:24 pm
by ColinP
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...

Code: Select all

String previous = asString();

<do the operation>

CreateUndoNode( undoDescriptionString,  undoDescriptionString, (Object) previous, (Object) asString() );

Then the undoredo handler just uses fromString to reinstate the state. Here's an example...

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 );
   }
}
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...

Code: Select all

return asString().getBytes( StandardCharsets.UTF_8 );
...and SetStateInformation is just...

Code: Select all

 fromString( new String( stateInfo, StandardCharsets.UTF_8 ) );
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.

Re: Undo Nodes

Posted: Mon Jun 05, 2023 2:28 pm
by poetix
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:

Code: Select all

interface Command {

	void doCommand();
	void undoCommand();
	
}
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.

Re: Undo Nodes

Posted: Fri Jun 30, 2023 8:54 am
by jclounge
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.