撤销架构:统一栈不应成为所有编辑域的巨型联合体

自动关联目录:撤销架构:统一栈不应成为所有编辑域的巨型联合体

核心判断:多个编辑前端共享同一条权威历史是正确的;但若每增加一种编辑能力,都向中央记录结构追加枚举和一组可空字段,统一栈会从一致性中心退化为全系统耦合点。

一、实际设计解决了什么

属性面板、场景树、Gizmo 和脚本工具最终写入同一套 C++ 历史,并按场景与 Prefab 文档分栈。它消除了“某个前端改了内容,另一个前端不知道如何撤销”的多真相问题,也让最大步数、单事务大小和清空时机能集中治理。

记录范围覆盖属性、选择、创建删除、重父、复制、片段编辑和行为配置。问题是中央记录采用“类型枚举 + 所有类型的全部载荷字段”。新增领域不仅要实现自己的逆操作,还要改中央结构、分发器、内存估算和清理路径。

二、结构性风险

  • 无效字段组合只能靠约定,类型系统不能证明记录完整。
  • 一个领域的载荷变化会触发中央模块重编译和回归。
  • 跨域批次容易把“同一用户意图”误解成“同一种记录结构”。
  • 序列化、诊断和内存计费会积累越来越大的 switch。

统一历史应该统一顺序、批次、预算和生命周期,而不是统一每种变更的内部表示。

三、目标设计:领域变更自描述,历史栈只编排

struct ChangeCodec {
    ChangeTypeId type;
    Bytes Encode(const ChangePayload&);
    Result Apply(EditModel&, BytesView);
    Result Revert(EditModel&, BytesView);
    size_t EstimateBytes(BytesView);
    Validation Validate(BytesView);
};

struct HistoryEntry {
    TransactionId transaction;
    EditScopeId scope;
    ChangeTypeId type;
    Bytes immutablePayload;
};

领域注册稳定 ChangeTypeId 与 Codec;历史核心只负责批次顺序、作用域、容量和调用。若不需要持久化,可用受控的类型擦除对象;若需要跨版本恢复,应使用显式版本化载荷,而不是 C++ 对象布局。

四、边界与验证

插件化会把编译期错误推迟到注册期,因此启动时必须校验类型唯一、Codec 版本和 Apply/Revert 成对存在。跨域事务仍由一个批次持有多条有序变更,失败时按逆序补偿。

验证重点不是“每个按钮按一次 Ctrl+Z”,而是生成混合领域批次,随机在第 N 条 Apply/Revert 注入失败,检查历史游标、模型哈希与错误诊断。可迁移的经验是:统一真相,不等于统一数据结构。