Files
ft/Client/Assets/Scripts/Services/PlayerData/PlayerSaveStores.cs

181 lines
6.8 KiB
C#

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<bool> SyncDataImmidiate(string key, string value)
{
PlayerKVGameSessionHandle original;
CancellationToken originalLifetime;
Task<bool> 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<bool> SyncDataImmidiate(IReadOnlyDictionary<string, string> values)
{
PlayerKVGameSessionHandle original;
CancellationToken originalLifetime;
Task<bool> 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<PlayerKVFlushResult> 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<bool> 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); }
}
}
}