feat: 增强存档管理,优化异步存档逻辑,添加错误处理机制

This commit is contained in:
2026-05-20 16:52:22 +08:00
parent 04aec4cc8e
commit e50cf57321
6 changed files with 30 additions and 15 deletions

View File

@@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
using BaseGames.Core.Events; using BaseGames.Core.Events;
@@ -32,7 +33,16 @@ namespace BaseGames.Core.Save
if (_timer >= _intervalSeconds) if (_timer >= _intervalSeconds)
{ {
_timer = 0f; _timer = 0f;
_ = _saveManager.SaveAsync(EmergencySlot); RunFireAndForget(_saveManager.SaveAsync(EmergencySlot), "EmergencySave");
}
}
private static async void RunFireAndForget(System.Threading.Tasks.Task task, string context)
{
try { await task; }
catch (Exception e)
{
UnityEngine.Debug.LogError($"[EmergencySave] {context} 失败: {e.Message}\n{e.StackTrace}");
} }
} }

View File

@@ -88,12 +88,13 @@ namespace BaseGames.Core.Save
_current.Meta.SlotIndex = targetSlot; _current.Meta.SlotIndex = targetSlot;
_current.Meta.SaveCount++; _current.Meta.SaveCount++;
// 清空 checksum序列化计算,再序列化含 checksum 的最终版本 // 清空 checksum序列化计算 HMAC然后直接注入序列化字符串——避免第二次完整序列化。
// 使用 Formatting.None 减少序列化字符串体积和 GC 分配 // Base64 字符集A-Z a-z 0-9 + / =)不含需要 JSON 转义的字符string.Replace 是安全替换。
_current.Meta.Checksum = null; _current.Meta.Checksum = null;
string jsonForChecksum = JsonConvert.SerializeObject(_current, Formatting.None); string jsonForChecksum = JsonConvert.SerializeObject(_current, Formatting.None);
_current.Meta.Checksum = ComputeChecksum(jsonForChecksum); string checksum = ComputeChecksum(jsonForChecksum);
string finalJson = JsonConvert.SerializeObject(_current, Formatting.None); string finalJson = jsonForChecksum.Replace("\"Checksum\":null", $"\"Checksum\":\"{checksum}\"");
_current.Meta.Checksum = checksum; // 同步回内存,保持对象一致
await _storage.WriteAsync(targetSlot, finalJson); await _storage.WriteAsync(targetSlot, finalJson);

View File

@@ -128,8 +128,7 @@ namespace BaseGames.Core.Save
[Serializable] [Serializable]
public class QuestSaveData public class QuestSaveData
{ {
public Dictionary<string, QuestState> QuestStates = new(); public Dictionary<string, QuestState> QuestStates = new();
public List<string> AvailableQuestIds = new();
} }
[Serializable] [Serializable]

View File

@@ -50,10 +50,7 @@ namespace BaseGames.Core.Save
// 2.2 删除 PlayerSaveData.ShieldHP / ShieldIsBroken护盾在存档点始终全满无需持久化 // 2.2 删除 PlayerSaveData.ShieldHP / ShieldIsBroken护盾在存档点始终全满无需持久化
// SettingsSaveData.Language 字段保留(由 LocalizationManager 负责按存档槽读写)。 // SettingsSaveData.Language 字段保留(由 LocalizationManager 负责按存档槽读写)。
// 旧存档中已删除的字段由 Newtonsoft.Json 的 [JsonExtensionData] 忽略,无需额外处理。 // 旧存档中已删除的字段由 Newtonsoft.Json 的 [JsonExtensionData] 忽略,无需额外处理。
// Equipment.MaxNotches旧存档若为 0EquipmentManager.OnLoad 回退到初始 Notch 数量 // Equipment.MaxNotches旧存档若为 0EquipmentManager.OnLoad 回退到 config.initialNotchCount无需额外处理
if (data.Equipment != null && data.Equipment.MaxNotches == 0)
data.Equipment.MaxNotches = 0; // 保持 0OnLoad 回退到 config.initialNotchCount
Debug.Log("[SaveMigrator] 从 '2.1' 迁移至 '2.2'。"); Debug.Log("[SaveMigrator] 从 '2.1' 迁移至 '2.2'。");
v = "2.2"; v = "2.2";
} }

View File

@@ -129,7 +129,6 @@ namespace BaseGames.Quest
public void OnSave(SaveData data) public void OnSave(SaveData data)
{ {
data.Quests.QuestStates.Clear(); data.Quests.QuestStates.Clear();
data.Quests.AvailableQuestIds.Clear();
foreach (var (id, state) in _questStates) foreach (var (id, state) in _questStates)
{ {
data.Quests.QuestStates[id] = new BaseGames.Core.Save.QuestState data.Quests.QuestStates[id] = new BaseGames.Core.Save.QuestState
@@ -138,7 +137,6 @@ namespace BaseGames.Quest
ObjectiveIndex = 0, ObjectiveIndex = 0,
ProgressCounts = BuildProgressList(id), ProgressCounts = BuildProgressList(id),
}; };
if (state == QuestStateEnum.Available) data.Quests.AvailableQuestIds.Add(id);
} }
} }

View File

@@ -1,3 +1,5 @@
using System;
using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
using BaseGames.Core; using BaseGames.Core;
using BaseGames.Core.Events; using BaseGames.Core.Events;
@@ -68,7 +70,7 @@ namespace BaseGames.World
// 触发存档OnSave() 由 SaveAsync 回调所有 ISaveable包含本组件 // 触发存档OnSave() 由 SaveAsync 回调所有 ISaveable包含本组件
var svc = ServiceLocator.GetOrDefault<ISaveService>(); var svc = ServiceLocator.GetOrDefault<ISaveService>();
if (svc != null) if (svc != null)
_ = svc.SaveAsync(svc.ActiveSlot); RunFireAndForget(svc.SaveAsync(svc.ActiveSlot));
else else
Debug.LogWarning("[SavePoint] ISaveService 未注册,跳过存档。", this); Debug.LogWarning("[SavePoint] ISaveService 未注册,跳过存档。", this);
} }
@@ -101,6 +103,14 @@ namespace BaseGames.World
_isActivated = !string.IsNullOrEmpty(_savePointId) _isActivated = !string.IsNullOrEmpty(_savePointId)
&& data.World.ActivatedSavePoints.Contains(_savePointId); && data.World.ActivatedSavePoints.Contains(_savePointId);
} }
private static async void RunFireAndForget(Task task)
{
try { await task; }
catch (Exception e)
{
Debug.LogError($"[SavePoint] 存档失败: {e.Message}\n{e.StackTrace}");
}
}
} }
} }