using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using game.PlayerDataStorage; namespace game { public partial class PlayFabUserService { readonly bool kvControlledInstance; PlayerKVGameLoadCoordinator kvCoordinator; Action> kvReceiver; int kvReceiverUsers; internal PlayerKVGameLoadError KVLastError { get; private set; } internal string CaptureSaveBackend() => config==null ? "playfab" : config.Get("SaveBackend","playfab"); internal bool RejectSaveBackend(string backend) { #if UNITY_EDITOR && !SERVICE_HOST if(PlayerKVGameplayLoop.Enabled && backend=="kv-loop" && !kvControlledInstance) { KVLastError=PlayerKVGameLoadError.None; return false; } #endif KVLastError=backend=="kv" ? PlayerKVGameLoadError.BackendNotEnabled : backend!="playfab" ? PlayerKVGameLoadError.BackendUnsupported : kvControlledInstance ? PlayerKVGameLoadError.Input : PlayerKVGameLoadError.None; return KVLastError!=PlayerKVGameLoadError.None; } internal void ConfigureKVForValidation(PlayerKVGameLoadTransaction transaction,PlayFabMgr pf,CustomServerMgr server,Func unstarted) { if(!kvControlledInstance || kvCoordinator!=null || pf==null || !pf.IsUnstartedForKVLoad || PlayFabMgr.HasLiveInstanceForKVLoad || server==null || !server.IsFreshForKVLoad || unstarted==null || !unstarted()) throw new InvalidOperationException("Fresh controlled services required."); kvCoordinator=new PlayerKVGameLoadCoordinator(transaction,()=>pf.IsUnstartedForKVLoad && InternalId==0 && unstarted(), NotifyKVLoaded,()=>KVIntents?.Count ?? 0,server.BindKVForValidation,server.ClearKVForValidation); server.KVUnauthorized+=version=> { if(version==server.SessionVersion) { InternalId=0; _=kvCoordinator.Revoke(); } }; } internal virtual async Task LoadKVForValidationAsync(PlayerKVGameLoadCapability capability) { if(!kvControlledInstance || kvCoordinator==null || capability==null) return new PlayerKVGameLoadResult(PlayerKVGameLoadError.Input,0); try { int loginMs=config==null ? 10000 : PlayerKVSaveSchema.Int32(config.Get("KVLoginCloseObserveMs","10000")); int quitMs=config==null ? 5000 : PlayerKVSaveSchema.Int32(config.Get("KVQuitCloseObserveMs","5000")); if(loginMs!=capability.LoginObserveMs || quitMs!=capability.QuitObserveMs || loginMs<1000 || loginMs>15000 || quitMs<1000 || quitMs>15000) return new PlayerKVGameLoadResult(PlayerKVGameLoadError.Input,0); } catch { return new PlayerKVGameLoadResult(PlayerKVGameLoadError.Input,0); } var requestedId=CustomId; var generation=logoutGeneration; var result=await kvCoordinator.LoadAsync(requestedId,capability,LoginLifetime,()=>requestedId==CustomId && generation==logoutGeneration); if(generation!=logoutGeneration || requestedId!=CustomId) { _=kvCoordinator.Revoke(); if(result.State==PlayerKVGameLoadState.LoadedForValidation) result=new PlayerKVGameLoadResult(PlayerKVGameLoadError.Stale,result.Epoch); } KVLastError=result.Error; return result; } internal Task CloseKVForValidationAsync() => kvCoordinator?.Revoke() ?? Task.CompletedTask; internal Task ObserveKVQuitAsync() => kvCoordinator?.ObserveQuitAsync() ?? Task.FromResult(PlayerKVShutdownStatus.Ready); internal bool KVLoadedForValidation => kvCoordinator?.LoadedForValidation ?? false; internal int KVReceiverCountForValidation => kvReceiverUsers; internal void EnterKVReceiver(Action> receiver) { if(!kvControlledInstance || receiver==null || (kvReceiver!=null && kvReceiver!=receiver)) throw new InvalidOperationException("Controlled receiver required."); if(kvReceiverUsers++==0) { kvReceiver=receiver; OnInitData+=receiver; } } internal void ExitKVReceiver() { if(--kvReceiverUsers==0) { OnInitData-=kvReceiver; kvReceiver=null; } } void NotifyKVLoaded() { var listeners=OnInitData?.GetInvocationList(); if(listeners!=null && (listeners.Length!=1 || kvReceiver==null || !listeners[0].Equals(kvReceiver))) throw new InvalidOperationException("Unregistered controlled receiver."); OnInitData?.Invoke(KVLoadingCopy.ToDictionary(p=>p.Key,p=>p.Value,StringComparer.Ordinal)); } internal int KVTapJoyBalance { get; private set; } internal PlayerKVInitializationIntents KVIntents { get; private set; } internal IReadOnlyDictionary KVLoadingCopy { get; private set; } internal int MMTestForKVValidation => MMTestValue; internal bool IsControlledKVInstance => kvControlledInstance; internal virtual void ResetForKVLoad() { bool admitted=kvControlledInstance, sessionAllowed=apiSession==null; #if UNITY_EDITOR && !SERVICE_HOST admitted=admitted || KVLoopLoading; sessionAllowed=sessionAllowed || KVLoopLoading; #endif if(!admitted || InternalId!=0 || !sessionAllowed) throw new InvalidOperationException("Unstarted controlled identity required."); KVIntents?.Discard(); KVIntents=null; KVLoadingCopy=null; MMTestValue=0; EAccoutUrl=new Dictionary(StringComparer.Ordinal); KVTapJoyBalance=0; } internal void ReplaceFromKVDecoded(PlayerKVLoginState state,long epoch,IReadOnlyDictionary raw) { bool admitted=kvControlledInstance; #if UNITY_EDITOR && !SERVICE_HOST admitted=admitted || KVLoopLoading; #endif if(!admitted || state==null || raw==null) throw new InvalidOperationException("Controlled login state required."); var copy=raw.ToDictionary(p=>p.Key,p=>p.Value,StringComparer.Ordinal); var intents=new PlayerKVInitializationIntents(epoch,state.Channel); foreach(var pair in state.Normalizations) { copy[pair.Key]=pair.Value; intents.Add(epoch,pair.Key,pair.Value); } KVIntents?.Discard(); KVIntents=intents; KVLoadingCopy=new System.Collections.ObjectModel.ReadOnlyDictionary(copy); MMTestValue=state.MMTest; KVTapJoyBalance=state.TapJoyBalance; EAccoutUrl=state.AccountUrls.ToDictionary(p=>p.Key,p=>p.Value,StringComparer.Ordinal); ReplaceKVLinked(state); } internal virtual void ReplaceKVLinked(PlayerKVLoginState state) { } } }