using System.Collections; using Newtonsoft.Json.Linq; using UnityEngine; namespace AibisDream.SaveSystem { /// /// 将 按 Phase + Barrier 写回运行时(纯内存,不读盘)。 /// public static class SnapshotRestore { /// /// Phase 0~2:Yarn 变量、场景、章节与所有 Provider;不包含锚点重进。 /// public static IEnumerator RestoreState( YarnVariableStorage storage, SaveSnapshot snapshot, SnapshotRestoreContext context) { if (snapshot == null) { context?.Error("snapshot 为 null"); yield break; } context ??= new SnapshotRestoreContext(snapshot); context.Log("Phase 0: Restore Yarn variables"); RestoreYarnVariables(storage, snapshot); context.Log("Phase 1: Load scene"); yield return RestoreScene(snapshot, context); context.Log("Phase 1.5: Restore scene SO"); RestoreSceneSo(snapshot, context); context.Log("Phase 2: Restore providers (ordered by RestoreOrder)"); yield return RestoreProvidersInOrder(snapshot, context); } /// /// Phase 3:按 加载对话工程;若有节点名则重新进入 Yarn 节点。 /// public static IEnumerator RestoreAnchor(SaveSnapshot snapshot, SnapshotRestoreContext context = null) { if (snapshot?.anchor == null) { context?.Log("Phase 3: No anchor, skip RestoreAnchor"); yield break; } context ??= new SnapshotRestoreContext(snapshot); var hasNode = !string.IsNullOrEmpty(snapshot.anchor.nodeName); context.Log(hasNode ? $"Phase 3: Restore anchor {snapshot.anchor.nodeName}" : "Phase 3: No anchor node, restore YarnProject only"); var dialog = DialogController.Instance; if (dialog == null) { context.Warn("DialogController 未初始化,无法恢复 Yarn 状态。"); yield break; } var runner = dialog.DialogueRunner; if (runner == null) { context.Warn("DialogueRunner 未初始化,无法恢复 Yarn 状态。"); yield break; } if (!string.IsNullOrEmpty(snapshot.anchor.yarnProjectId)) { var sceneSo = GameManager.Instance?.GetCurrentTalkSceneSo(); if (sceneSo?.yarnProject == null) { context.Error($"章节 {snapshot.anchor.sceneSoName} 没有可用的 YarnProject。"); yield break; } if (sceneSo.yarnProject.name != snapshot.anchor.yarnProjectId) { context.Error( $"章节 YarnProject 不匹配:章节 {sceneSo.yarnProject.name},存档 {snapshot.anchor.yarnProjectId}"); yield break; } if (runner.YarnProject == null || runner.YarnProject.name != snapshot.anchor.yarnProjectId) { dialog.LoadDialog(sceneSo.yarnProject); } } if (!hasNode) { yield break; } if (runner.IsDialogueRunning) { yield return runner.Stop(); } if (runner.YarnProject == null || !System.Array.Exists(runner.YarnProject.NodeNames, node => node == snapshot.anchor.nodeName)) { context.Error( $"YarnProject {runner.YarnProject?.name ?? "none"} 不包含节点 {snapshot.anchor.nodeName}。"); yield break; } yield return runner.StartDialogue(snapshot.anchor.nodeName); } private static void RestoreYarnVariables(YarnVariableStorage storage, SaveSnapshot snapshot) { var vars = snapshot.yarnVariables; if (vars == null || storage == null) return; storage.SetAllVariables(vars.floats, vars.strings, vars.bools); } private static IEnumerator RestoreScene(SaveSnapshot snapshot, SnapshotRestoreContext context) { if (snapshot.scene == null || string.IsNullOrEmpty(snapshot.scene.sceneName)) { context.Warn("快照缺少 scene.sceneName,跳过场景加载。"); yield break; } var sceneLoader = SceneLoader.Instance; if (sceneLoader == null) { context.Error("SceneLoader 未初始化,无法加载场景。"); yield break; } yield return sceneLoader.LoadSceneAsync(snapshot.scene.sceneName); // 再等一帧,等Awake执行完 yield return null; } private static void RestoreSceneSo(SaveSnapshot snapshot, SnapshotRestoreContext context) { if (snapshot.anchor == null || string.IsNullOrEmpty(snapshot.anchor.sceneSoName)) { context.Warn("快照缺少 anchor.sceneSoName,跳过章节 SO 设置。"); return; } var gameManager = GameManager.Instance; if (gameManager == null) { context.Warn("GameManager 未初始化,无法设置章节 SO。"); return; } if (!gameManager.SetSceneSoByName(snapshot.anchor.sceneSoName)) { context.Error($"找不到章节 SO:{snapshot.anchor.sceneSoName},无法恢复 YarnProject。"); } } /// /// 按 依次还原; /// 同步 Provider 连续调用,异步 Provider 作为 Barrier 挂起,二者混排而非分两批。 /// private static IEnumerator RestoreProvidersInOrder(SaveSnapshot snapshot, SnapshotRestoreContext context) { foreach (var provider in SnapshotRegistry.GetOrderedProviders()) { if (!TryGetSectionDto(snapshot, provider, context, out var dto)) { continue; } switch (provider) { case ISyncSnapshotProvider syncProvider: context.Log($"Sync provider [{provider.RestoreOrder}]: {provider.SaveId}"); syncProvider.Restore(dto, context); break; case IAsyncSnapshotProvider asyncProvider: context.Log($"Async provider [{provider.RestoreOrder}]: {provider.SaveId}"); yield return asyncProvider.RestoreAsync(dto, context); break; default: context.Warn($"Provider {provider.SaveId} 未实现同步或异步还原契约,已跳过。"); break; } } } private static bool TryGetSectionDto( SaveSnapshot snapshot, ISnapshotProvider provider, SnapshotRestoreContext context, out object dto) { dto = null; if (snapshot.sections == null || !snapshot.sections.TryGetValue(provider.SaveId, out dto) || dto == null) { context.Log($"Provider {provider.SaveId}: no section, skip"); return false; } dto = CoerceSectionDto(provider.SaveId, dto); return true; } /// /// 读盘后 中的值常为 ; /// 按 SaveId 转回各 Provider 所需的强类型 DTO。 /// private static object CoerceSectionDto(string saveId, object dto) { if (dto is not JObject jobj) { return dto; } return saveId switch { SnapshotProviderIds.Environment => jobj.ToObject(), SnapshotProviderIds.Actor => jobj.ToObject(), SnapshotProviderIds.Audio => jobj.ToObject(), SnapshotProviderIds.Timeline => jobj.ToObject(), SnapshotProviderIds.Subway => jobj.ToObject(), SnapshotProviderIds.PunchTape => jobj.ToObject(), SnapshotProviderIds.Fix => jobj.ToObject(), SnapshotProviderIds.FixPanel => jobj.ToObject(), SnapshotProviderIds.BodyModule => jobj.ToObject(), SnapshotProviderIds.Eye => jobj.ToObject(), SnapshotProviderIds.Screen => jobj.ToObject(), _ => jobj }; } } }