using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using UnityEditor; using UnityEngine; using UnityObject = UnityEngine.Object; [Serializable, DebuggerDisplay("Count = {Count}")] public class SerializableDictionary : IDictionary { [SerializeField, HideInInspector] int[] _Buckets; [SerializeField, HideInInspector] int[] _HashCodes; [SerializeField, HideInInspector] int[] _Next; [SerializeField, HideInInspector] int _Count; [SerializeField, HideInInspector] int _Version; [SerializeField, HideInInspector] int _FreeList; [SerializeField, HideInInspector] int _FreeCount; [SerializeField, HideInInspector] TKey[] _Keys; [SerializeField, HideInInspector] TValue[] _Values; readonly IEqualityComparer _Comparer; // Mainly for debugging purposes - to get the key-value pairs display public Dictionary AsDictionary { get { return new Dictionary(this); } } public int Count { get { return _Count - _FreeCount; } } public TValue this[TKey key, TValue defaultValue] { get { int index = FindIndex(key); if (index >= 0) return _Values[index]; return defaultValue; } } public TValue this[TKey key] { get { int index = FindIndex(key); if (index >= 0) return _Values[index]; throw new KeyNotFoundException(key.ToString()); } set { Insert(key, value, false); } } public SerializableDictionary() : this(0, null) { } public SerializableDictionary(int capacity) : this(capacity, null) { } public SerializableDictionary(IEqualityComparer comparer) : this(0, comparer) { } public SerializableDictionary(int capacity, IEqualityComparer comparer) { if (capacity < 0) throw new ArgumentOutOfRangeException("capacity"); Initialize(capacity); _Comparer = (comparer ?? EqualityComparer.Default); } public SerializableDictionary(IDictionary dictionary) : this(dictionary, null) { } public SerializableDictionary(IDictionary dictionary, IEqualityComparer comparer) : this((dictionary != null) ? dictionary.Count : 0, comparer) { if (dictionary == null) throw new ArgumentNullException("dictionary"); foreach (KeyValuePair current in dictionary) Add(current.Key, current.Value); } public bool ContainsValue(TValue value) { if (value == null) { for (int i = 0; i < _Count; i++) { if (_HashCodes[i] >= 0 && _Values[i] == null) return true; } } else { var defaultComparer = EqualityComparer.Default; for (int i = 0; i < _Count; i++) { if (_HashCodes[i] >= 0 && defaultComparer.Equals(_Values[i], value)) return true; } } return false; } public bool ContainsKey(TKey key) { return FindIndex(key) >= 0; } public void Clear() { if (_Count <= 0) return; for (int i = 0; i < _Buckets.Length; i++) _Buckets[i] = -1; Array.Clear(_Keys, 0, _Count); Array.Clear(_Values, 0, _Count); Array.Clear(_HashCodes, 0, _Count); Array.Clear(_Next, 0, _Count); _FreeList = -1; _Count = 0; _FreeCount = 0; _Version++; } public void Add(TKey key, TValue value) { Insert(key, value, true); } private void Resize(int newSize, bool forceNewHashCodes) { int[] bucketsCopy = new int[newSize]; for (int i = 0; i < bucketsCopy.Length; i++) bucketsCopy[i] = -1; var keysCopy = new TKey[newSize]; var valuesCopy = new TValue[newSize]; var hashCodesCopy = new int[newSize]; var nextCopy = new int[newSize]; Array.Copy(_Values, 0, valuesCopy, 0, _Count); Array.Copy(_Keys, 0, keysCopy, 0, _Count); Array.Copy(_HashCodes, 0, hashCodesCopy, 0, _Count); Array.Copy(_Next, 0, nextCopy, 0, _Count); if (forceNewHashCodes) { for (int i = 0; i < _Count; i++) { if (hashCodesCopy[i] != -1) hashCodesCopy[i] = (_Comparer.GetHashCode(keysCopy[i]) & 2147483647); } } for (int i = 0; i < _Count; i++) { int index = hashCodesCopy[i] % newSize; nextCopy[i] = bucketsCopy[index]; bucketsCopy[index] = i; } _Buckets = bucketsCopy; _Keys = keysCopy; _Values = valuesCopy; _HashCodes = hashCodesCopy; _Next = nextCopy; } private void Resize() { Resize(PrimeHelper.ExpandPrime(_Count), false); } public bool Remove(TKey key) { if (key == null) throw new ArgumentNullException("key"); int hash = _Comparer.GetHashCode(key) & 2147483647; int index = hash % _Buckets.Length; int num = -1; for (int i = _Buckets[index]; i >= 0; i = _Next[i]) { if (_HashCodes[i] == hash && _Comparer.Equals(_Keys[i], key)) { if (num < 0) _Buckets[index] = _Next[i]; else _Next[num] = _Next[i]; _HashCodes[i] = -1; _Next[i] = _FreeList; _Keys[i] = default(TKey); _Values[i] = default(TValue); _FreeList = i; _FreeCount++; _Version++; return true; } num = i; } return false; } private void Insert(TKey key, TValue value, bool add) { if (key == null) throw new ArgumentNullException("key"); if (_Buckets == null) Initialize(0); int hash = _Comparer.GetHashCode(key) & 2147483647; int index = hash % _Buckets.Length; int num1 = 0; for (int i = _Buckets[index]; i >= 0; i = _Next[i]) { if (_HashCodes[i] == hash && _Comparer.Equals(_Keys[i], key)) { if (add) throw new ArgumentException("Key already exists: " + key); _Values[i] = value; _Version++; return; } num1++; } int num2; if (_FreeCount > 0) { num2 = _FreeList; _FreeList = _Next[num2]; _FreeCount--; } else { if (_Count == _Keys.Length) { Resize(); index = hash % _Buckets.Length; } num2 = _Count; _Count++; } _HashCodes[num2] = hash; _Next[num2] = _Buckets[index]; _Keys[num2] = key; _Values[num2] = value; _Buckets[index] = num2; _Version++; //if (num3 > 100 && HashHelpers.IsWellKnownEqualityComparer(comparer)) //{ // comparer = (IEqualityComparer)HashHelpers.GetRandomizedEqualityComparer(comparer); // Resize(entries.Length, true); //} } private void Initialize(int capacity) { int prime = PrimeHelper.GetPrime(capacity); _Buckets = new int[prime]; for (int i = 0; i < _Buckets.Length; i++) _Buckets[i] = -1; _Keys = new TKey[prime]; _Values = new TValue[prime]; _HashCodes = new int[prime]; _Next = new int[prime]; _FreeList = -1; } private int FindIndex(TKey key) { if (key == null) throw new ArgumentNullException("key"); if (_Buckets != null) { int hash = _Comparer.GetHashCode(key) & 2147483647; for (int i = _Buckets[hash % _Buckets.Length]; i >= 0; i = _Next[i]) { if (_HashCodes[i] == hash && _Comparer.Equals(_Keys[i], key)) return i; } } return -1; } public bool TryGetValue(TKey key, out TValue value) { int index = FindIndex(key); if (index >= 0) { value = _Values[index]; return true; } value = default(TValue); return false; } private static class PrimeHelper { public static readonly int[] Primes = new int[] { 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919, 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591, 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437, 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263, 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369 }; public static bool IsPrime(int candidate) { if ((candidate & 1) != 0) { int num = (int)Math.Sqrt((double)candidate); for (int i = 3; i <= num; i += 2) { if (candidate % i == 0) { return false; } } return true; } return candidate == 2; } public static int GetPrime(int min) { if (min < 0) throw new ArgumentException("min < 0"); for (int i = 0; i < PrimeHelper.Primes.Length; i++) { int prime = PrimeHelper.Primes[i]; if (prime >= min) return prime; } for (int i = min | 1; i < 2147483647; i += 2) { if (PrimeHelper.IsPrime(i) && (i - 1) % 101 != 0) return i; } return min; } public static int ExpandPrime(int oldSize) { int num = 2 * oldSize; if (num > 2146435069 && 2146435069 > oldSize) { return 2146435069; } return PrimeHelper.GetPrime(num); } } public ICollection Keys { get { return _Keys.Take(Count).ToArray(); } } public ICollection Values { get { return _Values.Take(Count).ToArray(); } } public void Add(KeyValuePair item) { Add(item.Key, item.Value); } public bool Contains(KeyValuePair item) { int index = FindIndex(item.Key); return index >= 0 && EqualityComparer.Default.Equals(_Values[index], item.Value); } public void CopyTo(KeyValuePair[] array, int index) { if (array == null) throw new ArgumentNullException("array"); if (index < 0 || index > array.Length) throw new ArgumentOutOfRangeException(string.Format("index = {0} array.Length = {1}", index, array.Length)); if (array.Length - index < Count) throw new ArgumentException(string.Format("The number of elements in the dictionary ({0}) is greater than the available space from index to the end of the destination array {1}.", Count, array.Length)); for (int i = 0; i < _Count; i++) { if (_HashCodes[i] >= 0) array[index++] = new KeyValuePair(_Keys[i], _Values[i]); } } public bool IsReadOnly { get { return false; } } public bool Remove(KeyValuePair item) { return Remove(item.Key); } public Enumerator GetEnumerator() { return new Enumerator(this); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } IEnumerator> IEnumerable>.GetEnumerator() { return GetEnumerator(); } public struct Enumerator : IEnumerator> { private readonly SerializableDictionary _Dictionary; private int _Version; private int _Index; private KeyValuePair _Current; public KeyValuePair Current { get { return _Current; } } internal Enumerator(SerializableDictionary dictionary) { _Dictionary = dictionary; _Version = dictionary._Version; _Current = default(KeyValuePair); _Index = 0; } public bool MoveNext() { if (_Version != _Dictionary._Version) throw new InvalidOperationException(string.Format("Enumerator version {0} != Dictionary version {1}", _Version, _Dictionary._Version)); while (_Index < _Dictionary._Count) { if (_Dictionary._HashCodes[_Index] >= 0) { _Current = new KeyValuePair(_Dictionary._Keys[_Index], _Dictionary._Values[_Index]); _Index++; return true; } _Index++; } _Index = _Dictionary._Count + 1; _Current = default(KeyValuePair); return false; } void IEnumerator.Reset() { if (_Version != _Dictionary._Version) throw new InvalidOperationException(string.Format("Enumerator version {0} != Dictionary version {1}", _Version, _Dictionary._Version)); _Index = 0; _Current = default(KeyValuePair); } object IEnumerator.Current { get { return Current; } } public void Dispose() { } } } public abstract class DictionaryDrawer : PropertyDrawer { private SerializableDictionary _Dictionary; private bool _Foldout; private const float kButtonWidth = 18f; public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { CheckInitialize(property, label); if (_Foldout) return (_Dictionary.Count + 1) * 17f; return 17f; } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { CheckInitialize(property, label); position.height = 17f; var foldoutRect = position; foldoutRect.width -= 2 * kButtonWidth; EditorGUI.BeginChangeCheck(); _Foldout = EditorGUI.Foldout(foldoutRect, _Foldout, label, true); if (EditorGUI.EndChangeCheck()) EditorPrefs.SetBool(label.text, _Foldout); var buttonRect = position; buttonRect.x = position.width - kButtonWidth + position.x; buttonRect.width = kButtonWidth + 2; if (GUI.Button(buttonRect, new GUIContent("+", "Add item"), EditorStyles.miniButton)) { AddNewItem(); } buttonRect.x -= kButtonWidth; if (GUI.Button(buttonRect, new GUIContent("X", "Clear dictionary"), EditorStyles.miniButtonRight)) { ClearDictionary(); } if (!_Foldout) return; foreach (var item in _Dictionary) { var key = item.Key; var value = item.Value; position.y += 17f; var keyRect = position; keyRect.width /= 2; keyRect.width -= 4; EditorGUI.BeginChangeCheck(); var newKey = DoField(keyRect, typeof(TK), key); if (EditorGUI.EndChangeCheck()) { try { _Dictionary.Remove(key); _Dictionary.Add(newKey, value); } catch (Exception e) { UnityEngine.Debug.Log(e.Message); } break; } var valueRect = position; valueRect.x = position.width / 2 + 15; valueRect.width = keyRect.width - kButtonWidth; EditorGUI.BeginChangeCheck(); value = DoField(valueRect, typeof(TV), value); if (EditorGUI.EndChangeCheck()) { _Dictionary[key] = value; break; } var removeRect = valueRect; removeRect.x = valueRect.xMax + 2; removeRect.width = kButtonWidth; if (GUI.Button(removeRect, new GUIContent("x", "Remove item"), EditorStyles.miniButtonRight)) { RemoveItem(key); break; } } } private void RemoveItem(TK key) { _Dictionary.Remove(key); } private void CheckInitialize(SerializedProperty property, GUIContent label) { if (_Dictionary == null) { var target = property.serializedObject.targetObject; _Dictionary = fieldInfo.GetValue(target) as SerializableDictionary; if (_Dictionary == null) { _Dictionary = new SerializableDictionary(); fieldInfo.SetValue(target, _Dictionary); } _Foldout = EditorPrefs.GetBool(label.text); } } private static readonly Dictionary> _Fields = new Dictionary>() { { typeof(int), (rect, value) => EditorGUI.IntField(rect, (int)value) }, { typeof(float), (rect, value) => EditorGUI.FloatField(rect, (float)value) }, { typeof(string), (rect, value) => EditorGUI.TextField(rect, (string)value) }, { typeof(bool), (rect, value) => EditorGUI.Toggle(rect, (bool)value) }, { typeof(Vector2), (rect, value) => EditorGUI.Vector2Field(rect, GUIContent.none, (Vector2)value) }, { typeof(Vector3), (rect, value) => EditorGUI.Vector3Field(rect, GUIContent.none, (Vector3)value) }, { typeof(Bounds), (rect, value) => EditorGUI.BoundsField(rect, (Bounds)value) }, { typeof(Rect), (rect, value) => EditorGUI.RectField(rect, (Rect)value) }, }; private static T DoField(Rect rect, Type type, T value) { Func field; if (_Fields.TryGetValue(type, out field)) return (T)field(rect, value); if (type.IsEnum) return (T)(object)EditorGUI.EnumPopup(rect, (Enum)(object)value); if (typeof(UnityObject).IsAssignableFrom(type)) return (T)(object)EditorGUI.ObjectField(rect, (UnityObject)(object)value, type, true); UnityEngine.Debug.Log("Type is not supported: " + type); return value; } private void ClearDictionary() { _Dictionary.Clear(); } private void AddNewItem() { TK key; if (typeof(TK) == typeof(string)) key = (TK)(object)""; else key = default(TK); var value = default(TV); try { _Dictionary.Add(key, value); } catch (Exception e) { UnityEngine.Debug.Log(e.Message); } } }