Notes for reviewers.
- The scope of this document is quite open ended, how much to go into details about implementation, corner cases etc.
- Currently this does not attempt (for example) to provide details on adding a new undo-system for e.g.
as there are enough examples in the current code-base that can be used as a reference.
- More time could be spent on documenting MemFile undo, although this could get into a lot of implementation details that I think would be better included in the code.
----
Undo for Editors / Window Manager
#################################
Overview
========
This section is an overview of the high level `ED_undo.h` API.
Typically undo step creation is handled by operator execution and only accessed by the user.
In general manually performing undo-pushes or undoing actions from operators logic isn't necessary.
Although there are some corner cases which are documented noted on this page.
Integration With Operators
==========================
In most cases enabling the UNDO flag (`OPTYPE_UNDO`) is sufficient for operators to support redo/undo.
Operators that return `FINISHED` will add an undo step otherwise no undo step is added (typically `CANCELLED`).
In rare cases it's necessary for operators to explicitly perform an undo push, this can be done by calling
`ED_undo_push` (or similarly named functions).
When performed from an operator, be sure to remove the `OPTYPE_UNDO` flag and add a comment that the operator
is responsible for handling it's own undo pushing.
Operator Redo
-------------
It's worth noting that adjusting settings for an action uses undo internally.
When redoing an action looses a change it means one of the actions performed is missing an undo push.
Undo Grouping
=============
There are some rare situations grouping undo steps it's necessary.
Grouping Consecutive Undo Steps
-------------------------------
Some actions such as changing the current frame (with arrow keys for example) make sense to treat as a single action
since having to undo so many actions isn't practical and they will quickly exceed the undo step limit making
previous states inaccessible.
In this case UNDO_GROUPED `OPTYPE_UNDO_GROUPED` should be set for operators,
this causes each newly added undo step to replace the previous state instead of adding a new step
(as long as their names match).
This way an operator executed many times in succession only adds a single undo step.
Grouping Arbitrary Undo Steps
-----------------------------
Grouping arbitrary undo steps of different types is also supported.
This is needed when a single action (from the users perspective)
involves storing multiple undo steps.
An example where this is needed is when the outliner changes the active object and sets it's mode.
Internally these are two actions which use different undo-system types.
While internally these remain multiple undo steps the undo-system ensures they are treated as a single step
as far as the user is concerned.
`ED_undo_group_begin` & `ED_undo_group_end` calls must surround actions that push undo steps.
Undo System
###########
The undo system stores a linear list of undo states tied to the currently loaded file.
The following components make up the undo system.
- `UndoStack` a container for all undo steps which also keeps track of the initial & active undo step.
- `UndoStep` a generic container for the undo data, which store a user readable name (displayed in the interface)
the in-memory size (used for limiting memory use) and some other settings to control how the undo step is applied.
- `UndoType` each undo step references an undo type, these have special handling for data-types
such as image painting, sculpt-data and text-editing.
Each undo type defines callbacks to encode/decode undo steps as well as freeing the undo data.
Undo System Types
=================
While it's not the purpose of this document to list every undo system type, there are some which are worth documenting.
MemFile Undo
------------
This is one of the most used undo-system types as it keeps track of all changes in the blend file,
making it a general purpose enough to be used in almost all actions in Blender.
Conceptually, each MemFile undo step can be thought of as a snapshot of the file which can be loaded just as a
regular blend file is loaded from disk.
Therefor, it can handle any data that is written to the blend file.
This is more efficient that it might sound as there are optimizations to avoid unnecessary work.
Other undo-types are still necessary, image pixel data for example are not necessarily saved into the blend file
so there is an undo-type used for image editing and painting.
There are other cases where MemFile undo works but isn't efficient.
If it were to be used for the text editor - every key stroke would be checking for differences in the blend file
only to store a changed character.
If this were to be used for meshes in edit-mode, the entire mesh would have to be converted to a regular mesh
(similar to exiting edit-mode).
Challenges
; Syncing Screen Data : Since undo/redo doesn't load screen data, some work is needed to ensure data referenced by the screens (the 3D viewport locked to an object for example), doesn't get out of sync, since you may undo to a time before the referenced data existed.
Implementation Notes
====================
Data Duplication
----------------
From the perspective of the undo system each undo step stores a snapshot.
Efficient data storage is up to each `UndoType` which can access next/previous undo steps
(with a matching undo-system type) - sharing data where possible.
- MemFile undo reuses memory that hasn't been modified between undo steps.
- Edit Mesh & Text Editing use a binary diffing library which de-duplicates memory chunks (see `BLI_array_store`).
- 2D Image painting only stores pixel data for the regions being pained onto for each stroke.
- Curves, armature & meta-ball object types store a complete copy of the data for each undo step,
as these data types tend not to be as memory intensive.
Context Following
-----------------
For undo the context is followed to some extent.
Changes to scene, view-layer are followed, so undo doesn't begin to operate on data in different scenes that aren't
currently displayed.
Changes to the active object and selection are already tracked by undo so there is no need to explicitly support this.
There are still cases where data being undone might not be visible: editing text or painting for example
might make changes to data which isn't displayed if the user changes their work-space.
Mixing Undo Types
-----------------
One of the complications with the undo system is mixing different kinds of undo systems.
Edit-mesh undo for example stores the state of meshes in edit-mode,
however loading this undo data relies on those objects being visible in the current view-layer so these objects can
enter edit-mode and have their data loaded.
This is the responsibility of MemFile undo, which keepds track of the active objects and their selection,
so other undo systems can load their data into the file in a context that matches the state when the undo data was
created.
Testing
#######
Undo tests is not currently part of Blender's unit tests (called by ``ctest``) since it requires a windowed environment.
There are tests in `../lib/tests/ui_simulate/run.py`, call with `--help` for how this can be used.
Before making changes to undo-sytem internals these tests should be run.
When fixing bugs in the undo-system it's useful to include these as tests too.
Known Limitations & Corner Cases
################################
There are some limitations and corner cases worth noting.
Mixing Data Types
=================
When in edit-mode, operators will add edit-mode type undo steps.
This causes problems when an operator manipulates data that isn't part of this mesh (such as adding a modifier).
In this case undo will fail to handle these changes.
It can also be a problem for Python scripts which may modify data across the entire file.
No Optional Undo Support
========================
No support for operators to optionally undo, see: https://developer.blender.org/T92099
in these cases it's necessary for the operator not to set the UNDO flag and perform explicit undo pushes.
Nested Operators
================
Performing undo/redo from a running modal operator is not supported.