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, System.Func isCancellationRequested = null) { if (snapshot == null) { context?.Error("snapshot 为 null"); yield break; } context ??= new SnapshotRestoreContext(snapshot); context.SetPhase("Phase 0: Restore Yarn variables"); RestoreYarnVariables(storage, snapshot); if (IsCancellationRequested(isCancellationRequested, context)) yield break; context.SetPhase("Phase 1: Load scene"); yield return RestoreScene(snapshot, context); if (IsCancellationRequested(isCancellationRequested, context)) yield break; if (context.StrictMode && context.HasErrors) yield break; context.SetPhase("Phase 2: Restore providers (ordered by RestoreOrder)"); yield return RestoreProvidersInOrder(snapshot, context, isCancellationRequested); } /// /// 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.SetPhase(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 = context.TargetTalkScene; 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; } SceneOperationResult result = null; yield return sceneLoader.LoadSceneAsync( snapshot.scene.sceneName, value => result = value); if (result?.Success != true) { context.Error(result?.Error ?? "Scene load did not return a result."); yield break; } // 再等一帧,等Awake执行完 yield return null; } /// /// 按 依次还原; /// 同步 Provider 连续调用,异步 Provider 作为 Barrier 挂起,二者混排而非分两批。 /// private static IEnumerator RestoreProvidersInOrder( SaveSnapshot snapshot, SnapshotRestoreContext context, System.Func isCancellationRequested) { foreach (var provider in SnapshotRegistry.GetOrderedProviders()) { if (IsCancellationRequested(isCancellationRequested, context)) { yield break; } 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; } if (context.StrictMode && context.HasErrors) { yield break; } if (IsCancellationRequested(isCancellationRequested, context)) { yield break; } } } private static bool IsCancellationRequested( System.Func isCancellationRequested, SnapshotRestoreContext context) { if (isCancellationRequested?.Invoke() != true) { return false; } context.Log("Restore cancellation requested between phases."); return true; } 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.Showcase => jobj.ToObject(), SnapshotProviderIds.Day2SleepPresentation => jobj.ToObject(), SnapshotProviderIds.PlayTool => jobj.ToObject(), SnapshotProviderIds.Screen => jobj.ToObject(), _ => jobj }; } } }