docs: expand undo manager API docs

This commit is contained in:
2026-04-03 13:10:55 +08:00
parent b3c5fb59a0
commit 0a76d2150a
15 changed files with 421 additions and 16 deletions

View File

@@ -0,0 +1,26 @@
# UndoManager::BeginInteractiveChange
开始一段可合并的交互式修改。
```cpp
void BeginInteractiveChange(const std::string& label) override;
```
## 参数
- `label` - 最终写入历史的命令标签。
## 行为说明
当前实现只有在没有 pending interactive change 时才会生效:
- 保存 `label`
- 立刻调用 [CaptureCurrentState](CaptureCurrentState.md) 抓取 `before` 快照
如果已经有一段 pending 交互,这次调用会被直接忽略。
## 相关文档
- [UndoManager](UndoManager.md)
- [HasPendingInteractiveChange](HasPendingInteractiveChange.md)
- [FinalizeInteractiveChange](FinalizeInteractiveChange.md)

View File

@@ -0,0 +1,21 @@
# UndoManager::CanRedo
判断当前是否可以重做。
```cpp
bool CanRedo() const override;
```
## 返回值
-`m_nextIndex < m_history.size()` 时返回 `true`
## 当前实现说明
- 只要插入一条新命令并截断 redo 尾巴,之前可重做的历史就会全部失效。
## 相关文档
- [UndoManager](UndoManager.md)
- [Redo](Redo.md)
- [PushCommand](PushCommand.md)

View File

@@ -0,0 +1,22 @@
# UndoManager::CanUndo
判断当前是否可以撤销。
```cpp
bool CanUndo() const override;
```
## 返回值
-`m_nextIndex > 0` 时返回 `true`
## 当前实现说明
- 只看历史游标,不会因为存在 pending interactive change 而直接返回 `true`
- 如果想把 pending 交互也并入历史,需要先 [FinalizeInteractiveChange](FinalizeInteractiveChange.md)。
## 相关文档
- [UndoManager](UndoManager.md)
- [Undo](Undo.md)
- [GetUndoLabel](GetUndoLabel.md)

View File

@@ -0,0 +1,24 @@
# UndoManager::CancelInteractiveChange
取消当前 pending 的交互式修改记录。
```cpp
void CancelInteractiveChange() override;
```
## 行为说明
当前实现只做一件事:
- `m_pendingInteractiveChange.reset()`
## 注意事项
- 它不会恢复场景或选择集到交互开始前的状态。
- 它只意味着“不要把这段交互写进 undo 历史”。
## 相关文档
- [UndoManager](UndoManager.md)
- [BeginInteractiveChange](BeginInteractiveChange.md)
- [FinalizeInteractiveChange](FinalizeInteractiveChange.md)

View File

@@ -0,0 +1,27 @@
# UndoManager::CaptureCurrentState
抓取当前场景和选择集的 undo 快照。
```cpp
UndoStateSnapshot CaptureCurrentState() const override;
```
## 行为说明
当前实现会:
1. 调用 `SceneManager::CaptureSceneSnapshot()` 保存场景快照。
2. 如果当前没有 active scene直接返回该快照。
3. 遍历 `selectionManager.GetSelectedEntities()`
4. 只把仍能通过 `SceneManager::GetEntity(id)` 找到的实体 ID 写入 `selectionIds`
## 当前实现含义
- 选择集快照会自动去掉无效实体,避免把悬空 ID 写进历史。
- 这也是交互式 undo 在开始和结束阶段抓取 `before` / `after` 状态的基础接口。
## 相关文档
- [UndoManager](UndoManager.md)
- [PushCommand](PushCommand.md)
- [BeginInteractiveChange](BeginInteractiveChange.md)

View File

@@ -0,0 +1,26 @@
# UndoManager::ClearHistory
清空撤销/重做历史。
```cpp
void ClearHistory() override;
```
## 行为说明
当前实现会同时:
- 清空 `m_history`
-`m_nextIndex` 重置为 `0`
- 丢弃尚未结束的交互式修改
## 当前实现含义
- 调用后 `CanUndo()``CanRedo()` 都会变为 `false`
- 这不会修改当前场景内容,只清理历史状态。
## 相关文档
- [UndoManager](UndoManager.md)
- [CanUndo](CanUndo.md)
- [CancelInteractiveChange](CancelInteractiveChange.md)

