using asap.core; using PlayFab; using PlayFab.ClientModels; using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using UnityEngine; public class PlayFabMgr : MonoBehaviour { const int USER_DATA_UPDATE_INTERVAL = 40; const int Buffer_Count = 5; const string Last_Update_Time_Key_Prefix = "LastUpdateTime"; const string Local_Data_Key_prefix = "LocalData"; private string Last_Update_Time_Key; private string Local_Data_Key; private LinkedList> dataBuffer; private Dictionary swapData; private Dictionary lastUpdateTime; private Dictionary localData; private CancellationTokenSource tokenSource; static PlayFabMgr _instance = null; public static PlayFabMgr Instance { get { if (_instance == null) { GameDebug.LogError("PlayFabMgr Instance doesn't exist yet!"); } return _instance; } } public Dictionary CompareDatas(Dictionary remoteDatas, string userId) { Last_Update_Time_Key = $"{Last_Update_Time_Key_Prefix}_{userId}"; Local_Data_Key = $"{Local_Data_Key_prefix}_{userId}"; var lastUpdateTimeStr = PlayerPrefs.GetString(Last_Update_Time_Key, string.Empty); var now = ZZTimeHelper.UtcNow().Ticks; localData = new Dictionary(); lastUpdateTime = new Dictionary(); if (string.IsNullOrEmpty(lastUpdateTimeStr)) { if (remoteDatas != null && remoteDatas.Count > 0) { lastUpdateTime = remoteDatas.ToDictionary(x => x.Key, x => x.Value.LastUpdated.Ticks); localData = remoteDatas.ToDictionary(x => x.Key, x => x.Value.Value); } } else { var tempLastUpdateTime = Newtonsoft.Json.JsonConvert.DeserializeObject>(lastUpdateTimeStr); var localDataStr = PlayerPrefs.GetString(Local_Data_Key); var tempLocalData = Newtonsoft.Json.JsonConvert.DeserializeObject>(localDataStr); var allKeys = tempLocalData.Keys.Concat(remoteDatas.Keys).Distinct(); foreach (var key in allKeys) { var localValue = tempLocalData.ContainsKey(key) ? tempLocalData[key] : null; var remoteValue = remoteDatas.ContainsKey(key) ? remoteDatas[key].Value : null; var localTime = tempLastUpdateTime.ContainsKey(key) ? tempLastUpdateTime[key] : 0; var remoteTime = remoteDatas.ContainsKey(key) ? remoteDatas[key].LastUpdated.Ticks : 0; if (localValue == null) { localData[key] = remoteValue; lastUpdateTime[key] = remoteTime; continue; } if (remoteValue == null) { UpdateUserDataValue(key, localValue); lastUpdateTime[key] = now; localData[key] = localValue; continue; } var timeDelta = localTime - remoteTime; if (Math.Abs(timeDelta) > TimeSpan.TicksPerSecond * 60) { if (localTime > remoteTime) { UpdateUserDataValue(key, localValue); lastUpdateTime[key] = now; localData[key] = localValue; } else { lastUpdateTime[key] = remoteTime; localData[key] = remoteValue; } } else { lastUpdateTime[key] = localTime; localData[key] = localValue; } } } PlayerPrefs.SetString(Last_Update_Time_Key, LitJson.JsonMapper.ToJson(lastUpdateTime)); PlayerPrefs.SetString(Local_Data_Key, LitJson.JsonMapper.ToJson(localData)); return localData; } void Awake() { _instance = this; InitBuffer(); tokenSource = new CancellationTokenSource(); UpdateUserDataValue(tokenSource.Token); } void InitBuffer() { dataBuffer = new LinkedList>(); for (int i = 0; i < Buffer_Count; i++) { dataBuffer.AddLast(new Dictionary()); } swapData = new Dictionary(); } void OnApplicationPause(bool is_pause) { if (is_pause) { tokenSource.Cancel(); tokenSource = null; SyncData(); } else { tokenSource = new CancellationTokenSource(); UpdateUserDataValue(tokenSource.Token); } } void OnApplicationQuit() { tokenSource.Cancel(); tokenSource = null; SyncData(); } void OnDestroy() { tokenSource?.Cancel(); tokenSource = null; SyncData(); } private async void UpdateUserDataValue(CancellationToken token) { try { while (!token.IsCancellationRequested) { SyncData(); await Task.Delay(USER_DATA_UPDATE_INTERVAL * 1000); } } catch (System.Exception) { } } public void UpdateUserDataValue(string key, string data, bool remoteStore = true) { if (remoteStore) { var cbuff = dataBuffer.First; while (cbuff != null) { var datas = cbuff.Value; if (!datas.ContainsKey(key) && datas.Count >= 10) { cbuff = cbuff.Next; continue; } cbuff.Value[key] = data; break; } } localData[key] = data; lastUpdateTime[key] = ZZTimeHelper.UtcNow().Ticks; //Debug.Log($"[PlayFabMgr]UpdateUserDataValue key:{key} data:{data}"); PlayerPrefs.SetString(Last_Update_Time_Key, LitJson.JsonMapper.ToJson(lastUpdateTime)); PlayerPrefs.SetString(Local_Data_Key, LitJson.JsonMapper.ToJson(localData)); } /// /// 没有数据返回 null 一定一定一定判空!!! /// /// /// public string GetLocalData(string key) { if (localData.ContainsKey(key)) { return localData[key]; } else { return null; } } private void SyncData() { var buf = dataBuffer.First; if (buf.Value.Count == 0) return; var datas = buf.Value; swapData.Clear(); buf.Value = swapData; swapData = datas; dataBuffer.RemoveFirst(); dataBuffer.AddLast(buf); var request = new UpdateUserDataRequest() { Data = swapData, Permission = UserDataPermission.Public }; PlayFabClientAPI.UpdateUserData(request, null, UpdateFaild); #if UNITY_EDITOR //IConfig config = GContext.container.Resolve(); //if (config != null && config.Get("DBUG", string.Empty) == "1") //{ try { //写入本地文件记录 key 和时间 string persistentPath = Application.persistentDataPath; // 拼接文件名,这里使用txt文件记录数据 string filePath = System.IO.Path.Combine(persistentPath, "syncData.csv"); string currentTime = ZZTimeHelper.UtcNow().ToString("yyyy-MM-dd HH:mm:ss"); string dataLine = $"{currentTime},{string.Join(',', swapData.Keys)}"; // 使用追加模式写入文件,每次写入一行数据 using (System.IO.StreamWriter writer = System.IO.File.AppendText(filePath)) { writer.WriteLine(dataLine); } } catch (Exception e) { Debug.LogError($"记录数据时出错: {e.Message}"); } //} #endif } private void UpdateFaild(PlayFabError error) { GameDebug.LogError("[PlayFabMgr]UpdateUserData Error: {0}", error.ToString()); } public async Task SyncDataImmidiate(string key, string data) { foreach (var buff in dataBuffer) { if (buff.ContainsKey(key)) { buff.Remove(key); } } var taskSource = new TaskCompletionSource(); var request = new UpdateUserDataRequest() { Data = new Dictionary() { { key, data } }, Permission = UserDataPermission.Public }; PlayFabClientAPI.UpdateUserData(request, (r) => { taskSource.SetResult(true); }, (e) => { taskSource.SetResult(false); UnityEngine.Debug.LogError($"[PlayFabMgr]UpdateUserData Error: {e.ToString()}"); } ); return await taskSource.Task; } }