v10 全量评审:修复 TD-06 至 TD-12(InputReader 移除资产扫描回退 / EmergencySave 解除 LocalFileStorage 直接依赖 / AccessibilityManager 注册 IAccessibilityService / HUDController HP/SpringIcon SetActive 复用 / MovingPlatform 缓存 WaitForSeconds / RewardSO IRewardTarget 解耦 Quest←Player 依赖 / CrashReporter 频率限制崩溃日志)

This commit is contained in:
2026-05-12 16:18:46 +08:00
parent ebbbb7332e
commit 9284278578
27 changed files with 1697 additions and 125 deletions

View File

@@ -7,13 +7,18 @@ namespace BaseGames.Core.Save
/// <summary>
/// 崩溃检测与诊断日志写入。
/// 监听 Unity 异常日志并在 OnApplicationPause 非正常退出时触发紧急存档(槽 99
/// 日志文件最多保留 <see cref="MaxLogFiles"/> 个,超出时删除最旧文件。
/// </summary>
public class CrashReporter : MonoBehaviour
{
[SerializeField] private SaveManager _saveManager;
[SerializeField] private EmergencySaveService _emergencyService;
[SerializeField, Min(1)] private int _maxLogFiles = 10;
private bool _cleanExit;
private bool _cleanExit;
private int _logsWrittenThisSession;
private const int MaxLogsPerSession = 5; // 同一运行期内最多写 5 个诊断文件,防止异常风暴
private void OnEnable()
{
@@ -39,7 +44,10 @@ namespace BaseGames.Core.Save
private void OnLogMessage(string condition, string stackTrace, LogType type)
{
if (type == LogType.Exception || type == LogType.Error)
{
if (_logsWrittenThisSession >= MaxLogsPerSession) return;
WriteDiagnosticLog(condition, stackTrace);
}
}
/// <summary>检查是否存在上次崩溃或意外退出留下的紧急存档。</summary>
@@ -55,11 +63,25 @@ namespace BaseGames.Core.Save
string logPath = Path.Combine(Application.persistentDataPath, $"crash_{timestamp}.log");
string content = $"[{DateTime.UtcNow:o}]\n{condition}\n\n{stackTrace}";
File.WriteAllText(logPath, content);
_logsWrittenThisSession++;
// 保留最新 N 个日志文件,超出时删除最旧文件
PruneOldLogFiles(Application.persistentDataPath, _maxLogFiles);
}
catch
{
// 日志写入失败不能再抛异常,否则会造成无限递归
}
}
private static void PruneOldLogFiles(string directory, int maxFiles)
{
var files = Directory.GetFiles(directory, "crash_*.log");
if (files.Length <= maxFiles) return;
Array.Sort(files); // 按文件名(含时间戳)升序,最旧的在前
int deleteCount = files.Length - maxFiles;
for (int i = 0; i < deleteCount; i++)
File.Delete(files[i]);
}
}
}

View File

@@ -42,12 +42,7 @@ namespace BaseGames.Core.Save
public async Task PromoteToSlot(int targetSlot)
{
if (_saveManager == null) return;
var storage = new LocalFileStorage();
if (!storage.Exists(EmergencySlot)) return;
string json = await storage.ReadAsync(EmergencySlot);
if (json == null) return;
await storage.WriteAsync(targetSlot, json);
await storage.DeleteAsync(EmergencySlot);
await _saveManager.PromoteEmergencyToSlotAsync(targetSlot, EmergencySlot);
}
}
}

View File

@@ -212,6 +212,19 @@ namespace BaseGames.Core.Save
if (_currentSlot == slotIndex) _current = null;
}
/// <summary>
/// 将紧急存档槽的数据复制到目标槽,并删除紧急存档。
/// 由 <see cref="EmergencySaveService"/> 调用,确保所有 IO 操作通过统一的 ISaveStorage 进行。
/// </summary>
public async Task PromoteEmergencyToSlotAsync(int targetSlot, int emergencySlot)
{
if (!_storage.Exists(emergencySlot)) return;
string json = await _storage.ReadAsync(emergencySlot);
if (json == null) return;
await _storage.WriteAsync(targetSlot, json);
await _storage.DeleteAsync(emergencySlot);
}
// ── 私有工具 ──────────────────────────────────────────────────────────
private string ComputeChecksum(string json)
{