diff --git a/1.3/Assemblies/1Fishery.dll b/1.3/Assemblies/1Fishery.dll
index bfc5879..ae26877 100644
Binary files a/1.3/Assemblies/1Fishery.dll and b/1.3/Assemblies/1Fishery.dll differ
diff --git a/1.3/Assemblies/1Fishery.pdb b/1.3/Assemblies/1Fishery.pdb
new file mode 100644
index 0000000..efc3455
Binary files /dev/null and b/1.3/Assemblies/1Fishery.pdb differ
diff --git a/1.3/Assemblies/IndexRange.dll b/1.3/Assemblies/IndexRange.dll
deleted file mode 100644
index be06f22..0000000
Binary files a/1.3/Assemblies/IndexRange.dll and /dev/null differ
diff --git a/1.4/Assemblies/1Fishery.dll b/1.4/Assemblies/1Fishery.dll
index 6fbb1f3..25f63b3 100644
Binary files a/1.4/Assemblies/1Fishery.dll and b/1.4/Assemblies/1Fishery.dll differ
diff --git a/1.4/Assemblies/1Fishery.pdb b/1.4/Assemblies/1Fishery.pdb
new file mode 100644
index 0000000..0904abb
Binary files /dev/null and b/1.4/Assemblies/1Fishery.pdb differ
diff --git a/1.4/Assemblies/IndexRange.dll b/1.4/Assemblies/IndexRange.dll
deleted file mode 100644
index be06f22..0000000
Binary files a/1.4/Assemblies/IndexRange.dll and /dev/null differ
diff --git a/About/About.xml b/About/About.xml
index d71e95d..53edef4 100644
--- a/About/About.xml
+++ b/About/About.xml
@@ -11,7 +11,6 @@
Ludeon.RimWorld
me.samboycoding.betterloading
- zetrith.prepatcher
brrainz.harmony
diff --git a/README.md b/README.md
index c067c6f..6ed94ad 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
# Fishery
Modding Library for RimWorld. Used by Performance Fish.
-Useful functions include various abstractions around IL in FishTranspiler, to make writing of transpilers easier and more robust. A couple collections like Dictionary and Span have extension methods for fast and convenient lookups. Some functionalities of Microsoft's Community Toolkit are included, expanded upon and modified to better fit Unity. Equality includes generic extension methods to use in place of Equals on structs without memory allocations. Convert provides a faster, generic alternative to System.Convert.ToType. FisheryLib.Reflection provides a couple reflection helpers, Aliases has a methodof function intended to be utilized with `global using static Aliases` to directly retrieve method infos from static method groups.
+Useful functions include various abstractions around IL in FishTranspiler, to make writing of transpilers easier and more robust. A couple collections like Dictionary and Span have extension methods for fast and convenient lookups. Some functionalities of Microsoft's Community Toolkit are included, expanded upon and modified to better fit Unity. Equality includes generic extension methods to use in place of Equals on structs without memory allocations. Convert provides a faster, generic alternative to System.Convert.ToType. FisheryLib.Reflection provides a couple reflection helpers, Aliases has a methodof function intended to be utilized with `global using static Aliases` to directly retrieve method infos from static method groups.
+Additionally this includes a fast and much more versatile System.Collections.Generic.Dictionary replacement in FisheryLib.Collections.FishTable, loosely based on Malte Skarupke's ska::bytell_hash_map, as well as several extensions and helpers for Mono.Cecil, which is utilized by Prepatcher.
\ No newline at end of file
diff --git a/Source/FisheryLib/.editorconfig b/Source/FisheryLib/.editorconfig
index 14c2131..729881f 100644
--- a/Source/FisheryLib/.editorconfig
+++ b/Source/FisheryLib/.editorconfig
@@ -1,6 +1,6 @@
[*.cs]
-file_header_template = Copyright (c) 2022 bradson\nThis Source Code Form is subject to the terms of the Mozilla Public\nLicense, v. 2.0. If a copy of the MPL was not distributed with this\nfile, You can obtain one at https://mozilla.org/MPL/2.0/.
+file_header_template = Copyright (c) 2022 bradson\nThis Source Code Form is subject to the terms of the Mozilla Public\nLicense, v.2.0.If a copy of the MPL was not distributed with this\nfile, You can obtain one at https://mozilla.org/MPL/2.0/.
# CA2211: Non-constant fields should not be visible
dotnet_diagnostic.CA2211.severity = none
@@ -47,6 +47,7 @@ dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion
+
[*.cs]
#### Naming styles ####
@@ -72,27 +73,27 @@ dotnet_naming_symbols.constant.required_modifiers = const
dotnet_naming_symbols.private_or_protected_field.applicable_kinds = field
dotnet_naming_symbols.private_or_protected_field.applicable_accessibilities = private, protected, protected_internal, private_protected
-dotnet_naming_symbols.private_or_protected_field.required_modifiers =
+dotnet_naming_symbols.private_or_protected_field.required_modifiers =
dotnet_naming_symbols.public_or_internal_field.applicable_kinds = field
dotnet_naming_symbols.public_or_internal_field.applicable_accessibilities = public, internal
-dotnet_naming_symbols.public_or_internal_field.required_modifiers =
+dotnet_naming_symbols.public_or_internal_field.required_modifiers =
# Naming styles
-dotnet_naming_style.all_upper.required_prefix =
-dotnet_naming_style.all_upper.required_suffix =
+dotnet_naming_style.all_upper.required_prefix =
+dotnet_naming_style.all_upper.required_suffix =
dotnet_naming_style.all_upper.word_separator = _
dotnet_naming_style.all_upper.capitalization = all_upper
dotnet_naming_style.camel_case_with__.required_prefix = _
-dotnet_naming_style.camel_case_with__.required_suffix =
-dotnet_naming_style.camel_case_with__.word_separator =
+dotnet_naming_style.camel_case_with__.required_suffix =
+dotnet_naming_style.camel_case_with__.word_separator =
dotnet_naming_style.camel_case_with__.capitalization = camel_case
-dotnet_naming_style.camel_case.required_prefix =
-dotnet_naming_style.camel_case.required_suffix =
-dotnet_naming_style.camel_case.word_separator =
+dotnet_naming_style.camel_case.required_prefix =
+dotnet_naming_style.camel_case.required_suffix =
+dotnet_naming_style.camel_case.word_separator =
dotnet_naming_style.camel_case.capitalization = camel_case
dotnet_diagnostic.CA1805.severity = none
csharp_style_prefer_tuple_swap = true:suggestion
@@ -138,31 +139,31 @@ dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.interface.required_modifiers =
+dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.types.required_modifiers =
+dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
-dotnet_naming_symbols.non_field_members.required_modifiers =
+dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.begins_with_i.required_prefix = I
-dotnet_naming_style.begins_with_i.required_suffix =
-dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
-dotnet_naming_style.pascal_case.required_prefix =
-dotnet_naming_style.pascal_case.required_suffix =
-dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
-dotnet_naming_style.pascal_case.required_prefix =
-dotnet_naming_style.pascal_case.required_suffix =
-dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_diagnostic.CA1838.severity = suggestion
dotnet_diagnostic.CA1848.severity = suggestion
diff --git a/Source/FisheryLib/Aliases.cs b/Source/FisheryLib/Aliases.cs
index fbaade2..bbc7b5f 100644
--- a/Source/FisheryLib/Aliases.cs
+++ b/Source/FisheryLib/Aliases.cs
@@ -5,12 +5,15 @@
global using static FisheryLib.Aliases;
using System.Runtime.CompilerServices;
+using JetBrains.Annotations;
namespace FisheryLib;
+[PublicAPI]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "alias")]
public static class Aliases
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ // ReSharper disable once InconsistentNaming
public static MethodInfo methodof(Delegate method) => method.Method;
}
\ No newline at end of file
diff --git a/Source/FisheryLib/ArraySortHelper.cs b/Source/FisheryLib/ArraySortHelper.cs
index 7e91c41..d1253dc 100644
--- a/Source/FisheryLib/ArraySortHelper.cs
+++ b/Source/FisheryLib/ArraySortHelper.cs
@@ -1,18 +1,21 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using UnityEngine;
+// ReSharper disable PossibleUnintendedReferenceComparison
namespace FisheryLib;
+
#region ArraySortHelper for single arrays
+#pragma warning disable CS8500
internal sealed class ArraySortHelper : IArraySortHelper
{
public static IArraySortHelper Default { get; } = CreateArraySortHelper();
private static IArraySortHelper CreateArraySortHelper()
=> typeof(IComparable).IsAssignableFrom(typeof(T))
- ? (IArraySortHelper)Activator.CreateInstance(typeof(GenericArraySortHelper<>).MakeGenericType(typeof(T)))
- : new ArraySortHelper();
+ ? (IArraySortHelper)Activator.CreateInstance(typeof(GenericArraySortHelper<>).MakeGenericType(typeof(T)))
+ : new ArraySortHelper();
#region IArraySortHelper Members
@@ -61,9 +64,7 @@ internal sealed class ArraySortHelper : IArraySortHelper
Debug.Assert(i != j);
if (comparer(keys[i], keys[j]) > 0)
- {
(keys[j], keys[i]) = (keys[i], keys[j]);
- }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -79,9 +80,7 @@ internal sealed class ArraySortHelper : IArraySortHelper
Debug.Assert(comparer != null);
if (keys.Length > 1)
- {
IntroSort(keys, 2 * (BitOperations.Log2((uint)keys.Length) + 1), comparer!);
- }
}
private static void IntroSort(Span keys, int depthLimit, Comparison comparer)
@@ -95,7 +94,6 @@ internal sealed class ArraySortHelper : IArraySortHelper
{
if (partitionSize <= SortUtils.INTROSORT_SIZE_THRESHOLD)
{
-
if (partitionSize == 2)
{
SwapIfGreater(keys, comparer!, 0, 1);
@@ -119,6 +117,7 @@ internal sealed class ArraySortHelper : IArraySortHelper
HeapSort(keys[..partitionSize], comparer!);
return;
}
+
depthLimit--;
var p = PickPivotAndPartition(keys[..partitionSize], comparer!);
@@ -141,12 +140,14 @@ internal sealed class ArraySortHelper : IArraySortHelper
// Sort lo, mid and hi appropriately, then pick mid as the pivot.
SwapIfGreater(keys, comparer!, 0, middle); // swap the low with the mid point
- SwapIfGreater(keys, comparer!, 0, hi); // swap the low with the high
+ SwapIfGreater(keys, comparer!, 0, hi); // swap the low with the high
SwapIfGreater(keys, comparer!, middle, hi); // swap the middle with the high
var pivot = keys[middle];
Swap(keys, middle, hi - 1);
- int left = 0, right = hi - 1; // We already partitioned lo and hi and put the pivot in hi - 1. And we pre-increment & decrement below.
+ int left = 0,
+ right = hi
+ - 1; // We already partitioned lo and hi and put the pivot in hi - 1. And we pre-increment & decrement below.
while (left < right)
{
@@ -163,9 +164,7 @@ internal sealed class ArraySortHelper : IArraySortHelper
// Put pivot in the right location.
if (left != hi - 1)
- {
Swap(keys, left, hi - 1);
- }
return left;
}
@@ -176,9 +175,7 @@ internal sealed class ArraySortHelper : IArraySortHelper
var n = keys.Length;
for (var i = n >> 1; i >= 1; i--)
- {
DownHeap(keys, i, n, comparer!);
- }
for (var i = n; i > 1; i--)
{
@@ -196,9 +193,7 @@ internal sealed class ArraySortHelper : IArraySortHelper
{
var child = 2 * i;
if (child < n && comparer!(keys[child - 1], keys[child]) < 0)
- {
child++;
- }
if (!(comparer!(d, keys[child - 1]) < 0))
break;
@@ -241,29 +236,25 @@ internal sealed class GenericArraySortHelper : IArraySortHelper
{
if (comparer == null || comparer == Comparer.Default)
{
- if (keys.Length > 1)
- {
- // For floating-point, do a pre-pass to move all NaNs to the beginning
- // so that we can do an optimized comparison as part of the actual sort
- // on the remainder of the values.
- if (typeof(T) == typeof(double) ||
- typeof(T) == typeof(float))
- {
- var nanLeft = SortUtils.MoveNansToFront(keys, default(Span));
- if (nanLeft == keys.Length)
- {
- return;
- }
- keys = keys[nanLeft..];
- }
+ if (keys.Length <= 1)
+ return;
- IntroSort(keys, 2 * (BitOperations.Log2((uint)keys.Length) + 1));
+ // For floating-point, do a pre-pass to move all NaNs to the beginning
+ // so that we can do an optimized comparison as part of the actual sort
+ // on the remainder of the values.
+ if (typeof(T) == typeof(double) || typeof(T) == typeof(float))
+ {
+ var nanLeft = SortUtils.MoveNansToFront(keys, default(Span));
+ if (nanLeft == keys.Length)
+ return;
+
+ keys = keys[nanLeft..];
}
+
+ IntroSort(keys, 2 * (BitOperations.Log2((uint)keys.Length) + 1));
}
else
- {
ArraySortHelper.IntrospectiveSort(keys, comparer.Compare);
- }
}
catch (IndexOutOfRangeException)
{
@@ -279,12 +270,10 @@ internal sealed class GenericArraySortHelper : IArraySortHelper
/// Swaps the values in the two references if the first is greater than the second.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void SwapIfGreater(ref T i, ref T j)
+ private static void SwapIfGreater([AllowNull] ref T i, ref T j)
{
if (i != null && GreaterThan(ref i, ref j))
- {
Swap(ref i, ref j);
- }
}
/// Swaps the values in the two references, regardless of whether the two references are the same.
@@ -333,6 +322,7 @@ internal sealed class GenericArraySortHelper : IArraySortHelper
HeapSort(keys[..partitionSize]);
return;
}
+
depthLimit--;
var p = PickPivotAndPartition(keys[..partitionSize]);
@@ -343,7 +333,7 @@ internal sealed class GenericArraySortHelper : IArraySortHelper
}
}
- private static int PickPivotAndPartition(Span keys)
+ private static unsafe int PickPivotAndPartition(Span keys)
{
Debug.Assert(keys.Length >= SortUtils.INTROSORT_SIZE_THRESHOLD);
@@ -361,38 +351,39 @@ internal sealed class GenericArraySortHelper : IArraySortHelper
Swap(ref middleRef, ref nextToLastRef);
// Walk the left and right pointers, swapping elements as necessary, until they cross.
+ // ReSharper disable once RedundantSuppressNullableWarningExpression
ref T leftRef = ref zeroRef!, rightRef = ref nextToLastRef!;
while (Unsafe.IsAddressLessThan(ref leftRef, ref rightRef))
{
if (pivot == null)
{
- while (Unsafe.IsAddressLessThan(ref leftRef!, ref nextToLastRef) && (leftRef = ref Unsafe.Add(ref leftRef, 1)) == null)
+ while (Unsafe.IsAddressLessThan(ref leftRef!, ref nextToLastRef)
+ && (leftRef = ref Unsafe.Add(ref leftRef, 1)) == null)
;
- while (Unsafe.IsAddressGreaterThan(ref rightRef, ref zeroRef) && (rightRef = ref Unsafe.Add(ref rightRef, -1)) != null)
+ while (Unsafe.IsAddressGreaterThan(ref rightRef, ref zeroRef)
+ && (rightRef = ref Unsafe.Add(ref rightRef, -1)) != null)
;
}
else
{
- while (Unsafe.IsAddressLessThan(ref leftRef, ref nextToLastRef) && GreaterThan(ref pivot, ref leftRef = ref Unsafe.Add(ref leftRef, 1)))
+ while (Unsafe.IsAddressLessThan(ref leftRef, ref nextToLastRef)
+ && GreaterThan(ref pivot, ref leftRef = ref Unsafe.Add(ref leftRef, 1)))
;
- while (Unsafe.IsAddressGreaterThan(ref rightRef, ref zeroRef) && LessThan(ref pivot, ref rightRef = ref Unsafe.Add(ref rightRef, -1)))
+ while (Unsafe.IsAddressGreaterThan(ref rightRef, ref zeroRef)
+ && LessThan(ref pivot, ref rightRef = ref Unsafe.Add(ref rightRef, -1)))
;
}
if (!Unsafe.IsAddressLessThan(ref leftRef, ref rightRef!))
- {
break;
- }
Swap(ref leftRef, ref rightRef);
}
// Put the pivot in the correct location.
if (!Unsafe.AreSame(ref leftRef, ref nextToLastRef))
- {
Swap(ref leftRef, ref nextToLastRef);
- }
- return (int)((nint)Unsafe.ByteOffset(ref zeroRef, ref leftRef) / Unsafe.SizeOf());
+ return (int)((nint)Unsafe.ByteOffset(ref zeroRef, ref leftRef) / sizeof(T));
}
private static void HeapSort(Span keys)
@@ -401,9 +392,7 @@ internal sealed class GenericArraySortHelper : IArraySortHelper
var n = keys.Length;
for (var i = n >> 1; i >= 1; i--)
- {
DownHeap(keys, i, n);
- }
for (var i = n; i > 1; i--)
{
@@ -419,9 +408,7 @@ internal sealed class GenericArraySortHelper : IArraySortHelper
{
var child = 2 * i;
if (child < n && (keys[child - 1] == null || LessThan(ref keys[child - 1], ref keys[child])))
- {
child++;
- }
if (keys[child - 1] == null || !LessThan(ref d, ref keys[child - 1]))
break;
@@ -442,7 +429,8 @@ internal sealed class GenericArraySortHelper : IArraySortHelper
var j = i;
while (j >= 0 && (t == null || LessThan(ref t, ref Unsafe.Add(ref MemoryMarshal.GetReference(keys), j))))
{
- Unsafe.Add(ref MemoryMarshal.GetReference(keys), j + 1) = Unsafe.Add(ref MemoryMarshal.GetReference(keys), j);
+ Unsafe.Add(ref MemoryMarshal.GetReference(keys), j + 1)
+ = Unsafe.Add(ref MemoryMarshal.GetReference(keys), j);
j--;
}
@@ -460,58 +448,34 @@ internal sealed class GenericArraySortHelper : IArraySortHelper
[MethodImpl(MethodImplOptions.AggressiveInlining)] // compiles to a single comparison or method call
private static bool LessThan(ref T left, ref T right)
- => typeof(T) == typeof(byte)
- ? (byte)(object)left < (byte)(object)right
- : typeof(T) == typeof(sbyte)
- ? (sbyte)(object)left < (sbyte)(object)right
- : typeof(T) == typeof(ushort)
- ? (ushort)(object)left < (ushort)(object)right
- : typeof(T) == typeof(short)
- ? (short)(object)left < (short)(object)right
- : typeof(T) == typeof(uint)
- ? (uint)(object)left < (uint)(object)right
- : typeof(T) == typeof(int)
- ? (int)(object)left < (int)(object)right
- : typeof(T) == typeof(ulong)
- ? (ulong)(object)left < (ulong)(object)right
- : typeof(T) == typeof(long)
- ? (long)(object)left < (long)(object)right
- : typeof(T) == typeof(nuint)
- ? (nuint)(object)left < (nuint)(object)right
- : typeof(T) == typeof(nint)
- ? (nint)(object)left < (nint)(object)right
- : typeof(T) == typeof(float)
- ? (float)(object)left < (float)(object)right
- : typeof(T) == typeof(double)
- ? (double)(object)left < (double)(object)right
+ => typeof(T) == typeof(byte) ? (byte)(object)left < (byte)(object)right
+ : typeof(T) == typeof(sbyte) ? (sbyte)(object)left < (sbyte)(object)right
+ : typeof(T) == typeof(ushort) ? (ushort)(object)left < (ushort)(object)right
+ : typeof(T) == typeof(short) ? (short)(object)left < (short)(object)right
+ : typeof(T) == typeof(uint) ? (uint)(object)left < (uint)(object)right
+ : typeof(T) == typeof(int) ? (int)(object)left < (int)(object)right
+ : typeof(T) == typeof(ulong) ? (ulong)(object)left < (ulong)(object)right
+ : typeof(T) == typeof(long) ? (long)(object)left < (long)(object)right
+ : typeof(T) == typeof(nuint) ? (nuint)(object)left < (nuint)(object)right
+ : typeof(T) == typeof(nint) ? (nint)(object)left < (nint)(object)right
+ : typeof(T) == typeof(float) ? (float)(object)left < (float)(object)right
+ : typeof(T) == typeof(double) ? (double)(object)left < (double)(object)right
: left.CompareTo(right) < 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)] // compiles to a single comparison or method call
private static bool GreaterThan(ref T left, ref T right)
- => typeof(T) == typeof(byte)
- ? (byte)(object)left > (byte)(object)right
- : typeof(T) == typeof(sbyte)
- ? (sbyte)(object)left > (sbyte)(object)right
- : typeof(T) == typeof(ushort)
- ? (ushort)(object)left > (ushort)(object)right
- : typeof(T) == typeof(short)
- ? (short)(object)left > (short)(object)right
- : typeof(T) == typeof(uint)
- ? (uint)(object)left > (uint)(object)right
- : typeof(T) == typeof(int)
- ? (int)(object)left > (int)(object)right
- : typeof(T) == typeof(ulong)
- ? (ulong)(object)left > (ulong)(object)right
- : typeof(T) == typeof(long)
- ? (long)(object)left > (long)(object)right
- : typeof(T) == typeof(nuint)
- ? (nuint)(object)left > (nuint)(object)right
- : typeof(T) == typeof(nint)
- ? (nint)(object)left > (nint)(object)right
- : typeof(T) == typeof(float)
- ? (float)(object)left > (float)(object)right
- : typeof(T) == typeof(double)
- ? (double)(object)left > (double)(object)right
+ => typeof(T) == typeof(byte) ? (byte)(object)left > (byte)(object)right
+ : typeof(T) == typeof(sbyte) ? (sbyte)(object)left > (sbyte)(object)right
+ : typeof(T) == typeof(ushort) ? (ushort)(object)left > (ushort)(object)right
+ : typeof(T) == typeof(short) ? (short)(object)left > (short)(object)right
+ : typeof(T) == typeof(uint) ? (uint)(object)left > (uint)(object)right
+ : typeof(T) == typeof(int) ? (int)(object)left > (int)(object)right
+ : typeof(T) == typeof(ulong) ? (ulong)(object)left > (ulong)(object)right
+ : typeof(T) == typeof(long) ? (long)(object)left > (long)(object)right
+ : typeof(T) == typeof(nuint) ? (nuint)(object)left > (nuint)(object)right
+ : typeof(T) == typeof(nint) ? (nint)(object)left > (nint)(object)right
+ : typeof(T) == typeof(float) ? (float)(object)left > (float)(object)right
+ : typeof(T) == typeof(double) ? (double)(object)left > (double)(object)right
: left.CompareTo(right) > 0;
}
@@ -525,8 +489,9 @@ internal sealed class ArraySortHelper : IArraySortHelper CreateArraySortHelper()
=> typeof(IComparable).IsAssignableFrom(typeof(TKey))
- ? (IArraySortHelper)Activator.CreateInstance(typeof(GenericArraySortHelper<,>).MakeGenericType(typeof(TKey), typeof(TValue)))
- : new ArraySortHelper();
+ ? (IArraySortHelper)Activator.CreateInstance(
+ typeof(GenericArraySortHelper<,>).MakeGenericType(typeof(TKey), typeof(TValue)))
+ : new ArraySortHelper();
public void Sort(Span keys, Span values, IComparer? comparer)
{
@@ -546,19 +511,20 @@ internal sealed class ArraySortHelper : IArraySortHelper keys, Span values, IComparer comparer, int i, int j)
+ private static void SwapIfGreaterWithValues(Span keys, Span values, IComparer comparer, int i,
+ int j)
{
Debug.Assert(comparer != null);
Debug.Assert(0 <= i && i < keys.Length && i < values.Length);
Debug.Assert(0 <= j && j < keys.Length && j < values.Length);
Debug.Assert(i != j);
- if (comparer!.Compare(keys[i], keys[j]) > 0)
- {
- (keys[j], keys[i]) = (keys[i], keys[j]);
+ if (comparer!.Compare(keys[i], keys[j]) <= 0)
+ return;
- (values[j], values[i]) = (values[i], values[j]);
- }
+ (keys[j], keys[i]) = (keys[i], keys[j]);
+
+ (values[j], values[i]) = (values[i], values[j]);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -577,9 +543,7 @@ internal sealed class ArraySortHelper : IArraySortHelper 1)
- {
IntroSort(keys, values, 2 * (BitOperations.Log2((uint)keys.Length) + 1), comparer!);
- }
}
public static void IntroSort(Span keys, Span values, int depthLimit, IComparer comparer)
@@ -594,7 +558,6 @@ internal sealed class ArraySortHelper : IArraySortHelper : IArraySortHelper : IArraySortHelper : IArraySortHelper : IArraySortHelper> 1; i >= 1; i--)
- {
DownHeap(keys, values, i, n, comparer!);
- }
for (var i = n; i > 1; i--)
{
@@ -697,9 +659,7 @@ internal sealed class ArraySortHelper : IArraySortHelper : IArraySortHelper : IArraySortHelper.IntrospectiveSort(keys, values, comparer);
- }
}
catch (IndexOutOfRangeException)
{
@@ -818,7 +774,6 @@ internal sealed class GenericArraySortHelper : IArraySortHelper : IArraySortHelper : IArraySortHelper 0 && keys[--right] != null)
;
@@ -895,9 +853,7 @@ internal sealed class GenericArraySortHelper : IArraySortHelper : IArraySortHelper> 1; i >= 1; i--)
- {
DownHeap(keys, values, i, n);
- }
for (var i = n; i > 1; i--)
{
@@ -927,9 +881,7 @@ internal sealed class GenericArraySortHelper : IArraySortHelper : IArraySortHelper typeof(TKey) == typeof(byte)
- ? (byte)(object)left < (byte)(object)right
- : typeof(TKey) == typeof(sbyte)
- ? (sbyte)(object)left < (sbyte)(object)right
- : typeof(TKey) == typeof(ushort)
- ? (ushort)(object)left < (ushort)(object)right
- : typeof(TKey) == typeof(short)
- ? (short)(object)left < (short)(object)right
- : typeof(TKey) == typeof(uint)
- ? (uint)(object)left < (uint)(object)right
- : typeof(TKey) == typeof(int)
- ? (int)(object)left < (int)(object)right
- : typeof(TKey) == typeof(ulong)
- ? (ulong)(object)left < (ulong)(object)right
- : typeof(TKey) == typeof(long)
- ? (long)(object)left < (long)(object)right
- : typeof(TKey) == typeof(nuint)
- ? (nuint)(object)left < (nuint)(object)right
- : typeof(TKey) == typeof(nint)
- ? (nint)(object)left < (nint)(object)right
- : typeof(TKey) == typeof(float)
- ? (float)(object)left < (float)(object)right
- : typeof(TKey) == typeof(double)
- ? (double)(object)left < (double)(object)right
+ => typeof(TKey) == typeof(byte) ? (byte)(object)left < (byte)(object)right
+ : typeof(TKey) == typeof(sbyte) ? (sbyte)(object)left < (sbyte)(object)right
+ : typeof(TKey) == typeof(ushort) ? (ushort)(object)left < (ushort)(object)right
+ : typeof(TKey) == typeof(short) ? (short)(object)left < (short)(object)right
+ : typeof(TKey) == typeof(uint) ? (uint)(object)left < (uint)(object)right
+ : typeof(TKey) == typeof(int) ? (int)(object)left < (int)(object)right
+ : typeof(TKey) == typeof(ulong) ? (ulong)(object)left < (ulong)(object)right
+ : typeof(TKey) == typeof(long) ? (long)(object)left < (long)(object)right
+ : typeof(TKey) == typeof(nuint) ? (nuint)(object)left < (nuint)(object)right
+ : typeof(TKey) == typeof(nint) ? (nint)(object)left < (nint)(object)right
+ : typeof(TKey) == typeof(float) ? (float)(object)left < (float)(object)right
+ : typeof(TKey) == typeof(double) ? (double)(object)left < (double)(object)right
: left.CompareTo(right) < 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)] // compiles to a single comparison or method call
private static bool GreaterThan(ref TKey left, ref TKey right)
- => typeof(TKey) == typeof(byte)
- ? (byte)(object)left > (byte)(object)right
- : typeof(TKey) == typeof(sbyte)
- ? (sbyte)(object)left > (sbyte)(object)right
- : typeof(TKey) == typeof(ushort)
- ? (ushort)(object)left > (ushort)(object)right
- : typeof(TKey) == typeof(short)
- ? (short)(object)left > (short)(object)right
- : typeof(TKey) == typeof(uint)
- ? (uint)(object)left > (uint)(object)right
- : typeof(TKey) == typeof(int)
- ? (int)(object)left > (int)(object)right
- : typeof(TKey) == typeof(ulong)
- ? (ulong)(object)left > (ulong)(object)right
- : typeof(TKey) == typeof(long)
- ? (long)(object)left > (long)(object)right
- : typeof(TKey) == typeof(nuint)
- ? (nuint)(object)left > (nuint)(object)right
- : typeof(TKey) == typeof(nint)
- ? (nint)(object)left > (nint)(object)right
- : typeof(TKey) == typeof(float)
- ? (float)(object)left > (float)(object)right
- : typeof(TKey) == typeof(double)
- ? (double)(object)left > (double)(object)right
+ => typeof(TKey) == typeof(byte) ? (byte)(object)left > (byte)(object)right
+ : typeof(TKey) == typeof(sbyte) ? (sbyte)(object)left > (sbyte)(object)right
+ : typeof(TKey) == typeof(ushort) ? (ushort)(object)left > (ushort)(object)right
+ : typeof(TKey) == typeof(short) ? (short)(object)left > (short)(object)right
+ : typeof(TKey) == typeof(uint) ? (uint)(object)left > (uint)(object)right
+ : typeof(TKey) == typeof(int) ? (int)(object)left > (int)(object)right
+ : typeof(TKey) == typeof(ulong) ? (ulong)(object)left > (ulong)(object)right
+ : typeof(TKey) == typeof(long) ? (long)(object)left > (long)(object)right
+ : typeof(TKey) == typeof(nuint) ? (nuint)(object)left > (nuint)(object)right
+ : typeof(TKey) == typeof(nint) ? (nint)(object)left > (nint)(object)right
+ : typeof(TKey) == typeof(float) ? (float)(object)left > (float)(object)right
+ : typeof(TKey) == typeof(double) ? (double)(object)left > (double)(object)right
: left.CompareTo(right) > 0;
}
@@ -1041,15 +969,13 @@ internal static class SortUtils
for (var i = 0; i < keys.Length; i++)
{
- if ((typeof(TKey) == typeof(double) && double.IsNaN((double)(object)keys[i])) ||
- (typeof(TKey) == typeof(float) && float.IsNaN((float)(object)keys[i])))
+ if ((typeof(TKey) == typeof(double) && double.IsNaN((double)(object)keys[i]))
+ || (typeof(TKey) == typeof(float) && float.IsNaN((float)(object)keys[i])))
{
(keys[i], keys[left]) = (keys[left], keys[i]);
if ((uint)i < (uint)values.Length) // check to see if we have values
- {
(values[i], values[left]) = (values[left], values[i]);
- }
left++;
}
@@ -1067,9 +993,12 @@ internal static class SortUtils
internal static void ThrowArgumentException_BadComparer(object? comparer)
=> throw new ArgumentException(string.Format(Arg_BogusIComparer, comparer));
- internal static string InvalidOperation_IComparerFailed => "Failed to compare two elements in the array.";
+ internal static string InvalidOperation_IComparerFailed = "Failed to compare two elements in the array.";
+
internal static string Arg_BogusIComparer
- => "Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. IComparer: '{0}'.";
+ = "Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not "
+ + "compare equal to itself, or one value repeatedly compared to another value yields different results. "
+ + "IComparer: '{0}'.";
}
internal interface IArraySortHelper
@@ -1085,12 +1014,40 @@ internal interface IArraySortHelper
internal static class BitOperations
{
private static ReadOnlySpan Log2DeBruijn
- => new byte[32]
+ => new byte[]
{
- 00, 09, 01, 10, 13, 21, 02, 29,
- 11, 14, 16, 18, 22, 25, 03, 30,
- 08, 12, 20, 28, 15, 17, 24, 07,
- 19, 27, 23, 06, 26, 05, 04, 31
+ 00,
+ 09,
+ 01,
+ 10,
+ 13,
+ 21,
+ 02,
+ 29,
+ 11,
+ 14,
+ 16,
+ 18,
+ 22,
+ 25,
+ 03,
+ 30,
+ 08,
+ 12,
+ 20,
+ 28,
+ 15,
+ 17,
+ 24,
+ 07,
+ 19,
+ 27,
+ 23,
+ 06,
+ 26,
+ 05,
+ 04,
+ 31
};
///
@@ -1160,4 +1117,5 @@ internal static class BitOperations
// uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here
(IntPtr)(int)((value * 0x07C4ACDDu) >> 27));
}
-}
\ No newline at end of file
+}
+#pragma warning restore CS8500
\ No newline at end of file
diff --git a/Source/FisheryLib/BoolExtensions.cs b/Source/FisheryLib/BoolExtensions.cs
index 5942908..b25c288 100644
--- a/Source/FisheryLib/BoolExtensions.cs
+++ b/Source/FisheryLib/BoolExtensions.cs
@@ -5,12 +5,14 @@
// https://github.com/CommunityToolkit/dotnet/blob/main/CommunityToolkit.HighPerformance/Extensions/BoolExtensions.cs
using System.Runtime.CompilerServices;
+using JetBrains.Annotations;
namespace FisheryLib;
///
/// Helpers for working with the type.
///
+[PublicAPI]
public static class BoolExtensions
{
///
@@ -27,7 +29,7 @@ public static class BoolExtensions
// remove unwanted stack spills if the caller is using constant arguments. This is
// because taking the address of an argument can interfere with some of the flow
// analysis executed by the JIT, which can in some cases block constant propagation.
- bool copy = flag;
+ var copy = flag;
return *(byte*)©
}
@@ -49,10 +51,10 @@ public static class BoolExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe int ToBitwiseMask32(this bool flag)
{
- bool copy = flag;
- byte rangeFlag = *(byte*)©
- int negativeFlag = rangeFlag - 1;
- int mask = ~negativeFlag;
+ var copy = flag;
+ var rangeFlag = *(byte*)©
+ var negativeFlag = rangeFlag - 1;
+ var mask = ~negativeFlag;
return mask;
}
@@ -67,10 +69,10 @@ public static class BoolExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe long ToBitwiseMask64(this bool flag)
{
- bool copy = flag;
- byte rangeFlag = *(byte*)©
- long negativeFlag = (long)rangeFlag - 1;
- long mask = ~negativeFlag;
+ var copy = flag;
+ var rangeFlag = *(byte*)©
+ var negativeFlag = (long)rangeFlag - 1;
+ var mask = ~negativeFlag;
return mask;
}
diff --git a/Source/FisheryLib/Cecil/CecilExtensions.cs b/Source/FisheryLib/Cecil/CecilExtensions.cs
new file mode 100644
index 0000000..2b2f4eb
--- /dev/null
+++ b/Source/FisheryLib/Cecil/CecilExtensions.cs
@@ -0,0 +1,559 @@
+// 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/.
+
+using System.Runtime.CompilerServices;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using Mono.Collections.Generic;
+using Verse;
+using CallSite = Mono.Cecil.CallSite;
+
+namespace FisheryLib.Cecil;
+
+public static class CecilExtensions
+{
+ public static MethodReference ImportReference(this ModuleDefinition module, Delegate method)
+ => module.ImportReference(method.Method);
+
+ public static MethodReference MakeGenericMethod(this MethodReference genericMethodDefinition,
+ ICollection genericArguments) where T : TypeReference
+ {
+ if (genericArguments.Count != genericMethodDefinition.GenericParameters.Count)
+ ThrowForInvalidGenericArgumentCount(genericMethodDefinition, genericArguments);
+
+ var genericInstanceMethod = new GenericInstanceMethod(genericMethodDefinition);
+ foreach (var genericArgument in genericArguments)
+ genericInstanceMethod.GenericArguments.Add(genericArgument);
+
+ return genericInstanceMethod;
+ }
+
+ private static void ThrowForInvalidGenericArgumentCount(IGenericParameterProvider genericParameterProvider,
+ ICollection genericArguments) where T : TypeReference
+ => throw new ArgumentException(
+ $"Invalid generic argument count for {genericParameterProvider}. Received {
+ genericArguments.Count}, must be {genericParameterProvider.GenericParameters.Count}");
+
+ public static MethodReference MakeGenericMethod(this MethodReference genericMethodDefinition,
+ params T[] genericArguments) where T : TypeReference
+ => MakeGenericMethod(genericMethodDefinition, (ICollection)genericArguments);
+
+ public static MethodReference ImportMethodWithGenericArguments(this ModuleDefinition module, MethodInfo method,
+ ICollection genericArguments) where T : TypeReference
+ => module.ImportReference(method.IsGenericMethodDefinition ? method : method.GetGenericMethodDefinition())
+ .MakeGenericMethod(genericArguments);
+
+ public static MethodReference ImportMethodWithGenericArguments(this ModuleDefinition module, MethodInfo method,
+ params T[] genericArguments) where T : TypeReference
+ => module.ImportMethodWithGenericArguments(method, (ICollection)genericArguments);
+
+ public static MethodReference ImportMethodWithGenericArguments(this ModuleDefinition module, Delegate method,
+ ICollection genericArguments) where T : TypeReference
+ => module.ImportMethodWithGenericArguments(method.Method, genericArguments);
+
+ public static MethodReference ImportMethodWithGenericArguments(this ModuleDefinition module, Delegate method,
+ params T[] genericArguments) where T : TypeReference
+ => module.ImportMethodWithGenericArguments(method.Method, (ICollection)genericArguments);
+
+ public static TypeReference MakeGenericType(this TypeReference genericTypeDefinition,
+ ICollection genericArguments) where T : TypeReference
+ {
+ if (genericArguments.Count != genericTypeDefinition.GenericParameters.Count)
+ ThrowForInvalidGenericArgumentCount(genericTypeDefinition, genericArguments);
+
+ var genericInstanceType = new GenericInstanceType(genericTypeDefinition);
+ foreach (var genericArgument in genericArguments)
+ genericInstanceType.GenericArguments.Add(genericArgument);
+
+ return genericInstanceType;
+ }
+
+ public static TypeReference MakeGenericType(this TypeReference genericTypeDefinition,
+ params T[] genericArguments) where T : TypeReference
+ => MakeGenericType(genericTypeDefinition, (ICollection)genericArguments);
+
+ public static TypeReference ImportTypeWithGenericArguments(this ModuleDefinition module, Type type,
+ ICollection genericArguments) where T : TypeReference
+ => module.ImportReference(type.IsGenericTypeDefinition ? type : type.GetElementType())
+ .MakeGenericType(genericArguments);
+
+ public static TypeReference ImportTypeWithGenericArguments(this ModuleDefinition module, Type type,
+ params T[] genericArguments) where T : TypeReference
+ => module.ImportTypeWithGenericArguments(type, (ICollection)genericArguments);
+
+ public static VariableDefinition DeclareLocal(this ILProcessor ilProcessor, Type localType)
+ {
+ var variableDefinition = new VariableDefinition(ilProcessor.GetModule().ImportReference(localType));
+ ilProcessor.Body.Variables.Add(variableDefinition);
+ return variableDefinition;
+ }
+
+ public static VariableDefinition DeclareLocal(this ILProcessor ilProcessor, TypeReference localType)
+ {
+ var variableDefinition = new VariableDefinition(localType);
+ ilProcessor.Body.Variables.Add(variableDefinition);
+ return variableDefinition;
+ }
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, Instruction instruction)
+ => ilProcessor.instructions.Insert(index, instruction);
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, TypeReference type)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, type));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, Type type)
+ => ilProcessor.InsertAt(index, opCode, ilProcessor.GetModule().ImportReference(type));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, CallSite site)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, site));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, MethodReference method)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, method));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, MethodBase method,
+ params Type[] genericArguments)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode,
+ ImportReferenceWithOptionalGenericArguments(method, ilProcessor.GetModule(), genericArguments)));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, Delegate method,
+ params Type[] genericArguments)
+ => ilProcessor.InsertAt(index, opCode, method.Method, genericArguments);
+
+ private static MethodReference ImportReferenceWithOptionalGenericArguments(MethodBase method,
+ ModuleDefinition module, params Type[] genericArguments)
+ => genericArguments.Length > 0
+ ? module.ImportMethodWithGenericArguments((MethodInfo)method, genericArguments)
+ : module.ImportReference(method);
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, FieldReference field)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, field));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, FieldInfo field)
+ => ilProcessor.InsertAt(index, opCode, ilProcessor.GetModule().ImportReference(field));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, string value)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, sbyte value)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, byte value)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, int value)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, long value)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, nint value)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, float value)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, double value)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, value));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, Instruction target)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, target));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, Instruction[] targets)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, targets));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, VariableDefinition variable)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, variable));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, ParameterDefinition parameter)
+ => ilProcessor.InsertAt(index, Instruction.Create(opCode, parameter));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, OpCode opCode, object operand)
+ => ilProcessor.InsertAt(index, CreateInstruction(opCode, operand));
+
+ public static void InsertAt(this ILProcessor ilProcessor, int index, object instruction)
+ {
+ var module = ilProcessor.GetModule();
+ ilProcessor.InsertAt(index, instruction switch
+ {
+ Instruction codeInstruction => codeInstruction,
+ OpCode opCode => Instruction.Create(opCode),
+ ITuple tuple => CreateInstruction(tuple is [OpCode opCode, _, ..]
+ ? opCode
+ : ThrowArgumentExceptionForInvalidType(instruction),
+ tuple[1] switch
+ {
+ MethodBase => ImportMethodFromTuple(tuple, module),
+ FieldInfo fieldInfo => module.ImportReference(fieldInfo),
+ Type => ImportTypeFromTuple(tuple, module),
+ ParameterInfo parameterInfo => GetParameterDefinition(ilProcessor, parameterInfo),
+ Delegate @delegate => module.ImportReference(@delegate.Method),
+ _ => tuple[1]
+ }),
+ _ => ThrowArgumentExceptionForInvalidType(instruction)
+ });
+ }
+
+ private static ParameterDefinition GetParameterDefinition(ILProcessor ilProcessor, ParameterInfo parameterInfo)
+ {
+ var parameters = ilProcessor.Body.Method.Parameters;
+ foreach (var parameter in parameters)
+ {
+ if (parameter.Name == parameterInfo.Name)
+ return parameter;
+ }
+
+ return ThrowHelper.ThrowArgumentException($"Could not find parameter named {
+ parameterInfo.Name} in method {ilProcessor.Body.Method.FullName}");
+ }
+
+ private static MethodReference ImportMethodFromTuple(ITuple tuple, ModuleDefinition module)
+ {
+ var tupleLength = tuple.Length;
+ Guard.IsGreaterThanOrEqualTo(tupleLength, 2);
+
+ if (tupleLength == 2)
+ return module.ImportReference((MethodBase)tuple[1]);
+
+ var methodInfo = (MethodInfo)tuple[1];
+ switch (tuple[2])
+ {
+ case Type[] typeArray:
+ {
+ return module.ImportMethodWithGenericArguments(methodInfo, typeArray);
+ }
+ case Type:
+ {
+ var genericArguments = new Type[tupleLength - 2];
+ for (var i = 2; i < tupleLength; i++)
+ genericArguments[i - 2] = (Type)tuple[i];
+
+ return module.ImportMethodWithGenericArguments(methodInfo, genericArguments);
+ }
+ case Collection genericParameterCollection:
+ {
+ return module.ImportMethodWithGenericArguments(methodInfo, genericParameterCollection);
+ }
+ case Collection typeReferenceCollection:
+ {
+ return module.ImportMethodWithGenericArguments(methodInfo, typeReferenceCollection);
+ }
+ case TypeReference:
+ {
+ var genericArguments = new TypeReference[tupleLength - 2];
+ for (var i = 2; i < tupleLength; i++)
+ genericArguments[i - 2] = (TypeReference)tuple[i];
+
+ return module.ImportMethodWithGenericArguments(methodInfo, genericArguments);
+ }
+ default:
+ {
+ return ThrowHelper.ThrowArgumentException(
+ $"Tuple contains invalid type: {tuple[2].GetType().FullName}");
+ }
+ }
+ }
+
+ private static TypeReference ImportTypeFromTuple(ITuple tuple, ModuleDefinition module)
+ {
+ var tupleLength = tuple.Length;
+ Guard.IsGreaterThanOrEqualTo(tupleLength, 2);
+
+ if (tupleLength == 2)
+ return module.ImportReference((Type)tuple[1]);
+
+ var type = (Type)tuple[1];
+ switch (tuple[2])
+ {
+ case Type[] typeArray:
+ {
+ return module.ImportTypeWithGenericArguments(type, typeArray);
+ }
+ case Type:
+ {
+ var genericArguments = new Type[tupleLength - 2];
+ for (var i = 2; i < tupleLength; i++)
+ genericArguments[i - 2] = (Type)tuple[i];
+
+ return module.ImportTypeWithGenericArguments(type, genericArguments);
+ }
+ case Collection genericParameterCollection:
+ {
+ return module.ImportTypeWithGenericArguments(type, genericParameterCollection);
+ }
+ case Collection typeReferenceCollection:
+ {
+ return module.ImportTypeWithGenericArguments(type, typeReferenceCollection);
+ }
+ case TypeReference:
+ {
+ var genericArguments = new TypeReference[tupleLength - 2];
+ for (var i = 2; i < tupleLength; i++)
+ genericArguments[i - 2] = (TypeReference)tuple[i];
+
+ return module.ImportTypeWithGenericArguments(type, genericArguments);
+ }
+ default:
+ {
+ return ThrowHelper.ThrowArgumentException(
+ $"Tuple contains invalid type: {tuple[2].GetType().FullName}");
+ }
+ }
+ }
+
+ public static void InsertRange(this ILProcessor ilProcessor, int index, IEnumerable instructions)
+ {
+ foreach (var instruction in instructions)
+ ilProcessor.InsertAt(index++, instruction);
+ }
+
+ public static void InsertRange(this ILProcessor ilProcessor, int index, params Instruction[] instructions)
+ {
+ for (var i = 0; i < instructions.Length; i++)
+ ilProcessor.InsertAt(index++, instructions[i]);
+ }
+
+ public static void InsertRange(this ILProcessor ilProcessor, int index, IEnumerable
+ /// The instance
/// The key
/// ref TValue or Unsafe.NullRef{TValue}
public static ref TValue TryGetReferenceUnsafe(this Dictionary dictionary, TKey key)
@@ -171,11 +204,11 @@ public static class CollectionExtensions
bucket = entry.next;
}
}
+
return ref Unsafe.NullRef();
}
public static ref TValue TryGetReferenceUnsafe(this Dictionary dictionary, ref TKey key)
- where TKey : struct
{
if (dictionary.buckets != null)
{
@@ -185,12 +218,13 @@ public static class CollectionExtensions
while (bucket >= 0)
{
ref var entry = ref dictionary.entries[bucket];
- if (entry.hashCode == keyCode && entry.key.Equals(ref key))
+ if (entry.hashCode == keyCode && Equality.EqualsByRef(ref entry.key, ref key))
return ref entry.value;
bucket = entry.next;
}
}
+
return ref Unsafe.NullRef();
}
@@ -210,6 +244,7 @@ public static class CollectionExtensions
bucket = entry.next;
}
}
+
return default;
}
@@ -230,8 +265,161 @@ public static class CollectionExtensions
bucket = entry.next;
}
}
+
return default;
}
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref TValue GetOrAddReference(this Dictionary dictionary, TKey key)
+ where TValue : new()
+ {
+ var keyCode = HashCode.Get(key) & 0x7FFFFFFF;
+
+ StartOfLookup:
+ if (dictionary.buckets != null)
+ {
+ var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
+ while (bucket >= 0)
+ {
+ ref var entry = ref dictionary.entries[bucket];
+ if (entry.hashCode == keyCode && entry.key.Equals(key))
+ return ref entry.value;
+
+ bucket = entry.next;
+ }
+ }
+
+ dictionary.Add(key, Reflection.New());
+ goto StartOfLookup;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref TValue GetOrAddReference(this Dictionary dictionary, ref TKey key)
+ where TValue : new()
+ {
+ var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
+
+ StartOfLookup:
+ if (dictionary.buckets != null)
+ {
+ var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
+ while (bucket >= 0)
+ {
+ ref var entry = ref dictionary.entries[bucket];
+ if (entry.hashCode == keyCode && Equality.EqualsByRef(ref entry.key, ref key))
+ return ref entry.value;
+
+ bucket = entry.next;
+ }
+ }
+
+ dictionary.Add(key, Reflection.New());
+ goto StartOfLookup;
+ }
+
+ // public static bool TryGetOrAddReference(this Dictionary dictionary, TKey key,
+ // ref TValue reference)
+ // {
+ // var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
+ // var result = true;
+ //
+ // StartOfLookup:
+ // if (dictionary.buckets != null)
+ // {
+ // var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
+ // while (bucket >= 0)
+ // {
+ // ref var entry = ref dictionary.entries[bucket];
+ // if (entry.hashCode == keyCode && entry.key.Equals(key))
+ // {
+ // reference.Value = ref entry.value;
+ // return result;
+ // }
+ //
+ // bucket = entry.next;
+ // }
+ // }
+ //
+ // result = false;
+ // dictionary.Add(key, Reflection.New());
+ // goto StartOfLookup;
+ // }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TValue GetOrAdd(this Dictionary dictionary, TKey key)
+ where TValue : new()
+ {
+ var keyCode = HashCode.Get(key) & 0x7FFFFFFF;
+
+ StartOfLookup:
+ if (dictionary.buckets != null)
+ {
+ var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
+ while (bucket >= 0)
+ {
+ ref var entry = ref dictionary.entries[bucket];
+ if (entry.hashCode == keyCode && entry.key.Equals(key))
+ return entry.value;
+
+ bucket = entry.next;
+ }
+ }
+
+ dictionary.Add(key, Reflection.New());
+ goto StartOfLookup;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TValue GetOrAdd(this Dictionary dictionary, ref TKey key)
+ where TValue : new()
+ {
+ var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
+
+ StartOfLookup:
+ if (dictionary.buckets != null)
+ {
+ var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
+ while (bucket >= 0)
+ {
+ ref var entry = ref dictionary.entries[bucket];
+ if (entry.hashCode == keyCode && Equality.EqualsByRef(ref entry.key, ref key))
+ return entry.value;
+
+ bucket = entry.next;
+ }
+ }
+
+ dictionary.Add(key, Reflection.New());
+ goto StartOfLookup;
+ }
+
+ public static bool TryGetOrAddValue(this Dictionary dictionary, TKey key,
+ out TValue value)
+ {
+ var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
+ var result = true;
+
+ StartOfLookup:
+ if (dictionary.buckets != null)
+ {
+ var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
+ while (bucket >= 0)
+ {
+ ref var entry = ref dictionary.entries[bucket];
+ if (entry.hashCode == keyCode && entry.key.Equals(key))
+ {
+ value = entry.value;
+ return result;
+ }
+
+ bucket = entry.next;
+ }
+ }
+
+ result = false;
+ dictionary.Add(key, Reflection.New());
+ goto StartOfLookup;
+ }
[DoesNotReturn]
private static ref V ThrowKeyNotFoundException(T key)
@@ -243,42 +431,106 @@ public static class CollectionExtensions
[DoesNotReturn]
private static void ThrowKeyNotFoundException(object key)
- => throw new KeyNotFoundException(
- string.Format("The given key '{0}' was not present in the dictionary.", key));
+ => throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary.");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref int Version(this List list) => ref list._version;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void AddRangeFast(this List list, List range)
+ {
+ if (range._size < 1)
+ return;
+
+ list.EnsureCapacity(list._size + range._size);
+
+ range.UnsafeCopyTo(list, list._size);
+
+ list._size += range._size;
+ list._version++;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void UnsafeCopyTo(this List source, List destination, int destinationStartIndex)
+ => UnsafeBlockCopy(ref source._items[0], ref destination._items[destinationStartIndex], source._size);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void UnsafeCopyTo(this T[] source, T[] destination, int destinationStartIndex)
+ => UnsafeBlockCopy(ref source[0], ref destination[destinationStartIndex], source.Length);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe void UnsafeBlockCopy(ref T source, ref T destination, int count)
+ => Unsafe.CopyBlock(ref Unsafe.As(ref destination),
+ ref Unsafe.As(ref source),
+ (uint)((nint)sizeof(T) * count));
///
- /// Returns a list's internal _items array. Keep in mind list.Version() needs to change every time items are modified.
+ /// Returns a list's internal _items array. Keep in mind list.Version() needs to change every time items are
+ /// modified.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] ItemsUnchecked(this List list) => list._items;
///
- /// Returns a span pointing to a list's internal _items array. Keep in mind list.Version() needs to change every time items are modified.
+ /// Returns a span pointing to a list's internal _items array. Keep in mind list.Version() needs to change every
+ /// time items are modified.
///
- public static Span AsSpanUnchecked(this List list) => new(list._items, 0, list.Count);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Span AsSpanUnchecked(this List list) => new(list._items, 0, list._size);
- public static ReadOnlySpan AsReadOnlySpan(this List list) => new(list._items, 0, list.Count);
-
- public static void ReplaceContentsWith(this List list, V collection) where V : ICollection
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ReadOnlySpan AsReadOnlySpan(this List list) => new(list._items, 0, list._size);
+
+ public static void ReplaceContentsWith(this List list, [AllowNull] List collection)
{
if (ReferenceEquals(list, collection))
return;
if (collection is null
- || (collection.Count is var collectionCount
- && collectionCount == 0))
+ || (collection._size is var collectionCount
+ && collectionCount == 0))
{
list.Clear();
return;
}
- if (collectionCount <= list.Count)
+ if (collectionCount <= list._size)
{
- if (collectionCount < list.Count)
- list.RemoveRange(collectionCount, list.Count - collectionCount);
+ if (collectionCount < list._size)
+ list.RemoveRange(collectionCount, list._size - collectionCount);
+
+ collection.UnsafeCopyTo(list, 0);
+ }
+ else
+ {
+ list._items = new T[collectionCount];
+ collection.UnsafeCopyTo(list, 0);
+ list._size = collectionCount;
+ }
+
+ list._version++;
+ }
+
+ public static void ReplaceContentsWith(this List list, [AllowNull] V collection) where V : ICollection
+ {
+ if (typeof(V) == typeof(List))
+ list.ReplaceContentsWith(Unsafe.As>(collection));
+
+ if (ReferenceEquals(list, collection))
+ return;
+
+ if (collection is null
+ || (collection.Count is var collectionCount
+ && collectionCount == 0))
+ {
+ list.Clear();
+ return;
+ }
+
+ if (collectionCount <= list._size)
+ {
+ if (collectionCount < list._size)
+ list.RemoveRange(collectionCount, list._size - collectionCount);
collection.CopyTo(list._items, 0);
}
else
@@ -287,18 +539,176 @@ public static class CollectionExtensions
collection.CopyTo(list._items, 0);
list._size = collectionCount;
}
+
list._version++;
}
+ public static FishTable ToFishTable(this IEnumerable source,
+ Func keySelector)
+ => source.ToFishTable(keySelector, static item => item);
+
+ public static FishTable ToFishTable(this IEnumerable source,
+ Func keySelector, Func valueSelector)
+ {
+ Guard.IsNotNull(source);
+ Guard.IsNotNull(keySelector);
+ Guard.IsNotNull(valueSelector);
+
+ var capacity = 0;
+ if (source is ICollection iCollection)
+ capacity = (int)(iCollection.Count * 1.5f);
+
+ var fishTable = new FishTable(capacity);
+ fishTable.AddRange(source, keySelector, valueSelector);
+ return fishTable;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Clear(this T[] array) => Array.Clear(array, 0, array.Length);
+
+ // public static List Copy(this List source)
+ // {
+ // Guard.IsNotNull(source);
+ //
+ // var destination = new List(source._size);
+ //
+ // if (source._size == 0)
+ // return destination;
+ //
+ // // Unsafe.CopyBlock(ref Unsafe.As(ref destination._items[0]),
+ // // ref Unsafe.As(ref source._items[0]), (uint)sizeof(T) * (uint)source._size);
+ //
+ // FisheryLib.CollectionExtensions.UnsafeBlockCopy(ref source._items[0], // does a type check that causes issues
+ // ref destination._items[0], source._size); // when called from transpiled methods
+ //
+ // destination._size = source._size;
+ //
+ // return destination;
+ // }
+
+ public static unsafe List Copy(this List source)
+ {
+ Guard.IsNotNull(source);
+
+ var destination = new List(source._size);
+
+ if (source._size == 0)
+ return destination;
+
+ fixed (void* destinationPointer = &destination._items[0], sourcePointer = &source._items[0])
+ Unsafe.CopyBlock(destinationPointer, sourcePointer, (uint)(source._size * sizeof(T)));
+
+ destination._size = source._size;
+
+ return destination;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void AddRangeFast(this List list, T[] range)
+ {
+ Guard.IsNotNull(list);
+ Guard.IsNotNull(range);
+
+ if (range.Length < 1)
+ return;
+
+ list.EnsureCapacity(list._size + range.Length);
+ range.UnsafeCopyTo(list._items, list._size);
+
+ list._size += range.Length;
+ list._version++;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Add(this List list, string value)
+ {
+ Guard.IsNotNull(list);
+ Guard.IsNotNull(value);
+
+ if (value.Length < 1)
+ return;
+
+ list.EnsureCapacity(list._size + value.Length);
+ UnsafeBlockCopy(ref value.m_firstChar,
+ ref list._items[list._size], value.Length);
+
+ list._size += value.Length;
+ list._version++;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static StringBuilder Append(this StringBuilder builder, List value)
+ => builder.Append(value._items, 0, value._size);
+
+ public static ref T GetReferenceUnverifiable(this List list, int index) => ref list._items[index];
+
public static IList TryNew(IEnumerable enumerable, Type type) => GetListExtensionMethods(type).New(enumerable);
- public static void TryAddRange(this IList list, T collection, Type type) where T : ICollection => GetListExtensionMethods(type).AddRange(list, collection);
+ public static void TryAddRange(this IList list, T collection, Type type) where T : ICollection
+ => GetListExtensionMethods(type).AddRange(list, collection);
public static Array TryToArray(this IList list, Type type) => GetListExtensionMethods(type).ToArray(list);
+
+ public static unsafe uint GetSizeEstimate(this Dictionary dictionary)
+ => dictionary.GetType().ComputeManagedObjectSizeEstimate()
+ + ((uint)sizeof(Dictionary.Entry) * (uint)(dictionary.entries?.Length ?? 0))
+ + (sizeof(int) * (uint)(dictionary.buckets?.Length ?? 0));
+
+ internal static unsafe uint ComputeManagedObjectSizeEstimate(this Type type)
+ {
+ var objectHeader = (uint)sizeof(void*) * 2u;
+ ref var cachedSize = ref CachedManagedObjectSizes.GetOrAddReference(type.TypeHandle.Value);
+
+ if (cachedSize.Size == uint.MaxValue)
+ UpdateCachedManagedObjectSize(type, ref cachedSize);
+
+ return objectHeader + cachedSize.Size;
+ }
+
+ private static void UpdateCachedManagedObjectSize(Type type, ref ManagedObjectSize cachedSize)
+ {
+ cachedSize.Size = 0;
+ var fields = type.GetFields(BindingFlags.Instance
+ | BindingFlags.Public
+ | BindingFlags.NonPublic);
+
+ for (var i = fields.Length; i-- > 0;)
+ {
+ var fieldType = fields[i].FieldType;
+ if (!typeof(ValueType).IsAssignableFrom(fieldType) || fieldType == typeof(ValueType))
+ // IsValueType and IsSubclassOf somehow cause StackOverflows here
+ cachedSize.Size += (uint)IntPtr.Size;
+ else if (fieldType.IsPrimitive)
+ cachedSize.Size += (uint)Marshal.SizeOf(fieldType);
+ else if (fieldType != type)
+ cachedSize.Size += fieldType.ComputeManagedObjectSizeEstimate();
+ else
+ cachedSize.Size += (uint)IntPtr.Size; // fuck it
+ }
+
+ var remainder = cachedSize.Size % (uint)IntPtr.Size;
+ if (remainder != 0u)
+ cachedSize.Size += (uint)IntPtr.Size - remainder;
+ }
+
+ private static FishTable CachedManagedObjectSizes => _cachedManagedObjectSizes ??= new();
+
+ [ThreadStatic]
+ private static FishTable? _cachedManagedObjectSizes;
+
+ private struct ManagedObjectSize
+ {
+ public uint Size = uint.MaxValue;
+
+ [UsedImplicitly]
+ public ManagedObjectSize()
+ {
+ }
+ }
public static GenericBase GetListExtensionMethods(Type type)
=> ListExtensionCache.TryGetValue(type)
- ?? CreateListExtensionMethods(type);
+ ?? CreateListExtensionMethods(type);
private static GenericBase CreateListExtensionMethods(Type type)
{
@@ -322,7 +732,8 @@ public static class CollectionExtensions
public class Generic : GenericBase
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override void AddRange(IList list, ICollection collection) => ((List)list).AddRange((IEnumerable)collection);
+ public override void AddRange(IList list, ICollection collection)
+ => ((List)list).AddRange((IEnumerable)collection);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override Array ToArray(IList list) => ((List)list).ToArray();
diff --git a/Source/FisheryLib/Collections/FishSet.cs b/Source/FisheryLib/Collections/FishSet.cs
new file mode 100644
index 0000000..ce432b3
--- /dev/null
+++ b/Source/FisheryLib/Collections/FishSet.cs
@@ -0,0 +1,862 @@
+// 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/.
+//
+// Loosely 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/
+
+using System.Collections;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using FisheryLib.FunctionPointers;
+using FisheryLib.Utility;
+using JetBrains.Annotations;
+
+namespace FisheryLib.Collections;
+
+#pragma warning disable CS8766, CS8767
+[PublicAPI]
+[StructLayout(LayoutKind.Sequential)]
+public class FishSet : ISet, IReadOnlyCollection, ICollection
+{
+ private const int FIBONACCI_HASH = -1640531527; // unchecked((int)(uint)Math.Round(uint.MaxValue / FishMath.PHI));
+
+ internal T?[] _buckets;
+
+ private unsafe delegate* _hashCodeGetter = GetHashCode.Default;
+ private unsafe delegate* _equalityComparer = Equals.Default;
+
+ private int
+ _bucketBitShift,
+ _count;
+
+ internal Tails _tails;
+
+ private int
+ _wrapAroundMask,
+ _version;
+
+ private float _maxLoadFactor = 0.5f;
+
+ public int Version
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _version;
+ set
+ {
+ Guard.IsGreaterThan(value, _version);
+ _version = value;
+ }
+ }
+
+ public int Count
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _count;
+ }
+
+ public object SyncRoot => throw new NotImplementedException();
+
+ public bool IsSynchronized => throw new NotImplementedException();
+
+ public bool IsReadOnly => false;
+
+ public event Action?
+ EntryAdded,
+ EntryRemoved;
+
+ public float MaxLoadFactor
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _maxLoadFactor;
+ set
+ {
+ Guard.IsInRange(value, 0f, 1f);
+ _maxLoadFactor = value;
+ }
+ }
+
+ private static T AssertKeyType(object key)
+ {
+ if (key is T tKey)
+ return tKey;
+ else if (key != null!)
+ ThrowHelper.ThrowWrongKeyTypeArgumentException(key);
+
+ return default!;
+ }
+
+ public bool this[T key]
+ {
+ [CollectionAccess(CollectionAccessType.Read)]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => Contains(key);
+
+ [CollectionAccess(CollectionAccessType.UpdatedContent)]
+ set => InsertEntry(key, value);
+ }
+
+ public FishSet() : this(0)
+ {
+ }
+
+ public FishSet(int minimumCapacity) => Initialize(minimumCapacity);
+
+ [MemberNotNull(nameof(_buckets))]
+ [MemberNotNull(nameof(_tails))]
+ private void Initialize(int minimumCapacity = 0)
+ {
+ minimumCapacity = minimumCapacity <= 4 ? 4 : FishMath.NextPowerOfTwo(minimumCapacity);
+ // Mathf.NextPowerOfTwo(minimumCapacity);
+
+ _buckets = new T?[minimumCapacity];
+ _tails = new((uint)minimumCapacity);
+ _bucketBitShift = 32 - FishMath.TrailingZeroCount((uint)_buckets.Length);
+ _wrapAroundMask = _buckets.Length - 1;
+ }
+
+ public FishSet(IEnumerable entries) : this(0)
+ {
+ foreach (var entry in entries)
+ InsertEntry(entry, false);
+ }
+
+ private int GetTailIndex(int entryIndex) => GetIndexForTail(entryIndex, _tails[(uint)entryIndex]);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private int GetIndexForTail(int entryIndex, uint tail) => (entryIndex + GetJumpDistance(tail)) & _wrapAroundMask;
+
+ #region Junk
+
+ // 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)
+ // };
+
+ #endregion
+
+ private static int GetJumpDistance(uint index)
+ => index switch
+ {
+ 2 => 1, 3 => 2, 4 => 3, 5 => 4, 6 => 5, 7 => 8, 8 => 13, 9 => 21, 10 => 34, 11 => 55, 12 => 89, 13 => 144,
+ 14 => 233, 15 => 377, _ => MultiplyWithPhi(index)
+ };
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ [DoesNotReturn]
+ private static int MultiplyWithPhi(uint index)
+ => throw new NotImplementedException(); // (int)(uint)Math.Round(index * FishMath.PHI);
+
+ // 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 bool IsTail(int bucketIndex)
+ => bucketIndex != GetBucketIndexForKeyAt(bucketIndex);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool IsTail(T entry, int bucketIndex)
+ => bucketIndex != GetBucketIndex(entry);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool HasTail(int entryIndex) => !_tails.IsSoloOrEmpty(entryIndex);
+
+ private void SetEntryWithoutTail(int bucketIndex, T entry)
+ {
+ _buckets[bucketIndex] = entry;
+ _tails.SetSolo(bucketIndex);
+ }
+
+ private void SetEntryAsTail(int bucketIndex, T entry, int parentIndex, uint offset)
+ {
+ SetEntryWithoutTail(bucketIndex, entry);
+ _tails[(uint)parentIndex] = offset;
+ }
+
+ private void SetParentTail(int entryIndex, uint newTail)
+ => _tails[(uint)GetParentBucketIndex(entryIndex)] = newTail;
+
+ private bool InsertEntry(T entry, bool allowReplace = true, bool shifting = false)
+ {
+ var addedNewEntry = InsertEntryInternal(entry, allowReplace);
+
+ if (shifting)
+ return addedNewEntry;
+
+ _version++;
+
+ if (!addedNewEntry)
+ return false;
+
+ OnEntryAdded(entry);
+ return true;
+ }
+
+ private void OnEntryAdded(T entry)
+ {
+ // _version++; handled separately
+ _count++;
+ EntryAdded?.Invoke(entry);
+ }
+
+ ///
+ /// Returns true when adding a new entry, false for replacing an existing entry. AllowReplace: false causes throwing
+ /// instead. Does not adjust Count, Version or invoke EntryAdded.
+ ///
+ private bool InsertEntryInternal(T entry, bool allowReplace = true)
+ {
+ StartOfMethod:
+ var bucketIndex = GetBucketIndex(entry);
+
+ ref var bucket = ref _buckets[bucketIndex];
+ if (IsBucketEmpty(bucketIndex))
+ {
+ SetEntryWithoutTail(bucketIndex, entry);
+ return true;
+ }
+
+ if (TryReplaceValueOfMatchingKey(entry, allowReplace, ref bucket!))
+ return false;
+
+ if (CheckResize())
+ goto StartOfMethod;
+
+ if (IsTail(bucket, bucketIndex))
+ {
+ var previousEntry = bucket;
+ var hasTail = !_tails.IsSolo(bucketIndex);
+ var tailingEntries = hasTail ? new T[GetTailCount(bucketIndex)] : null;
+
+ if (hasTail)
+ {
+ var i = 0;
+ var tailIndex = GetTailIndex(bucketIndex);
+
+ while (true)
+ {
+ ref var tailingBucket = ref _buckets[tailIndex];
+ tailingEntries![i++] = tailingBucket!;
+ tailingBucket = default;
+
+ if (!_tails.IsSoloOrEmpty(tailIndex))
+ {
+ var nextTailIndex = GetTailIndex(tailIndex);
+ _tails.SetEmpty(tailIndex);
+ tailIndex = nextTailIndex;
+ }
+ else
+ {
+ _tails.SetEmpty(tailIndex);
+ break;
+ }
+ }
+ }
+
+ SetParentTail(bucketIndex, Tails.SOLO);
+ SetEntryWithoutTail(bucketIndex, entry);
+
+ InsertEntry(previousEntry, shifting: true);
+ if (hasTail)
+ {
+ for (var i = 0; i < tailingEntries!.Length; i++)
+ InsertEntry(tailingEntries[i], shifting: true);
+ }
+
+ return true;
+ }
+ else
+ {
+ return !TryFindTailIndexAndReplace(entry, ref bucketIndex, allowReplace)
+ && InsertAsTail(entry, bucketIndex);
+ }
+ }
+
+ private int GetTailCount(int index)
+ {
+ var tailCount = 0;
+
+ while (HasTail(index))
+ {
+ tailCount++;
+ index = GetTailIndex(index);
+ }
+
+ return tailCount;
+ }
+
+ private bool IsBucketEmpty(int bucketIndex) => IsBucketEmpty(bucketIndex, _tails, _buckets);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool IsBucketEmpty(int bucketIndex, Tails tails, T?[] buckets) => tails.IsEmpty(bucketIndex);
+
+ private bool TryFindTailIndexAndReplace(T entry, ref int bucketIndex, bool allowReplace)
+ {
+ while (true)
+ {
+ if (!TryContinueWithTail(ref bucketIndex))
+ return false;
+
+ if (TryReplaceValueOfMatchingKey(entry, allowReplace, ref _buckets[bucketIndex]!))
+ return true;
+ }
+ }
+
+ private unsafe bool TryReplaceValueOfMatchingKey(T entry, bool allowReplace, ref T bucket)
+ {
+ if (!_equalityComparer(bucket, entry))
+ return false;
+
+ if (!allowReplace)
+ ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(entry);
+
+ bucket = entry;
+ return true;
+ }
+
+ private bool InsertAsTail(T entry, int bucketIndex)
+ {
+ var parentIndex = bucketIndex;
+ var offset = 2u;
+
+ while (true)
+ {
+ bucketIndex = GetIndexForTail(parentIndex, offset);
+
+ if (IsBucketEmpty(bucketIndex))
+ {
+ SetEntryAsTail(bucketIndex, entry, parentIndex, offset);
+ return true;
+ }
+
+ if (IsTail(bucketIndex) && offset < _tails[(uint)GetParentBucketIndex(bucketIndex)])
+ break;
+
+ if (++offset <= 15u)
+ continue;
+
+ if (_buckets.Length > Count * 5)
+ WarnForPossiblyExcessiveResizing(entry);
+
+ Resize();
+ InsertEntry(entry, false, true);
+ return true;
+ }
+
+ var previousEntry = _buckets[bucketIndex];
+ var hasTail = !_tails.IsSolo(bucketIndex);
+
+ var tailingEntries = hasTail ? new T?[GetTailCount(bucketIndex)] : null;
+
+ if (hasTail)
+ {
+ var k = 0;
+ var tailIndex = GetTailIndex(bucketIndex);
+
+ while (true)
+ {
+ ref var tailingBucket = ref _buckets[tailIndex];
+ tailingEntries![k] = tailingBucket;
+
+ k++;
+ tailingBucket = default;
+
+ if (!_tails.IsSoloOrEmpty(tailIndex))
+ {
+ var nextTailIndex = GetTailIndex(tailIndex);
+ _tails.SetEmpty(tailIndex);
+ tailIndex = nextTailIndex;
+ }
+ else
+ {
+ _tails.SetEmpty(tailIndex);
+ break;
+ }
+ }
+ }
+
+ SetParentTail(bucketIndex, Tails.SOLO);
+ SetEntryAsTail(bucketIndex, entry, parentIndex, offset);
+
+ InsertEntry(previousEntry!, shifting: true);
+
+ if (hasTail)
+ {
+ for (var k = 0; k < tailingEntries!.Length; k++)
+ InsertEntry(tailingEntries[k]!, shifting: true);
+ }
+
+ return true;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void WarnForPossiblyExcessiveResizing(T entry)
+ => Log.Warning($"FishTable is resizing from a large number of clashing hashCodes. Last inserted key: '{
+ entry}', count: '{_count}', bucket array length: '{
+ _buckets.Length}', tailing entries: '{GetTailingEntriesCount()}'");
+
+ private int GetTailingEntriesCount()
+ {
+ var count = 0;
+ for (var i = _buckets.Length; i-- > 0;)
+ {
+ if (!IsBucketEmpty(i) && IsTail(i))
+ count++;
+ }
+
+ return count;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private bool CheckResize()
+ {
+ if (_count <= (int)(_buckets.Length * _maxLoadFactor))
+ return false;
+
+ Resize();
+ return true;
+ }
+
+ private void Resize()
+ {
+ var oldBuckets = _buckets;
+ var oldTails = _tails;
+
+ _tails = new((uint)_buckets.Length << 1);
+ _buckets = new T?[_buckets.Length << 1];
+
+ _bucketBitShift = 32 - FishMath.TrailingZeroCount((uint)_buckets.Length);
+ _wrapAroundMask = _buckets.Length - 1;
+
+ for (var i = 0; i < oldBuckets.Length; i++)
+ {
+ if (!IsBucketEmpty(i, oldTails, oldBuckets))
+ InsertEntry(oldBuckets[i]!, false, true);
+ }
+ }
+
+ [CollectionAccess(CollectionAccessType.Read)]
+ public unsafe bool Contains(T key)
+ {
+ var bucketIndex = GetBucketIndex(key);
+
+ while (true)
+ {
+ if (_equalityComparer(key, _buckets[bucketIndex]))
+ return true;
+
+ if (!TryContinueWithTail(ref bucketIndex))
+ return false;
+ }
+ }
+
+ #region GetBucketIndexMethods
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe int GetBucketIndex(T key) => GetBucketIndex(_hashCodeGetter(key));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private int GetBucketIndex(int hashCode) => (hashCode * FIBONACCI_HASH) >>> _bucketBitShift;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private int GetBucketIndexForKeyAt(int index) => GetBucketIndex(_buckets[index]!);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private int GetParentBucketIndex(int childBucketIndex)
+ {
+ var ancestorIndex = GetBucketIndexForKeyAt(childBucketIndex);
+
+ var i = 0;
+
+ while (true)
+ {
+ if (i++ > 32)
+ ThrowHelper.ThrowFailedToFindParentInvalidOperationException(this, childBucketIndex);
+
+ var nextIndex = GetTailIndex(ancestorIndex);
+ if (nextIndex == childBucketIndex)
+ break;
+
+ ancestorIndex = nextIndex;
+ }
+
+ return ancestorIndex;
+ }
+
+ #endregion
+
+ ///
+ /// Returns false for solo or empty, otherwise sets the index to the tail index and returns true
+ ///
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private bool TryContinueWithTail(ref int entryIndex)
+ {
+ if (!HasTail(entryIndex))
+ return false;
+
+ entryIndex = GetTailIndex(entryIndex);
+ return true;
+ }
+
+ [CollectionAccess(CollectionAccessType.UpdatedContent)]
+ public bool Add(T key)
+ {
+ Guard.IsNotNull(key);
+ return InsertEntry(key, false);
+ }
+
+ public void UnionWith(IEnumerable other) => throw new NotImplementedException();
+
+ public void IntersectWith(IEnumerable other) => throw new NotImplementedException();
+
+ public void ExceptWith(IEnumerable other) => throw new NotImplementedException();
+
+ public void SymmetricExceptWith(IEnumerable other) => throw new NotImplementedException();
+
+ public bool IsSubsetOf(IEnumerable other) => throw new NotImplementedException();
+
+ public bool IsSupersetOf(IEnumerable other) => throw new NotImplementedException();
+
+ public bool IsProperSupersetOf(IEnumerable other) => throw new NotImplementedException();
+
+ public bool IsProperSubsetOf(IEnumerable other) => throw new NotImplementedException();
+
+ public bool Overlaps(IEnumerable other) => throw new NotImplementedException();
+
+ public bool SetEquals(IEnumerable other) => throw new NotImplementedException();
+
+ [CollectionAccess(CollectionAccessType.ModifyExistingContent)]
+ public bool Remove(T key)
+ {
+ if (!RemoveInternal(key))
+ return false;
+
+ OnEntryRemoved(key);
+ return true;
+ }
+
+ private void EmplaceWithTails(int entryIndex)
+ {
+ ref var bucket = ref _buckets[entryIndex];
+ var entry = bucket;
+
+ Span tailIndices = stackalloc int[GetTailCount(entryIndex)];
+
+ if (HasTail(entryIndex))
+ {
+ tailIndices[0] = GetTailIndex(entryIndex);
+ for (var i = 1; i < tailIndices.Length; i++)
+ tailIndices[i] = GetTailIndex(tailIndices[i - 1]);
+ }
+
+ if (IsTail(entry!, entryIndex))
+ SetParentTail(entryIndex, Tails.SOLO);
+
+ bucket = default;
+ _tails.SetEmpty(entryIndex);
+ InsertEntry(entry!, shifting: true);
+
+ for (var i = 1; i < tailIndices.Length; i++)
+ {
+ var tailIndex = tailIndices[i];
+ ref var tailBucket = ref _buckets[tailIndex];
+ var tailEntry = tailBucket;
+
+ tailBucket = default;
+ _tails.SetEmpty(tailIndex);
+ InsertEntry(tailEntry!, shifting: true);
+ }
+ }
+
+ private unsafe bool RemoveInternal(T key)
+ {
+ var bucketIndex = GetBucketIndex(key);
+
+ while (true)
+ {
+ ref var entry = ref _buckets[bucketIndex];
+
+ if (_equalityComparer(key, entry))
+ {
+ var tailIndex = -1;
+ if (IsTail(entry!, bucketIndex))
+ {
+ if (HasTail(bucketIndex))
+ tailIndex = GetTailIndex(bucketIndex);
+ else
+ SetParentTail(bucketIndex, Tails.SOLO);
+ }
+
+ entry = default;
+ _tails.SetEmpty(bucketIndex);
+
+ if (tailIndex != -1)
+ EmplaceWithTails(tailIndex);
+
+ return true;
+ }
+
+ if (TryContinueWithTail(ref bucketIndex))
+ continue;
+
+ return false;
+ }
+ }
+
+ void ICollection.Add(T item) => Add(item);
+
+ [CollectionAccess(CollectionAccessType.ModifyExistingContent)]
+ public void Clear()
+ {
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
+ if (_buckets is null)
+ Initialize();
+
+ Array.Clear(_buckets, 0, _buckets.Length);
+ _tails.Reset();
+
+ _count = 0;
+ _version++;
+ }
+
+ [CollectionAccess(CollectionAccessType.Read)]
+ public void CopyTo(T[] array, int arrayIndex)
+ {
+ var length = _buckets.Length;
+ for (var i = 0; i < length; i++)
+ {
+ if (IsBucketEmpty(i))
+ continue;
+
+ array[arrayIndex] = _buckets[i]!;
+ arrayIndex++;
+ }
+ }
+
+ private void OnEntryRemoved(T entry)
+ {
+ _version++;
+ _count--;
+ EntryRemoved?.Invoke(entry);
+ }
+
+ #region InterfaceMethods
+
+ [CollectionAccess(CollectionAccessType.Read)]
+ 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);
+
+ void ICollection.CopyTo(Array array, int index)
+ {
+ if (array is T[] typedArray)
+ {
+ CopyTo(typedArray, index);
+ return;
+ }
+
+ var length = _buckets.Length;
+ for (var i = 0; i < length; i++)
+ {
+ if (IsBucketEmpty(i))
+ continue;
+
+ ((IList)array)[index] = _buckets[i];
+ index++;
+ }
+ }
+
+ #endregion
+
+ #region Structs
+
+ internal struct Tails
+ {
+ public const uint
+ EMPTY = 0u,
+ SOLO = 1u;
+
+ private NibbleArray _values;
+
+ public Tails(uint length) => _values = new(length);
+
+ public uint this[uint index]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _values[index];
+
+ set => _values[index] = value;
+ }
+
+ public void Reset() => _values.Clear(); //_values.Initialize(15u);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsEmpty(int index) => _values[(uint)index] == EMPTY;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsSolo(int index) => _values[(uint)index] == SOLO;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsSoloOrEmpty(int index) => _values[(uint)index] <= 1u;
+
+ public void SetSolo(int index) => _values[(uint)index] = SOLO;
+
+ public void SetEmpty(int index) => _values[(uint)index] = EMPTY;
+ }
+
+ public record struct Enumerator : IEnumerator
+ {
+ private readonly FishSet _dictionary;
+ private readonly int _version;
+ private uint _index;
+ private T? _current;
+
+ internal const int DICT_ENTRY = 1;
+ internal const int KEY_VALUE_PAIR = 2;
+
+ internal Enumerator(FishSet dictionary, int getEnumeratorRetType)
+ {
+ _dictionary = dictionary;
+ _version = dictionary._version;
+ _index = 0u;
+ _current = default;
+ }
+
+ public bool MoveNext()
+ {
+ if (_version != _dictionary._version)
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+
+ while (_index < (uint)_dictionary._buckets.Length)
+ {
+ if (_dictionary.IsBucketEmpty((int)_index++))
+ continue;
+
+ _current = _dictionary._buckets[_index - 1]!;
+ return true;
+ }
+
+ _index = (uint)_dictionary._buckets.Length + 1u;
+ _current = default;
+ return false;
+ }
+
+ [CollectionAccess(CollectionAccessType.Read)]
+ public T Current => _current!;
+
+ public void Dispose()
+ {
+ }
+
+ object IEnumerator.Current
+ {
+ get
+ {
+ if (_index == 0u || _index == (uint)_dictionary._buckets.Length + 1u)
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
+
+ return _current!;
+ }
+ }
+
+ void IEnumerator.Reset()
+ {
+ if (_version != _dictionary._version)
+ ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
+
+ _index = 0u;
+ _current = default;
+ }
+ }
+
+ #endregion
+
+ 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 ThrowWrongKeyTypeArgumentException(object? key)
+ {
+ Guard.IsNotNull(key);
+ throw new ArgumentException($"The value \"{key}\" is not of type \"{
+ typeof(T)}\" and cannot be used in this generic collection.",
+ nameof(key));
+ }
+
+ [DoesNotReturn]
+ internal static void ThrowAddingDuplicateWithKeyArgumentException(T key)
+ {
+ Guard.IsNotNull(key);
+ ThrowAddingDuplicateWithKeyArgumentException((object)key);
+ }
+
+ [DoesNotReturn]
+ internal static unsafe void ThrowFailedToFindParentInvalidOperationException(FishSet fishTable,
+ int childBucketIndex)
+ {
+ var entry = fishTable._buckets[childBucketIndex];
+ throw new InvalidOperationException($"Failed to find parent index in FishTable for key: '{
+ entry}', hashCode: '{fishTable._hashCodeGetter(entry)}', count: '{
+ fishTable._count}', bucket array length: '{fishTable._buckets.Length}', tailing entries: '{
+ fishTable.GetTailingEntriesCount()}'");
+ }
+
+ [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
\ No newline at end of file
diff --git a/Source/FisheryLib/Collections/FishTable.cs b/Source/FisheryLib/Collections/FishTable.cs
new file mode 100644
index 0000000..718249d
--- /dev/null
+++ b/Source/FisheryLib/Collections/FishTable.cs
@@ -0,0 +1,1475 @@
+// 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/.
+//
+// Loosely 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/
+
+using System.Collections;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Security;
+using FisheryLib.FunctionPointers;
+using FisheryLib.Utility;
+using JetBrains.Annotations;
+
+namespace FisheryLib.Collections;
+
+#pragma warning disable CS8766, CS8767
+[PublicAPI]
+[StructLayout(LayoutKind.Sequential)]
+public class FishTable : IDictionary, IDictionary, IReadOnlyDictionary
+{
+ private const int FIBONACCI_HASH = -1640531527; // unchecked((int)(uint)Math.Round(uint.MaxValue / FishMath.PHI));
+
+ 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;
+
+ internal Tails _tails;
+
+ private int
+ _wrapAroundMask,
+ _version;
+
+ private float _maxLoadFactor = 0.5f;
+
+ public ICollection Keys => throw new NotImplementedException();
+
+ public ICollection Values => throw new NotImplementedException();
+
+ public int Version
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _version;
+ set
+ {
+ Guard.IsGreaterThan(value, _version);
+ _version = value;
+ }
+ }
+
+ public int Count
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _count;
+ }
+
+ public bool IsReadOnly => false;
+
+ public unsafe uint SizeEstimate
+ => GetType().ComputeManagedObjectSizeEstimate()
+ + ((uint)sizeof(Entry) * (uint)_buckets.Length)
+ + ((_tails.Length + 1u) >> 1);
+
+ public event Action>?
+ EntryAdded,
+ EntryRemoved;
+
+ public delegate void EntryEventHandler(TKey key, ref TValue value);
+
+ public event Action? KeyAdded
+ {
+ add => AddEvent(ref EntryAdded, value);
+ remove => RemoveEvent(ref EntryAdded, value);
+ }
+
+ public event Action? KeyRemoved
+ {
+ add => AddEvent(ref EntryRemoved, value);
+ remove => RemoveEvent(ref EntryRemoved, value);
+ }
+
+ public event Action? ValueAdded
+ {
+ add => AddEvent(ref EntryAdded, value);
+ remove => RemoveEvent(ref EntryAdded, value);
+ }
+
+ public event Action]