Skip to content

Commit

Permalink
perf: use non-allocating enumerators in scriptable dictionary
Browse files Browse the repository at this point in the history
  • Loading branch information
Hertzole committed Jan 16, 2024
1 parent 9700a6a commit 8e6adb2
Show file tree
Hide file tree
Showing 3 changed files with 296 additions and 55 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Fixed scriptable value editor breaking if the value is null
- Fixed scriptable value editor having the wrong height in newer Unity versions
- Fixed the package not having an author
- Fixed allocation when using `foreach` on scriptable lists and dictionaries

## [1.2.0] - 2023-05-20

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,18 @@ internal virtual bool IsIndexUnique(int index)
/// </summary>
/// <typeparam name="TKey">The key of the dictionary.</typeparam>
/// <typeparam name="TValue">The value of the dictionary.</typeparam>
public abstract class ScriptableDictionary<TKey, TValue> : ScriptableDictionary, ISerializationCallbackReceiver, IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>, IDictionary where TKey : notnull
public abstract class ScriptableDictionary<TKey, TValue> : ScriptableDictionary,
ISerializationCallbackReceiver,
IDictionary<TKey, TValue>,
IReadOnlyDictionary<TKey, TValue>,
IDictionary where TKey : notnull
{
[SerializeField]
[Tooltip("If read only, the dictionary cannot be changed at runtime and won't be cleared on start.")]
private bool isReadOnly = false;
[SerializeField]
[Tooltip("If true, an equality check will be run before setting an item through the indexer to make sure the new object is not the same as the old one.")]
[Tooltip(
"If true, an equality check will be run before setting an item through the indexer to make sure the new object is not the same as the old one.")]
private bool setEqualityCheck = true;
[SerializeField]
[Tooltip("If true, the dictionary will be cleared on play mode start/game boot.")]
Expand All @@ -58,7 +63,11 @@ object IDictionary.this[object key]
}
}

public TValue this[TKey key] { get { return dictionary[key]; } set { SetValue(key, value); } }
public TValue this[TKey key]
{
get { return dictionary[key]; }
set { SetValue(key, value); }
}

/// <summary>
/// Gets or sets the <see cref="IEqualityComparer{T}" /> that is used to determine equality of keys for the dictionary.
Expand All @@ -72,7 +81,8 @@ public IEqualityComparer<TKey> Comparer
// Make sure it's a new comparer than the old one.
if (!EqualityComparer<IEqualityComparer<TKey>>.Default.Equals(dictionary.Comparer, value))
{
Dictionary<TKey, TValue> newDictionary = value == null ? new Dictionary<TKey, TValue>(dictionary) : new Dictionary<TKey, TValue>(dictionary, value);
Dictionary<TKey, TValue> newDictionary =
value == null ? new Dictionary<TKey, TValue>(dictionary) : new Dictionary<TKey, TValue>(dictionary, value);

dictionary = newDictionary;
}
Expand All @@ -83,40 +93,92 @@ public IEqualityComparer<TKey> Comparer
/// If true, an equality check will be run before setting an item through the indexer to make sure the new object is
/// not the same as the old one.
/// </summary>
public bool SetEqualityCheck { get { return setEqualityCheck; } set { setEqualityCheck = value; } }
public bool SetEqualityCheck
{
get { return setEqualityCheck; }
set { setEqualityCheck = value; }
}
/// <summary>
/// If true, the dictionary will be cleared on play mode start/game boot.
/// </summary>
public bool ClearOnStart { get { return clearOnStart; } set { clearOnStart = value; } }

bool IDictionary.IsFixedSize { get { return isReadOnly; } }
bool ICollection.IsSynchronized { get { return false; } }
object ICollection.SyncRoot { get { return this; } }
ICollection IDictionary.Values { get { return dictionary.Values; } }
ICollection IDictionary.Keys { get { return dictionary.Keys; } }
public bool ClearOnStart
{
get { return clearOnStart; }
set { clearOnStart = value; }
}

