using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace game.PlayerDataStorage { public sealed class KVPlayerSaveStore : IPlayerSaveStore, IDisposable { private PlayerKVGameSessionHandle handle; private CancellationToken lifetime; private bool bound, disposed; private readonly object receiptsGate = new object(); public bool IsCurrent => bound && !disposed && !lifetime.IsCancellationRequested && handle.Status != PlayerKVSyncStatus.Closed; internal void Bind(PlayerKVGameSessionHandle original, CancellationToken loginLifetime) { if (bound || disposed || original == null || loginLifetime.IsCancellationRequested) throw new InvalidOperationException("Player save store cannot be rebound."); // This also checks the load guard before permitting business use. if (!KVMgr.ReadKV(original, "LastLoginTimeUtc").Success) throw new InvalidOperationException("Player save handle is not ready."); handle = original; lifetime = loginLifetime; bound = true; } internal void ReleaseFailedBinding(PlayerKVGameSessionHandle original) { lock (receiptsGate) { if (!bound) { return; } if (disposed || !ReferenceEquals(handle, original) || original.Status != PlayerKVSyncStatus.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"); var result = KVMgr.ReadKV(handle, key); return result.Success ? new PlayerSaveReadResult(result.Found.Value, result.Value) : new PlayerSaveReadResult(result.Error.ToString()); } public PlayerSaveWriteResult Write(string key, string value, bool remoteStore = true) { lock (receiptsGate) { if (!IsCurrent) { var rejected = new PlayerSaveWriteResult("StaleOrNotReady"); return rejected; } var result = KVMgr.WriteKV(handle, key, value, remoteStore); var write = result.Accepted ? new PlayerSaveWriteResult(new PlayerSaveReceipt(this, result.Ticket)) : new PlayerSaveWriteResult(result.StateResult.Error.ToString()); return write; } } public async Task SyncDataImmidiate(string key, string value) { PlayerKVGameSessionHandle 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 = KVMgr.SyncDataImmidiate(original, key, value); } var success = await save; lock (receiptsGate) { return success && IsCurrent && !originalLifetime.IsCancellationRequested && ReferenceEquals(handle, original); } } internal async Task SyncDataImmidiate(IReadOnlyDictionary values) { PlayerKVGameSessionHandle 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 = KVMgr.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 PlayerKVFlushResult(0, 0, 1, 0, PlayerKVError.Cancelled, original == null ? PlayerKVSyncStatus.NotLoaded : original.Status)); } return KVMgr.SyncKVAsync(original); } } public void Dispose() { lock (receiptsGate) { disposed = true; } } } public sealed class PlayFabPlayerSaveStore : IPlayerSaveStore { public Task SyncDataImmidiate(string key, string value) { return manager.SyncDataImmidiate(key, value); } readonly PlayFabMgr manager; readonly CancellationToken lifetime; public PlayFabPlayerSaveStore(IUserService user) : this(PlayFabMgr.Instance, user.LoginLifetime) { } internal PlayFabPlayerSaveStore(PlayFabMgr manager, CancellationToken lifetime) { this.manager = manager; this.lifetime = lifetime; } public bool IsCurrent => manager != null && !lifetime.IsCancellationRequested; public PlayerSaveReadResult Read(string key) { if (!IsCurrent) return new PlayerSaveReadResult("StaleOrNotReady"); try { var value = manager.GetLocalData(key); return new PlayerSaveReadResult(value != null, value); } catch (Exception ex) { return new PlayerSaveReadResult(ex.GetType().Name); } } public PlayerSaveWriteResult Write(string key, string value, bool remoteStore = true) { if (!IsCurrent) return new PlayerSaveWriteResult("StaleOrNotReady"); try { manager.UpdateUserDataValue(key, value, remoteStore); return new PlayerSaveWriteResult(new PlayerSaveReceipt(this, remoteStore ? (object)true : null)); } catch (Exception ex) { return new PlayerSaveWriteResult(ex.GetType().Name); } } } }