using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using AibisDream.Utility; using Newtonsoft.Json; using UnityEngine; namespace AibisDream.SaveSystem { /// /// 存档槽位目录与文件路径工具。 /// public static class SlotDirectory { public static string GetSlotPath(int slotIndex) { return Path.Combine(ConstRef.SaveFilePath, $"{ConstRef.SaveSlotDirPrefix}{slotIndex}"); } public static string GetSnapshotPath(int slotIndex) { return Path.Combine(GetSlotPath(slotIndex), $"{ConstRef.SaveSnapshotFileName}.json"); } public static string GetMetaPath(int slotIndex) { return Path.Combine(GetSlotPath(slotIndex), $"{ConstRef.SaveMetaFileName}.json"); } public static string GetThumbnailPath(int slotIndex) { return Path.Combine(GetSlotPath(slotIndex), $"{ConstRef.SaveThumbnailFileName}.png"); } public static string GetLatestSlotIndexPath() { return Path.Combine(ConstRef.SaveFilePath, $"{ConstRef.LatestSlotFileName}.json"); } public static bool Exists(int slotIndex) { return File.Exists(GetMetaPath(slotIndex)); } public static IEnumerable EnumerateExistingSlots() { if (!Directory.Exists(ConstRef.SaveFilePath)) { yield break; } for (var i = SlotIndex.Auto; i <= SlotIndex.ManualEnd; i++) { if (Exists(i)) { yield return i; } } } public static void EnsureSlotDirectory(int slotIndex) { Directory.CreateDirectory(GetSlotPath(slotIndex)); } } /// /// 槽位文件原子写工具。 /// 先写入临时文件,再重命名为最终文件,避免写入过程中崩溃导致存档损坏。 /// public static class SlotAtomicWriter { public static void WriteJson(T obj, string finalPath) { var json = JsonConvert.SerializeObject(obj, Formatting.Indented); WriteText(json, finalPath); } public static void WriteText(string text, string finalPath) { var tempPath = finalPath + ".tmp"; var dir = Path.GetDirectoryName(finalPath); if (!string.IsNullOrEmpty(dir)) { Directory.CreateDirectory(dir); } File.WriteAllText(tempPath, text); CommitTempFile(tempPath, finalPath); } public static void WriteBytes(byte[] data, string finalPath) { if (data == null) { return; } var tempPath = finalPath + ".tmp"; var dir = Path.GetDirectoryName(finalPath); if (!string.IsNullOrEmpty(dir)) { Directory.CreateDirectory(dir); } File.WriteAllBytes(tempPath, data); CommitTempFile(tempPath, finalPath); } private static void CommitTempFile(string tempPath, string finalPath) { const int maxAttempts = 5; for (var attempt = 0; attempt < maxAttempts; attempt++) { try { if (File.Exists(finalPath)) { // Unity 使用的 .NET Standard 2.0 无 File.Move(..., overwrite);Replace 为原子覆盖。 File.Replace(tempPath, finalPath, null); } else { File.Move(tempPath, finalPath); } return; } catch (IOException) when (attempt < maxAttempts - 1) { Thread.Sleep(20 * (attempt + 1)); } } } } }