Files
back_cantanBuilding/Assets/Scripts/Core/PlayFabMgr.cs
2026-05-26 16:15:54 +08:00

272 lines
8.1 KiB
C#

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<Dictionary<string, string>> dataBuffer;
private Dictionary<string, string> swapData;
private Dictionary<string, long> lastUpdateTime;
private Dictionary<string, string> 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<string, string> CompareDatas(Dictionary<string, UserDataRecord> 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<string, string>();
lastUpdateTime = new Dictionary<string, long>();
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<Dictionary<string, long>>(lastUpdateTimeStr);
var localDataStr = PlayerPrefs.GetString(Local_Data_Key);
var tempLocalData = Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, string>>(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<Dictionary<string, string>>();
for (int i = 0; i < Buffer_Count; i++)
{
dataBuffer.AddLast(new Dictionary<string, string>());
}
swapData = new Dictionary<string, string>();
}
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;
PlayerPrefs.SetString(Last_Update_Time_Key, LitJson.JsonMapper.ToJson(lastUpdateTime));
PlayerPrefs.SetString(Local_Data_Key, LitJson.JsonMapper.ToJson(localData));
}
/// <summary>
/// 没有数据返回 null 一定一定一定判空!!!
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
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);
}
private void UpdateFaild(PlayFabError error)
{
GameDebug.LogError("[PlayFabMgr]UpdateUserData Error: {0}", error.ToString());
}
public async Task<bool> SyncDataImmidiate(string key, string data)
{
foreach(var buff in dataBuffer)
{
if(buff.ContainsKey(key))
{
buff.Remove(key);
}
}
var taskSource = new TaskCompletionSource<bool>();
var request = new UpdateUserDataRequest()
{
Data = new Dictionary<string, string>() { { 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;
}
}