diff --git a/OctaneEngine/.idea/.idea.OctaneEngine.dir/.idea/indexLayout.xml b/OctaneEngine/.idea/.idea.OctaneEngine.dir/.idea/indexLayout.xml
new file mode 100644
index 0000000..7b08163
--- /dev/null
+++ b/OctaneEngine/.idea/.idea.OctaneEngine.dir/.idea/indexLayout.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OctaneEngine/.idea/.idea.OctaneEngine.dir/.idea/workspace.xml b/OctaneEngine/.idea/.idea.OctaneEngine.dir/.idea/workspace.xml
new file mode 100644
index 0000000..bdd2997
--- /dev/null
+++ b/OctaneEngine/.idea/.idea.OctaneEngine.dir/.idea/workspace.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/OctaneEngine/Collections.Pooled/BitHelper.cs b/OctaneEngine/Collections.Pooled/BitHelper.cs
new file mode 100644
index 0000000..0470daa
--- /dev/null
+++ b/OctaneEngine/Collections.Pooled/BitHelper.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Collections.Pooled
+{
+ internal ref struct BitHelper
+ {
+ private const int IntSize = sizeof(int) * 8;
+ private readonly Span _span;
+
+ internal BitHelper(Span span, bool clear)
+ {
+ if (clear)
+ {
+ span.Clear();
+ }
+ _span = span;
+ }
+
+ internal void MarkBit(int bitPosition)
+ {
+ int bitArrayIndex = bitPosition / IntSize;
+ if ((uint)bitArrayIndex < (uint)_span.Length)
+ {
+ _span[bitArrayIndex] |= (1 << (bitPosition % IntSize));
+ }
+ }
+
+ internal bool IsMarked(int bitPosition)
+ {
+ int bitArrayIndex = bitPosition / IntSize;
+ return
+ (uint)bitArrayIndex < (uint)_span.Length &&
+ (_span[bitArrayIndex] & (1 << (bitPosition % IntSize))) != 0;
+ }
+
+ internal int FindFirstUnmarked(int startPosition = 0)
+ {
+ int i = startPosition;
+ for (int bi = i / IntSize; (uint)bi < (uint)_span.Length; bi = ++i / IntSize)
+ {
+ if ((_span[bi] & (1 << (i % IntSize))) == 0)
+ return i;
+ }
+ return -1;
+ }
+
+ internal int FindFirstMarked(int startPosition = 0)
+ {
+ int i = startPosition;
+ for (int bi = i / IntSize; (uint)bi < (uint)_span.Length; bi = ++i / IntSize)
+ {
+ if ((_span[bi] & (1 << (i % IntSize))) != 0)
+ return i;
+ }
+ return -1;
+ }
+
+ /// How many ints must be allocated to represent n bits. Returns (n+31)/32, but avoids overflow.
+ internal static int ToIntArrayLength(int n) => n > 0 ? ((n - 1) / IntSize + 1) : 0;
+ }
+}
diff --git a/OctaneEngine/Collections.Pooled/ClearMode.cs b/OctaneEngine/Collections.Pooled/ClearMode.cs
new file mode 100644
index 0000000..3e5b4e0
--- /dev/null
+++ b/OctaneEngine/Collections.Pooled/ClearMode.cs
@@ -0,0 +1,37 @@
+namespace Collections.Pooled
+{
+ ///
+ /// This enum allows control over how data is treated when internal
+ /// arrays are returned to the ArrayPool. Be careful to understand
+ /// what each option does before using anything other than the default
+ /// of Auto.
+ ///
+ public enum ClearMode
+ {
+ ///
+ /// Auto
has different behavior depending on the host project's target framework.
+ /// .NET Core 2.1: Reference types and value types that contain reference types are cleared
+ /// when the internal arrays are returned to the pool. Value types that do not contain reference
+ /// types are not cleared when returned to the pool.
+ /// .NET Standard 2.0: All user types are cleared before returning to the pool, in case they
+ /// contain reference types.
+ /// For .NET Standard, Auto and Always have the same behavior.
+ ///
+ Auto = 0,
+ ///
+ /// The Always
setting has the effect of always clearing user types before returning to the pool.
+ /// This is the default behavior on .NET Standard.You might want to turn this on in a .NET Core project
+ /// if you were concerned about sensitive data stored in value types leaking to other pars of your application.
+ ///
+ Always = 1,
+ ///
+ /// Never
will cause pooled collections to never clear user types before returning them to the pool.
+ /// You might want to use this setting in a .NET Standard project when you know that a particular collection stores
+ /// only value types and you want the performance benefit of not taking time to reset array items to their default value.
+ /// Be careful with this setting: if used for a collection that contains reference types, or value types that contain
+ /// reference types, this setting could cause memory issues by making the garbage collector unable to clean up instances
+ /// that are still being referenced by arrays sitting in the ArrayPool.
+ ///
+ Never = 2
+ }
+}
diff --git a/OctaneEngine/Collections.Pooled/HashHelpers.SerializationInfoTable.cs b/OctaneEngine/Collections.Pooled/HashHelpers.SerializationInfoTable.cs
new file mode 100644
index 0000000..86add66
--- /dev/null
+++ b/OctaneEngine/Collections.Pooled/HashHelpers.SerializationInfoTable.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Used by Hashtable and Dictionary's SeralizationInfo .ctor's to store the SeralizationInfo
+// object until OnDeserialization is called.
+
+using System.Threading;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+
+namespace Collections.Pooled
+{
+ internal static partial class HashHelpers
+ {
+ private static ConditionalWeakTable s_serializationInfoTable;
+
+ public static ConditionalWeakTable SerializationInfoTable
+ {
+ get
+ {
+ if (s_serializationInfoTable == null)
+ Interlocked.CompareExchange(ref s_serializationInfoTable, new ConditionalWeakTable(), null);
+
+ return s_serializationInfoTable;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/OctaneEngine/Collections.Pooled/HashHelpers.cs b/OctaneEngine/Collections.Pooled/HashHelpers.cs
new file mode 100644
index 0000000..c915084
--- /dev/null
+++ b/OctaneEngine/Collections.Pooled/HashHelpers.cs
@@ -0,0 +1,93 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+using System;
+using System.Diagnostics;
+
+namespace Collections.Pooled
+{
+ internal static partial class HashHelpers
+ {
+ public const int HashCollisionThreshold = 100;
+
+ // This is the maximum prime smaller than Array.MaxArrayLength
+ public const int MaxPrimeArrayLength = 0x7FEFFFFD;
+
+ public const int HashPrime = 101;
+
+ // Table of prime numbers to use as hash table sizes.
+ // A typical resize algorithm would pick the smallest prime number in this array
+ // that is larger than twice the previous capacity.
+ // Suppose our Hashtable currently has capacity x and enough elements are added
+ // such that a resize needs to occur. Resizing first computes 2x then finds the
+ // first prime in the table greater than 2x, i.e. if primes are ordered
+ // p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n.
+ // Doubling is important for preserving the asymptotic complexity of the
+ // hashtable operations such as add. Having a prime guarantees that double
+ // hashing does not lead to infinite loops. IE, your hash function will be
+ // h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime.
+ // We prefer the low computation costs of higher prime numbers over the increased
+ // memory allocation of a fixed prime number i.e. when right sizing a HashSet.
+ public static readonly int[] primes = {
+ 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 limit = (int)Math.Sqrt(candidate);
+ for (int divisor = 3; divisor <= limit; divisor += 2)
+ {
+ if ((candidate % divisor) == 0)
+ return false;
+ }
+ return true;
+ }
+ return (candidate == 2);
+ }
+
+ public static int GetPrime(int min)
+ {
+ if (min < 0)
+ throw new ArgumentException("Cannot get the next prime from a negative number.");
+
+ for (int i = 0; i < primes.Length; i++)
+ {
+ int prime = primes[i];
+ if (prime >= min)
+ return prime;
+ }
+
+ //outside of our predefined table.
+ //compute the hard way.
+ for (int i = (min | 1); i < int.MaxValue; i += 2)
+ {
+ if (IsPrime(i) && ((i - 1) % HashPrime != 0))
+ return i;
+ }
+ return min;
+ }
+
+ // Returns size of hashtable to grow to.
+ public static int ExpandPrime(int oldSize)
+ {
+ int newSize = 2 * oldSize;
+
+ // Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow.
+ // Note that this check works even when _items.Length overflowed thanks to the (uint) cast
+ if ((uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > oldSize)
+ {
+ Debug.Assert(MaxPrimeArrayLength == GetPrime(MaxPrimeArrayLength), "Invalid MaxPrimeArrayLength");
+ return MaxPrimeArrayLength;
+ }
+
+ return GetPrime(newSize);
+ }
+ }
+}
diff --git a/OctaneEngine/Collections.Pooled/ICollectionDebugView.cs b/OctaneEngine/Collections.Pooled/ICollectionDebugView.cs
new file mode 100644
index 0000000..2471d42
--- /dev/null
+++ b/OctaneEngine/Collections.Pooled/ICollectionDebugView.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Collections.Pooled
+{
+ internal sealed class ICollectionDebugView
+ {
+ private readonly ICollection _collection;
+
+ public ICollectionDebugView(ICollection collection)
+ {
+ _collection = collection ?? throw new ArgumentNullException(nameof(collection));
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public T[] Items
+ {
+ get
+ {
+ T[] items = new T[_collection.Count];
+ _collection.CopyTo(items, 0);
+ return items;
+ }
+ }
+ }
+}
diff --git a/OctaneEngine/Collections.Pooled/IDictionaryDebugView.cs b/OctaneEngine/Collections.Pooled/IDictionaryDebugView.cs
new file mode 100644
index 0000000..1f22664
--- /dev/null
+++ b/OctaneEngine/Collections.Pooled/IDictionaryDebugView.cs
@@ -0,0 +1,73 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace Collections.Pooled
+{
+ internal sealed class IDictionaryDebugView
+ {
+ private readonly IDictionary _dict;
+
+ public IDictionaryDebugView(IDictionary dictionary)
+ {
+ _dict = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public KeyValuePair[] Items
+ {
+ get
+ {
+ KeyValuePair[] items = new KeyValuePair[_dict.Count];
+ _dict.CopyTo(items, 0);
+ return items;
+ }
+ }
+ }
+
+ internal sealed class DictionaryKeyCollectionDebugView
+ {
+ private readonly ICollection _collection;
+
+ public DictionaryKeyCollectionDebugView(ICollection collection)
+ {
+ _collection = collection ?? throw new ArgumentNullException(nameof(collection));
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public TKey[] Items
+ {
+ get
+ {
+ TKey[] items = new TKey[_collection.Count];
+ _collection.CopyTo(items, 0);
+ return items;
+ }
+ }
+ }
+
+ internal sealed class DictionaryValueCollectionDebugView
+ {
+ private readonly ICollection _collection;
+
+ public DictionaryValueCollectionDebugView(ICollection collection)
+ {
+ _collection = collection ?? throw new ArgumentNullException(nameof(collection));
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public TValue[] Items
+ {
+ get
+ {
+ TValue[] items = new TValue[_collection.Count];
+ _collection.CopyTo(items, 0);
+ return items;
+ }
+ }
+ }
+}
diff --git a/OctaneEngine/Collections.Pooled/IReadOnlyPooledList.cs b/OctaneEngine/Collections.Pooled/IReadOnlyPooledList.cs
new file mode 100644
index 0000000..ca94bae
--- /dev/null
+++ b/OctaneEngine/Collections.Pooled/IReadOnlyPooledList.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+
+namespace Collections.Pooled
+{
+ ///
+ /// Represents a read-only collection of pooled elements that can be accessed by index
+ ///
+ /// The type of elements in the read-only pooled list.
+
+ public interface IReadOnlyPooledList : IReadOnlyList
+ {
+ ///
+ /// Gets a for the items currently in the collection.
+ ///
+ ReadOnlySpan Span { get; }
+ }
+}
diff --git a/OctaneEngine/Collections.Pooled/NonRandomizedStringEqualityComparer.cs b/OctaneEngine/Collections.Pooled/NonRandomizedStringEqualityComparer.cs
new file mode 100644
index 0000000..9fa3f3a
--- /dev/null
+++ b/OctaneEngine/Collections.Pooled/NonRandomizedStringEqualityComparer.cs
@@ -0,0 +1,77 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+
+namespace Collections.Pooled
+{
+ ///
+ /// NonRandomizedStringEqualityComparer is the comparer used by default with the PooledDictionary.
+ /// We use NonRandomizedStringEqualityComparer as default comparer as it doesnt use the randomized string hashing which
+ /// keeps the performance not affected till we hit collision threshold and then we switch to the comparer which is using
+ /// randomized string hashing.
+ ///
+ [Serializable] // Required for compatibility with .NET Core 2.0 as we exposed the NonRandomizedStringEqualityComparer inside the serialization blob
+ public sealed class NonRandomizedStringEqualityComparer : EqualityComparer, ISerializable
+ {
+ private static readonly int s_empyStringHashCode = string.Empty.GetHashCode();
+
+ internal static new IEqualityComparer Default { get; } = new NonRandomizedStringEqualityComparer();
+
+ private NonRandomizedStringEqualityComparer() { }
+
+ // This is used by the serialization engine.
+ private NonRandomizedStringEqualityComparer(SerializationInfo information, StreamingContext context) { }
+
+ public sealed override bool Equals(string x, string y) => string.Equals(x, y);
+
+ public sealed override int GetHashCode(string str)
+ => str is null ? 0 : str.Length == 0 ? s_empyStringHashCode : GetNonRandomizedHashCode(str);
+
+ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ info.SetType(typeof(NonRandomizedStringEqualityComparer));
+ }
+
+ // Use this if and only if 'Denial of Service' attacks are not a concern (i.e. never used for free-form user input),
+ // or are otherwise mitigated.
+ // This code was ported from an internal method on String, which relied on private members to get the char* pointer.
+ private static unsafe int GetNonRandomizedHashCode(string str)
+ {
+ ReadOnlySpan chars = str.AsSpan();
+ fixed (char* src = chars)
+ {
+ Debug.Assert(src[chars.Length] == '\0', "src[this.Length] == '\\0'");
+ Debug.Assert(((int)src) % 4 == 0, "Managed string should start at 4 bytes boundary");
+
+ uint hash1 = (5381 << 16) + 5381;
+ uint hash2 = hash1;
+
+ uint* ptr = (uint*)src;
+ int length = chars.Length;
+
+ while (length > 2)
+ {
+ length -= 4;
+ // Where length is 4n-1 (e.g. 3,7,11,15,19) this additionally consumes the null terminator
+ hash1 = (((hash1 << 5) | (hash1 >> 27)) + hash1) ^ ptr[0];
+ hash2 = (((hash2 << 5) | (hash2 >> 27)) + hash2) ^ ptr[1];
+ ptr += 2;
+ }
+
+ if (length > 0)
+ {
+ // Where length is 4n-3 (e.g. 1,5,9,13,17) this additionally consumes the null terminator
+ hash2 = (((hash2 << 5) | (hash2 >> 27)) + hash2) ^ ptr[0];
+ }
+
+ return (int)(hash1 + (hash2 * 1566083941));
+ }
+ }
+ }
+}
diff --git a/OctaneEngine/Collections.Pooled/PooledDictionary.cs b/OctaneEngine/Collections.Pooled/PooledDictionary.cs
new file mode 100644
index 0000000..8a7ef3d
--- /dev/null
+++ b/OctaneEngine/Collections.Pooled/PooledDictionary.cs
@@ -0,0 +1,2029 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Buffers;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Threading;
+
+namespace Collections.Pooled
+{
+ ///
+ /// Used internally to control behavior of insertion into a .
+ ///
+ internal enum InsertionBehavior : byte
+ {
+ ///
+ /// The default insertion behavior.
+ ///
+ None = 0,
+
+ ///
+ /// Specifies that an existing entry with the same key should be overwritten if encountered.
+ ///
+ OverwriteExisting = 1,
+
+ ///
+ /// Specifies that if an existing entry with the same key is encountered, an exception should be thrown.
+ ///
+ ThrowOnExisting = 2
+ }
+
+ ///
+ /// A can support multiple readers concurrently, as long as the collection is not modified.
+ /// Even so, enumerating through a collection is intrinsically not a thread-safe procedure.
+ /// In the rare case where an enumeration contends with write accesses, the collection must be locked during the entire enumeration.
+ /// To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.
+ ///
+ [DebuggerTypeProxy(typeof(IDictionaryDebugView<,>))]
+ [DebuggerDisplay("Count = {Count}")]
+ [Serializable]
+ public class PooledDictionary : IDictionary, IDictionary, IReadOnlyDictionary,
+ ISerializable, IDeserializationCallback, IDisposable
+ {
+ private struct Entry
+ {
+ public int hashCode; // Lower 31 bits of hash code, -1 if unused
+ public int next; // Index of next entry, -1 if last
+ public TKey key; // Key of entry
+ public TValue value; // Value of entry
+ }
+
+ // store lower 31 bits of hash code
+ private const int Lower31BitMask = 0x7FFFFFFF;
+
+ // constants for serialization
+ private const string VersionName = "Version"; // Do not rename (binary serialization)
+ private const string HashSizeName = "HashSize"; // Do not rename (binary serialization). Must save buckets.Length
+ private const string KeyValuePairsName = "KeyValuePairs"; // Do not rename (binary serialization)
+ private const string ComparerName = "Comparer"; // Do not rename (binary serialization)
+ private const string ClearKeyName = "CK"; // Do not rename (binary serialization)
+ private const string ClearValueName = "CV"; // Do not rename (binary serialization)
+
+ private static readonly ArrayPool s_bucketPool = ArrayPool.Shared;
+ private static readonly ArrayPool s_entryPool = ArrayPool.Shared;
+
+ // WARNING:
+ // It's important that the number of buckets be prime, and these arrays could exceed
+ // that size as they come from ArrayPool. Be careful not to index past _size or bad
+ // things will happen.
+ private int[] _buckets;
+ private Entry[] _entries;
+ private int _size;
+
+ private int _count;
+ private int _freeList;
+ private int _freeCount;
+ private int _version;
+ private IEqualityComparer _comparer;
+ private KeyCollection _keys;
+ private ValueCollection _values;
+ private object _syncRoot;
+ private readonly bool _clearKeyOnFree;
+ private readonly bool _clearValueOnFree;
+
+ #region Constructors
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary() : this(0, ClearMode.Auto, null) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(ClearMode clearMode) : this(0, clearMode, null) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(int capacity) : this(capacity, ClearMode.Auto, null) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(int capacity, ClearMode clearMode) : this(capacity, clearMode, null) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(IEqualityComparer comparer) : this(0, ClearMode.Auto, comparer) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(int capacity, IEqualityComparer comparer) : this(capacity, ClearMode.Auto, comparer) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(ClearMode clearMode, IEqualityComparer comparer) : this(0, clearMode, comparer) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(int capacity, ClearMode clearMode, IEqualityComparer comparer)
+ {
+ if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
+ if (capacity > 0) Initialize(capacity);
+ if (comparer != EqualityComparer.Default)
+ {
+ _comparer = comparer;
+ }
+
+ _clearKeyOnFree = ShouldClearKey(clearMode);
+ _clearValueOnFree = ShouldClearValue(clearMode);
+
+ if (typeof(TKey) == typeof(string) && _comparer == null)
+ {
+ // To start, move off default comparer for string which is randomised
+ _comparer = (IEqualityComparer)NonRandomizedStringEqualityComparer.Default;
+ }
+ }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(IDictionary dictionary) : this(dictionary, ClearMode.Auto, null) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(IDictionary dictionary, ClearMode clearMode) : this(dictionary, clearMode, null) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(IDictionary dictionary, IEqualityComparer comparer) : this(dictionary, ClearMode.Auto, comparer) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(IDictionary dictionary, ClearMode clearMode, IEqualityComparer comparer) :
+ this(dictionary?.Count ?? 0, clearMode, comparer)
+ {
+ if (dictionary == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
+
+ // It is likely that the passed-in dictionary is PooledDictionary. When this is the case,
+ // avoid the enumerator allocation and overhead by looping through the entries array directly.
+ // We only do this when dictionary is PooledDictionary and not a subclass, to maintain
+ // back-compat with subclasses that may have overridden the enumerator behavior.
+ if (dictionary is PooledDictionary pooled)
+ {
+ int count = pooled._count;
+ var entries = pooled._entries;
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0)
+ {
+ TryInsert(entries[i].key, entries[i].value, InsertionBehavior.ThrowOnExisting);
+ }
+ }
+ return;
+ }
+
+ foreach (var pair in dictionary)
+ {
+ TryInsert(pair.Key, pair.Value, InsertionBehavior.ThrowOnExisting);
+ }
+ }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(IEnumerable> collection)
+ : this(collection, ClearMode.Auto, null) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(IEnumerable> collection, ClearMode clearMode)
+ : this(collection, clearMode, null) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(IEnumerable> collection, IEqualityComparer comparer)
+ : this(collection, ClearMode.Auto, comparer) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(IEnumerable> collection, ClearMode clearMode, IEqualityComparer comparer) :
+ this((collection as ICollection>)?.Count ?? 0, clearMode, comparer)
+ {
+ if (collection == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
+
+ foreach (var pair in collection)
+ {
+ TryInsert(pair.Key, pair.Value, InsertionBehavior.ThrowOnExisting);
+ }
+ }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(IEnumerable<(TKey key, TValue value)> collection)
+ : this(collection, ClearMode.Auto, null) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(IEnumerable<(TKey key, TValue value)> collection, ClearMode clearMode)
+ : this(collection, clearMode, null) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(IEnumerable<(TKey key, TValue value)> collection, IEqualityComparer comparer)
+ : this(collection, ClearMode.Auto, comparer) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(IEnumerable<(TKey key, TValue value)> collection, ClearMode clearMode, IEqualityComparer comparer)
+ : this((collection as ICollection<(TKey, TValue)>)?.Count ?? 0, clearMode, comparer)
+ {
+ if (collection == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection);
+
+ foreach (var (key, value) in collection)
+ {
+ TryInsert(key, value, InsertionBehavior.ThrowOnExisting);
+ }
+ }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary((TKey key, TValue value)[] array)
+ : this(array.AsSpan(), ClearMode.Auto, null) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary((TKey key, TValue value)[] array, ClearMode clearMode)
+ : this(array.AsSpan(), clearMode, null) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary((TKey key, TValue value)[] array, IEqualityComparer comparer)
+ : this(array.AsSpan(), ClearMode.Auto, comparer) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary((TKey key, TValue value)[] array, ClearMode clearMode, IEqualityComparer comparer)
+ : this(array.AsSpan(), clearMode, comparer) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(ReadOnlySpan<(TKey key, TValue value)> span)
+ : this(span, ClearMode.Auto, null) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(ReadOnlySpan<(TKey key, TValue value)> span, ClearMode clearMode)
+ : this(span, clearMode, null) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(ReadOnlySpan<(TKey key, TValue value)> span, IEqualityComparer comparer)
+ : this(span, ClearMode.Auto, comparer) { }
+
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ public PooledDictionary(ReadOnlySpan<(TKey key, TValue value)> span, ClearMode clearMode, IEqualityComparer comparer)
+ : this(span.Length, clearMode, comparer)
+ {
+ foreach (var (key, value) in span)
+ {
+ TryInsert(key, value, InsertionBehavior.ThrowOnExisting);
+ }
+ }
+
+#pragma warning disable IDE0060 // Remove unused parameter
+ ///
+ /// Creates a new instance of PooledDictionary.
+ ///
+ protected PooledDictionary(SerializationInfo info, StreamingContext context)
+#pragma warning restore IDE0060
+ {
+ _clearKeyOnFree = (bool?)info.GetValue(ClearKeyName, typeof(bool)) ?? ShouldClearKey(ClearMode.Auto);
+ _clearValueOnFree = (bool?)info.GetValue(ClearValueName, typeof(bool)) ?? ShouldClearValue(ClearMode.Auto);
+
+ // We can't do anything with the keys and values until the entire graph has been deserialized
+ // and we have a resonable estimate that GetHashCode is not going to fail. For the time being,
+ // we'll just cache this. The graph is not valid until OnDeserialization has been called.
+ HashHelpers.SerializationInfoTable.Add(this, info);
+ }
+
+#endregion
+
+ ///
+ /// The used to compare keys in this dictionary.
+ ///
+ public IEqualityComparer Comparer
+ {
+ get
+ {
+ return (_comparer == null || _comparer is NonRandomizedStringEqualityComparer)
+ ? EqualityComparer.Default : _comparer;
+ }
+ }
+
+ ///
+ /// The number of items in the dictionary.
+ ///
+ public int Count => _count - _freeCount;
+
+ ///
+ /// Returns the ClearMode behavior for the collection, denoting whether values are
+ /// cleared from internal arrays before returning them to the pool.
+ ///
+ public ClearMode KeyClearMode => _clearKeyOnFree ? ClearMode.Always : ClearMode.Never;
+
+ ///
+ /// Returns the ClearMode behavior for the collection, denoting whether values are
+ /// cleared from internal arrays before returning them to the pool.
+ ///
+ public ClearMode ValueClearMode => _clearValueOnFree ? ClearMode.Always : ClearMode.Never;
+
+ ///
+ /// The keys in this dictionary.
+ ///
+ public KeyCollection Keys
+ {
+ get
+ {
+ if (_keys == null) _keys = new KeyCollection(this);
+ return _keys;
+ }
+ }
+
+ ICollection IDictionary.Keys
+ {
+ get
+ {
+ if (_keys == null) _keys = new KeyCollection(this);
+ return _keys;
+ }
+ }
+
+ IEnumerable IReadOnlyDictionary.Keys
+ {
+ get
+ {
+ if (_keys == null) _keys = new KeyCollection(this);
+ return _keys;
+ }
+ }
+
+ ///
+ /// The values in this dictionary.
+ ///
+ public ValueCollection Values
+ {
+ get
+ {
+ if (_values == null) _values = new ValueCollection(this);
+ return _values;
+ }
+ }
+
+ ICollection IDictionary.Values
+ {
+ get
+ {
+ if (_values == null) _values = new ValueCollection(this);
+ return _values;
+ }
+ }
+
+ IEnumerable IReadOnlyDictionary.Values
+ {
+ get
+ {
+ if (_values == null) _values = new ValueCollection(this);
+ return _values;
+ }
+ }
+
+ ///
+ /// Gets or sets an item in the dictionary by key.
+ ///
+ public TValue this[TKey key]
+ {
+ get
+ {
+ int i = FindEntry(key);
+ if (i >= 0) return _entries[i].value;
+ ThrowHelper.ThrowKeyNotFoundException(key);
+ return default;
+ }
+ set
+ {
+ bool modified = TryInsert(key, value, InsertionBehavior.OverwriteExisting);
+ Debug.Assert(modified);
+ }
+ }
+
+ ///
+ /// Adds a key/value pair to the dictionary.
+ ///
+ public void Add(TKey key, TValue value)
+ {
+ bool modified = TryInsert(key, value, InsertionBehavior.ThrowOnExisting);
+ Debug.Assert(modified); // If there was an existing key and the Add failed, an exception will already have been thrown.
+ }
+
+ public void AddRange(IEnumerable> enumerable)
+ {
+ if (enumerable is null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.enumerable);
+
+ if (enumerable is ICollection> collection)
+ EnsureCapacity(_count + collection.Count);
+
+ foreach (var pair in enumerable)
+ {
+ TryInsert(pair.Key, pair.Value, InsertionBehavior.ThrowOnExisting);
+ }
+ }
+
+ public void AddRange(IEnumerable<(TKey key, TValue value)> enumerable)
+ {
+ if (enumerable is null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.enumerable);
+
+ if (enumerable is ICollection> collection)
+ EnsureCapacity(_count + collection.Count);
+
+ foreach (var (key, value) in enumerable)
+ {
+ TryInsert(key, value, InsertionBehavior.ThrowOnExisting);
+ }
+ }
+
+ public void AddRange(ReadOnlySpan<(TKey key, TValue value)> span)
+ {
+ EnsureCapacity(_count + span.Length);
+
+ foreach (var (key, value) in span)
+ {
+ TryInsert(key, value, InsertionBehavior.ThrowOnExisting);
+ }
+ }
+
+ public void AddRange((TKey key, TValue value)[] array)
+ => AddRange(array.AsSpan());
+
+ public void AddOrUpdate(TKey key, TValue addValue, Func updater)
+ {
+ if (TryGetValue(key, out TValue value))
+ {
+ var updatedValue = updater(key, value);
+ TryInsert(key, updatedValue, InsertionBehavior.OverwriteExisting);
+ }
+ else
+ {
+ TryInsert(key, addValue, InsertionBehavior.ThrowOnExisting);
+ }
+ }
+
+ public void AddOrUpdate(TKey key, Func addValueFactory, Func updater)
+ {
+ if (TryGetValue(key, out TValue value))
+ {
+ var updatedValue = updater(key, value);
+ TryInsert(key, updatedValue, InsertionBehavior.OverwriteExisting);
+ }
+ else
+ {
+ var addValue = addValueFactory(key);
+ TryInsert(key, addValue, InsertionBehavior.ThrowOnExisting);
+ }
+ }
+
+ void ICollection>.Add(KeyValuePair keyValuePair)
+ => Add(keyValuePair.Key, keyValuePair.Value);
+
+ bool ICollection>.Contains(KeyValuePair keyValuePair)
+ {
+ int i = FindEntry(keyValuePair.Key);
+ if (i >= 0 && EqualityComparer.Default.Equals(_entries[i].value, keyValuePair.Value))
+ {
+ return true;
+ }
+ return false;
+ }
+
+ bool ICollection>.Remove(KeyValuePair keyValuePair)
+ {
+ int i = FindEntry(keyValuePair.Key);
+ if (i >= 0 && EqualityComparer.Default.Equals(_entries[i].value, keyValuePair.Value))
+ {
+ Remove(keyValuePair.Key);
+ return true;
+ }
+ return false;
+ }
+
+ public void Clear()
+ {
+ int count = _count;
+ if (count > 0)
+ {
+ Array.Clear(_buckets, 0, _size);
+
+ _count = 0;
+ _freeList = -1;
+ _freeCount = 0;
+ _size = 0;
+ Array.Clear(_entries, 0, count);
+ _version++;
+ }
+ }
+
+ public bool ContainsKey(TKey key)
+ => FindEntry(key) >= 0;
+
+ public bool ContainsValue(TValue value)
+ {
+ var entries = _entries;
+ if (value == null)
+ {
+ for (int i = 0; i < _count; i++)
+ {
+ if (entries[i].hashCode >= 0 && entries[i].value == null) return true;
+ }
+ }
+ else
+ {
+ if (default(TValue) != null)
+ {
+ // ValueType: Devirtualize with EqualityComparer.Default intrinsic
+ for (int i = 0; i < _count; i++)
+ {
+ if (entries[i].hashCode >= 0 && EqualityComparer.Default.Equals(entries[i].value, value)) return true;
+ }
+ }
+ else
+ {
+ // Object type: Shared Generic, EqualityComparer.Default won't devirtualize
+ // https://github.com/dotnet/coreclr/issues/17273
+ // So cache in a local rather than get EqualityComparer per loop iteration
+ var defaultComparer = EqualityComparer.Default;
+ for (int i = 0; i < _count; i++)
+ {
+ if (entries[i].hashCode >= 0 && defaultComparer.Equals(entries[i].value, value)) return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private void CopyTo(KeyValuePair[] array, int index)
+ {
+ if (array == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ }
+
+ if ((uint)index > (uint)array.Length)
+ {
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ }
+
+ if (array.Length - index < Count)
+ {
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+ }
+
+ int count = _count;
+ var entries = _entries;
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0)
+ {
+ array[index++] = new KeyValuePair(entries[i].key, entries[i].value);
+ }
+ }
+ }
+
+ public Enumerator GetEnumerator()
+ => new Enumerator(this, Enumerator.KeyValuePair);
+
+ IEnumerator> IEnumerable>.GetEnumerator()
+ => new Enumerator(this, Enumerator.KeyValuePair);
+
+ void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+ => GetObjectData(info, context);
+
+ ///
+ /// Allows child classes to add their own serialization data.
+ ///
+ ///
+ ///
+ protected virtual void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info);
+ }
+
+ info.AddValue(VersionName, _version);
+ info.AddValue(ComparerName, _comparer ?? EqualityComparer.Default, typeof(IEqualityComparer));
+ info.AddValue(HashSizeName, _size); // This is the length of the bucket array
+ info.AddValue(ClearKeyName, _clearKeyOnFree);
+ info.AddValue(ClearValueName, _clearValueOnFree);
+
+ if (_buckets != null)
+ {
+ var array = new KeyValuePair[Count];
+ CopyTo(array, 0);
+ info.AddValue(KeyValuePairsName, array, typeof(KeyValuePair[]));
+ }
+ }
+
+ private int FindEntry(TKey key)
+ {
+ if (key == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ }
+
+ int i = -1;
+ int length = _size;
+ if (length <= 0)
+ return i;
+
+ var buckets = _buckets;
+ var entries = _entries;
+ int collisionCount = 0;
+ IEqualityComparer comparer = _comparer;
+
+ if (comparer == null)
+ {
+ int hashCode = key.GetHashCode() & Lower31BitMask;
+ // Value in _buckets is 1-based
+ i = buckets[hashCode % length] - 1;
+ if (default(TKey) != null)
+ {
+ // ValueType: Devirtualize with EqualityComparer.Default intrinsic
+ do
+ {
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
+ // Test in if to drop range check for following array access
+ if ((uint)i >= (uint)length || (entries[i].hashCode == hashCode && EqualityComparer.Default.Equals(entries[i].key, key)))
+ {
+ break;
+ }
+
+ i = entries[i].next;
+ if (collisionCount >= length)
+ {
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ }
+ collisionCount++;
+ } while (true);
+ }
+ else
+ {
+ // Object type: Shared Generic, EqualityComparer.Default won't devirtualize
+ // https://github.com/dotnet/coreclr/issues/17273
+ // So cache in a local rather than get EqualityComparer per loop iteration
+ var defaultComparer = EqualityComparer.Default;
+ do
+ {
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
+ // Test in if to drop range check for following array access
+ if ((uint)i >= (uint)length || (entries[i].hashCode == hashCode && defaultComparer.Equals(entries[i].key, key)))
+ {
+ break;
+ }
+
+ i = entries[i].next;
+ if (collisionCount >= length)
+ {
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ }
+ collisionCount++;
+ } while (true);
+ }
+ }
+ else
+ {
+ int hashCode = comparer.GetHashCode(key) & Lower31BitMask;
+ // Value in _buckets is 1-based
+ i = buckets[hashCode % length] - 1;
+ do
+ {
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
+ // Test in if to drop range check for following array access
+ if ((uint)i >= (uint)length ||
+ (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)))
+ {
+ break;
+ }
+
+ i = entries[i].next;
+ if (collisionCount >= length)
+ {
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ }
+ collisionCount++;
+ } while (true);
+ }
+
+ return i;
+ }
+
+ private int Initialize(int capacity)
+ {
+ _size = HashHelpers.GetPrime(capacity);
+ _freeList = -1;
+ _buckets = s_bucketPool.Rent(_size);
+ Array.Clear(_buckets, 0, _buckets.Length);
+ _entries = s_entryPool.Rent(_size);
+
+ return _size;
+ }
+
+ private bool TryInsert(TKey key, TValue value, InsertionBehavior behavior)
+ {
+ if (key == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ }
+
+ if (_buckets == null || _size == 0)
+ {
+ Initialize(0);
+ }
+
+ var entries = _entries;
+ var comparer = _comparer;
+ var size = _size;
+
+ int hashCode = ((comparer == null) ? key.GetHashCode() : comparer.GetHashCode(key)) & Lower31BitMask;
+
+ int collisionCount = 0;
+ ref int bucket = ref _buckets[hashCode % size];
+ // Value in _buckets is 1-based
+ int i = bucket - 1;
+
+ if (comparer == null)
+ {
+ if (default(TKey) != null)
+ {
+ // ValueType: Devirtualize with EqualityComparer.Default intrinsic
+ do
+ {
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
+ // Test uint in if rather than loop condition to drop range check for following array access
+ if ((uint)i >= (uint)size)
+ {
+ break;
+ }
+
+ if (entries[i].hashCode == hashCode && EqualityComparer.Default.Equals(entries[i].key, key))
+ {
+ if (behavior == InsertionBehavior.OverwriteExisting)
+ {
+ entries[i].value = value;
+ _version++;
+ return true;
+ }
+
+ if (behavior == InsertionBehavior.ThrowOnExisting)
+ {
+ ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
+ }
+
+ return false;
+ }
+
+ i = entries[i].next;
+ if (collisionCount >= size)
+ {
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ }
+ collisionCount++;
+ } while (true);
+ }
+ else
+ {
+ // Object type: Shared Generic, EqualityComparer.Default won't devirtualize
+ // https://github.com/dotnet/coreclr/issues/17273
+ // So cache in a local rather than get EqualityComparer per loop iteration
+ var defaultComparer = EqualityComparer.Default;
+ do
+ {
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
+ // Test uint in if rather than loop condition to drop range check for following array access
+ if ((uint)i >= (uint)size)
+ {
+ break;
+ }
+
+ if (entries[i].hashCode == hashCode && defaultComparer.Equals(entries[i].key, key))
+ {
+ if (behavior == InsertionBehavior.OverwriteExisting)
+ {
+ entries[i].value = value;
+ _version++;
+ return true;
+ }
+
+ if (behavior == InsertionBehavior.ThrowOnExisting)
+ {
+ ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
+ }
+
+ return false;
+ }
+
+ i = entries[i].next;
+ if (collisionCount >= size)
+ {
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ }
+ collisionCount++;
+ } while (true);
+ }
+ }
+ else
+ {
+ do
+ {
+ // Should be a while loop https://github.com/dotnet/coreclr/issues/15476
+ // Test uint in if rather than loop condition to drop range check for following array access
+ if ((uint)i >= (uint)size)
+ {
+ break;
+ }
+
+ if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key))
+ {
+ if (behavior == InsertionBehavior.OverwriteExisting)
+ {
+ entries[i].value = value;
+ _version++;
+ return true;
+ }
+
+ if (behavior == InsertionBehavior.ThrowOnExisting)
+ {
+ ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
+ }
+
+ return false;
+ }
+
+ i = entries[i].next;
+ if (collisionCount >= size)
+ {
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ }
+ collisionCount++;
+ } while (true);
+
+ }
+
+ bool updateFreeList = false;
+ int index;
+ if (_freeCount > 0)
+ {
+ index = _freeList;
+ updateFreeList = true;
+ _freeCount--;
+ }
+ else
+ {
+ int count = _count;
+ if (count == size)
+ {
+ Resize();
+ size = _size;
+ bucket = ref _buckets[hashCode % size];
+ }
+ index = count;
+ _count = count + 1;
+ entries = _entries;
+ }
+
+ ref Entry entry = ref entries[index];
+
+ if (updateFreeList)
+ {
+ _freeList = entry.next;
+ }
+ entry.hashCode = hashCode;
+ // Value in _buckets is 1-based
+ entry.next = bucket - 1;
+ entry.key = key;
+ entry.value = value;
+ // Value in _buckets is 1-based
+#pragma warning disable IDE0059 // Value assigned to symbol is never used
+ bucket = index + 1;
+#pragma warning restore IDE0059
+ _version++;
+
+ // Value types never rehash
+ if (default(TKey) == null && collisionCount > HashHelpers.HashCollisionThreshold && comparer is NonRandomizedStringEqualityComparer)
+ {
+ // If we hit the collision threshold we'll need to switch to the comparer which is using randomized string hashing
+ // i.e. EqualityComparer.Default.
+ _comparer = null;
+ Resize(size, true);
+ }
+
+ return true;
+ }
+
+ public virtual void OnDeserialization(object sender)
+ {
+ HashHelpers.SerializationInfoTable.TryGetValue(this, out SerializationInfo siInfo);
+
+ if (siInfo == null)
+ {
+ // We can return immediately if this function is called twice.
+ // Note we remove the serialization info from the table at the end of this method.
+ return;
+ }
+
+ int realVersion = siInfo.GetInt32(VersionName);
+ int hashsize = siInfo.GetInt32(HashSizeName);
+ _comparer = (IEqualityComparer)siInfo.GetValue(ComparerName, typeof(IEqualityComparer));
+
+ if (hashsize != 0)
+ {
+ Initialize(hashsize);
+
+ var array = (KeyValuePair[])
+ siInfo.GetValue(KeyValuePairsName, typeof(KeyValuePair[]));
+
+ if (array == null)
+ {
+ throw new SerializationException("Serialized PooledDictionary missing data.");
+ }
+
+ for (int i = 0; i < array.Length; i++)
+ {
+ if (array[i].Key == null)
+ {
+ throw new SerializationException("Serialized PooledDictionary had null key.");
+ }
+ Add(array[i].Key, array[i].Value);
+ }
+ }
+ else
+ {
+ _buckets = null;
+ }
+
+ _version = realVersion;
+ HashHelpers.SerializationInfoTable.Remove(this);
+ }
+
+ private void Resize()
+ => Resize(HashHelpers.ExpandPrime(_count), false);
+
+ private void Resize(int newSize, bool forceNewHashCodes)
+ {
+ // Value types never rehash
+ Debug.Assert(!forceNewHashCodes || default(TKey) == null);
+ Debug.Assert(newSize >= _size);
+
+ int[] buckets;
+ Entry[] entries;
+ bool replaceArrays;
+ int count = _count;
+
+ // Because ArrayPool might give us larger arrays than we asked for, see if we can
+ // use the existing capacity without actually resizing.
+ if (_buckets.Length >= newSize && _entries.Length >= newSize)
+ {
+ Array.Clear(_buckets, 0, _buckets.Length);
+ Array.Clear(_entries, _size, newSize - _size);
+ buckets = _buckets;
+ entries = _entries;
+ replaceArrays = false;
+ }
+ else
+ {
+ buckets = s_bucketPool.Rent(newSize);
+ entries = s_entryPool.Rent(newSize);
+
+ Array.Clear(buckets, 0, buckets.Length);
+ Array.Copy(_entries, 0, entries, 0, count);
+ replaceArrays = true;
+ }
+
+ if (default(TKey) == null && forceNewHashCodes)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0)
+ {
+ Debug.Assert(_comparer == null);
+ entries[i].hashCode = (entries[i].key.GetHashCode() & Lower31BitMask);
+ }
+ }
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0)
+ {
+ int bucket = entries[i].hashCode % newSize;
+ // Value in _buckets is 1-based
+ entries[i].next = buckets[bucket] - 1;
+ // Value in _buckets is 1-based
+ buckets[bucket] = i + 1;
+ }
+ }
+
+ if (replaceArrays)
+ {
+ ReturnArrays();
+ _buckets = buckets;
+ _entries = entries;
+ }
+ _size = newSize;
+ }
+
+ // The overload Remove(TKey key, out TValue value) is a copy of this method with one additional
+ // statement to copy the value for entry being removed into the output parameter.
+ // Code has been intentionally duplicated for performance reasons.
+ public bool Remove(TKey key)
+ {
+ if (key == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ }
+
+ var buckets = _buckets;
+ var entries = _entries;
+ int collisionCount = 0;
+ if (_size > 0)
+ {
+ int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & Lower31BitMask;
+ int bucket = hashCode % _size;
+ int last = -1;
+ // Value in buckets is 1-based
+ int i = buckets[bucket] - 1;
+ while (i >= 0)
+ {
+ ref Entry entry = ref entries[i];
+
+ if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer.Default.Equals(entry.key, key)))
+ {
+ if (last < 0)
+ {
+ // Value in buckets is 1-based
+ buckets[bucket] = entry.next + 1;
+ }
+ else
+ {
+ entries[last].next = entry.next;
+ }
+ entry.hashCode = -1;
+ entry.next = _freeList;
+
+ if (_clearKeyOnFree)
+ entry.key = default;
+ if (_clearValueOnFree)
+ entry.value = default;
+
+ _freeList = i;
+ _freeCount++;
+ _version++;
+ return true;
+ }
+
+ last = i;
+ i = entry.next;
+ if (collisionCount >= _size)
+ {
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ }
+ collisionCount++;
+ }
+ }
+ return false;
+ }
+
+ // This overload is a copy of the overload Remove(TKey key) with one additional
+ // statement to copy the value for entry being removed into the output parameter.
+ // Code has been intentionally duplicated for performance reasons.
+ public bool Remove(TKey key, out TValue value)
+ {
+ if (key == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ }
+
+ var buckets = _buckets;
+ var entries = _entries;
+ int collisionCount = 0;
+ int hashCode = (_comparer?.GetHashCode(key) ?? key.GetHashCode()) & Lower31BitMask;
+ int bucket = hashCode % _size;
+ int last = -1;
+ // Value in buckets is 1-based
+ int i = buckets[bucket] - 1;
+ while (i >= 0)
+ {
+ ref Entry entry = ref entries[i];
+
+ if (entry.hashCode == hashCode && (_comparer?.Equals(entry.key, key) ?? EqualityComparer.Default.Equals(entry.key, key)))
+ {
+ if (last < 0)
+ {
+ // Value in buckets is 1-based
+ buckets[bucket] = entry.next + 1;
+ }
+ else
+ {
+ entries[last].next = entry.next;
+ }
+
+ value = entry.value;
+
+ entry.hashCode = -1;
+ entry.next = _freeList;
+
+ if (_clearKeyOnFree)
+ entry.key = default;
+ if (_clearValueOnFree)
+ entry.value = default;
+
+ _freeList = i;
+ _freeCount++;
+ return true;
+ }
+
+ last = i;
+ i = entry.next;
+ if (collisionCount >= _size)
+ {
+ // The chain of entries forms a loop; which means a concurrent update has happened.
+ // Break out of the loop and throw, rather than looping forever.
+ ThrowHelper.ThrowInvalidOperationException_ConcurrentOperationsNotSupported();
+ }
+ collisionCount++;
+ }
+ value = default;
+ return false;
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ int i = FindEntry(key);
+ if (i >= 0)
+ {
+ value = _entries[i].value;
+ return true;
+ }
+ value = default;
+ return false;
+ }
+
+ public bool TryAdd(TKey key, TValue value)
+ => TryInsert(key, value, InsertionBehavior.None);
+
+ public TValue GetOrAdd(TKey key, TValue addValue)
+ {
+ if (TryGetValue(key, out TValue value))
+ return value;
+
+ Add(key, addValue);
+ return addValue;
+ }
+
+ public TValue GetOrAdd(TKey key, Func valueFactory)
+ {
+ if (TryGetValue(key, out TValue value))
+ return value;
+
+ var addValue = valueFactory(key);
+ Add(key, addValue);
+ return addValue;
+ }
+
+ bool ICollection>.IsReadOnly => false;
+
+ void ICollection>.CopyTo(KeyValuePair[] array, int index)
+ => CopyTo(array, index);
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ if (array == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ if (array.Rank != 1)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
+ if (array.GetLowerBound(0) != 0)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
+ if ((uint)index > (uint)array.Length)
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ if (array.Length - index < Count)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+
+ if (array is KeyValuePair[] pairs)
+ {
+ CopyTo(pairs, index);
+ }
+ else if (array is DictionaryEntry[] dictEntryArray)
+ {
+ Entry[] entries = _entries;
+ for (int i = 0; i < _count; i++)
+ {
+ if (entries[i].hashCode >= 0)
+ {
+ dictEntryArray[index++] = new DictionaryEntry(entries[i].key, entries[i].value);
+ }
+ }
+ }
+ else if (array is object[] objects)
+ {
+ try
+ {
+ int count = _count;
+ var entries = _entries;
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0)
+ {
+ objects[index++] = new KeyValuePair(entries[i].key, entries[i].value);
+ }
+ }
+ }
+ catch (ArrayTypeMismatchException)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+ }
+ else
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => new Enumerator(this, Enumerator.KeyValuePair);
+
+ ///
+ /// Ensures that the dictionary can hold up to 'capacity' entries without any further expansion of its backing storage
+ ///
+ public int EnsureCapacity(int capacity)
+ {
+ if (capacity < 0)
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity);
+ int currentCapacity = _size;
+ if (currentCapacity >= capacity)
+ return currentCapacity;
+ _version++;
+ if (_buckets == null || _size == 0)
+ return Initialize(capacity);
+ int newSize = HashHelpers.GetPrime(capacity);
+ Resize(newSize, forceNewHashCodes: false);
+ return newSize;
+ }
+
+ ///
+ /// Sets the capacity of this dictionary to what it would be if it had been originally initialized with all its entries
+ ///
+ /// This method can be used to minimize the memory overhead
+ /// once it is known that no new elements will be added.
+ ///
+ /// To allocate minimum size storage array, execute the following statements:
+ ///
+ /// dictionary.Clear();
+ /// dictionary.TrimExcess();
+ ///
+ public void TrimExcess()
+ => TrimExcess(Count);
+
+ ///
+ /// Sets the capacity of this dictionary to hold up 'capacity' entries without any further expansion of its backing storage
+ ///
+ /// This method can be used to minimize the memory overhead
+ /// once it is known that no new elements will be added.
+ ///
+ public void TrimExcess(int capacity)
+ {
+ if (capacity < Count)
+ throw new ArgumentOutOfRangeException(nameof(capacity));
+ int newSize = HashHelpers.GetPrime(capacity);
+
+ Entry[] oldEntries = _entries;
+ int[] oldBuckets = _buckets;
+ int currentCapacity = oldEntries == null ? 0 : oldEntries.Length;
+ if (newSize >= currentCapacity)
+ return;
+
+ int oldCount = _count;
+ _version++;
+ Initialize(newSize);
+ var entries = _entries;
+ var buckets = _buckets;
+ int count = 0;
+ for (int i = 0; i < oldCount; i++)
+ {
+ int hashCode = oldEntries[i].hashCode;
+ if (hashCode >= 0)
+ {
+#pragma warning disable IDE0059 // Value assigned to symbol is never used
+ ref Entry entry = ref entries[count];
+#pragma warning restore IDE0059
+ entry = oldEntries[i];
+ int bucket = hashCode % newSize;
+ // Value in _buckets is 1-based
+ entry.next = buckets[bucket] - 1;
+ // Value in _buckets is 1-based
+ buckets[bucket] = count + 1;
+ count++;
+ }
+ }
+ _count = count;
+ _size = newSize;
+ _freeCount = 0;
+ s_bucketPool.Return(oldBuckets);
+ s_entryPool.Return(entries, clearArray: _clearKeyOnFree || _clearValueOnFree);
+ }
+
+ bool ICollection.IsSynchronized => false;
+
+ object ICollection.SyncRoot
+ {
+ get
+ {
+ if (_syncRoot == null)
+ {
+ Interlocked.CompareExchange(ref _syncRoot, new object(), null);
+ }
+ return _syncRoot;
+ }
+ }
+
+ bool IDictionary.IsFixedSize => false;
+
+ bool IDictionary.IsReadOnly => false;
+
+ ICollection IDictionary.Keys => Keys;
+
+ ICollection IDictionary.Values => Values;
+
+ object IDictionary.this[object key]
+ {
+ get
+ {
+ if (IsCompatibleKey(key))
+ {
+ int i = FindEntry((TKey)key);
+ if (i >= 0)
+ {
+ return _entries[i].value;
+ }
+ }
+ return null;
+ }
+ set
+ {
+ if (key == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ }
+ ThrowHelper.IfNullAndNullsAreIllegalThenThrow(value, ExceptionArgument.value);
+
+ try
+ {
+ TKey tempKey = (TKey)key;
+ try
+ {
+ this[tempKey] = (TValue)value;
+ }
+ catch (InvalidCastException)
+ {
+ ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(TValue));
+ }
+ }
+ catch (InvalidCastException)
+ {
+ ThrowHelper.ThrowWrongKeyTypeArgumentException(key, typeof(TKey));
+ }
+ }
+ }
+
+ private void ReturnArrays()
+ {
+ if (_entries?.Length > 0)
+ {
+ try
+ {
+ s_entryPool.Return(_entries, clearArray: _clearKeyOnFree || _clearValueOnFree);
+ }
+ catch (ArgumentException)
+ {
+ // oh well, the array pool didn't like our array
+ }
+ }
+
+ if (_buckets?.Length > 0)
+ {
+ try
+ {
+ s_bucketPool.Return(_buckets);
+ }
+ catch (ArgumentException)
+ {
+ // shucks
+ }
+ }
+
+ _entries = null;
+ _buckets = null;
+ }
+
+ private static bool ShouldClearKey(ClearMode mode)
+ {
+#if NETCOREAPP2_1
+ return mode == ClearMode.Always
+ || (mode == ClearMode.Auto && RuntimeHelpers.IsReferenceOrContainsReferences());
+#else
+ return mode != ClearMode.Never;
+#endif
+ }
+
+ private static bool ShouldClearValue(ClearMode mode)
+ {
+#if NETCOREAPP2_1
+ return mode == ClearMode.Always
+ || (mode == ClearMode.Auto && RuntimeHelpers.IsReferenceOrContainsReferences());
+#else
+ return mode != ClearMode.Never;
+#endif
+ }
+
+ private static bool IsCompatibleKey(object key)
+ {
+ if (key == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ }
+ return key is TKey;
+ }
+
+ void IDictionary.Add(object key, object value)
+ {
+ if (key == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
+ }
+ ThrowHelper.IfNullAndNullsAreIllegalThenThrow(value, ExceptionArgument.value);
+
+ try
+ {
+ TKey tempKey = (TKey)key;
+
+ try
+ {
+ Add(tempKey, (TValue)value);
+ }
+ catch (InvalidCastException)
+ {
+ ThrowHelper.ThrowWrongValueTypeArgumentException(value, typeof(TValue));
+ }
+ }
+ catch (InvalidCastException)
+ {
+ ThrowHelper.ThrowWrongKeyTypeArgumentException(key, typeof(TKey));
+ }
+ }
+
+ bool IDictionary.Contains(object key)
+ {
+ if (IsCompatibleKey(key))
+ {
+ return ContainsKey((TKey)key);
+ }
+
+ return false;
+ }
+
+ IDictionaryEnumerator IDictionary.GetEnumerator()
+ => new Enumerator(this, Enumerator.DictEntry);
+
+ void IDictionary.Remove(object key)
+ {
+ if (IsCompatibleKey(key))
+ {
+ Remove((TKey)key);
+ }
+ }
+
+ public void Dispose()
+ {
+ ReturnArrays();
+ _count = 0;
+ _size = 0;
+ _freeList = -1;
+ _freeCount = 0;
+ }
+
+ public struct Enumerator : IEnumerator>, IDictionaryEnumerator
+ {
+ private readonly PooledDictionary _dictionary;
+ private readonly int _version;
+ private int _index;
+ private KeyValuePair _current;
+ private readonly int _getEnumeratorRetType; // What should Enumerator.Current return?
+
+ internal const int DictEntry = 1;
+ internal const int KeyValuePair = 2;
+
+ internal Enumerator(PooledDictionary dictionary, int getEnumeratorRetType)
+ {
+ _dictionary = dictionary;
+ _version = dictionary._version;
+ _index = 0;
+ _getEnumeratorRetType = getEnumeratorRetType;
+ _current = new KeyValuePair();
+ }
+
+ public bool MoveNext()
+ {
+ if (_version != _dictionary._version)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+
+ // Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
+ // dictionary.count+1 could be negative if dictionary.count is int.MaxValue
+ while ((uint)_index < (uint)_dictionary._count)
+ {
+ ref Entry entry = ref _dictionary._entries[_index++];
+
+ if (entry.hashCode >= 0)
+ {
+ _current = new KeyValuePair(entry.key, entry.value);
+ return true;
+ }
+ }
+
+ _index = _dictionary._count + 1;
+ _current = new KeyValuePair();
+ return false;
+ }
+
+ public KeyValuePair Current => _current;
+
+ public void Dispose()
+ {
+ }
+
+ object IEnumerator.Current
+ {
+ get
+ {
+ if (_index == 0 || (_index == _dictionary._count + 1))
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+ }
+
+ if (_getEnumeratorRetType == DictEntry)
+ {
+ return new DictionaryEntry(_current.Key, _current.Value);
+ }
+ else
+ {
+ return new KeyValuePair(_current.Key, _current.Value);
+ }
+ }
+ }
+
+ void IEnumerator.Reset()
+ {
+ if (_version != _dictionary._version)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+
+ _index = 0;
+ _current = new KeyValuePair();
+ }
+
+ DictionaryEntry IDictionaryEnumerator.Entry
+ {
+ get
+ {
+ if (_index == 0 || (_index == _dictionary._count + 1))
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+ }
+
+ return new DictionaryEntry(_current.Key, _current.Value);
+ }
+ }
+
+ object IDictionaryEnumerator.Key
+ {
+ get
+ {
+ if (_index == 0 || (_index == _dictionary._count + 1))
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+ }
+
+ return _current.Key;
+ }
+ }
+
+ object IDictionaryEnumerator.Value
+ {
+ get
+ {
+ if (_index == 0 || (_index == _dictionary._count + 1))
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+ }
+
+ return _current.Value;
+ }
+ }
+ }
+
+ [DebuggerTypeProxy(typeof(DictionaryKeyCollectionDebugView<,>))]
+ [DebuggerDisplay("Count = {Count}")]
+ public sealed class KeyCollection : ICollection, ICollection, IReadOnlyCollection
+ {
+ private readonly PooledDictionary _dictionary;
+
+ public KeyCollection(PooledDictionary dictionary)
+ {
+ _dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary));
+ }
+
+ public Enumerator GetEnumerator()
+ => new Enumerator(_dictionary);
+
+ public void CopyTo(TKey[] array, int index)
+ {
+ if (array == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+
+ if (index < 0 || index > array.Length)
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+
+ if (array.Length - index < _dictionary.Count)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+
+ int count = _dictionary._count;
+ var entries = _dictionary._entries;
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0) array[index++] = entries[i].key;
+ }
+ }
+
+ public int Count => _dictionary.Count;
+
+ bool ICollection.IsReadOnly => true;
+
+ void ICollection.Add(TKey item)
+ => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
+
+ void ICollection.Clear()
+ => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
+
+ bool ICollection.Contains(TKey item)
+ => _dictionary.ContainsKey(item);
+
+ bool ICollection.Remove(TKey item)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_KeyCollectionSet);
+ return false;
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => new Enumerator(_dictionary);
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => new Enumerator(_dictionary);
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ if (array == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ if (array.Rank != 1)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
+ if (array.GetLowerBound(0) != 0)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
+ if ((uint)index > (uint)array.Length)
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ if (array.Length - index < _dictionary.Count)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+
+ if (array is TKey[] keys)
+ {
+ CopyTo(keys, index);
+ }
+ else
+ {
+ object[] objects = array as object[];
+ if (objects == null)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+
+ int count = _dictionary._count;
+ var entries = _dictionary._entries;
+ try
+ {
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0) objects[index++] = entries[i].key;
+ }
+ }
+ catch (ArrayTypeMismatchException)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+ }
+ }
+
+ bool ICollection.IsSynchronized => false;
+
+ object ICollection.SyncRoot => ((ICollection)_dictionary).SyncRoot;
+
+ public struct Enumerator : IEnumerator, IEnumerator
+ {
+ private readonly PooledDictionary _dictionary;
+ private int _index;
+ private readonly int _version;
+ private TKey _currentKey;
+
+ internal Enumerator(PooledDictionary dictionary)
+ {
+ _dictionary = dictionary;
+ _version = dictionary._version;
+ _index = 0;
+ _currentKey = default;
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public bool MoveNext()
+ {
+ if (_version != _dictionary._version)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+
+ while ((uint)_index < (uint)_dictionary._count)
+ {
+ ref Entry entry = ref _dictionary._entries[_index++];
+
+ if (entry.hashCode >= 0)
+ {
+ _currentKey = entry.key;
+ return true;
+ }
+ }
+
+ _index = _dictionary._count + 1;
+ _currentKey = default;
+ return false;
+ }
+
+ public TKey Current => _currentKey;
+
+ object IEnumerator.Current
+ {
+ get
+ {
+ if (_index == 0 || (_index == _dictionary._count + 1))
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+ }
+
+ return _currentKey;
+ }
+ }
+
+ void IEnumerator.Reset()
+ {
+ if (_version != _dictionary._version)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+
+ _index = 0;
+ _currentKey = default;
+ }
+ }
+ }
+
+ [DebuggerTypeProxy(typeof(DictionaryValueCollectionDebugView<,>))]
+ [DebuggerDisplay("Count = {Count}")]
+ public sealed class ValueCollection : ICollection, ICollection, IReadOnlyCollection
+ {
+ private readonly PooledDictionary _dictionary;
+
+ public ValueCollection(PooledDictionary dictionary)
+ {
+ if (dictionary == null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.dictionary);
+ }
+ _dictionary = dictionary;
+ }
+
+ public Enumerator GetEnumerator()
+ => new Enumerator(_dictionary);
+
+ public void CopyTo(TValue[] array, int index)
+ {
+ if (array == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+
+ if (index < 0 || index > array.Length)
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+
+ if (array.Length - index < _dictionary.Count)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+
+ int count = _dictionary._count;
+ var entries = _dictionary._entries;
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0) array[index++] = entries[i].value;
+ }
+ }
+
+ public int Count => _dictionary.Count;
+
+ bool ICollection.IsReadOnly => true;
+
+ void ICollection.Add(TValue item)
+ => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
+
+ bool ICollection.Remove(TValue item)
+ {
+ ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
+ return false;
+ }
+
+ void ICollection.Clear()
+ => ThrowHelper.ThrowNotSupportedException(ExceptionResource.NotSupported_ValueCollectionSet);
+
+ bool ICollection.Contains(TValue item)
+ => _dictionary.ContainsValue(item);
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => new Enumerator(_dictionary);
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => new Enumerator(_dictionary);
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ if (array == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ if (array.Rank != 1)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported);
+ if (array.GetLowerBound(0) != 0)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound);
+ if ((uint)index > (uint)array.Length)
+ ThrowHelper.ThrowIndexArgumentOutOfRange_NeedNonNegNumException();
+ if (array.Length - index < _dictionary.Count)
+ ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall);
+
+ if (array is TValue[] values)
+ {
+ CopyTo(values, index);
+ }
+ else if (array is object[] objects)
+ {
+ int count = _dictionary._count;
+ var entries = _dictionary._entries;
+ try
+ {
+ for (int i = 0; i < count; i++)
+ {
+ if (entries[i].hashCode >= 0) objects[index++] = entries[i].value;
+ }
+ }
+ catch (ArrayTypeMismatchException)
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+ }
+ else
+ {
+ ThrowHelper.ThrowArgumentException_Argument_InvalidArrayType();
+ }
+ }
+
+ bool ICollection.IsSynchronized => false;
+
+ object ICollection.SyncRoot => ((ICollection)_dictionary).SyncRoot;
+
+ public struct Enumerator : IEnumerator, IEnumerator
+ {
+ private readonly PooledDictionary _dictionary;
+ private int _index;
+ private readonly int _version;
+ private TValue _currentValue;
+
+ internal Enumerator(PooledDictionary dictionary)
+ {
+ _dictionary = dictionary;
+ _version = dictionary._version;
+ _index = 0;
+ _currentValue = default;
+ }
+
+ public void Dispose()
+ {
+ }
+
+ public bool MoveNext()
+ {
+ if (_version != _dictionary._version)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+
+ while ((uint)_index < (uint)_dictionary._count)
+ {
+ ref Entry entry = ref _dictionary._entries[_index++];
+
+ if (entry.hashCode >= 0)
+ {
+ _currentValue = entry.value;
+ return true;
+ }
+ }
+ _index = _dictionary._count + 1;
+ _currentValue = default;
+ return false;
+ }
+
+ public TValue Current => _currentValue;
+
+ object IEnumerator.Current
+ {
+ get
+ {
+ if (_index == 0 || (_index == _dictionary._count + 1))
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+ }
+
+ return _currentValue;
+ }
+ }
+
+ void IEnumerator.Reset()
+ {
+ if (_version != _dictionary._version)
+ {
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+ }
+ _index = 0;
+ _currentValue = default;
+ }
+ }
+ }
+ }
+}
diff --git a/OctaneEngine/Collections.Pooled/PooledExtensions.cs b/OctaneEngine/Collections.Pooled/PooledExtensions.cs
new file mode 100644
index 0000000..f7379f0
--- /dev/null
+++ b/OctaneEngine/Collections.Pooled/PooledExtensions.cs
@@ -0,0 +1,335 @@
+using System;
+using System.Collections.Generic;
+
+namespace Collections.Pooled
+{
+ ///
+ /// Extension methods for creating pooled collections.
+ ///
+ public static class PooledExtensions
+ {
+ #region PooledList
+
+ public static PooledList ToPooledList(this IEnumerable items)
+ => new PooledList(items);
+
+ public static PooledList ToPooledList(this IEnumerable items, int suggestCapacity)
+ => new PooledList(items, suggestCapacity);
+
+ public static PooledList ToPooledList(this T[] array)
+ => new PooledList(array.AsSpan());
+
+ public static PooledList ToPooledList(this ReadOnlySpan span)
+ => new PooledList(span);
+
+ public static PooledList ToPooledList(this Span span)
+ => new PooledList(span);
+
+ public static PooledList ToPooledList(this ReadOnlyMemory memory)
+ => new PooledList(memory.Span);
+
+ public static PooledList ToPooledList(this Memory memory)
+ => new PooledList(memory.Span);
+
+ #endregion
+
+ #region PooledDictionary
+
+ ///
+ /// Creates a from an according to specified
+ /// key selector and element selector functions, as well as a comparer.
+ ///
+ public static PooledDictionary ToPooledDictionary(
+ this IEnumerable source,
+ Func keySelector, Func valueSelector,
+ IEqualityComparer comparer = null)
+ {
+ if (source == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
+ var dict = new PooledDictionary((source as ICollection)?.Count ?? 0, comparer);
+ foreach (var item in source)
+ {
+ dict.Add(keySelector(item), valueSelector(item));
+ }
+
+ return dict;
+ }
+
+ ///
+ /// Creates a from a according to specified
+ /// key selector and element selector functions, as well as a comparer.
+ ///
+ public static PooledDictionary ToPooledDictionary(
+ this ReadOnlySpan source,
+ Func keySelector, Func valueSelector,
+ IEqualityComparer comparer = null)
+ {
+ var dict = new PooledDictionary(source.Length, comparer);
+ foreach (var item in source)
+ {
+ dict.Add(keySelector(item), valueSelector(item));
+ }
+
+ return dict;
+ }
+
+ ///
+ /// Creates a from a according to specified
+ /// key selector and element selector functions, as well as a comparer.
+ ///
+ public static PooledDictionary ToPooledDictionary(
+ this Span source,
+ Func keySelector, Func valueSelector, IEqualityComparer comparer)
+ {
+ return ToPooledDictionary((ReadOnlySpan)source, keySelector, valueSelector, comparer);
+ }
+
+ ///
+ /// Creates a from a according to specified
+ /// key selector and element selector functions, as well as a comparer.
+ ///
+ public static PooledDictionary ToPooledDictionary(
+ this ReadOnlyMemory source,
+ Func keySelector, Func valueSelector, IEqualityComparer comparer)
+ {
+ return ToPooledDictionary(source.Span, keySelector, valueSelector, comparer);
+ }
+
+ ///
+ /// Creates a from a according to specified
+ /// key selector and element selector functions, as well as a comparer.
+ ///
+ public static PooledDictionary ToPooledDictionary(
+ this Memory source,
+ Func keySelector, Func valueSelector, IEqualityComparer comparer)
+ {
+ return ToPooledDictionary(source.Span, keySelector, valueSelector, comparer);
+ }
+
+ ///
+ /// Creates a from an according to specified
+ /// key selector and comparer.
+ ///
+ public static PooledDictionary ToPooledDictionary(
+ this IEnumerable source,
+ Func keySelector, IEqualityComparer comparer = null)
+ {
+ if (source == null)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
+ var dict = new PooledDictionary((source as ICollection)?.Count ?? 0, comparer);
+ foreach (var item in source)
+ {
+ dict.Add(keySelector(item), item);
+ }
+
+ return dict;
+ }
+
+ ///
+ /// Creates a from an according to specified
+ /// key selector and comparer.
+ ///
+ public static PooledDictionary ToPooledDictionary(
+ this ReadOnlySpan source,
+ Func keySelector, IEqualityComparer comparer = null)
+ {
+ var dict = new PooledDictionary(source.Length, comparer);
+ foreach (var item in source)
+ {
+ dict.Add(keySelector(item), item);
+ }
+
+ return dict;
+ }
+
+ ///
+ /// Creates a from an according to specified
+ /// key selector and comparer.
+ ///
+ public static PooledDictionary ToPooledDictionary(this Span source,
+ Func keySelector, IEqualityComparer comparer = null)
+ {
+ return ToPooledDictionary((ReadOnlySpan)source, keySelector, comparer);
+ }
+
+ ///
+ /// Creates a from an according to specified
+ /// key selector and comparer.
+ ///
+ public static PooledDictionary ToPooledDictionary(
+ this ReadOnlyMemory source,
+ Func keySelector, IEqualityComparer comparer = null)
+ {
+ return ToPooledDictionary(source.Span, keySelector, comparer);
+ }
+
+ ///
+ /// Creates a from an according to specified
+ /// key selector and comparer.
+ ///
+ public static PooledDictionary ToPooledDictionary(this Memory source,
+ Func keySelector, IEqualityComparer comparer = null)
+ {
+ return ToPooledDictionary(source.Span, keySelector, comparer);
+ }
+
+ ///
+ /// Creates a