feat(save): 验证工具增加运行时自动存档历史

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-18 14:34:29 +08:00
co-authored by Cursor
parent 0a6e892504
commit 0b0d22d009
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -20,6 +20,8 @@ namespace AibisDream.SaveSystem.Editor
private const string MenuPath = "AIBIS/存档系统验证工具";
private const string WindowTitle = "SaveSystem Validator";
private const float SaveHistoryBoxMinHeight = 400f;
private Vector2 _scrollPosition;
private string _lastLog = string.Empty;
private int _manualSlotIndex = 1;
@@ -27,10 +29,109 @@ namespace AibisDream.SaveSystem.Editor
private string _testNodeName = "SomeNode";
private string _testTags = "";
private readonly List<SaveHistoryEntry> _saveHistory = new();
private string _lastObservedSavedAt;
/// <summary>运行时自动存档历史条目,仅内存缓存。</summary>
private class SaveHistoryEntry
{
public string SavedAt;
public string NodeName;
public string SceneName;
}
[MenuItem(MenuPath)]
public static void Open()
{
GetWindow<SaveSystemValidationWindow>(WindowTitle);
var window = GetWindow<SaveSystemValidationWindow>(WindowTitle);
window.minSize = new Vector2(420, 640);
}
private void OnEnable()
{
EditorApplication.update += OnEditorUpdate;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
InitializeLastObservedSavedAt();
}
private void OnDisable()
{
EditorApplication.update -= OnEditorUpdate;
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
}
private void OnPlayModeStateChanged(PlayModeStateChange state)
{
if (state == PlayModeStateChange.EnteredPlayMode)
{
_saveHistory.Clear();
InitializeLastObservedSavedAt();
TrySeedHistoryFromCurrentAutoSlot();
Repaint();
return;
}
if (state == PlayModeStateChange.ExitingPlayMode)
{
_saveHistory.Clear();
_lastObservedSavedAt = null;
}
}
private void TrySeedHistoryFromCurrentAutoSlot()
{
var meta = SlotManager.LoadMeta(SlotIndex.Auto);
if (meta == null)
{
return;
}
_saveHistory.Add(CreateHistoryEntry(meta));
_lastObservedSavedAt = meta.savedAt;
}
private static SaveHistoryEntry CreateHistoryEntry(SlotMeta meta)
{
return new SaveHistoryEntry
{
SavedAt = meta.savedAt,
NodeName = string.IsNullOrEmpty(meta.nodeName) ? "(无节点)" : meta.nodeName,
SceneName = meta.sceneName
};
}
private void InitializeLastObservedSavedAt()
{
var meta = SlotManager.LoadMeta(SlotIndex.Auto);
_lastObservedSavedAt = meta?.savedAt;
}
/// <summary>
/// 监听 slot_0 的 meta 变化,运行时每次自动存档都会记录到内存列表中。
/// 纯 Editor 行为,不侵入运行时代码。
/// </summary>
private void OnEditorUpdate()
{
if (!Application.isPlaying)
{
return;
}
var meta = SlotManager.LoadMeta(SlotIndex.Auto);
if (meta == null)
{
return;
}
if (!string.IsNullOrEmpty(_lastObservedSavedAt) && _lastObservedSavedAt == meta.savedAt)
{
return;
}
_lastObservedSavedAt = meta.savedAt;
_saveHistory.Add(CreateHistoryEntry(meta));
Repaint();
}
private void OnGUI()
@@ -40,6 +141,8 @@ namespace AibisDream.SaveSystem.Editor
DrawHeader();
DrawSlotOverview();
GUILayout.Space(12);
DrawSaveHistorySection();
GUILayout.Space(12);
DrawDirectCaptureSection();
GUILayout.Space(12);
DrawAutoSaveSimulationSection();
@@ -131,6 +234,51 @@ namespace AibisDream.SaveSystem.Editor
}
}
private void DrawSaveHistorySection()
{
EditorGUILayout.LabelField("自动存档历史 (运行时)", EditorStyles.boldLabel);
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox, GUILayout.MinHeight(SaveHistoryBoxMinHeight)))
{
if (!Application.isPlaying)
{
EditorGUILayout.HelpBox(
"进入 Play Mode 后,每次 slot_0 自动存档都会在此记录节点名与时间。\n" +
"数据仅保存在当前窗口内存中,退出 Play Mode 后清空。",
MessageType.Info);
}
else if (_saveHistory.Count == 0)
{
EditorGUILayout.LabelField("等待自动存档…", EditorStyles.wordWrappedLabel);
EditorGUILayout.LabelField(
"触发 content / hub / linear 节点,或点击下方 TryAutoSave。",
EditorStyles.miniLabel);
}
else
{
EditorGUILayout.LabelField($"共 {_saveHistory.Count} 条记录", EditorStyles.miniLabel);
for (var i = 0; i < _saveHistory.Count; i++)
{
var entry = _saveHistory[i];
EditorGUILayout.LabelField(
$"{i + 1}. [{entry.SavedAt}] {entry.NodeName}",
EditorStyles.wordWrappedLabel);
EditorGUILayout.LabelField(
$" 场景: {entry.SceneName ?? "N/A"}",
EditorStyles.miniLabel);
}
GUILayout.FlexibleSpace();
if (GUILayout.Button("清空历史记录"))
{
_saveHistory.Clear();
}
}
}
}
private void DrawManualSlotSection()
{
EditorGUILayout.LabelField("手动档操作", EditorStyles.boldLabel);
@@ -243,7 +391,16 @@ namespace AibisDream.SaveSystem.Editor
var beforeLatest = SlotManager.GetLatestSlotIndex();
var beforeSnapshot = SlotManager.LoadSnapshot(0);
SaveRestoreOrchestrator.TryAutoSave();
if (!SavePointEvaluator.CanAutoSave(out var rejectReason))
{
Log($"CanAutoSave 拒绝: {rejectReason}");
return;
}
var routine = SaveRestoreOrchestrator.AutoSaveRoutine();
while (routine.MoveNext())
{
}
var afterSnapshot = SlotManager.LoadSnapshot(0);
var afterLatest = SlotManager.GetLatestSlotIndex();