(This is in huge WIP status)
The Undo/Redo system in a game editor is a functionality that allows developers to cancel (Undo) or repeat (Redo) the last actions performed in the process of creating or editing game content.
This system typically includes:
Such functionality helps developers experiment with different ideas, quickly fix mistakes, and effectively manage the game creation process.
Implementing such a system in an editor for Bevy would significantly improve the usability and productivity of developers working with this game engine.
Implementing an effective Undo/Redo system for Bevy requires careful consideration of the engine's unique architecture and the needs of its community. While there are many existing implementations in other game engines and editors, it's crucial to tailor our approach to Bevy's Entity-Component-System (ECS) paradigm and the specific workflows of Bevy developers.
To design an appropriate Undo/Redo system, we need to address two key questions:
By clearly defining these aspects, we can create a system that integrates seamlessly with Bevy's architecture and provides a smooth, intuitive experience for developers.
In any editor, no matter what it edits, what you want to be mutating/undoing/redoing is the ultimate source of truth; you don't want the editor to be modifying data that is generated from the source of truth.
For example, a .bsn (Bevy Scene Notation) file could serve as the source of truth. The entire scene displayed in the editor might be visualized based on this file, and only changes to the .bsn file would have a real impact on the game content state. Therefore, we need to clearly identify what constitutes the source of truth for our data in a Bevy editor context.
The main data sources that may be encountered in a Bevy editor:
Data Source | Actions that can be performed | Example |
---|---|---|
Components | adding, modifying, deleting | Transform, Sprite, Collider |
Resources | adding, modifying, deleting | Editor settings, global game state |
Assets | adding, modifying, deleting | Height map, textures, models, audio files |
Entities | creating, destroying | |
Remote data | creating, modifying, deleting | Any data that is outside the ecs world. For example, a file scene in .bsn format or remote ecs world. In either case, this is data to which we do not have direct byte access within ecs world. |
In my view, this is an exhaustive list of what a Bevy editor can operate on. Even if a Bevy editor manages some remote game, we're still talking about changing some components, resources, entities, assets, and files, as these are fundamental data units in the ECS paradigm. Anything that falls outside these categories is likely not ECS and is not our main focus.
The following structures, inherent to ECS, cannot be a source of the true state of game content, as they are tools for changing it:
Blender documentation (https://developer.blender.org/docs/features/core/undo/) provides a clear classification of actions that can be registered in the undo/redo system:
Furthermore, we have two kinds of steps:
In the context of game development, not all types of undo steps are equally applicable. For long-term version management in game development, version control systems such as Git or Subversion are typically used. The built-in undo/redo system in an editor is usually used for a small number of sequential rollbacks, which corresponds well with the logic of relative steps. Thus, it is proposed to implement only relative undo steps.
It's important to assign clear names to all user actions. This will allow for creating convenient menus, for example: "Undo Create Resource Ctrl-Z". And it will increase understanding of what is being undone in the editor.
Stateful approaches:
Differential approaches:
In Bevy undo/redo, support for both stateful and differential approaches is required:
Also, a useful property of the Undo system could be informing the user about unsaved actions on the data source.
Undo/redo timeline is an ordered sequence of data changes, along which we modify data using Undo and Redo. Timeline is not store data. It stores only changes for data.
It's important to note that Undo is not only about BSN and scenes, but also about editor settings, text fields, changes in the state of editor-specific interface elements, as well as the basis for custom in-game editors.
Obviously, for example, the timelines for editor settings and scene changes should be separate. While in the process of editing the game, we don't want to accidentally roll back an important editor setting. So the presence of multiple undo/redo timelines is definitely required.
Each Undo/redo timeline is tied to some data, which we will further refer to as timeline data. It is proposed to build the undo/redo timeline based on three principles: nested timelines, directional acyclic graph (DAG), and collapsible changes.
Scene editing involves manipulations at different levels of hierarchy. We can delete or create an entire scene (top level of hierarchy), as well as modify the name of an entity in a text field (lowest level of hierarchy). At the text field level, it's convenient to use undo down to a single character. Whereas at the scene level, it's more convenient to move between the points of the start and end of name editing. That is, all small changes are nested within one large change in the higher-level timeline.
Some changes can be prolonged and almost continuous in time. For example, moving an object using a gizmo or dragging a slider. Such changes should store in the undo/redo timeline only one change that modifies the data between the start point and end point of the changes.
Multiple independent undo/redo timelines can exist simultaneously. However, these multiple timelines can be created from a single point. For example, scene viewer widget and scene editor widget should have related but different timelines. And the linking point will be the creation/opening/closing of the scene. If we undo the scene creation, the scene should be deleted, but with redo we should be able to return to the scene and its timeline at the moment of closing by the undo operation.
It is proposed to implement the Directed Acyclic Graph (DAG) for Undo/Redo timelines as follows:
The following operations can be performed on the DAG:
If an action deletes data that another timeline depends on, that line along with the data should be cached for restoration in undo. If an action closes a window that worked with a timeline, the timeline data and the timeline itself are cached so that they can be restored to undo the action.
In this approach, all changes in the game world pass through a centralized command system before being applied. For example, you can create an EditorCommand structure through which all user actions will pass.
fn system(mut commannds: EditorCommands // EditorCommands will auto register changes in undo/redo system for world
) {
commands.spawn((Something)).id()
}
File changes
fn system(mut commands: FileCommands) { // Also will automaticly create changes in undo/redo timelines
commands.file(path).append(new_text);
commands.file(path).write(replace_text);
}
Advantages:
Disadvantages:
In this approach, the undo system automatically tracks changes in the game world without requiring explicit command calls.
Advantages:
Disadvantages:
Both approaches have their advantages and disadvantages. The choice between them depends on the specific project requirements, existing architecture, and preferences of the development team. It is also possible to consider a hybrid approach, combining elements of both methods to achieve an optimal balance between control and ease of use.
(Coming soon…?)