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

188 lines
6.7 KiB
C#

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"/> 与槽位系统。
/// <para>
/// 对外入口:<see cref="TryAutoSave"/>(手动/调试触发)、
/// <see cref="AutoSaveRoutine"/>(节点进入事件触发)、
/// <see cref="CreateManualSlot"/>(手动存档)、
/// <see cref="RestoreFromSlot"/>(读档)、
/// <see cref="RestoreFromFile"/>(旧档/调试)。
/// </para>
/// </summary>
public static class SaveRestoreOrchestrator
{
/// <summary>是否正在读档还原中;为 true 时禁止自动存档覆盖 slot_0。</summary>
public static bool IsRestoring { get; private set; }
private static bool _isAutoSaving;
/// <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>
/// <param name="triggerNodeName"><c>OnNodeStart</c> 触发存档时的节点名,作为 anchor 写入快照。</param>
public static IEnumerator AutoSaveRoutine(string triggerNodeName = null)
{
if (_isAutoSaving)
{
yield break;
}
yield return null;
// 可能在 settle 帧内已有另一条协程开始写盘,须二次检查避免并发覆盖 slot_0。
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)
{
IsRestoring = true;
try
{
var snapshot = SlotManager.LoadSnapshot(slotIndex);
if (snapshot == null)
{
Debug.LogError($"[SaveRestoreOrchestrator] 槽位 {slotIndex} 不存在或读取失败");
yield break;
}
yield return RestoreSnapshot(snapshot);
}
finally
{
IsRestoring = false;
}
}
/// <summary>从文件读档并还原;自动区分新快照格式与 legacy 格式。</summary>
public static IEnumerator RestoreFromFile(string savePath)
{
IsRestoring = true;
try
{
if (SnapshotPersistence.IsLegacyFormat(savePath))
{
Debug.LogWarning("[SaveRestoreOrchestrator] 检测到旧格式存档,请使用新快照格式重新保存。");
yield return RestoreLegacy(savePath);
yield break;
}
SaveSnapshot snapshot;
using (new CodeTimer("LoadSnapshot"))
{
snapshot = SnapshotPersistence.Load(savePath);
}
yield return RestoreSnapshot(snapshot);
}
finally
{
IsRestoring = false;
}
}
/// <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 RestoreSnapshot(SaveSnapshot snapshot)
{
yield return SnapshotService.Restore(snapshot);
}
}
}