/// <summary>
/// If read only, the dictionary cannot be changed at runtime and won't be cleared on start.
/// Gets a collection containing the keys in the dictionary.
/// </summary>
public bool IsReadOnly { get { return isReadOnly; } set { isReadOnly = value; } }
public Dictionary<TKey, TValue>.KeyCollection Keys
{
get { return dictionary.Keys; }
}

/// <summary>
/// Gets the number of key/value pairs contained in the dictionary.
/// Gets a collection containing the values in the dictionary.
/// </summary>
public int Count { get { return dictionary.Count; } }
public Dictionary<TKey, TValue>.ValueCollection Values
{
get { return dictionary.Values; }
}

bool IDictionary.IsFixedSize
{
get { return isReadOnly; }
}
bool ICollection.IsSynchronized
{
get { return false; }
}
object ICollection.SyncRoot
{
get { return this; }
}
ICollection IDictionary.Values
{
get { return dictionary.Values; }
}
ICollection IDictionary.Keys
{
get { return dictionary.Keys; }
}

/// <summary>
/// Gets a collection containing the keys in the dictionary.
/// If read only, the dictionary cannot be changed at runtime and won't be cleared on start.
/// </summary>
public ICollection<TKey> Keys { get { return dictionary.Keys; } }
public bool IsReadOnly
{
get { return isReadOnly; }
set { isReadOnly = value; }
}

/// <summary>
/// Gets a collection containing the values in the dictionary.
/// Gets the number of key/value pairs contained in the dictionary.
/// </summary>
public ICollection<TValue> Values { get { return dictionary.Values; } }
public int Count
{
get { return dictionary.Count; }
}

ICollection<TKey> IDictionary<TKey, TValue>.Keys
{
get { return dictionary.Keys; }
}

IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys { get { return dictionary.Keys; } }
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values { get { return dictionary.Values; } }
ICollection<TValue> IDictionary<TKey, TValue>.Values
{
get { return dictionary.Values; }
}

IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys
{
get { return dictionary.Keys; }
}
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values
{
get { return dictionary.Values; }
}

/// <summary>
/// Called when an item was added. Gives you the key and value of the newly added item.
Expand Down Expand Up @@ -300,7 +362,7 @@ public bool ContainsValue(TValue value)
public bool TryFindKey(Predicate<TKey> predicate, out TKey key)
{
ThrowHelper.ThrowIfNull(predicate, nameof(predicate));

foreach (TKey dictionaryKey in dictionary.Keys)
{
if (predicate(dictionaryKey))
Expand All @@ -323,7 +385,7 @@ public bool TryFindKey(Predicate<TKey> predicate, out TKey key)
public bool TryFindValue(Predicate<TValue> predicate, out TValue value)
{
ThrowHelper.ThrowIfNull(predicate, nameof(predicate));

foreach (TValue dictionaryValue in dictionary.Values)
{
if (predicate(dictionaryValue))
Expand Down Expand Up @@ -388,17 +450,17 @@ protected override void OnStart()
#if UNITY_EDITOR
ResetStackTraces();
#endif

ClearSubscribers();

if (!isReadOnly && clearOnStart)
{
dictionary.Clear();
keys.Clear();
values.Clear();
}
}

/// <summary>
/// Removes any subscribers from the event.
/// </summary>
Expand Down Expand Up @@ -461,6 +523,15 @@ private void AddFastPath(TKey key, TValue value)
OnChanged?.Invoke(DictionaryChangeType.Added);
}

/// <summary>
/// Returns an enumerator that iterates through the dictionary.
/// </summary>
/// <returns>A enumerator for the dictionary.</returns>
public Dictionary<TKey, TValue>.Enumerator GetEnumerator()
{
return dictionary.GetEnumerator();
}

/// <summary>
/// Determines whether the dictionary contains the specified key.
/// </summary>
Expand Down Expand Up @@ -694,6 +765,7 @@ void ISerializationCallbackReceiver.OnAfterDeserialize()

// Update the dictionary with the serialized keys and values.
dictionary.Clear();
dictionary.EnsureCapacity(keys.Count);

for (int i = 0; i < keys.Count; i++)
{
Expand Down
Loading

0 comments on commit 8e6adb2

Please sign in to comment.