using System.Collections.Generic; using UnityEngine; namespace Ubisoft { /// /// Implementation of HtPlayerPrefs that persists data in UnityEngine.PlayerPrefs. /// All get/set methods in this class need to be called from the main thread, since it's a requirement to access UnityEngine.PlayerPrefs. /// internal class HtPlayerPrefs_Unity : HtPlayerPrefs_Unity_Base { protected const string KEY_STORED_KEYS = "__storedKeys"; struct KeyMetadata { internal IHtPlayerPrefs.EType Type { get; } internal string TypeAsKey { get { return ETypeToKey(Type); } } internal string FullKey { get; } internal KeyMetadata(IHtPlayerPrefs.EType type, string fullKey) { Type = type; FullKey = fullKey; } } private string StoredKeysFullKey { get; } private Dictionary StoredKeys { get; set; } /// /// Since Unity PlayerPrefs doesn't support long values we need to stored them as strings. /// Long keys are stored in memory to avoid memory allocations every time they're retrieved. /// private Dictionary LongValues { get; set; } /// /// Create an HtPlayerPrefs_Unity object to store data in the channel passed in. /// By default AutomaticSave is enabled. Feel free to change it if you're concerned about its performance impact. /// /// Name of the channel to use to store data saved through this object. /// An HtLogger.Channel object to use to log data to. /// If no object is passed in then a default one called 'HT_PlayerPrefs('channel')' is created. /// When true logs are printed out. Use it only for debug purposes. /// Make sure that Trace is the minimum log level set in your Logger configuration. internal HtPlayerPrefs_Unity(string channel, HtLogger.Channel logger = null, bool isVerbose = false) : base(channel, logger, isVerbose) { StoredKeys = new Dictionary(); StoredKeysFullKey = GetFullKey(KEY_STORED_KEYS); LoadStoredKeys(); } public override bool GetBool(string key, bool defaultValue = false) { return IntToBool(PlayerPrefs.GetInt(GetFullKey(key), BoolToInt(defaultValue))); } public override float GetFloat(string key, float defaultValue = 0f) { return PlayerPrefs.GetFloat(GetFullKey(key), defaultValue); } public override int GetInt(string key, int defaultValue = 0) { return PlayerPrefs.GetInt(GetFullKey(key), defaultValue); } public override long GetLong(string key, long defaultValue = 0) { if (LongValues == null) { LongValues = new Dictionary(); } if (!LongValues.TryGetValue(key, out long returnValue)) { string fullKey = GetFullKey(key); if (PlayerPrefs.HasKey(fullKey)) { string rawValue = PlayerPrefs.GetString(fullKey); StringToLong(key, rawValue, out returnValue); } } return returnValue; } public override string GetString(string key, string defaultValue = "") { return PlayerPrefs.GetString(GetFullKey(key), defaultValue); } public override IHtPlayerPrefs.EType GetType(string key) { return StoredKeys.ContainsKey(key) ? StoredKeys[key].Type : IHtPlayerPrefs.EType.None; } public override bool HasKey(string key) { return PlayerPrefs.HasKey(GetFullKey(key)); } protected override void ExtendedDeleteAll() { foreach (KeyValuePair pair in StoredKeys) { PlayerPrefs.DeleteKey(GetFullKey(pair.Key)); } PlayerPrefs.DeleteKey(StoredKeysFullKey); StoredKeys.Clear(); NeedsToSave = true; } protected override void ExtendedDeleteKey(string key) { if (StoredKeys.ContainsKey(key)) { PlayerPrefs.DeleteKey(GetFullKey(key)); _ = StoredKeys.Remove(key); UpdateRawStoredKeys(); NeedsToSave = true; } } protected override void ExtendedSetBool(string key, bool value) { ExtendedSetInt(key, BoolToInt(value)); NeedsToSave = true; } protected override void ExtendedSetFloat(string key, float value) { PlayerPrefs.SetFloat(GetFullKey(key), value); AddStoredKey(key, IHtPlayerPrefs.EType.Float); NeedsToSave = true; } protected override void ExtendedSetInt(string key, int value) { PlayerPrefs.SetInt(GetFullKey(key), value); AddStoredKey(key, IHtPlayerPrefs.EType.Int); NeedsToSave = true; } protected override void ExtendedSetLong(string key, long value) { if (LongValues == null) { LongValues = new Dictionary(); } if (LongValues.ContainsKey(key)) { LongValues[key] = value; } else { LongValues.Add(key, value); } // Stored as string because Unity PlayerPrefs doesn't support long type PlayerPrefs.SetString(GetFullKey(key), LongToString(value)); AddStoredKey(key, IHtPlayerPrefs.EType.Long); NeedsToSave = true; } protected override void ExtendedSetString(string key, string value) { PlayerPrefs.SetString(GetFullKey(key), value); AddStoredKey(key, IHtPlayerPrefs.EType.String); NeedsToSave = true; } public override string ToString() { return $"StoredKeys: {RawStoredKeys}\nContent: {ContentAsString()}"; } private string ContentAsString() { string returnValue = ""; string valueAsString; foreach (KeyValuePair pair in StoredKeys) { if (HasKey(pair.Key)) { if (pair.Value.Type != IHtPlayerPrefs.EType.None) { valueAsString = null; switch (pair.Value.Type) { case IHtPlayerPrefs.EType.Bool: valueAsString = GetBool(pair.Key).ToString(); break; case IHtPlayerPrefs.EType.Int: valueAsString = GetInt(pair.Key).ToString(); break; case IHtPlayerPrefs.EType.Long: valueAsString = GetLong(pair.Key).ToString(); break; case IHtPlayerPrefs.EType.Float: valueAsString = GetFloat(pair.Key).ToString(); break; case IHtPlayerPrefs.EType.String: valueAsString = GetString(pair.Key); break; } if (valueAsString != null) { if (!string.IsNullOrEmpty(returnValue)) { returnValue += $","; } returnValue += $"{pair.Key}:{valueAsString}"; } } } } return returnValue; } private void LoadStoredKeys() { string key; string rawStoredKeys = RawStoredKeys; if (!string.IsNullOrEmpty(rawStoredKeys)) { bool needsToCorrectRawStoredKeys = false; string[] keys = rawStoredKeys.Split(STORED_KEYS_KEY_SEPARATOR); string[] tokens; int count = keys.Length; for (int i = 0; i < count; ++i) { key = keys[i].Trim(); if (!string.IsNullOrEmpty(key)) { tokens = key.Split(STORED_KEYS_TOKEN_SEPARATOR); if (tokens.Length < 2) { Logger.LogError($"Malformed raw stored key {Debug.FormatTextInUserContext(key)}"); needsToCorrectRawStoredKeys = true; } else if (StoredKeys.ContainsKey(tokens[0])) { Logger.LogError($"Duplicate raw stored key {Debug.FormatTextInUserContext(key)} in {Debug.FormatTextInUserContext(rawStoredKeys)}"); needsToCorrectRawStoredKeys = true; } else { IHtPlayerPrefs.EType type = KeyToEType(tokens[1]); if (type == IHtPlayerPrefs.EType.None) { Logger.LogError($"Unsupported type {Debug.FormatTextInUserContext(tokens[1])} in raw stored key {Debug.FormatTextInUserContext(key)}"); needsToCorrectRawStoredKeys = true; } else { StoredKeys.Add(tokens[0], new KeyMetadata(type, GetFullKey(tokens[0]))); } } } else { needsToCorrectRawStoredKeys = true; Logger.LogError($"Empty raw stored key in {Debug.FormatTextInUserContext(rawStoredKeys)}"); } } if (needsToCorrectRawStoredKeys) { UpdateRawStoredKeys(); Logger.LogWarning($"Correct raw stored keys from: {Debug.FormatTextInUserContext(rawStoredKeys)} to {Debug.FormatTextInUserContext(RawStoredKeys)}"); } } } private void AddStoredKey(string key, IHtPlayerPrefs.EType type) { if (!StoredKeys.ContainsKey(key) && type != IHtPlayerPrefs.EType.None) { StoredKeys.Add(key, new KeyMetadata(type, GetFullKey(key))); RawStoredKeys = AddRawStoredKey(key, type, RawStoredKeys); } } private string RawStoredKeys { get { return GetString(KEY_STORED_KEYS); } set { PlayerPrefs.SetString(StoredKeysFullKey, value); NeedsToSave = true; } } private void UpdateRawStoredKeys() { string value = ""; foreach (KeyValuePair pair in StoredKeys) { value = AddRawStoredKey(pair.Key, pair.Value.Type, value); } RawStoredKeys = value; } private string GetFullKey(string key) { return StoredKeys.ContainsKey(key) ? StoredKeys[key].FullKey : $"{Channel}.{key}"; } } }