I have an app where the user can create a number of UI objects, which adds it under a parent and adds an associated data track script to a list of scripts. The objects have a number of input fields and can change the values in these fields, and the ability to delete the object with a button. There are more than 1 type of UI object and data list, so it wouldn’t be practical to just make copies of the list as a backup.
I want to implement an undo system so that the user can revert a change or add/delete. I have zero experience or even any awareness how undo systems work in other programs. I assumed the program saves a state of itself with each action, and the undo just reverts to the previous state by iterating through these saved states?
My app also has a camera system that lets the player move a camera around, which i want to exclude from this undo system. I don’t want undo/redo to change the camera back and forth.
What’s a good approach to this system? I’m open to all suggestions. Thanks in advance for the help.
Or the Command pattern (same site). This only records the changes made and each command has a method to undo the change it made. This is trickier to implement but allows playback of a command list on a different item (think: macro recording).
I’ve found that state recording has been easy and reliable. For example one implementation might be that when an operation occurs which you want undo/redo support for, you can simply create a class to represent the current state and future and put it into an undo history stack. When that undo gets consumed, you can just transfer basically that same state information to the redo list.
Basically though, it’s just storing state information and moving it from place to place, then applying those recorded states based on context.
Other patterns are also great, but it depends on what works best for your scope. Sometimes in deterministic situations the command pattern works excellent and fits into your existing code very well. For more fluid concepts, state injection can do a lot.
Well from the top-down engineering perspective there are only two approaches: stateless and stateful.
Stateless would be command patterns and similar techniques where you incrementally build the current state through some modifier stack, and are able to recreate the current state by changing this stack. The downside of this approach is that some modifiers might not be commutative or distributive so you must keep their order or apply them one by one. Another big downside is that for each undo you need to reiterate the full stack, which could be an intensive process, depending on what you’re doing exactly and how big or complex your state is. However, for light applications, GUI, simpler state models this is usually the best approach.
Stateful would be taking snapshots of the whole state, so it’s always in absolute terms. And speedy because you just swap them around, but this again depends on the complexity of your model. If straightforward this kind of undo/redo is very low-level (because you simply redirect), but then, depending on the size of the state in memory, you have to limit the amount of snapshots or you risk hogging too much memory, just to support the undo/redo behavior. Adobe Photoshop used to have limited number of undos prior to version 4 if I recall correctly – it had exactly 1 undo, and good luck with that. Rumors has it that the professional PS users didn’t have any hair in the nineties (I certainly lost most of mine).
You can also do both. For example, Photoshop nowadays also supports live image effects (not used for undo/redo, but still a similar solution based on a command stack, where each effect can be toggled on or off, and the state is recreated on the fly), while also doing regular bounded snapshots for pixel data per layer for the traditional destructive workflow (imagine painting something with a custom brush preset, nobody would undo each affected pixel incrementally, here the entire modified part of the image is fully preserved for eventual restoration). Similarly, the image ground state can always be restored by selecting Revert from the File menu.
That would be the stateful approach. Hey, if your data model is simple (and small) enough then this would be the easiest to implement as well. If it works, it works!
If I understand what you mean by stateful, basically it’s like how a Rom emulators have a save-state function, right? It saves a snapshot of everything that the game as in memory and the state its in, and just reloads the entire thing? So you basically revert to whenever you saved?
If this is so, that would be my initial approach since its very straight forward. But the problem is that it reverts everything. To give you a better idea of my problem, please refer to the screenshot below of the app I’m working on:
This is a digital crash cart builder. The cart as a number of text inputs on the left, with its name, ID, etc. And on the right there are a number of Drawers, and when opened, it lists all the items. Items can be edited (changing its name, position, etc) and can be added or deleted. The UI will reflect this when done so.
The middle is a 3D model of the cart the user can rotate around to see where the items are located. Right now, everything in the app is functioning as it should, lacking only the undo and redo.
If I used a stateful, as you’ve described it, if I understanding you correct, it will reload everything in the app back to where it was when I name the state snapshot. Including where the camera is, which drawer is opened, which item was selected, etc. This wouldn’t be a deal breaker, and if that’s the most efficient way, then I would use it without hesitation.
However, I am trying to avoid having unnecessary things revert. To use your Photoshop example, if I used Stateful, would it not cause my canvas to go back to where it was during the state snapshot? As well as any tool I was using at the time? Say I had the brush tool selected during the state-save. I then changed to the lasso. I then undo. Would that not cause my brush to go back to the brush?
Or can I exclude certain things from the state? Like UI elements? In my case, the camera and item I have selected, using the Stateful approach?
I’m currently trying to use what you might describe as stateless. I have a data object in the app that saves everything that is in the cart which is serialized as a json file for local saving. When loading a cart, this json is deserialized back to data object. It’s read and the UI is loaded accordingly.
My current approach is using a C# queue, and every action that the user makes that I want to be revertable I just make a data object of the cart at that moment and store it in the queue object. The queue is limited to a certain certain number of data objects.
To Undo or redo, I just load the data object in the queue, compare it to the current state of the cart. I delete any items that wasn’t in the older object, and add any was missing. At least that’s the item. This does cause problems that I need to save. Say I add a new item to the cart, I select it, loading its data. If I undo, I have check if I have this item selected, unselect it, then delete it. Otherwise I end up with null exceptions.
That’s my currently idea, but it does seem much more tedious than just loading the state.
The idea is to only save the portion of things you want to undo.
That could or could not include tool settings, in the photoshop analogy. Photoshop doesn’t save such things in its undo history, at least last time I checked.
Given that you probably already are going to need a way to save the state of the thing you are editing, as long as it’s not a massive amount of data, I would personally always go with the snapshot method for undo: every time you’re about to apply a forward change, take another snapshot and put it in a list.
Then ctrl-Z moves you back up that list one step at a time, ctrl-shift-z (redo) moves you forward, and making a new change chops off all newer entries in that list, assuming you want “standard” undo / redo / continue behaviour.
Regarding your questions, yes and no. You are free to delimit your state containers however you want. In your analogy, you are free to separately track the “current tool” state from the “canvas” state, which is exactly what Photoshop does as well. It does not undo your tool selection, mostly because undoing the tool selection a) isn’t really a destructive operation that requires undo, b) doesn’t count as the application domain, c) is something that would confuse users instead of aiding them. I’m sure you can figure this out on your own. So what can you do?
Well, try by specifying exactly what constitutes your undoable state, determine whether camera position is useful in that context etc. Some applications separately track camera movement from the actual object composition, so you can undo them separately, these things aren’t unheard of, however I’m not sure if that’s something that truly affects the state of the core concern of your game/logic.
Let’s say for example in Factorio you can move around and destroy buildings. If you would want to accomplish an undo feature, the valid state would encompass only the building placements, and then you would maintain that order in such a way to just bring back a building that was recently destroyed. You would not reposition the player, and you would not reinstate all the monsters on the map, reset the time of day, rewind music and so on.
So once you define what is this undoable state, make sure you define a container (in object oriented paradigm this is some data holder, i.e. class or a struct) that is responsible for state encapsulation. States typically consists of one or more values that represent some logical status, whether it’s position or boolean values, or count, or order of things etc.
This is what means when something is stateful: it acknowledges some state that had all values set in a way that was logically valid (for your use case, only you can tell what is logically valid for your desk example), so that you can organize multitudes of these states into series, which practically represent snapshots of various states in time.
If you consider an analogy of traditional handrawn image-by-image animation, such a program doesn’t care about the individual elements of each image, but simply stores the entire sheet as a snapshot and is capable of flicking between them. A stateless system would be a system of keyframing and animation tweening in this analogy. You can reposition individual keys and tweening parameters, but then the true state would have to be reconstructed on the fly because the true full image composite is not actually stored anywhere.
-every undoable action has a function that writes that section of the cart file to a custom object that stores the original and new state. This “state” object has some marker that tells the app what sort of action it is and what it should do.
-the state has a struct for the original and new state of the object affected. Adding new items will have a null in the original state, and an object in the new state. The reverse would be done for deleting an item.
-this is store in list of actions state objects.
-undoing will load the original, undoing the changes to the new one. Redoing will do the opposite.
The hardest part is turning off the UI at the right points during the undo/redo. I’ll report after I do some testing and debugging.
This is hard to answer. But an easy approach using the command pattern is to keep the reference to the GO in the delete command and disabled it. Then when you undo you enable the GO again.