using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using AibisDream.Framework;
using AibisDream.UI;
using UnityEngine;
namespace AibisDream.SaveSystem
{
///
/// 存读档流程编排层:连接 UI、 与槽位系统。
///
public static class SaveRestoreOrchestrator
{
/// 是否正在读档还原中;供 UI / 日志 / 外部系统观察。
public static bool IsRestoring { get; private set; }
/// 是否处于自动存档抑制作用域;真正用于防止读档期间覆盖 slot_0。
public static bool IsAutoSaveSuppressed => _autoSaveSuppressDepth > 0;
public static IReadOnlyList LastRestoreLog => _lastRestoreLog;
private static bool _isAutoSaving;
private static int _autoSaveSuppressDepth;
private static readonly List _lastRestoreLog = new();
private const float RestoreFadeDuration = 0.2f;
/// 启动自动存档协程(手动/调试入口)。
public static void TryAutoSave()
{
if (YarnVariableStorage.Instance == null)
{
Debug.LogError("[SaveRestoreOrchestrator] YarnVariableStorage 未初始化");
return;
}
if (!SavePointEvaluator.CanAutoSave(out var reason))
{
Debug.LogWarning($"[SaveRestoreOrchestrator] 当前不是可存点,跳过自动存档:{reason}");
return;
}
var (triggerNodeName, _) = DialogController.Instance.GetCurrentNodeContext();
DialogController.Instance.StartCoroutine(AutoSaveRoutine(triggerNodeName));
}
/// 自动存档协程:settle 一帧 → 主线程 Capture → 后台序列化写盘(不阻塞主线程)。
/// 见 ;显式 save 传 null。
/// 为 true 时不写入 Yarn 节点 anchor。
public static IEnumerator AutoSaveRoutine(string triggerNodeName = null, bool omitAnchor = false)
{
if (_isAutoSaving)
{
Debug.LogWarning("[SaveRestoreOrchestrator] 存档进行中,跳过重复请求。");
yield break;
}
yield return null;
if (_isAutoSaving)
{
Debug.LogWarning("[SaveRestoreOrchestrator] 存档进行中,跳过重复请求。");
yield break;
}
_isAutoSaving = true;
var infoPanel = UIManager.Instance.GetPanel();
infoPanel?.ShowSaveLoading();
SaveSnapshot snapshot;
byte[] thumbnail;
try
{
using (new CodeTimer("SaveSnapshot"))
{
snapshot = SnapshotService.Capture(triggerNodeName, omitAnchor);
thumbnail = SlotThumbnailCapture.CapturePng();
}
}
catch (Exception ex)
{
Debug.LogError($"[SaveRestoreOrchestrator] 自动存档 Capture 失败: {ex}");
infoPanel?.HideSaveLoading();
_isAutoSaving = false;
yield break;
}
infoPanel?.HideSaveLoading();
if (TestSaveArchive.IsEnabled)
{
TestSaveArchive.Archive(snapshot, thumbnail);
Debug.Log("[SaveRestoreOrchestrator] 测试存档模式:已写入 testsavs(不覆盖 slot_0)。");
_isAutoSaving = false;
}
else
{
_ = SlotManager.SaveToAutoSlotAsync(snapshot, thumbnail).ContinueWith(
writeTask =>
{
if (writeTask.IsFaulted)
{
Debug.LogError(
$"[SaveRestoreOrchestrator] 自动存档写盘失败: {writeTask.Exception?.GetBaseException()}");
}
_isAutoSaving = false;
},
TaskContinuationOptions.ExecuteSynchronously);
}
if (omitAnchor || (snapshot?.anchor != null && string.IsNullOrEmpty(snapshot.anchor.nodeName)))
{
Debug.Log("[SaveRestoreOrchestrator] 已保存无 Yarn 节点 anchor 的状态。");
}
}
///
/// Yarn <<save>> 显式存档:默认 omit anchor,调用方需已通过 。
///
public static IEnumerator ExplicitSaveRoutine()
{
yield return AutoSaveRoutine(triggerNodeName: null, omitAnchor: true);
}
/// 将当前自动档复制到指定手动档。
public static void CreateManualSlot(int slotIndex)
{
if (!SavePointEvaluator.CanManualSave(out var reason))
{
Debug.LogWarning($"[SaveRestoreOrchestrator] 当前不可手动存档:{reason}");
return;
}
SlotManager.CopyAutoToManual(slotIndex);
}
/// 从指定槽位读档并还原。
public static IEnumerator RestoreFromSlot(int slotIndex)
{
var snapshot = SlotManager.LoadSnapshot(slotIndex);
if (snapshot == null)
{
Debug.LogError($"[SaveRestoreOrchestrator] 槽位 {slotIndex} 不存在或读取失败");
yield break;
}
yield return RestoreSnapshot(snapshot, $"slot_{slotIndex}");
}
/// 从文件读档并还原;自动区分新快照格式与 legacy 格式。
public static IEnumerator RestoreFromFile(string savePath)
{
yield return RestoreFromFile(savePath, RestoreOptions.Default, null);
}
/// 从文件恢复并返回结构化结果;开发跳转使用严格校验和完整会话清理。
public static IEnumerator RestoreFromFile(
string savePath,
RestoreOptions options,
Action completed)
{
options ??= RestoreOptions.Default;
var result = new RestoreResult();
bool isLegacy;
try
{
isLegacy = SnapshotPersistence.IsLegacyFormat(savePath);
}
catch (Exception ex)
{
result.Success = false;
result.FailedPhase = "LoadSnapshot";
result.Errors = new[] { ex.Message };
completed?.Invoke(result);
yield break;
}
if (isLegacy)
{
if (options.StrictValidation)
{
result.Success = false;
result.FailedPhase = "Preflight";
result.Errors = new[] { "legacy format is not supported by strict restore" };
completed?.Invoke(result);
yield break;
}
Debug.LogWarning("[SaveRestoreOrchestrator] 检测到旧格式存档,请使用新快照格式重新保存。");
yield return RestoreLegacyWithFlow(savePath);
result.Success = true;
result.Log = new List(_lastRestoreLog);
completed?.Invoke(result);
yield break;
}
SaveSnapshot snapshot = null;
try
{
using (new CodeTimer("LoadSnapshot"))
{
snapshot = SnapshotPersistence.Load(savePath);
}
}
catch (Exception ex)
{
result.Success = false;
result.FailedPhase = "LoadSnapshot";
result.Errors = new[] { ex.Message };
completed?.Invoke(result);
yield break;
}
yield return RestoreSnapshot(snapshot, savePath, options, result);
completed?.Invoke(result);
}
public static IDisposable SuppressAutoSaveScope(string reason)
{
_autoSaveSuppressDepth++;
Debug.Log($"[SaveRestoreOrchestrator] SuppressAutoSave begin: {reason}, depth={_autoSaveSuppressDepth}");
return new AutoSaveSuppressScope(reason);
}
/// 旧档:仅恢复 Yarn 变量,再 Capture 写入 slot_0 完成格式迁移。
private static IEnumerator RestoreLegacy(string savePath)
{
var saveData = SnapshotPersistence.ReadLegacySaveRoot(savePath);
var floatDict = saveData["floatDict"]?.ToObject>();
var stringDict = saveData["stringDict"]?.ToObject>();
var boolDict = saveData["boolDict"]?.ToObject>();
YarnVariableStorage.Instance.SetAllVariables(floatDict, stringDict, boolDict);
if (saveData["systemData"] != null)
{
Debug.LogWarning(
"[SaveRestoreOrchestrator] 旧档 systemData 段已无法还原(IData 已移除);" +
"仅恢复 Yarn 变量并迁移为新快照格式。");
}
var snapshot = SnapshotService.Capture();
SlotManager.SaveToAutoSlot(snapshot, null);
yield break;
}
private static IEnumerator RestoreLegacyWithFlow(string savePath)
{
using (SuppressAutoSaveScope("RestoreLegacy"))
{
IsRestoring = true;
ResetRestoreLog("legacy");
try
{
PrepareGameSessionForRestore();
yield return FadeInForRestore();
yield return RestoreLegacy(savePath);
yield return null;
FinalizeGameSessionAfterRestore();
yield return FadeOutForRestore();
ScreenSnapshotHelper.ApplyDeferredFadeScreenIfNeeded();
}
finally
{
IsRestoring = false;
AddRestoreLog("Legacy restore finished");
}
}
}
private static IEnumerator RestoreSnapshot(SaveSnapshot snapshot, string sourceLabel)
{
yield return RestoreSnapshot(snapshot, sourceLabel, RestoreOptions.Default, new RestoreResult());
}
private static IEnumerator RestoreSnapshot(
SaveSnapshot snapshot,
string sourceLabel,
RestoreOptions options,
RestoreResult result)
{
if (snapshot == null)
{
Debug.LogError("[SaveRestoreOrchestrator] snapshot 为 null,无法读档。");
result.Success = false;
result.FailedPhase = "Preflight";
result.Errors = new[] { "snapshot is null" };
yield break;
}
using (SuppressAutoSaveScope($"RestoreSnapshot:{sourceLabel}"))
{
IsRestoring = true;
ResetRestoreLog(sourceLabel);
var context = new SnapshotRestoreContext(
snapshot,
strictMode: options.StrictValidation,
logStep: AddRestoreLog);
try
{
context.SetPhase("Preflight");
ValidateSnapshotForRestore(snapshot, context);
if (context.StrictMode && context.HasErrors)
{
yield break;
}
if (options.CleanSessionFirst && GameManager.Instance != null)
{
context.SetPhase("Clean game session");
yield return GameManager.Instance.ResetGameSessionRoutine();
}
PrepareGameSessionForRestore();
yield return FadeInForRestore();
SnapshotRegistry.EnsureInitialized();
yield return SnapshotRestore.RestoreState(YarnVariableStorage.Instance, snapshot, context);
if (!context.StrictMode || !context.HasErrors)
{
yield return SnapshotRestore.RestoreAnchor(snapshot, context);
}
if (!context.StrictMode || !context.HasErrors)
{
context.SetPhase("Phase 3.5: settle one frame");
yield return null;
ValidateRestoredRuntime(snapshot, context);
}
if (!context.StrictMode || !context.HasErrors)
{
FinalizeGameSessionAfterRestore();
}
yield return FadeOutForRestore();
ScreenSnapshotHelper.ApplyDeferredFadeScreenIfNeeded();
}
finally
{
IsRestoring = false;
AddRestoreLog("Restore finished");
result.Success = !context.HasErrors;
result.FailedPhase = context.HasErrors ? context.CurrentPhase : null;
result.Errors = new List(context.Errors);
result.Warnings = new List(context.Warnings);
result.Log = new List(_lastRestoreLog);
}
}
}
private static void ValidateSnapshotForRestore(SaveSnapshot snapshot, SnapshotRestoreContext context)
{
if (snapshot.schemaVersion != SaveSnapshotSchema.CurrentVersion)
context.Error($"Unsupported schema {snapshot.schemaVersion}; expected {SaveSnapshotSchema.CurrentVersion}.");
if (string.IsNullOrWhiteSpace(snapshot.scene?.sceneName))
context.Error("Snapshot scene is missing.");
if (string.IsNullOrWhiteSpace(snapshot.anchor?.sceneSoName))
context.Error("Snapshot TalkSceneSO is missing.");
if (string.IsNullOrWhiteSpace(snapshot.anchor?.yarnProjectId))
context.Error("Snapshot YarnProject is missing.");
if (string.IsNullOrWhiteSpace(snapshot.anchor?.nodeName))
context.Error("Snapshot Yarn node is missing.");
if (context.HasErrors) return;
var sceneSo = GameManager.Instance?.FindSceneSoByName(snapshot.anchor.sceneSoName);
if (sceneSo == null)
{
context.Error($"TalkSceneSO does not exist: {snapshot.anchor.sceneSoName}.");
return;
}
if (sceneSo.yarnProject == null
|| !string.Equals(sceneSo.yarnProject.name, snapshot.anchor.yarnProjectId, StringComparison.Ordinal))
{
context.Error($"TalkSceneSO YarnProject mismatch: {snapshot.anchor.yarnProjectId}.");
return;
}
if (!Array.Exists(sceneSo.yarnProject.NodeNames,
node => string.Equals(node, snapshot.anchor.nodeName, StringComparison.Ordinal)))
{
context.Error($"Yarn node does not exist: {snapshot.anchor.nodeName}.");
}
}
private static void ValidateRestoredRuntime(SaveSnapshot snapshot, SnapshotRestoreContext context)
{
context.SetPhase("Postflight validation");
var sceneLoader = SceneLoader.Instance;
if (sceneLoader == null || sceneLoader.IsLoading)
context.Error("SceneLoader is not ready.");
else if (!string.Equals(sceneLoader.CurrentSceneName, snapshot.scene.sceneName, StringComparison.Ordinal))
context.Error($"Scene mismatch: {sceneLoader.CurrentSceneName ?? "none"}.");
var sceneSo = GameManager.Instance?.GetCurrentTalkSceneSo();
if (!string.Equals(sceneSo?.name, snapshot.anchor.sceneSoName, StringComparison.Ordinal))
context.Error($"TalkSceneSO mismatch: {sceneSo?.name ?? "none"}.");
var runner = DialogController.Instance?.DialogueRunner;
if (!string.Equals(runner?.YarnProject?.name, snapshot.anchor.yarnProjectId, StringComparison.Ordinal))
context.Error($"YarnProject mismatch: {runner?.YarnProject?.name ?? "none"}.");
if (!string.IsNullOrEmpty(snapshot.anchor.nodeName) && runner != null && !runner.IsDialogueRunning)
context.Error($"Yarn node did not start: {snapshot.anchor.nodeName}.");
if (snapshot.sections != null
&& snapshot.sections.ContainsKey(SnapshotProviderIds.Fix)
&& (FixSystem.FixSystemCenter.Instance == null
|| !FixSystem.FixSystemCenter.Instance.IsDirectorReady))
{
context.Error("FixSystem is not ready.");
}
}
///
/// 读档前对齐「已进入游戏」的 UI 与输入状态。
/// 编辑器测试工具等路径可能跳过 ,导致 TerminalPanel 未关闭、
/// 为 false,从而无法推进对话。
///
private static void PrepareGameSessionForRestore()
{
AddRestoreLog("Prepare game session for restore");
// 读档淡入使用 DOTween(受 timeScale 影响);若处于暂停态须先恢复,否则 FadeIn 永不结束、画面卡在诊室/终端。
Time.timeScale = 1f;
var gameManager = GameManager.Instance;
if (gameManager != null && !gameManager.state.isInGame)
{
EnumEventSystem.Global.Send(GameLoopEnum.GameStart);
}
var ui = UIManager.Instance;
if (ui != null)
{
ui.HidePanel();
ui.HidePanel();
ui.ShowPanel();
ui.HidePanel();
ui.HidePanel();
}
if (gameManager != null && gameManager.state.isInPause)
{
gameManager.ContinueGame();
}
else
{
DialogUIManager.Instance?.OpenCanvas();
}
}
/// 读档完成后确保对话 Canvas 可用、退出暂停态。
private static void FinalizeGameSessionAfterRestore()
{
AddRestoreLog("Finalize game session after restore");
DialogUIManager.Instance?.OpenCanvas();
var gameManager = GameManager.Instance;
if (gameManager != null && gameManager.state.isInPause)
{
gameManager.ContinueGame();
}
}
private static IEnumerator FadeInForRestore()
{
AddRestoreLog("Begin restore fade in");
var panel = UIManager.Instance?.GetPanel();
if (panel != null)
{
yield return panel.FadeInAsync(RestoreFadeDuration, useUnscaledTime: true);
}
}
private static IEnumerator FadeOutForRestore()
{
AddRestoreLog("End restore fade out");
var panel = UIManager.Instance?.GetPanel();
if (panel != null)
{
yield return panel.FadeOutAsync(RestoreFadeDuration, useUnscaledTime: true);
}
}
private static void ResetRestoreLog(string sourceLabel)
{
_lastRestoreLog.Clear();
AddRestoreLog($"Restore source: {sourceLabel}");
}
private static void AddRestoreLog(string message)
{
_lastRestoreLog.Add($"[{DateTime.Now:HH:mm:ss}] {message}");
Debug.Log($"[SaveRestoreOrchestrator] {message}");
}
private sealed class AutoSaveSuppressScope : IDisposable
{
private readonly string _reason;
private bool _disposed;
public AutoSaveSuppressScope(string reason)
{
_reason = reason;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_autoSaveSuppressDepth = Math.Max(0, _autoSaveSuppressDepth - 1);
Debug.Log(
$"[SaveRestoreOrchestrator] SuppressAutoSave end: {_reason}, depth={_autoSaveSuppressDepth}");
}
}
}
}