View File

@@ -0,0 +1,21 @@
# UndoManager::Constructor
构造默认的撤销/重做管理器。
```cpp
UndoManager(SceneManager& sceneManager, ISelectionManager& selectionManager);
```
## 参数
- `sceneManager` - 用于抓取和恢复场景快照。
- `selectionManager` - 用于读取和恢复当前选择集。
## 行为说明
当前构造函数只保存这两个依赖的引用,不会在构造时自动抓取初始快照,也不会预填任何历史记录。
## 相关文档
- [UndoManager](UndoManager.md)
- [CaptureCurrentState](CaptureCurrentState.md)

View File

@@ -0,0 +1,29 @@
# UndoManager::FinalizeInteractiveChange
结束当前交互式修改,并把它提交为一条历史记录。
```cpp
void FinalizeInteractiveChange() override;
```
## 行为说明
如果当前没有 pending interactive change直接返回。
否则当前实现会:
1. 取出 pending 的 `label``before` 快照。
2. 再次调用 [CaptureCurrentState](CaptureCurrentState.md) 抓取当前 `after` 快照。
3. 通过 [PushCommand](PushCommand.md) 写入历史。
4. 清空 pending 状态。
## 当前实现含义
- 如果交互前后状态完全没变,`PushCommand()` 会把这次提交静默丢弃。
- `Undo()` / `Redo()` 在发现有 pending 交互时,也会先走这里。
## 相关文档
- [UndoManager](UndoManager.md)
- [PushCommand](PushCommand.md)
- [CancelInteractiveChange](CancelInteractiveChange.md)

View File

@@ -0,0 +1,18 @@
# UndoManager::GetRedoLabel
获取下一次重做操作的标签。
```cpp
const std::string& GetRedoLabel() const override;
```
## 返回值
- 若当前可重做,返回 `m_history[m_nextIndex].label`
- 否则返回内部持有的空字符串引用 `m_emptyLabel`
## 相关文档
- [UndoManager](UndoManager.md)
- [CanRedo](CanRedo.md)
- [Redo](Redo.md)

View File

@@ -0,0 +1,23 @@
# UndoManager::GetUndoLabel
获取下一次撤销操作的标签。
```cpp
const std::string& GetUndoLabel() const override;
```
## 返回值
- 若当前可撤销,返回 `m_history[m_nextIndex - 1].label`
- 否则返回内部持有的空字符串引用 `m_emptyLabel`
## 注意事项
- 返回的是稳定引用,不是临时字符串。
- 标签只用于 UI 展示,不代表命令类型系统。
## 相关文档
- [UndoManager](UndoManager.md)
- [CanUndo](CanUndo.md)
- [Undo](Undo.md)

View File

@@ -0,0 +1,17 @@
# UndoManager::HasPendingInteractiveChange
判断当前是否有尚未结束的交互式修改。
```cpp
bool HasPendingInteractiveChange() const override;
```
## 返回值
-`m_pendingInteractiveChange.has_value()` 为真时返回 `true`
## 相关文档
- [UndoManager](UndoManager.md)
- [BeginInteractiveChange](BeginInteractiveChange.md)
- [FinalizeInteractiveChange](FinalizeInteractiveChange.md)

View File

