490 lines
16 KiB
C#
490 lines
16 KiB
C#
//Based of the following thread https://forum.unity.com/threads/finally-a-serializable-dictionary-for-unity-extracted-from-system-collections-generic.335797/
|
|
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using UnityEngine;
|
|
|
|
namespace RotaryHeart.Lib.SerializableDictionary
|
|
{
|
|
/// <summary>
|
|
/// This class is only used to be able to draw the custom property drawer
|
|
/// </summary>
|
|
public abstract class DrawableDictionary
|
|
{
|
|
[UnityEngine.HideInInspector]
|
|
public ReorderableList reorderableList = null;
|
|
[UnityEngine.HideInInspector]
|
|
public RequiredReferences reqReferences;
|
|
public bool isExpanded;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Base class that most be used for any dictionary that wants to be implemented
|
|
/// </summary>
|
|
/// <typeparam name="TKey">Key type</typeparam>
|
|
/// <typeparam name="TValue">Value type</typeparam>
|
|
[System.Serializable]
|
|
public class SerializableDictionaryBase<TKey, TValue> : DrawableDictionary, IDictionary<TKey, TValue>, UnityEngine.ISerializationCallbackReceiver
|
|
{
|
|
Dictionary<TKey, TValue> _dict;
|
|
static readonly Dictionary<TKey, TValue> _staticEmptyDict = new Dictionary<TKey, TValue>(0);
|
|
|
|
/// <summary>
|
|
/// Copies the data from a dictionary. If an entry with the same key is found it replaces the value
|
|
/// </summary>
|
|
/// <param name="src">Dictionary to copy the data from</param>
|
|
public void CopyFrom(IDictionary<TKey, TValue> src)
|
|
{
|
|
foreach (KeyValuePair<TKey, TValue> data in src)
|
|
{
|
|
if (ContainsKey(data.Key))
|
|
{
|
|
this[data.Key] = data.Value;
|
|
}
|
|
else
|
|
{
|
|
Add(data.Key, data.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the data from a dictionary. If an entry with the same key is found it replaces the value. Note that if the <paramref name="src"/> is not a dictionary of the same type it will not be copied
|
|
/// </summary>
|
|
/// <param name="src">Dictionary to copy the data from</param>
|
|
public void CopyFrom(object src)
|
|
{
|
|
Dictionary<TKey, TValue> dictionary = src as Dictionary<TKey, TValue>;
|
|
if (dictionary != null)
|
|
{
|
|
CopyFrom(dictionary);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the data to a dictionary. If an entry with the same key is found it replaces the value
|
|
/// </summary>
|
|
/// <param name="dest">Dictionary to copy the data to</param>
|
|
public void CopyTo(IDictionary<TKey, TValue> dest)
|
|
{
|
|
foreach (KeyValuePair<TKey, TValue> data in this)
|
|
{
|
|
if (dest.ContainsKey(data.Key))
|
|
{
|
|
dest[data.Key] = data.Value;
|
|
}
|
|
else
|
|
{
|
|
dest.Add(data.Key, data.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a copy of the dictionary.
|
|
/// </summary>
|
|
public Dictionary<TKey, TValue> Clone()
|
|
{
|
|
Dictionary<TKey, TValue> dest = new Dictionary<TKey, TValue>(Count);
|
|
|
|
foreach (KeyValuePair<TKey, TValue> data in this)
|
|
{
|
|
dest.Add(data.Key, data.Value);
|
|
}
|
|
|
|
return dest;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the value exists; otherwise, false
|
|
/// </summary>
|
|
/// <param name="value">Value to check</param>
|
|
public bool ContainsValue(TValue value)
|
|
{
|
|
if (_dict == null)
|
|
return false;
|
|
|
|
return _dict.ContainsValue(value);
|
|
}
|
|
|
|
#region IDictionary Interface
|
|
|
|
#region Properties
|
|
|
|
public TValue this[TKey key]
|
|
{
|
|
get
|
|
{
|
|
if (_dict == null) throw new KeyNotFoundException();
|
|
return _dict[key];
|
|
}
|
|
set
|
|
{
|
|
if (_dict == null) _dict = new Dictionary<TKey, TValue>();
|
|
_dict[key] = value;
|
|
}
|
|
}
|
|
|
|
public ICollection<TKey> Keys
|
|
{
|
|
get
|
|
{
|
|
if (_dict == null)
|
|
_dict = new Dictionary<TKey, TValue>();
|
|
|
|
return _dict.Keys;
|
|
}
|
|
}
|
|
|
|
public ICollection<TValue> Values
|
|
{
|
|
get
|
|
{
|
|
if (_dict == null)
|
|
_dict = new Dictionary<TKey, TValue>();
|
|
|
|
return _dict.Values;
|
|
}
|
|
}
|
|
|
|
public int Count
|
|
{
|
|
get
|
|
{
|
|
return (_dict != null) ? _dict.Count : 0;
|
|
}
|
|
}
|
|
|
|
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly
|
|
{
|
|
get { return false; }
|
|
}
|
|
|
|
#endregion Properties
|
|
|
|
|
|
public bool ContainsKey(TKey key)
|
|
{
|
|
if (_dict == null)
|
|
return false;
|
|
|
|
return _dict.ContainsKey(key);
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
public void Add(TKey key, TValue value)
|
|
{
|
|
if (_dict == null)
|
|
_dict = new Dictionary<TKey, TValue>();
|
|
|
|
_dict.Add(key, value);
|
|
|
|
if (_keyValues == null)
|
|
_keyValues = new List<TKey>();
|
|
if (_keys == null)
|
|
_keys = new List<TKey>();
|
|
if (_values == null)
|
|
_values = new List<TValue>();
|
|
|
|
_keyValues.Add(key);
|
|
_keys.Add(key);
|
|
_values.Add(value);
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
if (_dict != null)
|
|
_dict.Clear();
|
|
|
|
if (_keyValues != null)
|
|
_keyValues.Clear();
|
|
if (_keys != null)
|
|
_keys.Clear();
|
|
if (_values != null)
|
|
_values.Clear();
|
|
}
|
|
|
|
public bool Remove(TKey key)
|
|
{
|
|
if (_dict == null)
|
|
return false;
|
|
|
|
int index = -1;
|
|
|
|
if (_keys != null)
|
|
{
|
|
index = _keys.IndexOf(key);
|
|
|
|
if (index != -1)
|
|
_keys.RemoveAt(index);
|
|
}
|
|
|
|
if (index != -1)
|
|
{
|
|
if (_keyValues != null)
|
|
_keyValues.RemoveAt(index);
|
|
|
|
if (_values != null)
|
|
_values.RemoveAt(index);
|
|
}
|
|
|
|
return _dict.Remove(key);
|
|
}
|
|
#else
|
|
|
|
public void Add(TKey key, TValue value)
|
|
{
|
|
if (_dict == null)
|
|
_dict = new Dictionary<TKey, TValue>();
|
|
|
|
_dict.Add(key, value);
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
if (_dict != null)
|
|
_dict.Clear();
|
|
}
|
|
|
|
public bool Remove(TKey key)
|
|
{
|
|
if (_dict == null)
|
|
return false;
|
|
|
|
return _dict.Remove(key);
|
|
}
|
|
|
|
#endif
|
|
|
|
public bool TryGetValue(TKey key, out TValue value)
|
|
{
|
|
if (_dict == null)
|
|
{
|
|
value = default(TValue);
|
|
return false;
|
|
}
|
|
|
|
return _dict.TryGetValue(key, out value);
|
|
}
|
|
|
|
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
|
|
{
|
|
if (_dict == null) _dict = new Dictionary<TKey, TValue>();
|
|
(_dict as ICollection<KeyValuePair<TKey, TValue>>).Add(item);
|
|
}
|
|
|
|
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
|
|
{
|
|
if (_dict == null) return false;
|
|
return (_dict as ICollection<KeyValuePair<TKey, TValue>>).Contains(item);
|
|
}
|
|
|
|
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
|
{
|
|
if (_dict == null) return;
|
|
(_dict as ICollection<KeyValuePair<TKey, TValue>>).CopyTo(array, arrayIndex);
|
|
}
|
|
|
|
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
|
|
{
|
|
if (_dict == null) return false;
|
|
return (_dict as ICollection<KeyValuePair<TKey, TValue>>).Remove(item);
|
|
}
|
|
|
|
public Dictionary<TKey, TValue>.Enumerator GetEnumerator()
|
|
{
|
|
if (_dict == null) return _staticEmptyDict.GetEnumerator();
|
|
return _dict.GetEnumerator();
|
|
}
|
|
|
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ISerializationCallbackReceiver
|
|
|
|
[SerializeField]
|
|
List<TKey> _keyValues;
|
|
|
|
[SerializeField]
|
|
List<TKey> _keys;
|
|
[SerializeField]
|
|
List<TValue> _values;
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
|
{
|
|
if (_keys != null && _values != null)
|
|
{
|
|
//Need to clear the dictionary
|
|
if (_dict == null)
|
|
_dict = new Dictionary<TKey, TValue>(_keys.Count);
|
|
else
|
|
_dict.Clear();
|
|
|
|
for (int i = 0; i < _keys.Count; i++)
|
|
{
|
|
//This should only happen with reference type keys (Generic, Object, etc)
|
|
if (_keys[i] == null)
|
|
{
|
|
//Special case for UnityEngine.Object classes
|
|
if (typeof(Object).IsAssignableFrom(typeof(TKey)))
|
|
{
|
|
//Key type
|
|
string tKeyType = typeof(TKey).ToString();
|
|
|
|
//We need the reference to the reference holder class
|
|
if (reqReferences == null)
|
|
{
|
|
Debug.LogError("A key of type: " + tKeyType + " requires to have a valid RequiredReferences reference");
|
|
continue;
|
|
}
|
|
|
|
//Use reflection to check all the fields included on the class
|
|
foreach (FieldInfo field in typeof(RequiredReferences).GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
|
|
{
|
|
//Only set the value if the type is the same
|
|
if (field.FieldType.ToString().Equals(tKeyType))
|
|
{
|
|
_keys[i] = (TKey)(field.GetValue(reqReferences));
|
|
break;
|
|
}
|
|
}
|
|
|
|
//References class is missing the field, skip the element
|
|
if (_keys[i] == null)
|
|
{
|
|
Debug.LogError("Couldn't find " + tKeyType + " reference.");
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Create a instance for the key
|
|
_keys[i] = System.Activator.CreateInstance<TKey>();
|
|
}
|
|
}
|
|
|
|
//Add the data to the dictionary. Value can be null so no special step is required
|
|
if (i < _values.Count)
|
|
_dict[_keys[i]] = _values[i];
|
|
else
|
|
_dict[_keys[i]] = default(TValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
#else
|
|
void ISerializationCallbackReceiver.OnAfterDeserialize()
|
|
{
|
|
if (_keys != null && _values != null)
|
|
{
|
|
//Need to clear the dictionary
|
|
if (_dict == null)
|
|
_dict = new Dictionary<TKey, TValue>(_keys.Count);
|
|
else
|
|
_dict.Clear();
|
|
|
|
for (int i = 0; i < _keys.Count; i++)
|
|
{
|
|
//This should only happen with reference type keys (Generic, Object, etc)
|
|
if (_keys[i] == null)
|
|
{
|
|
//Special case for UnityEngine.Object classes
|
|
if (typeof(Object).IsAssignableFrom(typeof(TKey)))
|
|
{
|
|
//Key type
|
|
string tKeyType = typeof(TKey).ToString();
|
|
|
|
//We need the reference to the reference holder class
|
|
if (reqReferences == null)
|
|
{
|
|
Debug.LogError("A key of type: " + tKeyType + " requires to have a valid RequiredReferences reference");
|
|
continue;
|
|
}
|
|
|
|
//Use reflection to check all the fields included on the class
|
|
foreach (FieldInfo field in typeof(RequiredReferences).GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
|
|
{
|
|
//Only set the value if the type is the same
|
|
if (field.FieldType.ToString().Equals(tKeyType))
|
|
{
|
|
_keys[i] = (TKey)(field.GetValue(reqReferences));
|
|
break;
|
|
}
|
|
}
|
|
|
|
//References class is missing the field, skip the element
|
|
if (_keys[i] == null)
|
|
{
|
|
Debug.LogError("Couldn't find " + tKeyType + " reference.");
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Create a instance for the key
|
|
_keys[i] = System.Activator.CreateInstance<TKey>();
|
|
}
|
|
}
|
|
|
|
//Add the data to the dictionary. Value can be null so no special step is required
|
|
if (i < _values.Count)
|
|
_dict[_keys[i]] = _values[i];
|
|
else
|
|
_dict[_keys[i]] = default(TValue);
|
|
}
|
|
}
|
|
|
|
_keyValues = null;
|
|
_keys = null;
|
|
_values = null;
|
|
}
|
|
|
|
#endif
|
|
|
|
void ISerializationCallbackReceiver.OnBeforeSerialize()
|
|
{
|
|
if (_dict == null || _dict.Count == 0)
|
|
{
|
|
//Dictionary is empty, erase data
|
|
_keyValues = null;
|
|
_keys = null;
|
|
_values = null;
|
|
}
|
|
else
|
|
{
|
|
//Initialize arrays
|
|
int cnt = _dict.Count;
|
|
_keyValues = new List<TKey>(cnt);
|
|
_keys = new List<TKey>(cnt);
|
|
_values = new List<TValue>(cnt);
|
|
|
|
using (Dictionary<TKey, TValue>.Enumerator e = _dict.GetEnumerator())
|
|
{
|
|
while (e.MoveNext())
|
|
{
|
|
//Set the respective data from the dictionary
|
|
_keyValues.Add(e.Current.Key);
|
|
_keys.Add(e.Current.Key);
|
|
_values.Add(e.Current.Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
}
|