using System; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace GometGames.Tools { /// /// Generic Serializable Dictionary for Unity 2020.1 and above. /// Simply declare your key/value types and you're good to go - zero boilerplate. /// [Serializable] public class GenericDictionary : IDictionary, ISerializationCallbackReceiver { // Internal [SerializeField] private List list = new List(); [SerializeField, HideInInspector] private Dictionary indexByKey = new Dictionary(); [SerializeField, HideInInspector] private Dictionary dict = new Dictionary(); #pragma warning disable 0414 [SerializeField, HideInInspector] private bool keyCollision; #pragma warning restore 0414 [Serializable] private struct KeyValuePair { public TKey Key; public TValue Value; public KeyValuePair(TKey Key, TValue Value) { this.Key = Key; this.Value = Value; } } // Lists are serialized natively by Unity, no custom implementation needed. public void OnBeforeSerialize() { } // Populate dictionary with pairs from list and flag key-collisions. public void OnAfterDeserialize() { dict.Clear(); indexByKey.Clear(); keyCollision = false; for (int i = 0; i < list.Count; i++) { var key = list[i].Key; if (key != null && !ContainsKey(key)) { dict.Add(key, list[i].Value); indexByKey.Add(key, i); } else { keyCollision = true; } } } // IDictionary public TValue this[TKey key] { get => dict[key]; set { dict[key] = value; if (indexByKey.ContainsKey(key)) { var index = indexByKey[key]; list[index] = new KeyValuePair(key, value); } else { list.Add(new KeyValuePair(key, value)); indexByKey.Add(key, list.Count - 1); } } } public ICollection Keys => dict.Keys; public ICollection Values => dict.Values; public void Add(TKey key, TValue value) { dict.Add(key, value); list.Add(new KeyValuePair(key, value)); indexByKey.Add(key, list.Count - 1); } public bool ContainsKey(TKey key) => dict.ContainsKey(key); public bool Remove(TKey key) { if (dict.Remove(key)) { var index = indexByKey[key]; list.RemoveAt(index); UpdateIndexLookup(index); indexByKey.Remove(key); return true; } else { return false; } } private void UpdateIndexLookup(int removedIndex) { for (int i = removedIndex; i < list.Count; i++) { var key = list[i].Key; indexByKey[key]--; } } public bool TryGetValue(TKey key, out TValue value) => dict.TryGetValue(key, out value); // ICollection public int Count => dict.Count; public bool IsReadOnly { get; set; } public void Add(KeyValuePair pair) { Add(pair.Key, pair.Value); } public void Clear() { dict.Clear(); list.Clear(); indexByKey.Clear(); } public bool Contains(KeyValuePair pair) { TValue value; if (dict.TryGetValue(pair.Key, out value)) { return EqualityComparer.Default.Equals(value, pair.Value); } else { return false; } } public void CopyTo(KeyValuePair[] array, int arrayIndex) { if (array == null) throw new ArgumentException("The array cannot be null."); if (arrayIndex < 0) throw new ArgumentOutOfRangeException("The starting array index cannot be negative."); if (array.Length - arrayIndex < dict.Count) throw new ArgumentException("The destination array has fewer elements than the collection."); foreach (var pair in dict) { array[arrayIndex] = pair; arrayIndex++; } } public bool Remove(KeyValuePair pair) { TValue value; if (dict.TryGetValue(pair.Key, out value)) { bool valueMatch = EqualityComparer.Default.Equals(value, pair.Value); if (valueMatch) { return Remove(pair.Key); } } return false; } // IEnumerable public IEnumerator> GetEnumerator() => dict.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => dict.GetEnumerator(); } }