266 lines
9.8 KiB
C#
266 lines
9.8 KiB
C#
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~2:Yarn 变量、场景、章节与所有 Provider;不包含锚点重进。
|
||
/// </summary>
|
||
public static IEnumerator RestoreState(
|
||
YarnVariableStorage storage,
|
||
SaveSnapshot snapshot,
|
||
SnapshotRestoreContext context,
|
||
System.Func<bool> 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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Phase 3:按 <see cref="AnchorSnapshot"/> 加载对话工程;若有节点名则重新进入 Yarn 节点。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按 <see cref="ISnapshotProvider.RestoreOrder"/> 依次还原;
|
||
/// 同步 Provider 连续调用,异步 Provider 作为 Barrier 挂起,二者混排而非分两批。
|
||
/// </summary>
|
||
private static IEnumerator RestoreProvidersInOrder(
|
||
SaveSnapshot snapshot,
|
||
SnapshotRestoreContext context,
|
||
System.Func<bool> 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<bool> 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;
|
||
}
|
||
|
||
/// <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.Subway => jobj.ToObject<SubwaySnapshotDto>(),
|
||
SnapshotProviderIds.PunchTape => jobj.ToObject<PunchTapeSnapshotDto>(),
|
||
SnapshotProviderIds.Fix => jobj.ToObject<FixSnapshotDto>(),
|
||
SnapshotProviderIds.FixPanel => jobj.ToObject<FixPanelSnapshotDto>(),
|
||
SnapshotProviderIds.BodyModule => jobj.ToObject<BodyModuleSnapshotDto>(),
|
||
SnapshotProviderIds.Eye => jobj.ToObject<EyeSnapshotDto>(),
|
||
SnapshotProviderIds.Screen => jobj.ToObject<ScreenSnapshotDto>(),
|
||
_ => jobj
|
||
};
|
||
}
|
||
}
|
||
}
|