#if UNITY_EDITOR || DEVELOPMENT_BUILD using System; using System.Linq; using System.Threading.Tasks; using AibisDream.Utility; using UnityEngine; namespace AibisDream.SaveSystem { /// 正式自动档成功后使用的非阻塞测试存档旁路。 public static class TestSaveRecorder { private static TestSaveRepository _repository; public static bool IsRecording { get; private set; } public static TestSaveRepository Repository => _repository ??= new TestSaveRepository(ConstRef.TestSavePath); public static event Action LibraryChanged; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void ResetRuntimeState() { IsRecording = false; _repository = null; } public static void SetRecording(bool recording) { IsRecording = recording; } internal static TestSaveRecordRequest CreateRequest( SaveSnapshot snapshot, byte[] thumbnail, string saveTrigger, string resumeMode, string sourceNodeName) { if (!IsRecording || snapshot?.anchor == null || string.IsNullOrWhiteSpace(snapshot.anchor.sceneSoName) || string.IsNullOrWhiteSpace(snapshot.anchor.yarnProjectId)) { return null; } var anchor = snapshot.anchor; var stateOnly = string.Equals( resumeMode, nameof(SaveResumeMode.StateOnly), StringComparison.Ordinal); var recordNodeName = stateOnly ? sourceNodeName : anchor.nodeName; if (string.IsNullOrWhiteSpace(recordNodeName)) { return null; } var chapter = GameManager.Instance?.RuntimeChapters? .FirstOrDefault(item => item != null && string.Equals(item.name, anchor.sceneSoName, StringComparison.Ordinal) && string.Equals(item.yarnProject?.name, anchor.yarnProjectId, StringComparison.Ordinal)); var dedupeKey = BuildDedupeKey( anchor.sceneSoName, anchor.yarnProjectId, recordNodeName, resumeMode); return new TestSaveRecordRequest { Snapshot = snapshot, Thumbnail = thumbnail, DedupeKey = dedupeKey, EntryId = TestSaveRepository.StableHash(dedupeKey), ChapterId = chapter?.name ?? anchor.sceneSoName, ChapterTitle = string.IsNullOrWhiteSpace(chapter?.title) ? anchor.sceneSoName : chapter.title, SceneSoName = anchor.sceneSoName, YarnProjectId = anchor.yarnProjectId, NodeName = recordNodeName, SaveTrigger = saveTrigger, ResumeMode = resumeMode, SceneName = snapshot.scene?.sceneName, GameVersion = snapshot.gameVersion }; } internal static void Enqueue(TestSaveRecordRequest request) { if (request == null) return; _ = Task.Run(() => { try { Repository.Record(request); LibraryChanged?.Invoke(); } catch (Exception ex) { Debug.LogError($"[TestSaveRecorder] 测试存档旁路写入失败,不影响正式存档:{ex}"); } }); } public static string BuildDedupeKey( string sceneSoName, string yarnProjectId, string nodeName, string resumeMode = null) { var baseKey = $"{sceneSoName ?? string.Empty}\n{yarnProjectId ?? string.Empty}\n{nodeName ?? string.Empty}"; return string.IsNullOrEmpty(resumeMode) || string.Equals( resumeMode, nameof(SaveResumeMode.RestartNode), StringComparison.Ordinal) ? baseKey : $"{baseKey}\n{resumeMode}"; } } } #endif