|
using System; |
|
using System.Collections; |
|
using System.Collections.Generic; |
|
using System.Diagnostics; |
|
using System.Linq; |
|
using UnityEngine; |
|
|
|
[Serializable, DebuggerDisplay("Count = {Count}"), HelpURL("https://forum.unity.com/threads/finally-a-serializable-dictionary-for-unity-extracted-from-system-collections-generic.335797/")] |
|
public class SerializableDictionary<TKey, TValue> : IDictionary<TKey, TValue> |
|
{ |
|
[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<TKey> _Comparer; |
|
|
|
// Mainly for debugging purposes - to get the key-value pairs display |
|
public Dictionary<TKey, TValue> AsDictionary |
|
{ |
|
get { return new Dictionary<TKey, TValue>(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<TKey> comparer) |
|
: this(0, comparer) |
|
{ |
|
} |
|
|
|
public SerializableDictionary(int capacity, IEqualityComparer<TKey> comparer) |
|
{ |
|
if (capacity < 0) |
|
throw new ArgumentOutOfRangeException("capacity"); |
|
|
|
Initialize(capacity); |
|
|
|
_Comparer = (comparer ?? EqualityComparer<TKey>.Default); |
|
} |
|
|
|
public SerializableDictionary(IDictionary<TKey, TValue> dictionary) |
|
: this(dictionary, null) |
|
{ |
|
} |
|
|
|
public SerializableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) |
|
: this((dictionary != null) ? dictionary.Count : 0, comparer) |
|
{ |
|
if (dictionary == null) |
|
throw new ArgumentNullException("dictionary"); |
|
|
|
foreach (KeyValuePair<TKey, TValue> 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<TValue>.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) |
|
{ |
|
if (typeof(TKey) == typeof(UnityEngine.Object)) |
|
{ |
|
key = ObjectToTKey(new UnityEngine.Object()); |
|
} |
|
else |
|
{ |
|
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++; |
|
} |
|
|
|
private TKey ObjectToTKey(UnityEngine.Object key) |
|
{ |
|
return (TKey)Convert.ChangeType(key, typeof(TKey)); |
|
} |
|
|
|
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<TKey> Keys |
|
{ |
|
get { return _Keys.Take(Count).ToArray(); } |
|
} |
|
|
|
public ICollection<TValue> Values |
|
{ |
|
get { return _Values.Take(Count).ToArray(); } |
|
} |
|
|
|
public void Add(KeyValuePair<TKey, TValue> item) |
|
{ |
|
Add(item.Key, item.Value); |
|
} |
|
|
|
public bool Contains(KeyValuePair<TKey, TValue> item) |
|
{ |
|
int index = FindIndex(item.Key); |
|
return index >= 0 && |
|
EqualityComparer<TValue>.Default.Equals(_Values[index], item.Value); |
|
} |
|
|
|
public void CopyTo(KeyValuePair<TKey, TValue>[] 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<TKey, TValue>(_Keys[i], _Values[i]); |
|
} |
|
} |
|
|
|
public bool IsReadOnly |
|
{ |
|
get { return false; } |
|
} |
|
|
|
public bool Remove(KeyValuePair<TKey, TValue> item) |
|
{ |
|
return Remove(item.Key); |
|
} |
|
|
|
public Enumerator GetEnumerator() |
|
{ |
|
return new Enumerator(this); |
|
} |
|
|
|
IEnumerator IEnumerable.GetEnumerator() |
|
{ |
|
return GetEnumerator(); |
|
} |
|
|
|
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() |
|
{ |
|
return GetEnumerator(); |
|
} |
|
|
|
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>> |
|
{ |
|
private readonly SerializableDictionary<TKey, TValue> _Dictionary; |
|
private int _Version; |
|
private int _Index; |
|
private KeyValuePair<TKey, TValue> _Current; |
|
|
|
public KeyValuePair<TKey, TValue> Current |
|
{ |
|
get { return _Current; } |
|
} |
|
|
|
internal Enumerator(SerializableDictionary<TKey, TValue> dictionary) |
|
{ |
|
_Dictionary = dictionary; |
|
_Version = dictionary._Version; |
|
_Current = default; |
|
_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<TKey, TValue>(_Dictionary._Keys[_Index], _Dictionary._Values[_Index]); |
|
_Index++; |
|
return true; |
|
} |
|
_Index++; |
|
} |
|
|
|
_Index = _Dictionary._Count + 1; |
|
_Current = default(KeyValuePair<TKey, TValue>); |
|
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; |
|
} |
|
|
|
object IEnumerator.Current |
|
{ |
|
get { return Current; } |
|
} |
|
|
|
public void Dispose() |
|
{ |
|
} |
|
} |
|
} |
Hi @Marsgames thanks for the code, it's been real useful! One slight annoyance, I get this error when I try to use this for an enum and float dictionary.

The drawer still works fine though, butthis one error pops up every time the drawer is shown in the inspector and the value doesn't save. I don't know how to fix this. I made sure my SerializableDictionary class has the same types as seen belowAny help would be greatly appreciated cause I'm super confused lol