# SaveSystem 存档快照层 > P1 实现:纯数据快照 + 逐项还原。设计文档见 [`Docs/存档系统设计方案.md`](../../../Docs/存档系统设计方案.md)(**§4.1 / D6** 描述读档编排终态)。 ## 职责边界 | 模块 | 位置 | 做什么 | | --- | --- | --- | | Yarn 变量 | `Game Loop/YarnVariableStorage.cs` | 运行时 `$` / `$global_` 读写 | | **本目录** | `SaveSystem/` | 快照结构、捕获/还原、落盘、流程编排 | | 深度维修(旧) | `DeepRepairDataRegistry.cs` | FixSystem 等 IData,P5 前临时保留 | | 槽位 / 截图 | P2 待做 | 接管 `SnapshotPersistence` 与目录布局 | **不要**再往 `YarnVariableStorage` 上堆存档逻辑。 ## 对外入口 ```csharp // 存盘(<>、调试) SaveRestoreOrchestrator.SaveToTestPath(); // 读盘 yield return SaveRestoreOrchestrator.RestoreFromFile(path); // 仅内存快照(P2 手动档固化等) var snap = SnapshotService.Capture(); yield return SnapshotService.Restore(snap); // Yarn 变量(与存档无关) YarnVariableStorage.Instance.SetValue("$foo", 1f); ``` P1 测试落盘路径:`persistentDataPath/AllOurBrokenParts/snapshot_test/latest_snapshot.json` ## 目录结构 ``` SaveSystem/ ├── README.md ← 本文件 ├── ISnapshotProvider.cs 契约:Capture / Restore(P1 临时形态,见下) ├── SaveSnapshot.cs 快照根对象 + 各 section DTO ├── SnapshotProviderIds.cs section 稳定 id("actor" 等) ├── SnapshotRegistry.cs provider 注册表 ├── SnapshotBootstrap.cs 启动时注册 7 个 provider ├── SnapshotCapture.cs 组装 SaveSnapshot ├── SnapshotRestore.cs 写回运行时 + 锚点重进节点 ├── SnapshotService.cs Capture / Restore 对外 API ├── SnapshotPersistence.cs 文件读写、旧档检测 ├── SnapshotSerializer.cs JSON + schemaVersion ├── SnapshotSectionDeserializer.cs JObject → 强类型 DTO ├── SaveRestoreOrchestrator.cs UI + 落盘 + 读盘编排 ├── DeepRepairSnapshot.cs 深度维修占位(P5) ├── DeepRepairDataRegistry.cs 旧 IData 容器(P5 前) ├── ScreenSnapshotHelper.cs screen section 实现细节 └── Providers/ 各子系统薄适配层 ├── SceneSnapshotProvider.cs order 10 ├── EnvironmentSnapshotProvider.cs order 20 ├── MacroSnapshotProvider.cs order 30 ├── ActorSnapshotProvider.cs order 40 ├── AudioSnapshotProvider.cs order 50 ├── TimelineSnapshotProvider.cs order 60 └── ScreenSnapshotProvider.cs order 70 ``` ## 存盘 / 读盘流程 ``` 存盘: SaveRestoreOrchestrator → SnapshotService.Capture() → SnapshotCapture(锚点 + Yarn 变量 + 各 Provider) → SnapshotPersistence.Save() 读盘: SaveRestoreOrchestrator → SnapshotPersistence.Load() (或 legacy 分支) → SnapshotService.Restore() → SnapshotRestore(见「读档还原编排」) ``` ## 读档还原编排(终态 vs P1 代码) > **勿将当前代码当作终态。** P1 中 `ISnapshotProvider.Restore` 统一返回 `IEnumerator`,`SnapshotRestore` 对每个 Provider 做 `yield return`——这是受旧 `IData.Load()` 影响的**临时 scaffolding**。终态见设计文档 **§4.1 / D6**;P4 实施前应 refactor。 ### 终态:Phase + Barrier 读档不是「每个 Provider 串行协程链」,而是分阶段推进,仅在**必须等待**的边界上 `yield`: ``` Phase 0 Yarn 变量(sync) ↓ Phase 1 场景加载(Barrier:LoadSceneAsync) ↓ Phase 2 macro / env / actor / audio / timeline / screen(sync 批量,同帧连续调用) ↓ Phase 2′ 可选 Barrier(如 Timeline Addressable 须显式等待) ↓ Phase 3 RestoreAnchor(Barrier:StartDialogue) ↓ Phase 4 P4:淡入淡出等演出时序 ``` - **逐项还原(D1)**指各子系统各自写回状态,**不是** Provider 之间逐步 `yield return`。 - **`RestoreOrder`** 表示同 Phase 内的建议顺序或软依赖(scene 先于 env/actor 等),**不是**「每步必须挂协程」。硬依赖用 Phase / Barrier 表达。 ### P1 临时形态 vs P4 目标 | | P1(当前) | P4 目标 | | --- | --- | --- | | Provider 还原 | 全部 `IEnumerator Restore` | 默认 `void Restore`;仅 scene 等实现显式 async | | 编排 | `foreach` 逐步 `yield return` | Phase 编排,仅 Barrier 步骤 `yield` | | Manager | 部分 `IEnumerator RestoreSnapshot` 仅 `yield break` | 默认 `void`;真有 async 才保留协程 | ### 已知偏离(tech debt) - `DirectorHandler` 在 Addressable Timeline 路径下内部 `StartCoroutine`,Provider 已返回,编排层无法感知——P4 应改为可等待路径。 - P2/P3 新增代码**不要**再复制「全 Provider 协程链」模式。 ## 快照 JSON 结构(schemaVersion = 1) | 字段 | 含义 | | --- | --- | | `anchor` | 恢复锚点:`nodeName` + `yarnProjectId`(独立阶段重进节点的自洽校验) | | `yarnVariables` | floats / strings / bools | | `sections` | key 为 `SnapshotProviderIds`,值为各 DTO;**宏观信息(场景/SceneSO/YarnProject)以 `scene`/`macro` 两节为唯一来源** | | `deepRepair` | P1 通常为空,P5 再填 | > 宏观阶段摘要不再冗余到顶层;槽位 / 「继续游戏」UI 需要展示场景名、章节、YarnProject 时,调用 `SaveSnapshotSummary.From(snapshot)` 从 `sections` 现算派生。 ## 新增一个可存子系统 1. 在 `SaveSnapshot.cs` 增加 DTO 类。 2. 在 `SnapshotProviderIds` 增加稳定 id,并加入 `RequiredForCapture`(若 P1 必须存)。 3. 在对应 Manager 实现 `CaptureSnapshot` / `RestoreSnapshot`(**默认 sync `void`**;仅确有 async 加载/状态切换时用 `IEnumerator` 并向编排层上报)。 4. 新建 `Providers/XxxSnapshotProvider.cs` 实现 `ISnapshotProvider`(P4 后:sync 默认 + 显式 async 接口)。 5. 在 `SnapshotBootstrap.EnsureInitialized` 中 `Register`。 6. 在 `SnapshotSectionDeserializer.Coerce` 增加 JObject 转换分支。 **禁止**在 Provider 内私自 `StartCoroutine` 而不纳入编排 Barrier。 ## 相关阶段(未在本目录完整实现) | 阶段 | 内容 | | --- | --- | | P2 | 槽位、原子写、截图 sidecar → 扩展 `SnapshotPersistence` | | P3 | 可存点判定、写盘门控 | | P4 | 按 §4.1 重写 `SnapshotRestore`(Phase + Barrier);refactor Provider 契约;`SaveRestoreOrchestrator` 接入淡入淡出 | | P5 | `deepRepair` 段、维修模块清单;还原模型单独定案,不默认套用 P1 Provider 协程链 |