跨资源编辑恢复
全库目录221
本文目录6
操作J把对象O的资产引用从V10切到V11,文件版本已经提交,宿主呈现却可能中途失败。本文定义共同提交日志、包内操作标记和条件逆操作,使每个恢复步骤都有明确前态。普通手势与宿主API见编辑手势与撤销,批次计划及回执见批处理条件发布。
对象与文件的共同提交
文件版本发布和宿主撤销缓冲是两个独立系统。若先发布资产再修改场景对象,中途退出会留下“新文件、旧引用”;先修改对象也可能在文件发布失败后留下悬空引用。这里采用持久变更日志作为共同提交点,宿主对象作为它的可恢复呈现。普通字段手势采用编辑手势与撤销的宿主事务;同时涉及对象与外部文件的操作进入以下协议。
每个操作保存唯一operationId、输入版本、对象稳定ID及创建删除快照、字段前后值和版本、旧新文件清单、预期发布代次,以及逆操作所需内容。日志中对象补丁保存其拥有的字段;未参与操作的字段不因恢复整对象快照而被覆盖。创建删除才需要完整身份、层级与引用快照。暂存文件验证并持久化后,日志写入Prepared。这个状态表示恢复材料齐全,尚未决定采用新结果。
随后执行器锁住涉及的对象和发布入口,阻止这些对象编辑、保存、构建与导出,重新比较对象版本及文件代次。一个数据库事务同时保存Committed决定、对象目标补丁、新文件版本指针和发布回执。文件内容在事务之前已就绪,事务只引用其不可变路径。对象目标补丁是持久权威状态;宿主内存中的对象与包文件是随后需要追上的呈现。读取资产的其他进程也从这个版本清单取配套对象数据,能够按清单解释对象目标补丁的消费者可读取这份逻辑版本;只支持宿主包的消费者等待Applied,才能读取已完成呈现的包。各入口都固定同一版本,不能一边读新指针、一边绕过清单读旧场景包。
提交之后,主线程按稳定ID将目标补丁呈现到宿主。每个受影响包都保存对象字段和同包序列化的materializedOperationId、targetVersion,二者经过同一次包保存持久化。单独更新数据库里的“字段已完成”只能记录进度,不能证明包已经保存。执行前持久记录呈现意图;当前值等于旧值且持久版本符合预期时应用新值,同包标记已是本操作且字段符合目标时视为重复恢复。创建对象先建立全部身份,再连接引用;引擎重建可能替换对象,所以每一步重新解析ID。
内存呈现到一半抛异常时,先取消这次未保存的宿主呈现,重新加载已保存包,再按持久目标重放。重新加载只影响被门控的相关包;进入协议前先保存包内其他用户修改或将它们纳入持久快照,不能丢弃操作外的未保存字段。包保存使用宿主安全保存入口,保存完成后重新读取字段和同包标记,全部符合目标才把包记为持久完成。若包写出成功、数据库进度尚未记录就退出,重启读到同包标记可补记;若宿主格式无法保证整包安全保存,先保存到新包文件并校验,再由版本清单选择它,保留原包用于恢复。
全部包完成后记录Applied并解除保存和导出门控。门控覆盖自动保存及构建回调,否则后台保存仍可能写出混合状态。启动器在开放项目、加载可编辑对象或启动构建之前扫描Committed日志,重建相关资源门控并完成恢复;它不依赖已经随进程消失的内存锁。
进程退出后先执行恢复再开放相关资源。Prepared且没有Committed决定的操作释放暂存资源,保留原指针和原对象。Committed操作则按目标补丁继续呈现;已保存的包从同包操作标记识别,未完成的包从最后安全保存版本重新呈现,不重复创建同ID对象。包保存成功但Applied未写入时,重新加载包并核对目标值后补记完成。包里存在第三方新值或缺失类型时进入Conflict,保持资源隔离并保存诊断;恢复器不覆盖无法归属本操作的修改。
例如对象O的引用原为资产版本V10,计划改到V11。共同提交已经把清单和O的持久目标一起指向V11,宿主修改O时抛异常。恢复器从日志读到Committed,重新加载O并应用V11,保存后开放资源。文件指针不会再次递增。如果失败持续存在,可以执行一个条件逆操作:比较当前仍属此次提交,创建新代次V12,其内容引用V10,并把O目标恢复到旧值。逆操作也通过同一共同提交点与呈现流程,不能把日志改成“未发生”。
逆操作还要处理原操作尚未呈现的包。设J的持久目标已是V11,但某包仍保存V10,另一个包已保存J标记和V11。逆操作K在同一资源门控下先比较逻辑当前目标仍属于J,再从J的准备记录与同包标记列出每个包允许的已保存前态:J之前的精确版本与字段值,或已经呈现J的精确版本与字段值。K把两种前态都映射到V10,并以K标记和新目标版本保存;任何第三方版本仍是冲突。已经处于V10的包只更新归属标记,不重复删除或创建对象。
因此失败恢复可以直接呈现最新的已提交逆目标,不必先把无法加载的V11强行装进宿主再撤销。记录允许前态时按包和对象逐项保存,创建删除也比较稳定身份是否存在及完整操作快照;不能用“当前值恰好等于目标”替代版本与来源检查。K共同提交后,启动恢复器把J标为被K承接,按K的允许前态继续,不再独立重放J。若K尚未提交,恢复仍只能完成J。这份承接关系与K决定同事务保存,避免崩溃后两个恢复任务分别执行相反方向。
宿主撤销与持久恢复
共同提交操作只在宿主登记一个命令代理,代理保存操作ID和期望方向。真实对象字段由上述执行器呈现,避免宿主先撤销对象、工具再撤销一次的双重修改。宿主撤销请求进入适配器时,先封锁相关对象的消费和保存,检查当前对象值、文件代次及后续依赖是否仍符合逆操作前提,再提交新的持久逆操作。恢复旧内容仍使用新代次;重做同样提交正向的新操作。
宿主能够在撤销前拦截命令时,条件失败直接保留原游标并报告冲突。只能在代理状态已经被宿主恢复后收到回调时,代理本身不触及真实对象;适配器先尝试逆操作,失败则在禁止递归通知的范围内把代理游标恢复到实际已提交方向。等待处理期间不接受下一次撤销,宿主游标与持久结果一致后才解除门控。一个宿主若既无法拦截也无法可靠恢复游标,跨资源命令使用工具的事务历史入口,宿主撤销只负责普通对象手势;两种入口都调用同一执行器,不能让不支持的宿主回调伪装成原子撤销。
持久Committed先于宿主代理登记。若在两者之间退出,重启恢复仍能找到操作并呈现完整结果。宿主撤销缓冲通常只在本会话有效,工具从持久日志提供可恢复历史;日志同时记录普通后续对象编辑的资源版本,逆操作检查字段值和版本,避免值改回原样时漏掉后续编辑;没有重建宿主栈的API时,不制造一条看似可用的宿主撤销记录,而在工具历史中显示该操作的逆操作入口。因而“没有撤销按钮”不会等于“失去恢复材料”。
假设操作J把V10发布成V11,用户随后通过正式入口编辑成V12。撤销J必须报告版本冲突,保留V12;即使内容碰巧相同也检查代次。若没有后续修改,撤销J创建V12但指向V10内容,重做创建V13并指向V11。三个动作各自有回执,进程在任何一个提交后退出,都只需完成该决定的对象呈现。对象、文件和撤销历史通过操作ID连接起来,失败点不再依赖一份随进程丢失的补偿列表。
该协议是本页给出的工程设计,SQLite事务提供日志与版本指针的共同提交;Unity或Unreal负责对象呈现、保存门控和宿主命令适配。编辑手势与撤销中的宿主API逐项操作对象,本节决定它们在提交与恢复中的先后关系。无法统一读取入口的外部工具仍会看到它自行打开的旧文件,因此正式构建和交互必须采用版本清单或持有项目锁;协议的可见性边界随这些入口一起部署。
验证方法
分别在Prepared、Committed、包保存后和Applied前退出,检查恢复方向。让J只完成部分包,再提交逆操作K,核对两种允许前态均由K承接,第三方版本进入Conflict。
实现与证据
本页共同提交和宿主代理是工程协议设计。SQLite协议实验覆盖持久发布与强退边界;未运行本页多包呈现、宿主撤销代理或跨资源恢复集成。
相关主题
参考资料
- SQLite Atomic Commit:参考实现中条件发布、回执与状态共同提交的本地持久事务依据。
对象呈现、同包标记和命令代理属于本文设计。Unity与Unreal的逐项对象事务API见编辑手势与撤销,这些API各自不提供跨资源共同提交。