Skip to content

命令历史 ​

History 是独立于 Engine core 的异步 undo/redo 栈,从 @tripo3d/engine/history 导入:

ts
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 和业务命令仍归应用。

基本操作 ​

ts
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 ​

ts
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 ​

ts
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 与差值,避免长期持有大场景。

下一步 ​

基于 MIT 协议发布(内部使用)