How to implement 'undo' feature

Another toddler-coder question from me… :slight_smile:

Pressing a button in my module changes the values of multiple knobs. But pressing Ctrl-Z afterwards fails to recover the previous state. I assume I miss a step adding values to a history list for undo.

Is this the history I had to study? Is there any module with a simple and clean implementation you could recommend?

For me it’d be much easier to learn about the usage from some existing code.

1 Like

Yeah, it’s history. You could look at how vcv does it.my Seq++ has a very robust implementation.

1 Like

The most important thing to remember is that you mustn’t try to store a reference to your module or control in your history object. Instead you can store the module id and then find the module via the id when you need to undo/redo.

The reason is that someone might turn your knob, then delete the module, then undo twice. Your module reference would be invalid by the time you need to action your undo.

2 Likes

Take a look at VCV Rack API: rack::history::Action Struct Reference

I used a complex action in my Flying Fader module. Check the code here starting at line 70:

1 Like

I think it’s probably more common to derive your own action rather than use the sdk complex action? But that looks pretty cool.

I am lazy :sunglasses:

Thanks for posting. I hadn’t considered undo, looks like my Polygene randomize channel button needs the special treatment.

Undo doesn’t undo context menu changes either? Is that something module developers should be handling, or a VCV ‘feature’? I can’t find any module that can undo a context menu change.

The Seq++ stuff might need looking at again. Undoing Rec and Scroll works, but Run doesn’t. Whilst VCV does know about ‘insert note’ actually running undo doesn’t update the display. I’ll file a bug - I’m using latest source builds for everything so could be a regression.

Just make your context menu entry a parameter, then undo works out of the box and the state also get saved out of the box (no noodling with json needed). This only works for numeric values.

1 Like

@ Ahornberg , thanks for the code. It is a starting point I need!


Actually that is why I’m confused. Calling following line with an onHoverKey shortkey the change is added to the undo-list.

params[SEQ_1_VOLTAGE_PARAM+k].setValue(abs(1-params[SEQ_1_VOLTAGE_PARAM+k].getValue()));

But if I call following line with a similar onHoverKey shortkey then it’s ignored by the automatic history list.

params[STEPS_PARAM].setValue(rand() % 7 + 2);

May you post both onHoverKey() methods completely?

Relevant parts are

  • line 266 onHoverKey calls
  • line 63 invKnob (pressing ‘i’)
  • line 70 rndKnob (pressing ‘r’)

I downloaded and compiled your code and gave it a test-drive: Whatever hotkey I use, nothing is pushed to the history.

I think, you want to recall the complete state before changing the values from a bunch of knobs, so ComplexAction will be your friend here.

2 Likes

I would suspect there are a lot of modules that don’t have proper undo. As @Ahornberg points out, the only thing you get for free is parameter changes. Which is enough for a majority of module, but there are a bunch that require more.

I remember @clone45 mentioned that Voxglitch modules don’t use parameters. Looks like undo doesn’t work on them.

Primarily out of curiosity, parameter changes via CV do not go into undo, do they?

no, the stuff in VCV is in the param widget class (I now see). Which is probably good - I don’t think you would want a CV change to be “undoable” - it wouldn’t even really make sense. But turning a knob, adding/removing a module, patching… Those things are undoable.

1 Like

Exactly. The only reason I ask is that there is still a bug in Rack (I believe) that may involve, undo history, autosave, selection files, editing and “reset” in some very deep manner that eludes me as well as a couple of others who have dug into this.

Here is a question that I am interested in. Which actions reset (clear) the undo history? Is there any way to manually reset the undo history?

not 100% sure, but a quick look at the rack code and I think loading a new patch clears the undo history?

This is an important question for me. If I work on a single patch for quite a while through multiple actions, does the undo history just grow larger and larger? There is some circumstantial evidence that this is the case. What happens if the user reloads the current patch? Does this clear the history.

I’m also concerned that there is also circumstantial evidence that history related crashes may be aggravated (or caused) by some plugins or modules. Is everyone who is developing module undo management doing so correctly?

I know this is off-topic, but, I think these are valid questions.

How is the undo history maintained? Is it part of the patch file, or is it a json file and if so, at what level? Or is it something else entirely?

I am very concerned about “complex actions” history, such as with selection files as well as select and copy or duplicate for multiple modules and cables, as well as deletes.

1 Like

undo is stored in memory. As far as i know it just grows and grows.

Its not part of the patch file, there’s no way to serialise it from memory to persistent storage.

Its possible to get it very wrong, so there could be modules which have faulty undo management.