@@ -0,0 +1,34 @@
# UndoManager::PushCommand
向历史中压入一条新的 undo/redo 记录。
```cpp
void PushCommand(const std::string& label, UndoStateSnapshot before, UndoStateSnapshot after) override;
```
## 参数
- `label` - 命令显示标签。
- `before` - 执行前快照。
- `after` - 执行后快照。
## 行为说明
当前实现会:
1. 用私有 `AreStatesEqual()` 比较 `before``after`,完全一致则直接丢弃。
2. 如果 `m_nextIndex` 还在历史中间位置,先删除 redo 尾巴。
3. 追加新的 `CommandEntry`
4. 若历史超过 `128` 条,则删除最旧一条,并同步调整游标。
5. 最终把 `m_nextIndex` 设到历史末尾。
## 当前实现含义
- 任何新命令都会使旧的 redo 分支失效。
- 历史上限是固定窗口,不是无限增长日志。
## 相关文档
- [UndoManager](UndoManager.md)
- [Undo](Undo.md)
- [Redo](Redo.md)

View File

@@ -0,0 +1,22 @@
# UndoManager::Redo
重做当前历史游标指向的下一条命令。
```cpp
void Redo() override;
```
## 行为说明
当前实现会按顺序执行:
1. 如果存在 pending interactive change先调用 [FinalizeInteractiveChange](FinalizeInteractiveChange.md)。
2. 如果 [CanRedo](CanRedo.md) 为假,直接返回。
3. 应用 `m_history[m_nextIndex].after` 快照。
4.`m_nextIndex` 向后移动一格。
## 相关文档
- [UndoManager](UndoManager.md)
- [Undo](Undo.md)
- [CanRedo](CanRedo.md)

View File

@@ -0,0 +1,27 @@
# UndoManager::Undo
回退到上一条历史记录之前的状态。
```cpp
void Undo() override;
```
## 行为说明
当前实现会按顺序执行:
1. 如果存在 pending interactive change先调用 [FinalizeInteractiveChange](FinalizeInteractiveChange.md)。
2. 如果 [CanUndo](CanUndo.md) 为假,直接返回。
3.`m_nextIndex` 向前移动一格。
4. 对新的当前位置应用 `before` 快照。
## 当前实现含义
- `Undo()` 会自动把尚未提交的交互式修改压成一条历史,再执行撤销。
- 选择集恢复会经过实体有效性过滤,已不存在的实体不会重新选中。
## 相关文档
- [UndoManager](UndoManager.md)
- [Redo](Redo.md)
- [FinalizeInteractiveChange](FinalizeInteractiveChange.md)

View File

