perf(save): 优化存档写入性能并支持关闭缩略图

This commit is contained in:
2026-07-02 19:15:11 +08:00
parent 784e1e8f0d
commit 517a03bbc3
6 changed files with 63 additions and 41 deletions
+2
View File
@@ -29,6 +29,8 @@ namespace AibisDream.Utility
/// <summary>P2 槽位相关常量。</summary>
public const int SaveSlotCount = 8;
/// <summary>存档槽位缩略图;关闭可避免主相机 Render + ReadPixels 的性能开销。</summary>
public const bool SaveThumbnailEnabled = false;
public const int SaveThumbnailWidth = 640;
public const int SaveThumbnailHeight = 360;
public const string SaveSlotDirPrefix = "slot_";
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using AibisDream.Framework;
using AibisDream.UI;
using UnityEngine;
@@ -45,7 +46,7 @@ namespace AibisDream.SaveSystem
DialogController.Instance.StartCoroutine(AutoSaveRoutine(triggerNodeName));
}
/// <summary>自动存档协程:settle 一帧 → 主线程 Capture → 异步写盘。</summary>
/// <summary>自动存档协程:settle 一帧 → 主线程 Capture → 后台序列化写盘(不阻塞主线程)。</summary>
/// <param name="triggerNodeName">见 <see cref="SnapshotCapture.Capture"/>;显式 save 传 null。</param>
/// <param name="omitAnchor">为 true 时不写入 Yarn 节点 anchor。</param>
public static IEnumerator AutoSaveRoutine(string triggerNodeName = null, bool omitAnchor = false)
@@ -68,47 +69,52 @@ namespace AibisDream.SaveSystem
var infoPanel = UIManager.Instance.GetPanel<InfoPanel>();
infoPanel?.ShowSaveLoading();
SaveSnapshot snapshot;
byte[] thumbnail;
try
{
SaveSnapshot snapshot;
byte[] thumbnail;
using (new CodeTimer("SaveSnapshot"))
{
snapshot = SnapshotService.Capture(triggerNodeName, omitAnchor);
thumbnail = SlotThumbnailCapture.CapturePng();
}
if (TestSaveArchive.IsEnabled)
{
TestSaveArchive.Archive(snapshot, thumbnail);
Debug.Log("[SaveRestoreOrchestrator] 测试存档模式:已写入 testsavs(不覆盖 slot_0)。");
}
else
{
var writeTask = SlotManager.SaveToAutoSlotAsync(snapshot, thumbnail);
while (!writeTask.IsCompleted)
{
yield return null;
}
if (writeTask.IsFaulted)
{
Debug.LogError(
$"[SaveRestoreOrchestrator] 自动存档失败: {writeTask.Exception?.GetBaseException()}");
yield break;
}
}
if (omitAnchor || (snapshot?.anchor != null && string.IsNullOrEmpty(snapshot.anchor.nodeName)))
{
Debug.Log("[SaveRestoreOrchestrator] 已保存无 Yarn 节点 anchor 的状态。");
}
}
finally
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 的状态。");
}
}
+15 -6
View File
@@ -33,17 +33,18 @@ namespace AibisDream.SaveSystem
}
/// <summary>
/// 主线程完成序列化后,在后台线程写入自动档(slot 0)。
/// 调用方须已在主线程完成 <see cref="SnapshotService.Capture"/>。
/// 主线程 Capture 后,在后台线程序列化并写入自动档(slot 0)。
/// </summary>
public static async Task SaveToAutoSlotAsync(SaveSnapshot snapshot, byte[] thumbnailPng)
{
EnsureMigrated();
var json = SnapshotPersistence.Serialize(snapshot);
var meta = BuildSlotMeta(SlotIndex.Auto, snapshot);
await Task.Run(() => WriteSlotFiles(SlotIndex.Auto, json, meta, thumbnailPng));
await Task.Run(() =>
{
var json = SnapshotPersistence.Serialize(snapshot);
var meta = BuildSlotMeta(SlotIndex.Auto, snapshot);
WriteSlotFiles(SlotIndex.Auto, json, meta, thumbnailPng);
});
SetLatestSlotIndex(SlotIndex.Auto);
CloudSaveManager.OnSlotWritten(SlotIndex.Auto);
@@ -147,6 +148,14 @@ namespace AibisDream.SaveSystem
}
}
/// <summary>是否存在可用的自动档(slot_0 meta + snapshot)。</summary>
public static bool HasAutoSave()
{
EnsureMigrated();
return SlotDirectory.Exists(SlotIndex.Auto)
&& File.Exists(SlotDirectory.GetSnapshotPath(SlotIndex.Auto));
}
/// <summary>读取槽位元数据。</summary>
public static SlotMeta LoadMeta(int slotIndex)
{
@@ -12,9 +12,14 @@ namespace AibisDream.SaveSystem
/// </summary>
public static class SlotThumbnailCapture
{
/// <summary>捕获当前画面的 PNG 字节数组。</summary>
/// <summary>捕获当前画面的 PNG 字节数组;禁用时返回 null,不阻塞存档流程。</summary>
public static byte[] CapturePng()
{
if (!ConstRef.SaveThumbnailEnabled)
{
return null;
}
var cam = Camera.main;
if (cam == null)
{
@@ -45,6 +45,7 @@ namespace AibisDream.SaveSystem
snapshot.sections[provider.SaveId] = dto;
}
snapshot.schemaVersion = SaveSnapshotSchema.CurrentVersion;
return snapshot;
}
@@ -40,11 +40,10 @@ namespace AibisDream.SaveSystem
return SlotManager.LoadSnapshot(slotIndex);
}
/// <summary>将快照序列化为 JSON 字符串。</summary>
/// <summary>将快照序列化为 JSON 字符串(紧凑格式)。</summary>
public static string Serialize(SaveSnapshot snapshot)
{
snapshot.schemaVersion = SaveSnapshotSchema.CurrentVersion;
return JsonConvert.SerializeObject(snapshot, Formatting.Indented, Settings);
return JsonConvert.SerializeObject(snapshot, Formatting.None, Settings);
}
/// <summary>从 JSON 字符串反序列化为快照。</summary>