Files
aibis-dream/Assets/Scripts/SaveSystem/SnapshotRestore.cs
T

219 lines
8.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections;
using Newtonsoft.Json.Linq;
using UnityEngine;
namespace AibisDream.SaveSystem
{
/// <summary>
/// 将 <see cref="SaveSnapshot"/> 按 Phase + Barrier 写回运行时(纯内存,不读盘)。
/// </summary>
public static class SnapshotRestore
{
/// <summary>
/// Phase 0~2Yarn 变量、场景、章节与所有 Provider;不包含锚点重进。
/// </summary>
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);
if (snapshot.deepRepair != null && snapshot.deepRepair.sections != null
&& snapshot.deepRepair.sections.Count > 0)
{
context.Warn("DeepRepair 段尚未实现,已跳过(P5)。");
}
}
/// <summary>
/// Phase 3:按 <see cref="AnchorSnapshot"/> 加载对话工程并重新进入 Yarn 节点。
/// </summary>
public static IEnumerator RestoreAnchor(SaveSnapshot snapshot, SnapshotRestoreContext context = null)
{
if (snapshot?.anchor == null || string.IsNullOrEmpty(snapshot.anchor.nodeName))
{
context?.Log("Phase 3: No anchor node, skip RestoreAnchor");
yield break;
}
context ??= new SnapshotRestoreContext(snapshot);
context.Log($"Phase 3: Restore anchor {snapshot.anchor.nodeName}");
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 && sceneSo.yarnProject.name == snapshot.anchor.yarnProjectId)
{
if (runner.YarnProject == null || runner.YarnProject.name != snapshot.anchor.yarnProjectId)
{
dialog.LoadDialog(sceneSo.yarnProject);
}
}
else if (runner.YarnProject != null && runner.YarnProject.name != snapshot.anchor.yarnProjectId)
{
context.Warn(
$"YarnProject 不匹配:当前 {runner.YarnProject.name},存档 {snapshot.anchor.yarnProjectId}");
}
}
if (runner.IsDialogueRunning)
{
yield return runner.Stop();
}
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);
}
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;
}
gameManager.SetSceneSoByName(snapshot.anchor.sceneSoName);
}
/// <summary>
/// 按 <see cref="ISnapshotProvider.RestoreOrder"/> 依次还原;
/// 同步 Provider 连续调用,异步 Provider 作为 Barrier 挂起,二者混排而非分两批。
/// </summary>
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;
}
/// <summary>
/// 读盘后 <see cref="SaveSnapshot.sections"/> 中的值常为 <see cref="JObject"/>
/// 按 SaveId 转回各 Provider 所需的强类型 DTO。
/// </summary>
private static object CoerceSectionDto(string saveId, object dto)
{
if (dto is not JObject jobj)
{
return dto;
}
return saveId switch
{
SnapshotProviderIds.Environment => jobj.ToObject<EnvironmentSnapshotDto>(),
SnapshotProviderIds.Actor => jobj.ToObject<ActorSnapshotDto>(),
SnapshotProviderIds.Audio => jobj.ToObject<AudioSnapshotDto>(),
SnapshotProviderIds.Timeline => jobj.ToObject<TimelineSnapshotDto>(),
SnapshotProviderIds.Fix => jobj.ToObject<FixSnapshotDto>(),
SnapshotProviderIds.Screen => jobj.ToObject<ScreenSnapshotDto>(),
_ => jobj
};
}
}
}