refactor(kv): 整理存档接线并清理迁移冗余实现
This commit is contained in:
329
Client/Assets/Scripts/Core/KvMgr.cs
Normal file
329
Client/Assets/Scripts/Core/KvMgr.cs
Normal file
@@ -0,0 +1,329 @@
|
||||
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<KvFlushResult> 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<bool> SyncDataImmidiate(string key, string value)
|
||||
{
|
||||
KvSessionOwner 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 = 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)
|
||||
{
|
||||
KvSessionOwner 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 = SyncDataImmidiate(original, data);
|
||||
}
|
||||
var success = await save;
|
||||
lock (receiptsGate)
|
||||
{
|
||||
return success && IsCurrent && !originalLifetime.IsCancellationRequested && ReferenceEquals(handle, original);
|
||||
}
|
||||
}
|
||||
|
||||
internal Task<KvFlushResult> 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<bool> 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<bool> SyncDataImmidiate(KvSessionOwner handle,
|
||||
IReadOnlyDictionary<string, string> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user