// Copyright (c) 2023 bradson // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. // // Based on https://github.com/matthewcrews/FastDictionaryTest/tree/main // which itself is based on // https://probablydance.com/2018/05/28/a-new-fast-hash-table-in-response-to-googles-new-fast-hash-table/ #if false using System.Collections; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using FisheryLib.FunctionPointers; using JetBrains.Annotations; namespace FisheryLib.Collections; #pragma warning disable CS8766, CS8767 [PublicAPI] [StructLayout(LayoutKind.Sequential)] public struct StructFishTable : ICollection //: IDictionary, IDictionary, IReadOnlyDictionary { private static ReadOnlySpan TrailingZeroCountDeBruijn => new byte[] { 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 }; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int TrailingZeroCount(uint value) => value == 0 ? 32 : Unsafe.AddByteOffset(ref TrailingZeroCountDeBruijn.DangerousGetPinnableReference(), (IntPtr)(int)(((value & unchecked(0 - value)) * 125613361) >> 27)); internal Entry[] _buckets; private unsafe delegate* _keyHashCodeByRefGetter = GetHashCodeByRef.Default; private unsafe delegate* _keyEqualityByRefComparer = EqualsByRef.Default; private int _bucketBitShift, _count; private unsafe delegate* _keyHashCodeGetter = GetHashCode.Default; private unsafe delegate* _keyEqualityComparer = Equals.Default; private int _wrapAroundMask, _version; //private KeyCollection _keys; //private ValueCollection _values; public ICollection Keys => throw new NotImplementedException(); public ICollection Values => throw new NotImplementedException(); public int Version { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _version; } public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _count; } public bool IsReadOnly => false; // ICollection IDictionary.Keys => throw new NotImplementedException(); // // ICollection IDictionary.Values => throw new NotImplementedException(); // // bool IDictionary.IsFixedSize => false; object ICollection.SyncRoot => this; // matching System.Collections.Generic.Dictionary bool ICollection.IsSynchronized => false; // // IEnumerable IReadOnlyDictionary.Keys => throw new NotImplementedException(); // // IEnumerable IReadOnlyDictionary.Values => throw new NotImplementedException(); // // object? IDictionary.this[object key] // { // get // => key is TKey tKey // ? this[tKey] // : ThrowHelper.ThrowKeyNotFoundException(key); // // set // { // if (key is TKey tKey) // { // switch (value) // { // case TValue tValue: // this[tKey] = tValue; // break; // case null: // this[tKey] = default; // break; // default: // ThrowHelper.ThrowWrongValueTypeArgumentException(value); // break; // } // } // else // { // ThrowHelper.ThrowWrongKeyTypeArgumentException(key); // } // } // } public unsafe TValue? this[TKey key] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { var hashCode = _keyHashCodeGetter(key); var entryIndex = ComputeBucketIndex(hashCode); while (true) { ref var entry = ref _buckets[entryIndex]; if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) return entry.Value; if (entry.IsLast) ThrowHelper.ThrowKeyNotFoundException(key); SetNextEntryIndex(ref entryIndex, entry.Next); } } set { AddEntry(key, value); Resize(); } } public StructFishTable() : this(0) { } public StructFishTable(int minimumCapacity) => Initialize(minimumCapacity); [MemberNotNull(nameof(_buckets))] private void Initialize(int minimumCapacity = 0) { //_buckets = new Entry[minimumCapacity <= 1 ? 1 : Mathf.NextPowerOfTwo(minimumCapacity)]; _buckets = new Entry[4]; for (var i = 0; i < _buckets.Length; i++) _buckets[i].Next = byte.MaxValue; _bucketBitShift = 32 - TrailingZeroCount((uint)_buckets.Length); _wrapAroundMask = _buckets.Length - 1; } public StructFishTable(IEnumerable> entries) : this(0) { foreach (var entry in entries) { AddEntry(entry.Key, entry.Value); Resize(); } } private void SetNextEntryIndex(ref int entryIndex, byte next) => entryIndex = GetNextEntryIndex(entryIndex, next); private int GetNextEntryIndex(int entryIndex, byte next) => (entryIndex + next /*GetJumpDistance(next)*/) & _wrapAroundMask; // private static long GetJumpDistanceLong(byte index) // => index switch // { // 0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, // 8 => 8, 9 => 9, 10 => 10, 11 => 11, 12 => 12, 13 => 13, 14 => 14, // 15 => 15, 16 => 21, 17 => 28, 18 => 36, 19 => 45, 20 => 55, 21 => 66, // 22 => 78, 23 => 91, 24 => 105, 25 => 120, 26 => 136, 27 => 153, // 28 => 171, 29 => 190, 30 => 210, 31 => 231, 32 => 253, 33 => 276, // 34 => 300, 35 => 325, 36 => 351, 37 => 378, 38 => 406, 39 => 435, // 40 => 465, 41 => 496, 42 => 528, 43 => 561, 44 => 595, 45 => 630, // 46 => 666, 47 => 703, 48 => 741, 49 => 780, 50 => 820, 51 => 861, // 52 => 903, 53 => 946, 54 => 990, 55 => 1035, 56 => 1081, 57 => 1128, // 58 => 1176, 59 => 1225, 60 => 1275, 61 => 1326, 62 => 1378, 63 => 1431, // 64 => 1485, 65 => 1540, 66 => 1596, 67 => 1653, 68 => 1711, 69 => 1770, // 70 => 1830, 71 => 1891, 72 => 1953, 73 => 2016, 74 => 2080, 75 => 2145, // 76 => 2211, 77 => 2278, 78 => 2346, 79 => 2415, 80 => 2485, 81 => 2556, // 82 => 3741, 83 => 8385, 84 => 18915, 85 => 42486, 86 => 95703, 87 => 215496, // 88 => 485605, 89 => 1091503, 90 => 2456436, 91 => 5529475, 92 => 12437578, // 93 => 27986421, 94 => 62972253, 95 => 141700195, 96 => 318819126, 97 => 717314626, // 98 => 1614000520, 99 => 3631437253, 100 => 8170829695, 101 => 18384318876, 102 => 41364501751, // 103 => 93070021080, 104 => 209407709220, 105 => 471167588430, 106 => 1060127437995, 107 => 2385287281530, // 108 => 5366895564381, 109 => 12075513791265, 110 => 27169907873235, 111 => 61132301007778, // 112 => 137547673121001, 113 => 309482258302503, 114 => 696335090510256, 115 => 1566753939653640, // 116 => 3525196427195653, 117 => 7931691866727775, 118 => 17846306747368716, // 119 => 40154190394120111, 120 => 90346928493040500, 121 => 203280588949935750, // 122 => 457381324898247375, 123 => 1029107980662394500, 124 => 2315492957028380766, // 125 => 5209859150892887590, > 125 => throw new() // }; // private static int GetJumpDistance(byte index) // => index switch // { // 0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => 6, 7 => 7, 8 => 8, 9 => 9, 10 => 10, 11 => 11, // 12 => 12, 13 => 13, 14 => 14, 15 => 15, 16 => 21, 17 => 28, 18 => 36, 19 => 45, 20 => 55, 21 => 66, // 22 => 78, 23 => 91, 24 => 105, 25 => 120, 26 => 136, 27 => 153, 28 => 171, 29 => 190, 30 => 210, 31 => 231, // 32 => 253, 33 => 276, 34 => 300, 35 => 325, 36 => 351, 37 => 378, 38 => 406, 39 => 435, 40 => 465, // 41 => 496, 42 => 528, 43 => 561, 44 => 595, 45 => 630, 46 => 666, 47 => 703, 48 => 741, 49 => 780, // 50 => 820, 51 => 861, 52 => 903, 53 => 946, 54 => 990, 55 => 1035, 56 => 1081, 57 => 1128, 58 => 1176, // 59 => 1225, 60 => 1275, 61 => 1326, 62 => 1378, 63 => 1431, 64 => 1485, 65 => 1540, 66 => 1596, 67 => 1653, // 68 => 1711, 69 => 1770, 70 => 1830, 71 => 1891, 72 => 1953, 73 => 2016, 74 => 2080, 75 => 2145, 76 => 2211, // 77 => 2278, 78 => 2346, 79 => 2415, 80 => 2485, 81 => 2556, 82 => 3741, 83 => 8385, 84 => 18915, // 85 => 42486, 86 => 95703, 87 => 215496, 88 => 485605, 89 => 1091503, 90 => 2456436, 91 => 5529475, // 92 => 12437578, 93 => 27986421, 94 => 62972253, 95 => 141700195, 96 => 318819126, 97 => 717314626, // 98 => 1614000520, 255 => int.MaxValue, > 98 => ThrowForInvalidIndex(index) // }; private static int ThrowForInvalidIndex(byte index) => throw new($"Jump distance index for FishTable too large. Should be < 99, got {index} instead."); [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ComputeBucketIndex(int hashCode) => (hashCode * -1640531527) >>> _bucketBitShift; [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe bool IsTail(int bucketIndex) => bucketIndex != ComputeBucketIndex(_keyHashCodeGetter(_buckets[bucketIndex].Key)); [MethodImpl(MethodImplOptions.AggressiveInlining)] private int GetParentBucketIndex(int hashCode, int childBucketIndex) { var ancestorIndex = ComputeBucketIndex(hashCode); while (true) { var nextIndex = GetNextEntryIndex(ancestorIndex, _buckets[ancestorIndex].Next); if (nextIndex == childBucketIndex) break; ancestorIndex = nextIndex; } return ancestorIndex; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe byte DistanceFromParent(int bucketIndex) => _buckets[GetParentBucketIndex(_keyHashCodeGetter(_buckets[bucketIndex].Key), bucketIndex)].Next; private void SetEntry(byte next, int hashCode, TKey key, TValue? value, int bucketIndex) { ref var entry = ref _buckets[bucketIndex]; entry.Next = next; entry.Key = key; entry.Value = value; } private unsafe void RemoveFromList(int entryIndex) { ref var entry = ref _buckets[entryIndex]; var parentEntryIndex = GetParentBucketIndex(_keyHashCodeGetter(entry.Key), entryIndex); _buckets[parentEntryIndex].Next = entry.IsLast ? (byte)0 : (byte)(_buckets[parentEntryIndex].Next + entry.Next); } private unsafe void AddEntry(TKey key, TValue? value) => AddEntry(_keyHashCodeGetter(key), key, value); private unsafe void AddEntry(int hashCode, TKey key, TValue? value) { _version++; var entryIndex = ComputeBucketIndex(hashCode); ref var entry = ref _buckets[entryIndex]; if (entry.IsAvailable) { SetEntry(0, hashCode, key, value, entryIndex); _count++; } else if (_keyHashCodeGetter(entry.Key) == hashCode && _keyEqualityComparer(entry.Key, key)) { entry.Value = value; } else if (IsTail(entryIndex)) { var previousEntry = _buckets[entryIndex]; RemoveFromList(entryIndex); SetEntry(0, hashCode, key, value, entryIndex); AddEntry(_keyHashCodeGetter(previousEntry.Key), previousEntry.Key, previousEntry.Value); _count++; } else { ListSearch(hashCode, key, value, entryIndex); } } private unsafe void ListSearch(int hashCode, TKey key, TValue? value, int bucketIndex) { while (true) { ref var bucket = ref _buckets[bucketIndex]; if (_keyHashCodeGetter(bucket.Key) == hashCode && _keyEqualityComparer(bucket.Key, key)) { bucket.Value = value; return; } if (bucket.IsLast | bucket.IsEmpty) break; SetNextEntryIndex(ref bucketIndex, bucket.Next); } InsertIntoNextEmptyBucket(bucketIndex, hashCode, key, value, 1, bucketIndex + 1); } private unsafe void InsertIntoNextEmptyBucket(int parentIndex, int hashCode, TKey key, TValue? value, byte offset, int bucketIndex) { while (true) { if (bucketIndex < _buckets.Length) { if (_buckets[bucketIndex].IsAvailable) { SetEntry(0, hashCode, key, value, bucketIndex); _count++; _buckets[parentIndex].Next = offset; return; } if (IsTail(bucketIndex) && (uint)offset > DistanceFromParent(bucketIndex)) break; bucketIndex++; offset++; } else { bucketIndex = 0; } } var previousEntry = _buckets[bucketIndex]; RemoveFromList(bucketIndex); SetEntry(0, hashCode, key, value, bucketIndex); _buckets[parentIndex].Next = offset; AddEntry(_keyHashCodeGetter(previousEntry.Key), previousEntry.Key, previousEntry.Value); } private void Resize() { if (_count <= (_buckets.Length >> 2) * 3) return; var oldBuckets = _buckets; _buckets = new Entry[_buckets.Length << 1]; for (var i = 0; i < _buckets.Length; i++) _buckets[i].Next = byte.MaxValue; _bucketBitShift = 64 - TrailingZeroCount((uint)_buckets.Length); _wrapAroundMask = _buckets.Length - 1; _count = 0; for (var i = 0; i < oldBuckets.Length; i++) { var bucket = oldBuckets[i]; if (bucket.IsEntry) AddEntry(bucket.Key, bucket.Value); } } public unsafe bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) { var hashCode = _keyHashCodeGetter(key); var entryIndex = ComputeBucketIndex(hashCode); while (true) { ref var entry = ref _buckets[entryIndex]; if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) { value = entry.Value!; return true; } if (entry.IsLast | entry.IsEmpty) { value = default; return false; } SetNextEntryIndex(ref entryIndex, entry.Next); } } public unsafe TValue? TryGetValue(TKey key) { var hashCode = _keyHashCodeGetter(key); var entryIndex = ComputeBucketIndex(hashCode); while (true) { ref var entry = ref _buckets[entryIndex]; if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) return entry.Value; if (entry.IsLast | entry.IsEmpty) return default; SetNextEntryIndex(ref entryIndex, entry.Next); } } public unsafe TValue GetOrAdd(TKey key) { while (true) { var hashCode = _keyHashCodeGetter(key); var entryIndex = ComputeBucketIndex(hashCode); while (true) { ref var entry = ref _buckets[entryIndex]; if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) return entry.Value!; if (entry.IsLast | entry.IsEmpty) { this[key] = Reflection.New(); break; } SetNextEntryIndex(ref entryIndex, entry.Next); } } } public unsafe TValue GetOrAdd(ref TKey key) { while (true) { var hashCode = _keyHashCodeByRefGetter(ref key); var entryIndex = ComputeBucketIndex(hashCode); while (true) { ref var entry = ref _buckets[entryIndex]; if ((hashCode == _keyHashCodeByRefGetter(ref entry.Key)) & _keyEqualityByRefComparer(ref key, ref entry.Key)) return entry.Value!; if (entry.IsLast | entry.IsEmpty) { this[key] = Reflection.New(); break; } SetNextEntryIndex(ref entryIndex, entry.Next); } } } public unsafe ref TValue GetOrAddReference(TKey key) { while (true) { var hashCode = _keyHashCodeGetter(key); var entryIndex = ComputeBucketIndex(hashCode); while (true) { ref var entry = ref _buckets[entryIndex]; if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) return ref entry.Value!; if (entry.IsLast | entry.IsEmpty) { this[key] = Reflection.New(); break; } SetNextEntryIndex(ref entryIndex, entry.Next); } } } public unsafe ref TValue GetOrAddReference(ref TKey key) { while (true) { var hashCode = _keyHashCodeByRefGetter(ref key); var entryIndex = ComputeBucketIndex(hashCode); while (true) { ref var entry = ref _buckets[entryIndex]; if ((hashCode == _keyHashCodeByRefGetter(ref entry.Key)) & _keyEqualityByRefComparer(ref key, ref entry.Key)) { return ref entry.Value!; } if (entry.IsLast | entry.IsEmpty) { this[key] = Reflection.New(); break; } SetNextEntryIndex(ref entryIndex, entry.Next); } } } public unsafe ref TValue GetReference(TKey key) { var hashCode = _keyHashCodeGetter(key); var entryIndex = ComputeBucketIndex(hashCode); while (true) { ref var entry = ref _buckets[entryIndex]; if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) return ref entry.Value!; if (entry.IsLast | entry.IsEmpty) ThrowHelper.ThrowKeyNotFoundException(key); SetNextEntryIndex(ref entryIndex, entry.Next); } } public unsafe ref TValue GetReference(ref TKey key) { var hashCode = _keyHashCodeByRefGetter(ref key); var entryIndex = ComputeBucketIndex(hashCode); while (true) { ref var entry = ref _buckets[entryIndex]; if ((hashCode == _keyHashCodeByRefGetter(ref entry.Key)) & _keyEqualityByRefComparer(ref key, ref entry.Key)) return ref entry.Value!; if (entry.IsLast | entry.IsEmpty) ThrowHelper.ThrowKeyNotFoundException(key); SetNextEntryIndex(ref entryIndex, entry.Next); } } /// /// Returns a reference to the value field of an entry if the key exists and an Unsafe.NullRef{TValue} if not. /// Must be checked with Unsafe.IsNullRef before assigning to a regular var or field without ref. /// /// The key /// ref TValue or Unsafe.NullRef{TValue} public unsafe ref TValue? TryGetReferenceUnsafe(TKey key) { var hashCode = _keyHashCodeGetter(key); var entryIndex = ComputeBucketIndex(hashCode); while (true) { ref var entry = ref _buckets[entryIndex]; if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) return ref entry.Value; if (entry.IsLast | entry.IsEmpty) return ref Unsafe.NullRef(); SetNextEntryIndex(ref entryIndex, entry.Next); } } public unsafe ref TValue? TryGetReferenceUnsafe(ref TKey key) { var hashCode = _keyHashCodeByRefGetter(ref key); var entryIndex = ComputeBucketIndex(hashCode); while (true) { ref var entry = ref _buckets[entryIndex]; if ((hashCode == _keyHashCodeByRefGetter(ref entry.Key)) & _keyEqualityByRefComparer(ref key, ref entry.Key)) { return ref entry.Value; } if (entry.IsLast | entry.IsEmpty) return ref Unsafe.NullRef(); SetNextEntryIndex(ref entryIndex, entry.Next); } } public unsafe bool ContainsKey(TKey key) { var hashCode = _keyHashCodeGetter(key); var entryIndex = ComputeBucketIndex(hashCode); while (true) { ref var entry = ref _buckets[entryIndex]; if ((hashCode == _keyHashCodeGetter(entry.Key)) & _keyEqualityComparer(key, entry.Key)) return true; if (entry.IsLast | entry.IsEmpty) return false; SetNextEntryIndex(ref entryIndex, entry.Next); } } #if false public void Add(TKey key, TValue value) { Guard.IsNotNull(key); if ( _count == _buckets.Length) // throwing DuplicateKeyExceptions happens later, so Expanding occurs based on expected Expand(); // behaviour instead of matching actual results like in System.Collections.Generic. var keyCode = (uint)HashCode.Get(key); ref var entry = ref _buckets[FastModulo(keyCode, (uint)_buckets.Length)]; while (entry.Next != null) { if (entry.HashCode == keyCode && entry.Key.Equals(key)) ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key); if (entry.Next == EntryReference.Empty) break; entry = ref entry.Next.Entry; } if (entry.Next == null) // means the entry is empty entry = new(keyCode, key, value); else // if (entry.Next == _freeSlot) // occupied entry with an empty slot in entry.Next entry.Next = new(keyCode, key, value); // entry.Next != _freeSlot would be a threading issue, as this was just checked in the loop. Unhandled. _count++; _version++; } private void Expand() { var newLength = Mathf.NextPowerOfTwo(_buckets.Length + 1); var newArray = new Entry[newLength]; for (var i = 0; i < _buckets.Length; i++) { var entry = _buckets[i]; var nextEntry = entry.Next; if (nextEntry is null) continue; BeforeAdding: ref var newEntry = ref newArray[FastModulo(entry.HashCode, (uint)newLength)]; while (newEntry.Next != null) { if (newEntry.HashCode == entry.HashCode && newEntry.Key.Equals(entry.Key)) ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(entry.Key); if (newEntry.Next == EntryReference.Empty) break; newEntry = ref newEntry.Next.Entry; } entry.Next = EntryReference.Empty; if (newEntry.Next == null) newEntry = entry; else newEntry.Next = new(entry); if (nextEntry != EntryReference.Empty) { entry = nextEntry.Entry; goto BeforeAdding; } } _buckets = newArray; } private ref Entry TryFindEntry(TKey key) { var keyCode = (uint)HashCode.Get(key); ref var entry = ref _buckets[FastModulo(keyCode, (uint)_buckets.Length)]; while (entry.HashCode != keyCode || !entry.Key.Equals(key)) { if (entry.Next != EntryReference.Empty && entry.Next != null) entry = ref entry.Next.Entry; else return ref Unsafe.NullRef(); } return ref entry; } private void RemoveEntry(ref Entry entry) { entry = entry.Next != EntryReference.Empty && entry.Next != null ? entry.Next.Entry : default; _count--; _version++; } public bool Remove(TKey key) { ref var entry = ref TryFindEntry(key); if (Unsafe.IsNullRef(ref entry)) return false; RemoveEntry(ref entry); return true; } public void Add(KeyValuePair item) => Add(item.Key, item.Value); #endif public void Clear() { if (_buckets is null) Initialize(); Array.Clear(_buckets, 0, _buckets.Length); for (var i = 0; i < _buckets.Length; i++) _buckets[i].Next = byte.MaxValue; _count = 0; _version++; } public bool Contains(KeyValuePair item) => TryGetValue(item.Key, out var value) && value.Equals(item.Value); public void CopyTo(KeyValuePair[] array, int arrayIndex) { var length = _buckets.Length; for (var i = 0; i < length; i++) { ref var entry = ref _buckets[i]; if (entry.IsEmpty) continue; array[arrayIndex] = new(entry.Key, entry.Value!); arrayIndex++; } } #if false public bool Remove(KeyValuePair item) { ref var entry = ref TryFindEntry(item.Key); if (Unsafe.IsNullRef(ref entry) || !entry.Value.Equals(item.Value)) { return false; } RemoveEntry(ref entry); return true; } #endif public Enumerator GetEnumerator() => new(this, Enumerator.KEY_VALUE_PAIR); // IEnumerator> IEnumerable>.GetEnumerator() // => new Enumerator(this, Enumerator.KEY_VALUE_PAIR); IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this, Enumerator.KEY_VALUE_PAIR); #if false bool IDictionary.Contains(object key) { Guard.IsNotNull(key); return key is TKey tkey && ContainsKey(tkey); } void IDictionary.Add(object key, object value) => throw new NotImplementedException(); //IDictionaryEnumerator IDictionary.GetEnumerator() => new Enumerator(this, Enumerator.DICT_ENTRY); void IDictionary.Remove(object key) { Guard.IsNotNull(key); if (key is TKey tkey) Remove(tkey); } #endif void ICollection.CopyTo(Array array, int index) { if (array is KeyValuePair[] typedArray) { CopyTo(typedArray, index); return; } var length = _buckets.Length; for (var i = 0; i < length; i++) { ref var entry = ref _buckets[i]; if (entry.IsEmpty) continue; ((IList)array)[index] = new KeyValuePair(entry.Key, entry.Value!); index++; } } [StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct Entry { public byte Next; public TKey Key; public TValue? Value; internal bool IsTombstone { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Next == 254; } internal bool IsEmpty { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Next == byte.MaxValue; } internal bool IsEntry { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Next < 254u; } internal bool IsOccupied { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Next <= 254u; } internal bool IsAvailable { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Next >= 254u; } internal bool IsLast { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Next == 0; } } public record struct Enumerator : IEnumerator>, IDictionaryEnumerator { private readonly StructFishTable _dictionary; private readonly int _version; private int _index; private KeyValuePair _current; private readonly int _getEnumeratorRetType; // What should Enumerator.Current return? internal const int DICT_ENTRY = 1; internal const int KEY_VALUE_PAIR = 2; internal Enumerator(StructFishTable dictionary, int getEnumeratorRetType) { _dictionary = dictionary; _version = dictionary._version; _index = 0; _getEnumeratorRetType = getEnumeratorRetType; _current = default; } 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._buckets.Length) { ref var entry = ref _dictionary._buckets[_index++]; if (entry.IsEmpty) continue; _current = new(entry.Key, entry.Value!); return true; } _index = _dictionary._buckets.Length + 1; _current = default; return false; } public KeyValuePair Current => _current; public void Dispose() { } object IEnumerator.Current { get { if (_index == 0 || _index == _dictionary._buckets.Length + 1) ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); return _getEnumeratorRetType == DICT_ENTRY ? new DictionaryEntry(_current.Key!, _current.Value) : new KeyValuePair(_current.Key, _current.Value); } } void IEnumerator.Reset() { if (_version != _dictionary._version) ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion(); _index = 0; _current = default; } DictionaryEntry IDictionaryEnumerator.Entry { get { if (_index == 0 || _index == _dictionary._buckets.Length + 1) ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); return new(_current.Key!, _current.Value); } } object? IDictionaryEnumerator.Key { get { if (_index == 0 || _index == _dictionary._buckets.Length + 1) ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); return _current.Key; } } object? IDictionaryEnumerator.Value { get { if (_index == 0 || _index == _dictionary._buckets.Length + 1) ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen(); return _current.Value; } } } private static class ThrowHelper { [DoesNotReturn] internal static void ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion() => throw new InvalidOperationException("Collection was modified; enumeration operation may not execute."); [DoesNotReturn] internal static void ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen() => throw new InvalidOperationException("Enumeration has either not started or has already finished."); [DoesNotReturn] internal static void ThrowKeyNotFoundException(T key) { Guard.IsNotNull(key); ThrowKeyNotFoundException((object)key); } [DoesNotReturn] internal static V ThrowKeyNotFoundException(T key) { Guard.IsNotNull(key); ThrowKeyNotFoundException((object)key); return default; } [DoesNotReturn] private static void ThrowKeyNotFoundException(object key) => throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary."); [DoesNotReturn] internal static void ThrowWrongValueTypeArgumentException(object value) => throw new ArgumentException( $"The value \"{value}\" is not of type \"{ typeof(TValue)}\" and cannot be used in this generic collection.", nameof(value)); [DoesNotReturn] internal static void ThrowWrongKeyTypeArgumentException(object? key) { Guard.IsNotNull(key); throw new ArgumentException($"The value \"{key}\" is not of type \"{ typeof(TKey)}\" and cannot be used in this generic collection.", nameof(key)); } [DoesNotReturn] internal static void ThrowAddingDuplicateWithKeyArgumentException(T key) { Guard.IsNotNull(key); ThrowAddingDuplicateWithKeyArgumentException((object)key); } [DoesNotReturn] private static void ThrowAddingDuplicateWithKeyArgumentException(object key) => throw new ArgumentException($"An item with the same key has already been added. Key: {key}"); } } #pragma warning restore CS8766, CS8767 #endif