@@ -6,34 +6,102 @@
**源文件**: `editor/src/Core/UndoManager.h`
**描述**: 基于场景快照和选择快照实现编辑器撤销/重做历史,并支持交互式修改合并提交
**描述**: 基于场景快照和选择快照维护编辑器撤销/重做历史,并把 gizmo 拖拽、属性编辑这类交互式修改合并成单条 undo 命令
## 概述
## 角色概述
`UndoManager` 是当前 [IUndoManager](../IUndoManager/IUndoManager.md) 的默认实现。
`UndoManager` 是当前 [IUndoManager](../IUndoManager/IUndoManager.md) 的默认实现。它不是细粒度命令对象系统,而是“场景快照 + 选择快照”的历史管理器。
它内部维护
核心依赖有两个
- 有标签的命令历史 `m_history`
- 当前历史游标 `m_nextIndex`
- 一个可选的交互式修改暂存 `m_pendingInteractiveChange`
- [SceneManager](../../Managers/SceneManager/SceneManager.md):负责抓取和恢复 `SceneSnapshot`
- `ISelectionManager`:负责恢复选择集。
## 当前实现说明
因此它真正管理的不是单个字段改动,而是整个编辑器状态快照:
- 历史上限当前固定为 `128`
- `Undo()` 会先 finalize 未结束的交互修改,再回退到 `before` 快照。
- `Redo()` 同理会应用 `after` 快照。
- `CaptureCurrentState()` 会抓取当前场景快照,并仅保留仍然有效的选中实体 ID。
- `PushCommand()` 会在状态没变化时直接丢弃该记录。
- 场景文档内容
- 场景路径和 dirty 标记
- 当前仍然有效的选中实体 ID 集合
## 当前状态模型
内部主要维护三组状态:
| 状态 | 类型 | 作用 |
|------|------|------|
| `m_history` | `std::vector<CommandEntry>` | 按顺序保存 undo/redo 历史。每项同时持有 `before``after` 快照。 |
| `m_nextIndex` | `size_t` | 指向“下一次 redo 应用的历史项”。也可理解为当前历史游标。 |
| `m_pendingInteractiveChange` | `std::optional<PendingInteractiveChange>` | 用于把拖拽或连续输入期间的多次变化合并成一条命令。 |
当前历史上限固定为 `128` 条;超出后会丢弃最旧记录。
## 历史行为
### `PushCommand()`
[PushCommand](PushCommand.md) 会把一条命令写入历史,并自动处理两件事:
- 如果当前在历史中间位置插入新命令,会先丢弃 redo 尾巴。
- 如果 `before``after` 快照完全相同,则直接忽略,不写空命令。
### `Undo()` / `Redo()`
[Undo](Undo.md) 与 [Redo](Redo.md) 都会先检查是否有尚未结束的交互式修改;如果有,会先 [FinalizeInteractiveChange](FinalizeInteractiveChange.md)。
之后:
- `Undo()` 回退到当前命令的 `before` 快照
- `Redo()` 应用当前命令的 `after` 快照
实际恢复由私有 `ApplyState()` 完成,它会先恢复场景,再把选择集过滤到仍然存在的实体上。
### 交互式修改合并
以下 API 共同组成“开始拖拽 -> 持续修改 -> 结束提交”的交互式 undo 流程:
- [BeginInteractiveChange](BeginInteractiveChange.md)
- [HasPendingInteractiveChange](HasPendingInteractiveChange.md)
- [FinalizeInteractiveChange](FinalizeInteractiveChange.md)
- [CancelInteractiveChange](CancelInteractiveChange.md)
这套模式当前被 viewport gizmo、PropertyGrid 和部分 Inspector 编辑器调用,用来把一整次拖拽合并成单条历史记录,而不是每帧都生成一条 undo。
## 快照内容
[CaptureCurrentState](CaptureCurrentState.md) 会生成 [UndoStateSnapshot](../IUndoManager/IUndoManager.md)
- `scene` 来自 `SceneManager::CaptureSceneSnapshot()`
- `selectionIds` 只保留当前仍能通过 `SceneManager::GetEntity()` 找到的实体
如果当前没有 active scene快照仍会返回但其中 `scene.hasScene` 为假,选择集也不会继续扩展。
## 当前实现边界
- 是快照式 undo是细粒度命令式 undo
- 历史存储大小和场景序列化数据量直接相关
- `ApplyState()` 当前依赖 [SceneManager](../../Managers/SceneManager/SceneManager.md) 恢复场景,然后再恢复选择
- 当前是快照式 undo适合超大场景的高频细粒度编辑
- `CancelInteractiveChange()` 只丢弃 pending 记录,不会把场景回滚到交互开始前
- `Undo()` / `Redo()` 调用了 `ApplyState()` 但没有处理其 `false` 返回值;当前行为更偏“尽力恢复”
- 历史标签只是 UI 文本,不携带额外命令元数据。
## 相关方法
- [Constructor](Constructor.md)
- [ClearHistory](ClearHistory.md)
- [CanUndo](CanUndo.md)
- [CanRedo](CanRedo.md)
- [GetUndoLabel](GetUndoLabel.md)
- [GetRedoLabel](GetRedoLabel.md)
- [Undo](Undo.md)
- [Redo](Redo.md)
- [CaptureCurrentState](CaptureCurrentState.md)
- [PushCommand](PushCommand.md)
- [BeginInteractiveChange](BeginInteractiveChange.md)
- [HasPendingInteractiveChange](HasPendingInteractiveChange.md)
- [FinalizeInteractiveChange](FinalizeInteractiveChange.md)
- [CancelInteractiveChange](CancelInteractiveChange.md)
## 相关文档
- [Core](../Core.md)
- [IUndoManager](../IUndoManager/IUndoManager.md)
- [SceneSnapshot](../SceneSnapshot/SceneSnapshot.md)
- [SceneManager](../../Managers/SceneManager/SceneManager.md)