Serialization-Based Undo
prideout.net
prideout.net
Just store each internal state you might want to recover, in full. Except use compression to store the states you want to recover. In this article, the compression is done based on the 'initial state'. I imagine there is flexibility on maybe 'checkpointing' some states once the difference becomes to big. Maybe do the compression not with a prefix of the 'initial' state but with a prefix of the entire undo history.
All this requires is halfway efficient serialization / deserialization implementation, and access to Zstandard (I guess other compression algorithms also work).
I think the key trick here is that, if you can serialize to bytes, you can just use compression methods as a replacement for storing diffs or other complicated methods for de-duplicating data. This could be a really powerful method to really easily keep a state history without high performance cost. From the developer p.o.v. its like you have each previous state at your fingertips. Whilst in reality the compression ensures it is stored efficiently.
This would not work, for instance, in an image editor with an "adjust brightness/contrast" command since that changes the whole image.
It's also inefficient since you need to examine the whole state to generate the diffs, so in general it's not a great solution.
Someone needs to play around with NLE video editors. Because yes, that's what they basically do.
The video editing steps are all items on the "timeline". When you hit the "render" button, the computer goes into overdrive: spinning up a bunch of threads (maybe even GPU-kernels) and actually calculates everything.
What you see in the preview-screen doesn't always match the final render. The preview-screen is just a quickie-calculation, much like how 3d programs (ex: Blender) have a realtime renderer (see Eevee) vs the offline, more accurate renderer (see Cycles).
---------
From an NLE perspective: the GUI is basically a glorified editor of commands.
But in general, every command would generate an "undo record" with information to undo it.
In case of the brightness/contrast change you can do that by undoing in the naive way and then storing the difference between the "naively undone" image and the origignal version, compressed, in the undo record.
In general the undo record will only be large if you lose a lot of information, and then there won't be as much information left to lose and thus the next undo records won't be able to be so large, so overall the total size of the compressed current image and undo record will amortize and be in the range of the size of the original image plus the command description.
For example, once you turn the image fully grey, then all per-pixel transformations will be either be reversible or the undo record will compress to the effect on one pixel since it's the same for all the image.
This doesn't hold however if you also have operations that create information (e.g. something that draws a pseudorandom image with you then adjust contrast on, resulting in a pseudorandom undo record that you can't compress with standard techniques): in that case I think reexecuting the command history is the only space-efficient solution that doesn't require coding for the specific application.
This stores complete game states using a compression library to keep memory under control, whereas Braid stored a combination of keyframes, and diffs from those keyframes, to avoid using a compression library.
This then grows more complex if items have parent-child relationships and you also need to start storing all the information required to completely reinstate all the children, possibly also in the same order they were originally created in.
You know, tricky.
yep, as soon as you have an object graph serialization is the only way to stay sane. Anecdotally that's what I do in https://ossia.io with Qt's QDataStream and serializing / deserializing hundreds of steps is pretty much instant. A nice benefit of it is to be able to provide a "restore on crash" feature: the undo/redo stacks get saved to disk and can be used to go back to the exact state the user left if a crash happens
Same reason debuggers don't have a "step back" button.
Strictly from a computation theory point of view, you could obviously store a list of executed opcodes and undo by replaying up to the (N-1)th operation, but I think they want to avoid the overhead.
After around 30 minutes of playing games in the simulator, pressing "Undo" would hang the client and likely disconnect most people connected to the server.
It's not a great way to implement undo.
It has the same tradeoffs as the usual command/action pattern (richer information about what was changed, but you have to implement a new type for each operation, and might end up copying a lot of memory anyway). However it's a lot easier to implement each command (less boilerplate, somewhat lower risk of implementing undo/redo incorrectly and altering state). As a bonus, apply/undo/redo perform zero memory allocations, because they only swap data, not copy or destroy it.
One arguable downside is that you can't transplant the same command from one point in the history to another (but most undo commands in traditional applications, as opposed to VCS systems or nonlinear editors, aren't built to do that either). And I'm not sure my "can_merge" system (which merely drops commands rather than bundling or intelligently combining their contents) was a good idea, since all mergable commands must alter the same state.
API: https://gitlab.com/exotracker/exotracker-cpp/-/blob/dev/src/...
Nobody does, it's one of the worst mistakes of C++.