using UnityEngine; using PlayFab; using PlayFab.ClientModels; using System.Collections; using System.Collections.Generic; public class PlayerPrefsSyncManager : MonoBehaviour { public static PlayerPrefsSyncManager instance; private const int MaxKeysPerRequest = 10; private static float syncDelay = 3f; private static float syncTimer = 0f; private static bool syncPending = false; private Coroutine periodicSyncCoroutine; private void Awake() { if (instance == null) { instance = this; DontDestroyOnLoad(this.gameObject); Application.wantsToQuit += OnWantsToQuit; } else { Destroy(gameObject); } } private void Start() { periodicSyncCoroutine = StartCoroutine(PeriodicSyncCoroutine()); } private void Update() { if (syncPending) { syncTimer -= Time.unscaledDeltaTime; if (syncTimer <= 0f) { syncPending = false; Debug.Log("🌀 Debounced sync triggered."); StartCoroutine(SyncCoroutine()); } } } public static void RequestSync() { if (!instance) return; syncTimer = syncDelay; syncPending = true; } private bool OnWantsToQuit() { if (PlayFabClientAPI.IsClientLoggedIn()) { StartCoroutine(SyncThenQuit()); return false; } return true; } private void OnApplicationPause(bool pause) { if (pause) { Debug.Log("📱 App paused — syncing."); StartCoroutine(SyncCoroutine()); } } private IEnumerator PeriodicSyncCoroutine() { float interval = 120f; // 2 minutes while (true) { yield return new WaitForSecondsRealtime(interval); if (PlayFabClientAPI.IsClientLoggedIn()) { Debug.Log("⏰ Periodic sync triggered."); yield return SyncCoroutine(); } } } private IEnumerator SyncThenQuit() { bool isDone = false; SyncPlayerPrefsToPlayFab(() => { isDone = true; }); float timeout = 5f; float timer = 0f; while (!isDone && timer < timeout) { timer += Time.unscaledDeltaTime; yield return null; } #if UNITY_EDITOR UnityEditor.EditorApplication.isPlaying = false; #else Application.Quit(); #endif } private IEnumerator SyncCoroutine() { bool isDone = false; SyncPlayerPrefsToPlayFab(() => { isDone = true; }); float timeout = 5f; float timer = 0f; while (!isDone && timer < timeout) { timer += Time.unscaledDeltaTime; yield return null; } } public void SyncPlayerPrefsToPlayFab(System.Action onComplete = null) { if (!PlayFabClientAPI.IsClientLoggedIn()) { onComplete?.Invoke(); return; } var keys = PlayerPrefsKeys.GetAllKeys(); if (keys.Count == 0) { Debug.Log("No PlayerPrefs keys registered, skipping sync."); onComplete?.Invoke(); return; } Dictionary allPrefs = new Dictionary(); foreach (var key in keys) { string strVal = PlayerPrefs.GetString(key, "__MISSING__"); if (strVal != "__MISSING__") { allPrefs[key] = "string:" + strVal; continue; } int intVal = PlayerPrefs.GetInt(key, int.MinValue + 1); if (intVal != int.MinValue + 1) { allPrefs[key] = "int:" + intVal; continue; } float floatVal = PlayerPrefs.GetFloat(key, float.MinValue + 1); if (floatVal != float.MinValue + 1) { allPrefs[key] = "float:" + floatVal.ToString("R"); } } // Batch in chunks of 10 var batches = new List>(); var currentBatch = new Dictionary(); foreach (var pair in allPrefs) { currentBatch[pair.Key] = pair.Value; if (currentBatch.Count == MaxKeysPerRequest) { batches.Add(currentBatch); currentBatch = new Dictionary(); } } if (currentBatch.Count > 0) { batches.Add(currentBatch); } UploadPlayerPrefsBatches(batches, 0, onComplete); } private void UploadPlayerPrefsBatches(List> batches, int index, System.Action onComplete) { if (index >= batches.Count) { Debug.Log("✅ All PlayerPrefs batches synced to PlayFab."); onComplete?.Invoke(); return; } var request = new UpdateUserDataRequest { Data = batches[index], Permission = UserDataPermission.Public }; PlayFabClientAPI.UpdateUserData(request, result => { Debug.Log($"✅ Synced batch {index + 1}/{batches.Count}"); UploadPlayerPrefsBatches(batches, index + 1, onComplete); }, error => { Debug.LogError($"❌ Failed to sync batch {index + 1}/{batches.Count}: {error.GenerateErrorReport()}"); onComplete?.Invoke(); // Fail fast or add retry logic }); } }