287 lines
9.9 KiB
C#
287 lines
9.9 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using AibisDream.Framework;
|
|
using AibisDream.UI;
|
|
using Newtonsoft.Json.Linq;
|
|
using UnityEngine;
|
|
|
|
namespace AibisDream.SaveSystem
|
|
{
|
|
/// <summary>
|
|
/// 存读档流程编排层:连接 UI、<see cref="SnapshotService"/> 与槽位系统。
|
|
/// </summary>
|
|
public static class SaveRestoreOrchestrator
|
|
{
|
|
/// <summary>是否正在读档还原中;供 UI / 日志 / 外部系统观察。</summary>
|
|
public static bool IsRestoring { get; private set; }
|
|
|
|
/// <summary>是否处于自动存档抑制作用域;真正用于防止读档期间覆盖 slot_0。</summary>
|
|
public static bool IsAutoSaveSuppressed => _autoSaveSuppressDepth > 0;
|
|
|
|
public static IReadOnlyList<string> LastRestoreLog => _lastRestoreLog;
|
|
|
|
private static bool _isAutoSaving;
|
|
private static int _autoSaveSuppressDepth;
|
|
private static readonly List<string> _lastRestoreLog = new();
|
|
|
|
private const float RestoreFadeDuration = 0.2f;
|
|
|
|
/// <summary>启动自动存档协程(手动/调试入口)。</summary>
|
|
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));
|
|
}
|
|
|
|
/// <summary>自动存档协程:settle 一帧 → 主线程 Capture → 异步写盘。</summary>
|
|
public static IEnumerator AutoSaveRoutine(string triggerNodeName = null)
|
|
{
|
|
if (_isAutoSaving)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
yield return null;
|
|
|
|
if (_isAutoSaving)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
_isAutoSaving = true;
|
|
var infoPanel = UIManager.Instance.GetPanel<InfoPanel>();
|
|
infoPanel?.ShowSaveLoading();
|
|
|
|
try
|
|
{
|
|
SaveSnapshot snapshot;
|
|
byte[] thumbnail;
|
|
|
|
using (new CodeTimer("SaveSnapshot"))
|
|
{
|
|
snapshot = SnapshotService.Capture(triggerNodeName);
|
|
thumbnail = SlotThumbnailCapture.CapturePng();
|
|
}
|
|
|
|
var writeTask = SlotManager.SaveToAutoSlotAsync(snapshot, thumbnail);
|
|
while (!writeTask.IsCompleted)
|
|
{
|
|
yield return null;
|
|
}
|
|
|
|
if (writeTask.IsFaulted)
|
|
{
|
|
Debug.LogError(
|
|
$"[SaveRestoreOrchestrator] 自动存档失败: {writeTask.Exception?.GetBaseException()}");
|
|
yield break;
|
|
}
|
|
|
|
if (snapshot?.anchor != null && string.IsNullOrEmpty(snapshot.anchor.nodeName))
|
|
{
|
|
Debug.Log("[SaveRestoreOrchestrator] 已保存无 Yarn 节点活跃状态。");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
infoPanel?.HideSaveLoading();
|
|
_isAutoSaving = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>将当前自动档复制到指定手动档。</summary>
|
|
public static void CreateManualSlot(int slotIndex)
|
|
{
|
|
if (!SavePointEvaluator.CanManualSave(out var reason))
|
|
{
|
|
Debug.LogWarning($"[SaveRestoreOrchestrator] 当前不可手动存档:{reason}");
|
|
return;
|
|
}
|
|
|
|
SlotManager.CopyAutoToManual(slotIndex);
|
|
}
|
|
|
|
/// <summary>从指定槽位读档并还原。</summary>
|
|
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}");
|
|
}
|
|
|
|
/// <summary>从文件读档并还原;自动区分新快照格式与 legacy 格式。</summary>
|
|
public static IEnumerator RestoreFromFile(string savePath)
|
|
{
|
|
if (SnapshotPersistence.IsLegacyFormat(savePath))
|
|
{
|
|
Debug.LogWarning("[SaveRestoreOrchestrator] 检测到旧格式存档,请使用新快照格式重新保存。");
|
|
yield return RestoreLegacyWithFlow(savePath);
|
|
yield break;
|
|
}
|
|
|
|
SaveSnapshot snapshot;
|
|
using (new CodeTimer("LoadSnapshot"))
|
|
{
|
|
snapshot = SnapshotPersistence.Load(savePath);
|
|
}
|
|
|
|
yield return RestoreSnapshot(snapshot, savePath);
|
|
}
|
|
|
|
public static IDisposable SuppressAutoSaveScope(string reason)
|
|
{
|
|
_autoSaveSuppressDepth++;
|
|
Debug.Log($"[SaveRestoreOrchestrator] SuppressAutoSave begin: {reason}, depth={_autoSaveSuppressDepth}");
|
|
return new AutoSaveSuppressScope(reason);
|
|
}
|
|
|
|
/// <summary>旧档:Yarn 变量 + DeepRepairDataRegistry systemData。</summary>
|
|
private static IEnumerator RestoreLegacy(string savePath)
|
|
{
|
|
var saveData = SnapshotPersistence.ReadLegacySaveRoot(savePath);
|
|
var floatDict = saveData["floatDict"]?.ToObject<Dictionary<string, float>>();
|
|
var stringDict = saveData["stringDict"]?.ToObject<Dictionary<string, string>>();
|
|
var boolDict = saveData["boolDict"]?.ToObject<Dictionary<string, bool>>();
|
|
YarnVariableStorage.Instance.SetAllVariables(floatDict, stringDict, boolDict);
|
|
|
|
if (saveData["systemData"] is JObject systemDataJson)
|
|
{
|
|
yield return DeepRepairDataRegistry.LoadByJson(systemDataJson);
|
|
}
|
|
|
|
var snapshot = SnapshotService.Capture();
|
|
SlotManager.SaveToAutoSlot(snapshot, null);
|
|
}
|
|
|
|
private static IEnumerator RestoreLegacyWithFlow(string savePath)
|
|
{
|
|
using (SuppressAutoSaveScope("RestoreLegacy"))
|
|
{
|
|
IsRestoring = true;
|
|
ResetRestoreLog("legacy");
|
|
|
|
try
|
|
{
|
|
yield return FadeInForRestore();
|
|
yield return RestoreLegacy(savePath);
|
|
yield return null;
|
|
yield return FadeOutForRestore();
|
|
}
|
|
finally
|
|
{
|
|
IsRestoring = false;
|
|
AddRestoreLog("Legacy restore finished");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static IEnumerator RestoreSnapshot(SaveSnapshot snapshot, string sourceLabel)
|
|
{
|
|
if (snapshot == null)
|
|
{
|
|
Debug.LogError("[SaveRestoreOrchestrator] snapshot 为 null,无法读档。");
|
|
yield break;
|
|
}
|
|
|
|
using (SuppressAutoSaveScope($"RestoreSnapshot:{sourceLabel}"))
|
|
{
|
|
IsRestoring = true;
|
|
ResetRestoreLog(sourceLabel);
|
|
var context = new SnapshotRestoreContext(snapshot, logStep: AddRestoreLog);
|
|
|
|
try
|
|
{
|
|
yield return FadeInForRestore();
|
|
|
|
SnapshotRegistry.EnsureInitialized();
|
|
yield return SnapshotRestore.RestoreState(YarnVariableStorage.Instance, snapshot, context);
|
|
yield return SnapshotRestore.RestoreAnchor(snapshot, context);
|
|
|
|
AddRestoreLog("Phase 3.5: settle one frame");
|
|
yield return null;
|
|
|
|
yield return FadeOutForRestore();
|
|
}
|
|
finally
|
|
{
|
|
IsRestoring = false;
|
|
AddRestoreLog("Restore finished");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static IEnumerator FadeInForRestore()
|
|
{
|
|
AddRestoreLog("Begin restore fade in");
|
|
var panel = UIManager.Instance?.GetPanel<PlayToolPanel>();
|
|
if (panel != null)
|
|
{
|
|
yield return panel.FadeInAsync(RestoreFadeDuration);
|
|
}
|
|
}
|
|
|
|
private static IEnumerator FadeOutForRestore()
|
|
{
|
|
AddRestoreLog("End restore fade out");
|
|
var panel = UIManager.Instance?.GetPanel<PlayToolPanel>();
|
|
if (panel != null)
|
|
{
|
|
yield return panel.FadeOutAsync(RestoreFadeDuration);
|
|
}
|
|
}
|
|
|
|
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}");
|
|
}
|
|
}
|
|
}
|
|
}
|