/** * 历史执行规划:把 undo/redo 给出的 changes 应用到私有文档副本。 * 本模块不移动 undo/redo 栈、不发请求;editor/coordinator 在规划成功后统一发布。 * 删除后恢复的元素沿用 ID,但必须获得大于墓碑的新 incarnation,使旧异步回调失效。 */ import type { Change, Document, ElementRecord, Id, Registry } from './types.ts'; import { applyChanges, validateDocument } from './document.ts'; import { freeze } from './values.ts'; /** Pure history execution, also used to validate recovery proposals before publication. */ /** 顺序回放一组历史修改,完成整份文档校验后返回候选文档和实际 changes。 * remap 供调用者把其它历史目标同步映射到恢复后的 incarnation;函数不发布任何候选。 * initial/planned 应来自已捕获的不可变状态,底层 applyChanges/freeze 不是任意外部对象的深隔离入口。 */ export function planHistoryChanges(initial:Document,planned:readonly Change[],registry:Registry){ // ID →(旧 incarnation → 新 incarnation);同一批恢复可能形成多级映射链。 const generations=new Map>(); // 追踪到本批最终代次;每次恢复分配更大代次,因此按这些内部生成的映射链前进。 const incarnation=(id:Id,value:number)=>{const mapping=generations.get(id);while(mapping?.has(value))value=mapping.get(value)!;return value;}; // 只改生命周期身份:字段目标与整元素 before/after 都要一致,canvas 变更原样通过。 const remap=(change:Change):Change=>{ if(change.kind==='field'){const next=incarnation(change.target.id,change.target.incarnation);return next===change.target.incarnation?change:freeze({...change,target:{...change.target,incarnation:next}});} // 整元素 before/after 只替换顶层 incarnation,记录内其它字段与引用不在此重新解释。 if(change.kind==='element'){const record=(value:ElementRecord|null)=>value?{...value,incarnation:incarnation(value.id,value.incarnation)}:null;return freeze({...change,before:record(change.before),after:record(change.after)});} return change; }; let document=initial;const changes:Change[]=[]; // 私有 draft 逐项推进,因此同一批后面的恢复可以看到前面已写入的墓碑。 for(const original of planned){ let change=remap(original); // 从不存在恢复为存在时,若原代次已死,分配 dead+1 并重写本条及后续相关变化。 if(change.kind==='element'&&!change.before&&change.after){const dead=document.tombstones.get(change.id);if(dead!==undefined&&change.after.incarnation<=dead){const mapping=generations.get(change.id)??new Map();mapping.set(change.after.incarnation,dead+1);generations.set(change.id,mapping);change=remap(change);}} document=applyChanges(document,[change]);changes.push(change); } // 暂态允许先恢复端点再恢复边;只有整批完成后才检查最终树、引用和 owner 一致性。 validateDocument(document,registry); // restored 只表示是否建立过恢复代次映射;即使 false,文档也可能因普通字段/删除变化而改变。 return{document,changes:freeze(changes),remap,restored:generations.size>0}; }