Undo Nodes

Post Reply
UrbanCyborg
Posts: 588
Joined: Mon Nov 15, 2021 9:23 pm

Undo Nodes

Post by UrbanCyborg »

Out of curiosity, how many of the developers here actually bother to define undo/redo nodes for their custom stuff?

Reid
Cyberwerks Heavy Industries -- viewforum.php?f=76
Centripidity
Posts: 140
Joined: Sun Jan 22, 2023 5:18 am
Location: Melbourne
Contact:

Re: Undo Nodes

Post 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.
User avatar
utdgrant
Posts: 535
Joined: Wed Apr 07, 2021 8:58 am
Location: Scotland
Contact:

Re: Undo Nodes

Post 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.
______________________
Dome Music Technologies
UrbanCyborg
Posts: 588
Joined: Mon Nov 15, 2021 9:23 pm

Re: Undo Nodes

Post 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
Cyberwerks Heavy Industries -- viewforum.php?f=76
ColinP
Posts: 939
Joined: Mon Aug 03, 2020 7:46 pm

Re: Undo Nodes

Post 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.
poetix
Posts: 54
Joined: Mon Nov 28, 2022 3:26 pm

Re: Undo Nodes

Post 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.
London, UK
Developer, Vulpus Labs
Musician, w/trem
jclounge
Posts: 14
Joined: Fri Aug 06, 2021 5:19 am

Re: Undo Nodes

Post 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.
Post Reply

Return to “Module Designer”