chore(m2): 保存功能验收及后续清理前检查点
This commit is contained in:
@@ -1,19 +1,156 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using game.PlayerDataStorage;
|
||||
|
||||
internal static class KVMgr
|
||||
internal class KVMgr : MonoBehaviour
|
||||
{
|
||||
private static KVMgr instance;
|
||||
private KVPlayerSaveStore store;
|
||||
private CancellationTokenSource tokenSource;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
instance = this;
|
||||
tokenSource = new CancellationTokenSource();
|
||||
UpdateUserDataValue(tokenSource.Token);
|
||||
}
|
||||
|
||||
internal static void Bind(KVPlayerSaveStore original)
|
||||
{
|
||||
if (instance != null)
|
||||
{
|
||||
instance.store = original;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Unbind(KVPlayerSaveStore original)
|
||||
{
|
||||
if (instance != null && ReferenceEquals(instance.store, original))
|
||||
{
|
||||
instance.store = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Stop(KVPlayerSaveStore 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<PlayerKVFlushResult> task)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await task;
|
||||
if (result.TransportError != PlayerKVError.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;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Task<PlayerKVFlushResult> SyncKVAsync(PlayerKVGameSessionHandle handle,
|
||||
CancellationToken waitToken = default)
|
||||
{
|
||||
if (PlayerKVGameLoadGuard.Reject(PlayerKVGuardPoint.Sync))
|
||||
{
|
||||
return Task.FromResult(new PlayerKVFlushResult(0, 0, 1, 0, PlayerKVError.Cancelled,
|
||||
handle == null ? PlayerKVSyncStatus.NotLoaded : handle.Status));
|
||||
}
|
||||
if (handle == null)
|
||||
{
|
||||
return Task.FromResult(new PlayerKVFlushResult(0, 0, 1, 0, PlayerKVError.Validation, PlayerKVSyncStatus.NotLoaded));
|
||||
}
|
||||
return handle.SyncDataAsync(waitToken);
|
||||
}
|
||||
|
||||
internal static PlayerKVKeyReadResult ReadKV(PlayerKVGameSessionHandle handle, string key)
|
||||
{
|
||||
if (PlayerKVGameLoadGuard.Reject(PlayerKVGuardPoint.ReadLocal))
|
||||
return new PlayerKVKeyReadResult(PlayerKVKeyReadError.LoadGuardActive, handle);
|
||||
{
|
||||
return new PlayerKVKeyReadResult(PlayerKVKeyReadError.LoadGuardActive, handle);
|
||||
}
|
||||
if (handle == null || !KVSnapshot.ValidKey(key))
|
||||
return new PlayerKVKeyReadResult(PlayerKVKeyReadError.InvalidInput, handle);
|
||||
{
|
||||
return new PlayerKVKeyReadResult(PlayerKVKeyReadError.InvalidInput, handle);
|
||||
}
|
||||
var read = handle.ReadLocalSnapshot();
|
||||
if (!read.Success)
|
||||
return new PlayerKVKeyReadResult(read.Error == PlayerKVLocalReadError.StaleOrClosing
|
||||
? PlayerKVKeyReadError.StaleOrClosing : PlayerKVKeyReadError.NotReady, handle);
|
||||
{
|
||||
return new PlayerKVKeyReadResult(read.Error == PlayerKVLocalReadError.StaleOrClosing
|
||||
? PlayerKVKeyReadError.StaleOrClosing : PlayerKVKeyReadError.NotReady, handle);
|
||||
}
|
||||
var found = read.Data.TryGetValue(key, out var value);
|
||||
return new PlayerKVKeyReadResult(PlayerKVKeyReadError.None, handle, found, value);
|
||||
}
|
||||
@@ -22,20 +159,58 @@ internal static class KVMgr
|
||||
string key, string value, bool remoteStore = true)
|
||||
{
|
||||
if (PlayerKVGameLoadGuard.Reject(PlayerKVGuardPoint.Update))
|
||||
return new PlayerKVEditResult(new PlayerKVStateResult(PlayerKVChangeError.Blocked));
|
||||
{
|
||||
return new PlayerKVEditResult(new PlayerKVStateResult(PlayerKVChangeError.Blocked));
|
||||
}
|
||||
if (handle == null)
|
||||
return new PlayerKVEditResult(new PlayerKVStateResult(PlayerKVChangeError.Validation));
|
||||
return handle.Update(key, value, remoteStore);
|
||||
{
|
||||
return new PlayerKVEditResult(new PlayerKVStateResult(PlayerKVChangeError.Validation));
|
||||
}
|
||||
return handle.Update(key, value, remoteStore, ZZTimeHelper.UtcNow().Ticks);
|
||||
}
|
||||
|
||||
internal static async Task<bool> SyncDataImmidiate(PlayerKVGameSessionHandle handle, string key, string value)
|
||||
{
|
||||
var success = false;
|
||||
if (!PlayerKVGameLoadGuard.Reject(PlayerKVGuardPoint.Immediate) && 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(PlayerKVGameSessionHandle handle,
|
||||
IReadOnlyDictionary<string, string> values)
|
||||
{
|
||||
var success = false;
|
||||
if (!PlayerKVGameLoadGuard.Reject(PlayerKVGuardPoint.Immediate) && handle != null && values != null && values.Count > 0)
|
||||
{
|
||||
success = await handle.SyncDataImmidiate(values);
|
||||
}
|
||||
if (!success)
|
||||
{
|
||||
UnityEngine.Debug.LogError("[KVMgr::SyncDataImmidiate] Save failed.");
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
internal static Task<PlayerKVFlushResult> FlushKVAsync(PlayerKVGameSessionHandle handle,
|
||||
CancellationToken waitToken = default)
|
||||
{
|
||||
if (PlayerKVGameLoadGuard.Reject(PlayerKVGuardPoint.Sync))
|
||||
return Task.FromResult(new PlayerKVFlushResult(0, 0, 1, 0, PlayerKVError.Cancelled,
|
||||
handle == null ? PlayerKVSyncStatus.NotLoaded : handle.Status));
|
||||
{
|
||||
return Task.FromResult(new PlayerKVFlushResult(0, 0, 1, 0, PlayerKVError.Cancelled,
|
||||
handle == null ? PlayerKVSyncStatus.NotLoaded : handle.Status));
|
||||
}
|
||||
if (handle == null)
|
||||
return Task.FromResult(new PlayerKVFlushResult(0, 0, 1, 0, PlayerKVError.Validation, PlayerKVSyncStatus.NotLoaded));
|
||||
{
|
||||
return Task.FromResult(new PlayerKVFlushResult(0, 0, 1, 0, PlayerKVError.Validation, PlayerKVSyncStatus.NotLoaded));
|
||||
}
|
||||
return handle.FlushAsync(waitToken);
|
||||
}
|
||||
|
||||
@@ -43,10 +218,14 @@ internal static class KVMgr
|
||||
PlayerKVSyncTicket ticket, CancellationToken waitToken = default)
|
||||
{
|
||||
if (PlayerKVGameLoadGuard.Reject(PlayerKVGuardPoint.Sync))
|
||||
return Task.FromResult(new PlayerKVTicketResult(PlayerKVTicketStatus.Blocked, PlayerKVError.Cancelled,
|
||||
handle == null ? PlayerKVSyncStatus.NotLoaded : handle.Status));
|
||||
{
|
||||
return Task.FromResult(new PlayerKVTicketResult(PlayerKVTicketStatus.Blocked, PlayerKVError.Cancelled,
|
||||
handle == null ? PlayerKVSyncStatus.NotLoaded : handle.Status));
|
||||
}
|
||||
if (handle == null)
|
||||
return Task.FromResult(new PlayerKVTicketResult(PlayerKVTicketStatus.Blocked, PlayerKVError.Validation, PlayerKVSyncStatus.NotLoaded));
|
||||
{
|
||||
return Task.FromResult(new PlayerKVTicketResult(PlayerKVTicketStatus.Blocked, PlayerKVError.Validation, PlayerKVSyncStatus.NotLoaded));
|
||||
}
|
||||
return handle.WaitForTicketAsync(ticket, waitToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,6 @@ public class PlayFabMgr : MonoBehaviour
|
||||
private CancellationTokenSource tokenSource;
|
||||
|
||||
static PlayFabMgr _instance = null;
|
||||
// An inactive, never-awakened component proves that no timer or PF send has ever been started.
|
||||
internal bool IsUnstartedForKVLoad => tokenSource==null && dataBuffer==null && swapData==null && localData==null && lastUpdateTime==null && _instance!=this;
|
||||
internal static bool HasLiveInstanceForKVLoad => _instance!=null;
|
||||
|
||||
public static PlayFabMgr Instance
|
||||
@@ -45,9 +43,6 @@ public class PlayFabMgr : MonoBehaviour
|
||||
|
||||
public Dictionary<string, string> CompareDatas(Dictionary<string, UserDataRecord> remoteDatas, string userId)
|
||||
{
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
if(PlayerKVGameplayLoop.Enabled) throw PlayerKVGameplayLoop.Stop("PF archive comparison is disabled in KV loop.");
|
||||
#endif
|
||||
PlayerKVGameLoadGuard.RequireLegacyAllowed(PlayerKVGuardPoint.Compare);
|
||||
Last_Update_Time_Key = $"{Last_Update_Time_Key_Prefix}_{userId}";
|
||||
Local_Data_Key = $"{Local_Data_Key_prefix}_{userId}";
|
||||
@@ -124,24 +119,11 @@ public class PlayFabMgr : MonoBehaviour
|
||||
{
|
||||
if(PlayerKVGameLoadGuard.Reject(PlayerKVGuardPoint.Lifecycle)) return;
|
||||
_instance = this;
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
if(PlayerKVGameplayLoop.Enabled) return;
|
||||
#endif
|
||||
InitBuffer();
|
||||
tokenSource = new CancellationTokenSource();
|
||||
UpdateUserDataValue(tokenSource.Token);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
internal void StartKVGameplayLoopSync()
|
||||
{
|
||||
if(!PlayerKVGameplayLoop.Ready || tokenSource!=null) return;
|
||||
tokenSource=new CancellationTokenSource();
|
||||
UpdateUserDataValue(tokenSource.Token);
|
||||
}
|
||||
internal void StopKVGameplayLoopSync()
|
||||
{ tokenSource?.Cancel(); tokenSource?.Dispose(); tokenSource=null; }
|
||||
#endif
|
||||
void InitBuffer()
|
||||
{
|
||||
dataBuffer = new LinkedList<Dictionary<string, string>>();
|
||||
@@ -154,14 +136,6 @@ public class PlayFabMgr : MonoBehaviour
|
||||
|
||||
void OnApplicationPause(bool is_pause)
|
||||
{
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
if(PlayerKVGameplayLoop.Enabled)
|
||||
{
|
||||
if(is_pause) { StopKVGameplayLoopSync(); _=PlayerKVGameplayLoop.FlushObservedAsync(); }
|
||||
else StartKVGameplayLoopSync();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if(PlayerKVGameLoadGuard.Reject(PlayerKVGuardPoint.Lifecycle)) return;
|
||||
if (is_pause)
|
||||
{
|
||||
@@ -178,9 +152,6 @@ public class PlayFabMgr : MonoBehaviour
|
||||
|
||||
void OnApplicationQuit()
|
||||
{
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
if(PlayerKVGameplayLoop.Enabled) { StopKVGameplayLoopSync(); _=PlayerKVGameplayLoop.CloseAsync(); return; }
|
||||
#endif
|
||||
if(PlayerKVGameLoadGuard.Reject(PlayerKVGuardPoint.Lifecycle)) return;
|
||||
tokenSource.Cancel();
|
||||
tokenSource = null;
|
||||
@@ -189,9 +160,6 @@ public class PlayFabMgr : MonoBehaviour
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
if(PlayerKVGameplayLoop.Enabled) { StopKVGameplayLoopSync(); _=PlayerKVGameplayLoop.CloseAsync(); return; }
|
||||
#endif
|
||||
if(PlayerKVGameLoadGuard.Reject(PlayerKVGuardPoint.Lifecycle)) return;
|
||||
tokenSource?.Cancel();
|
||||
tokenSource = null;
|
||||
@@ -215,9 +183,6 @@ public class PlayFabMgr : MonoBehaviour
|
||||
|
||||
public void UpdateUserDataValue(string key, string data, bool remoteStore = true)
|
||||
{
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
if(PlayerKVGameplayLoop.Enabled) { PlayerKVGameplayLoop.Write(key,data,remoteStore); return; }
|
||||
#endif
|
||||
PlayerKVGameLoadGuard.RequireLegacyAllowed(PlayerKVGuardPoint.Update);
|
||||
if (remoteStore)
|
||||
{
|
||||
@@ -248,9 +213,6 @@ public class PlayFabMgr : MonoBehaviour
|
||||
/// <returns></returns>
|
||||
public string GetLocalData(string key)
|
||||
{
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
if(PlayerKVGameplayLoop.Enabled) return PlayerKVGameplayLoop.Read(key);
|
||||
#endif
|
||||
PlayerKVGameLoadGuard.RequireLegacyAllowed(PlayerKVGuardPoint.ReadLocal);
|
||||
if (localData.ContainsKey(key))
|
||||
{
|
||||
@@ -264,9 +226,6 @@ public class PlayFabMgr : MonoBehaviour
|
||||
|
||||
private void SyncData()
|
||||
{
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
if(PlayerKVGameplayLoop.Enabled) { _=PlayerKVGameplayLoop.FlushObservedAsync(); return; }
|
||||
#endif
|
||||
if(PlayerKVGameLoadGuard.Reject(PlayerKVGuardPoint.Sync)) return;
|
||||
// Logout clears credentials before RootCtx is destroyed. Keep pending data local.
|
||||
if (!PlayFabClientAPI.IsClientLoggedIn()) return;
|
||||
@@ -320,13 +279,9 @@ public class PlayFabMgr : MonoBehaviour
|
||||
GameDebug.LogError("[PlayFabMgr]UpdateUserData Error: {0}", error.ToString());
|
||||
}
|
||||
|
||||
internal void FlushBufferedData() => SyncData();
|
||||
|
||||
public async Task<bool> SyncDataImmidiate(string key, string data)
|
||||
{
|
||||
#if UNITY_EDITOR && !SERVICE_HOST
|
||||
if(PlayerKVGameplayLoop.Enabled) throw PlayerKVGameplayLoop.Stop("Immediate/consuming saves are outside the equipment loop.");
|
||||
#endif
|
||||
if(PlayerKVGameLoadGuard.Reject(PlayerKVGuardPoint.Immediate)) return false;
|
||||
foreach (var buff in dataBuffer)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user