命令历史
History 是独立于 Engine core 的异步 undo/redo 栈,从 @tripo3d/engine/history 导入:
import {
createHistory,
createHistoryPlugin,
} from '@tripo3d/engine/history';
const history = createHistory(100);
const engine = createEngine({
plugins: [createHistoryPlugin(history)],
});createHistoryPlugin 只在 Engine dispose 时调用 borrowed History 的 clear();History identity 和业务命令仍归应用。
基本操作
await history.execute({
label: 'move',
redo: () => object.position.x = 2,
undo: () => object.position.x = 0,
});
if (history.canUndo) {
await history.undo();
}
if (history.canRedo) {
await history.redo();
}execute/undo/redo/transaction 都返回 Promise。History 同一时刻只允许一个 active operation;并发或 reentrant 调用会直接拒绝,不排队。
命令失败时不会移动栈,但 History 无法自动逆转业务 callback 已经产生的部分副作用。需要原子性的命令应在自己的 redo/undo 内完成 rollback。
transaction
await history.transaction('batch move', async (transaction) => {
await transaction.execute(firstCommand);
await transaction.execute(secondCommand);
});提交规则:
- 0 条成功命令:不入栈;
- 1 条:直接作为普通命令入栈;
- 多条:形成一条 composite,redo 正序、undo 逆序。
每个 child 必须 await;executor settlement 时仍有 active child 会拒绝整个 transaction。transaction context 结束后不能保留再用。
任一 child 的准入或执行失败都会使整个 transaction 失败。即使 executor 捕获该错误,transaction() 最终仍会以首个 child 错误 reject,并且不提交任何 History entry。Transaction 不提供自动补偿:此前已经成功执行的 child 不会入栈,也不会被自动 undo,其业务副作用仍然存在。
需要部分成功语义时,应拆成多次顶层 execute();需要原子性时,应由业务命令负责显式 rollback。
clear
history.clear();clear 同步清空 undo/redo 栈。如果恰有 active operation,它不会取消业务 Promise,但会阻止该 operation 后续 child 或 commit。适合切换项目、销毁编辑会话或 Engine dispose。
Vue 状态
History 是命令服务,不是响应式 store。UI 可以在每次 History 操作完成后更新少量 Vue refs;不要把 History 或命令捕获的 Three.js 对象放入深响应式状态。
所有权建议
- History 由需要 undo/redo 的业务模块创建;
- Engine 不提供
engine.history; - 想在 Engine dispose 时清栈,显式安装
createHistoryPlugin(history); - 其他清栈时机,如打开新项目或重置场景,由业务调用
clear(); - 命令闭包只捕获必要 identity 与差值,避免长期持有大场景。