181 lines
6.8 KiB
C#
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); }
|
|
}
|
|
|
|
}
|
|
}
|