Pioneer
Loading...
Searching...
No Matches
UndoSystem.h
Go to the documentation of this file.
1// Copyright © 2008-2024 Pioneer Developers. See AUTHORS.txt for details
2// Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3
4#pragma once
5
6#include "core/StringName.h"
7
8#include <memory>
9#include <string_view>
10#include <vector>
11
12namespace Editor {
13
14/*
15 * UndoStep represents a single unit of work (a single "change") made as part
16 * of some user action.
17 *
18 * An UndoStep should store only enough state to be able to undo or redo said
19 * change, as the non-transient state of the application is expected to be
20 * exactly the same no matter how many times the user has undone/redone the
21 * step or any other steps around it.
22 *
23 * Changes to transient application data (e.g. caches, derived data, etc)
24 * should be triggered immediately by the UndoStep where possible to maintain
25 * consistency in all areas of the application's data model.
26 *
27 * It is invalid for the Undo() or Redo() methods to directly or indirectly
28 * create a new UndoEntry or UndoStep while being executed, as this will
29 * corrupt the undo manager's state.
30 */
31class UndoStep {
32public:
33 UndoStep() = default;
34 virtual ~UndoStep() = default;
35
36 // Execute an undo step (replace application state with stored state)
37 virtual void Undo() { Swap(); }
38
39 // Execute a redo step (replace stored state with application state)
40 virtual void Redo() { Swap(); }
41
42 // Helper method to allow state changes in a single function
43 virtual void Swap() {};
44
45 // Optimization step: entries for which none of the steps represent a
46 // change in state will not be added to the undo stack
47 virtual bool HasChanged() const { return true; }
48};
49
50/*
51 * An UndoEntry is a mostly implementation-specific detail - it represents a
52 * high-level action by the user (e.g. Transform Object) at the most granular
53 * level of undo. One Ctrl+Z should undo one UndoEntry, and vice versa.
54 *
55 * All managed UndoSteps in an entry are applied in program order to maintain
56 * application state consistency (back-to-front undo, front-to-back redo).
57 */
58class UndoEntry {
59public:
60 std::string_view GetName() const { return m_name.sv(); }
61
62private:
63 friend class UndoSystem;
64 UndoEntry() = default;
65
66 void Undo();
67 void Redo();
68
69 bool HasChanged() const;
70
71 StringName m_name;
72 size_t m_id;
73 std::vector<std::unique_ptr<UndoStep>> m_steps;
74};
75
76/*
77 * The UndoSystem provides a central place to manage undo entries for a
78 * specific "undo queue" (e.g. document, window, editor, etc).
79 *
80 * It maintains separate undo/redo stacks under the hood, and allows for
81 * recursion of entries to allow maximum flexibility in calling code.
82 *
83 * Recursive BeginEntry() calls must be matched with EndEntry() calls, and the
84 * name of sub-entries is lost. Undo operates on the granularity of top-level
85 * UndoEntries, so sub-entries only allow composing program operations that
86 * would otherwise require multiple undo interactions for a single user action.
87 *
88 * AddUndoStep() creates a new undo step and adds it to the currently active
89 * UndoEntry. The Redo method of the undo entry is not triggered automatically,
90 * as the semantics of an UndoStep are simpler without the Undo/Do/Redo split.
91 *
92 * Calling BeginEntry() / ResetEntry() / EndEntry() / AddUndoStep() while an
93 * UndoEntry is being undone or redone is a violation of program state and will
94 * cause a crash.
95 *
96 * ============================================================================
97 *
98 * A common pattern is to begin an UndoEntry when the user signals they want
99 * to begin editing (e.g. user clicks on a transform handle) and update that
100 * entry while the user performs some continuous action (such as dragging).
101 *
102 * There are two options to achieve this pattern: if your UndoSteps store only
103 * the "prior state" of the application, you can call BeginEntry() once, submit
104 * one or more UndoSteps, and modify application state as needed over multiple
105 * frames before calling EndEntry().
106 *
107 * Alternatively, if your undo steps must know both the prior and current state
108 * of the application, you can call ResetEntry() each frame to undo and discard
109 * all submitted UndoSteps() in the currently active entry. All submitted steps
110 * will be undone, regardless of BeginEntry() recursion level.
111 */
113public:
114 UndoSystem();
115 ~UndoSystem();
116
117 // Return the total number of undo/redo entries in the stack
118 size_t GetNumEntries() const;
119
120 // Return the index of the "current entry" that describes application state.
121 // This points to the first entry in the redo stack, if any.
122 size_t GetCurrentEntry() const;
123
124 // Return a pointer to the given undo entry if stackIdx < GetNumEntries().
125 const UndoEntry *GetEntry(size_t stackIdx) const;
126
127 // Calculate a hash value used to describe the current undo state
128 size_t GetStateHash();
129
130 bool CanUndo() const { return !m_undoStack.empty(); }
131 bool CanRedo() const { return !m_redoStack.empty(); }
132
133 // Undo the most recent entry in the undo stack (rewind the application state)
134 void Undo();
135
136 // Redo the most recent entry in the redo stack (advance the application state)
137 void Redo();
138
139 // Destroy all undo/redo history (e.g. when closing a file)
140 void Clear();
141
142 // Begin a new undo entry (can be recursive)
143 void BeginEntry(std::string_view name);
144
145 // Return the number of open entries (for debugging purposes)
146 size_t GetEntryDepth() const { return m_openUndoDepth; }
147
148 // Reset the current undo entry to a "clean state"
149 void ResetEntry();
150
151 // Add an undo step to the active undo entry
152 template<typename T, typename ...Args>
153 void AddUndoStep(Args&& ...args)
154 {
155 AddUndoStep(new T(std::forward<Args>(args)...));
156 }
157
158 // End the current undo entry
159 void EndEntry();
160
161private:
162 void AddUndoStep(UndoStep *undo);
163
164 std::vector<std::unique_ptr<UndoEntry>> m_undoStack;
165 std::vector<std::unique_ptr<UndoEntry>> m_redoStack;
166
167 std::unique_ptr<UndoEntry> m_openUndoEntry;
168 size_t m_openUndoDepth;
169 size_t m_entryId;
170
171 bool m_doing;
172};
173
174} // namespace Editor
Definition: UndoSystem.h:58
std::string_view GetName() const
Definition: UndoSystem.h:60
Definition: UndoSystem.h:31
virtual ~UndoStep()=default
virtual void Redo()
Definition: UndoSystem.h:40
virtual void Undo()
Definition: UndoSystem.h:37
virtual bool HasChanged() const
Definition: UndoSystem.h:47
UndoStep()=default
virtual void Swap()
Definition: UndoSystem.h:43
Definition: UndoSystem.h:112
size_t GetEntryDepth() const
Definition: UndoSystem.h:146
void Clear()
Definition: UndoSystem.cpp:110
void Redo()
Definition: UndoSystem.cpp:95
size_t GetCurrentEntry() const
Definition: UndoSystem.cpp:53
void ResetEntry()
Definition: UndoSystem.cpp:137
size_t GetStateHash()
Definition: UndoSystem.cpp:71
size_t GetNumEntries() const
Definition: UndoSystem.cpp:48
void Undo()
Definition: UndoSystem.cpp:80
void AddUndoStep(Args &&...args)
Definition: UndoSystem.h:153
bool CanRedo() const
Definition: UndoSystem.h:131
const UndoEntry * GetEntry(size_t stackIdx) const
Definition: UndoSystem.cpp:58
void BeginEntry(std::string_view name)
Definition: UndoSystem.cpp:124
bool CanUndo() const
Definition: UndoSystem.h:130
void EndEntry()
Definition: UndoSystem.cpp:146
~UndoSystem()
Definition: UndoSystem.cpp:44
UndoSystem()
Definition: UndoSystem.cpp:37
Definition: StringName.h:18
std::string_view sv() const
Definition: StringName.h:46
Definition: ActionBinder.h:14