Undo System

DISCLAIMER: All this is WIP! Those are mostly quick notes from working in this area, arranged in an attempt of proper documentation layout/organization.

Concepts & Definitions

Undo is organized as an 'undo stack' storing a list of 'undo steps'. The whole stack represents the history of what is undoable.

Typically, only data is stored and managed in undo, UI changes are not.

Undo steps could be either:

  • Relative: A step can only be loaded if the previous (or next) one has already be loaded. In other words, the stack needs to be processed sequentially through all steps from the current (active) one until the target one is reached.
  • Absolute: A step contains the whole data and can be directly loaded as its own from any point of the history.

Currently, Blender undo stack is fully relative. Some parts of it could also be handled as absolute if needed in the future (at least the global undo ones effectively store the whole data).

Further more, we have two kinds of steps:

  • Stateful: The undo step stores the state of some data, and can be loaded regardless of the direction (undo or redo).
  • Differential: The undo step only stores the difference to the previous step. Further more, they cannot be loaded, but either applied (redo) or unapplied (undo). One key consequence of this is that with such steps, when undoing, you actually need to process (unapply) the n + 1 step, not the target n step.

Overview of Implementation

There is a single stack that gathers steps of different types (like global undo, edit modes undo, sculpt or paint undo, etc.).

The current state in Blender matches the active undo step, this information is stored in the stack as well.

The undo system in Blender is currently fully relative, which means to reach a given undo step, you also need to load all those in-between the target and the current active one.

Steps have a type, with a set of callbacks that actually implement the undo storage and loading.

Some skipped steps may be hidden from the user (in the history e.g.), those are used when an intermediate state storage is needed, that does not actually match a state in the history from the user point of view. 'Skipped' steps should never become 'active', they should always only be intermediary steps.

The main part of step creation ('undo push') is controlled by the operator management system (from the Window manager code).

Code Layout

Undo system is implemented in three layers:

Top level Editor Code

This code (mostly located in ed_undo.c file) is essentially operators to provide various undo-related operations (basic one-step undo/redo, exposing the undo stack as an history, handle operators parameters editing as an undo/re-execution process, etc.). It also contains some high-level API to push undo steps.

BKE Undo System

In undo_system.c there is the main undo stack management code, including creation of steps, and undoing/redoing.

This code handles the processing of intermediate steps, proper handling of 'skipped' ones, and deals with the specificities of the two kinds of steps (stateful vs. differential).

Specific Step Type Implementation

Actual undo types implementations happens by defining and registering an UndoType.

They are scattered in several sub-modules of the ED area (like sculpt_undo.c, memfile_undo.c, etc.). Many of those use in turn low-level code from BKE or other areas (e.g. memfile undo uses BKE_memfile_ functions from blender_undo.c, which in turns uses read/write .blend file code from BLO).