切换关卡:Load 与 Unload 必须组成可补偿事务
自动关联目录:切换关卡:Load 与 Unload 必须组成可补偿事务
核心判断:编辑态切关同时修改磁盘版本、活对象树和当前关卡身份。它不是两个函数的顺序调用,而是带提交点和补偿动作的 Saga。
一、业务问题:切关失败后不能只显示错误提示
创作者从关卡 A 切到 B,需要先保存 A、验证 B、卸载 A、挂载 B,再把 B 发布为活动关卡。B 可能因内容损坏、依赖缺失或对象创建失败而只挂载一半;Manifest 提交也可能在活场景已经切换后失败。
如果实现只是 Unload(A); Load(B),失败后编辑器可能没有可编辑关卡,或者界面显示 A、对象树却属于 B。
二、实际实现:明确阶段、提交点和源片段补偿
工程中的切关流程已经很接近事务模型:
Save A
-> Build and validate merged B
-> Preflight B
-> Capture A/B fragments
-> Detach A
-> Incrementally attach B
-> Commit active-level manifest在提交前,B 挂载失败会重新挂载刚保存的 A 片段;B 已挂载但提交失败,会先清理 B 再恢复 A。若清理或恢复失败,协调器进入致命一致性状态,禁止继续假装可编辑。若 Manifest 已经提交,只是后续快照或清理失败,则保留 B,并明确返回“目标已提交但维护失败”,不能错误恢复已经不再权威的 A。
此外,切关会拒绝正在拖 Gizmo、属性编辑、待附着或其他结构操作的状态,避免把半个编辑事务带到新关卡。
三、值得保留与改进方向
值得保留的是:预检发生在卸载前;源片段来自刚完成的保存;活动关卡身份只在提交后改变;每个阶段都有稳定错误分类。
仍需提高的是把补偿责任集中到一个显式状态机,而不是依赖多个回调共同遵守顺序:
enum Phase { Saving, Prepared, SourceDetached, TargetAttached, Committed };
onFailure(phase):
if (phase >= TargetAttached && phase < Committed) detachTarget();
if (phase >= SourceDetached && phase < Committed) restoreSourceSnapshot();
if (!proveConsistent()) enterRecoveryOnlyMode();每次事务带 SwitchId 和源/目标 generation,迟到的异步 Attach 结果必须核对身份。UE Actor 销毁和 Component 注销可能跨帧,恢复完成也不能只看函数返回,需要等待对象树、碰撞和选择索引达到一致屏障。
四、验证结论
在保存、预检、卸载、挂载、提交、清理和恢复各阶段注入失败;验证 UI 当前关卡、Catalog、活对象树、选择/Gizmo、脚本上下文和磁盘 Manifest 始终指向同一结果。还要覆盖连续快速切换、创建后首次切入、切换中关闭项目,以及提交成功后快照失败。
切关正确性的标准不是“多数时候能切过去”,而是任意失败点都能说清楚当前权威状态,以及下一步由谁恢复。