using Core.KV; using game.KV; using System; using System.Linq; using UnityEngine; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; internal class KvMgr : MonoBehaviour { private static KvMgr instance; private KvSaveStore store; private CancellationTokenSource tokenSource; private void Awake() { instance = this; tokenSource = new CancellationTokenSource(); UpdateUserDataValue(tokenSource.Token); } internal static void Bind(KvSaveStore original) { if (instance != null) { instance.store = original; } } internal static void Unbind(KvSaveStore original) { if (instance != null && ReferenceEquals(instance.store, original)) { instance.store = null; } } internal static void Stop(KvSaveStore original) { if (instance != null && ReferenceEquals(instance.store, original)) { instance.Stop(); } } private void Stop() { tokenSource?.Cancel(); tokenSource?.Dispose(); tokenSource = null; } private async void UpdateUserDataValue(CancellationToken token) { try { while (!token.IsCancellationRequested) { SyncData(); await Task.Delay(40000); } } catch (Exception ex) { Debug.LogError("KV save schedule failed: " + ex.GetType().Name); } } private void SyncData() { var original = store; if (original != null && original.IsCurrent) { ObserveSync(original.SyncDataAsync()); } } private static async void ObserveSync(Task task) { try { var result = await task; if (result.TransportError != KVError.None || result.Blocked != 0 || result.Unknown != 0) { Debug.LogError("KV ordinary save failed: " + result.TransportError); } } catch (Exception ex) { Debug.LogError("KV ordinary save failed: " + ex.GetType().Name); } } private void OnApplicationPause(bool paused) { if (paused) { Stop(); SyncData(); } else { tokenSource = new CancellationTokenSource(); UpdateUserDataValue(tokenSource.Token); } } private void OnApplicationQuit() { Stop(); } private void OnDestroy() { Stop(); SyncData(); store = null; if (ReferenceEquals(instance, this)) { instance = null; } } } namespace game.KV { public sealed class KvSaveStore : IPlayerSaveStore, IDisposable { private KvSessionOwner handle; private CancellationToken lifetime; private bool bound, disposed; private readonly object receiptsGate = new object(); public bool IsCurrent => bound && !disposed && !lifetime.IsCancellationRequested && handle.Status != KVSyncStatus.Closed; internal void Bind(KvSessionOwner original, CancellationToken loginLifetime) { if (bound || disposed || original == null || loginLifetime.IsCancellationRequested) { throw new InvalidOperationException("Player save store cannot be rebound."); } if (!ReadKV(original, "LastLoginTimeUtc").Success) { throw new InvalidOperationException("Player save handle is not ready."); } handle = original; lifetime = loginLifetime; bound = true; } internal void ReleaseFailedBinding(KvSessionOwner original) { lock (receiptsGate) { if (!bound) { return; } if (disposed || !ReferenceEquals(handle, original) || original.Status != KVSyncStatus.Closed) { throw new InvalidOperationException("Failed save binding has not closed."); } handle = null; bound = false; } } public PlayerSaveReadResult Read(string key) { if (!IsCurrent) { return new PlayerSaveReadResult("StaleOrNotReady"); } return ReadKV(handle, key); } public PlayerSaveWriteResult Write(string key, string value, bool remoteStore = true) { lock (receiptsGate) { if (!IsCurrent) { var rejected = new PlayerSaveWriteResult("StaleOrNotReady"); return rejected; } var result = WriteKV(handle, key, value, remoteStore); var write = result.Success ? new PlayerSaveWriteResult() : new PlayerSaveWriteResult(result.Error.ToString()); return write; } } public async Task SyncDataImmidiate(string key, string value) { KvSessionOwner original; CancellationToken originalLifetime; Task save; lock (receiptsGate) { if (!IsCurrent) { return false; } // Preserve the former current-store dictionary lookup's null-key behavior. if (key == null) { throw new ArgumentNullException(nameof(key)); } original = handle; originalLifetime = lifetime; save = SyncDataImmidiate(original, key, value); } var success = await save; lock (receiptsGate) { return success && IsCurrent && !originalLifetime.IsCancellationRequested && ReferenceEquals(handle, original); } } internal async Task SyncDataImmidiate(IReadOnlyDictionary values) { KvSessionOwner original; CancellationToken originalLifetime; Task save; lock (receiptsGate) { if (!IsCurrent || values == null || values.Count == 0) { return false; } var data = values.ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.Ordinal); original = handle; originalLifetime = lifetime; save = SyncDataImmidiate(original, data); } var success = await save; lock (receiptsGate) { return success && IsCurrent && !originalLifetime.IsCancellationRequested && ReferenceEquals(handle, original); } } internal Task SyncDataAsync() { lock (receiptsGate) { var original = handle; if (!IsCurrent) { return Task.FromResult(new KvFlushResult(1, 0, KVError.Cancelled)); } if (original == null) { return Task.FromResult(new KvFlushResult(1, 0, KVError.Validation)); } return original.SyncDataAsync(); } } public void Dispose() { lock (receiptsGate) { disposed = true; } } internal static PlayerSaveReadResult ReadKV(KvSessionOwner handle, string key) { if (handle == null || !KvSnapshot.ValidKey(key)) { return new PlayerSaveReadResult("InvalidInput"); } var read = handle.ReadLocalSnapshot(); if (!read.Success) { return new PlayerSaveReadResult(read.Error == KVLocalReadError.StaleOrClosing ? "StaleOrClosing" : "NotReady"); } var found = read.Data.TryGetValue(key, out var value); if (found && value == null) { throw new ArgumentException("A successful key observation requires an existence verdict."); } return new PlayerSaveReadResult(found, found ? value : null); } internal static KvStateResult WriteKV(KvSessionOwner handle, string key, string value, bool remoteStore = true) { if (handle == null) { return new KvStateResult(KVChangeError.Validation); } return handle.Update(key, value, remoteStore, ZZTimeHelper.UtcNow().Ticks); } internal static async Task SyncDataImmidiate(KvSessionOwner handle, string key, string value) { var success = false; if (handle != null && KvSnapshot.ValidKey(key) && value != null) { success = await handle.SyncDataImmidiate(key, value); } if (!success) { UnityEngine.Debug.LogError("[KVMgr::SyncDataImmidiate] Save failed."); } return success; } internal static async Task SyncDataImmidiate(KvSessionOwner handle, IReadOnlyDictionary values) { var success = false; if (handle != null && values != null && values.Count > 0) { success = await handle.SyncDataImmidiate(values); } if (!success) { UnityEngine.Debug.LogError("[KVMgr::SyncDataImmidiate] Save failed."); } return success; } } }