diff --git a/1.2/Assemblies/0PrepatcherAPI.dll b/1.2/Assemblies/0PrepatcherAPI.dll
new file mode 100644
index 0000000..d869ece
Binary files /dev/null and b/1.2/Assemblies/0PrepatcherAPI.dll differ
diff --git a/1.2/Assemblies/1Fishery.dll b/1.2/Assemblies/1Fishery.dll
new file mode 100644
index 0000000..e24ccf0
Binary files /dev/null and b/1.2/Assemblies/1Fishery.dll differ
diff --git a/1.2/Assemblies/1Fishery.pdb b/1.2/Assemblies/1Fishery.pdb
new file mode 100644
index 0000000..0c555cb
Binary files /dev/null and b/1.2/Assemblies/1Fishery.pdb differ
diff --git a/1.2/Assemblies/System.Runtime.CompilerServices.Unsafe.dll b/1.2/Assemblies/System.Runtime.CompilerServices.Unsafe.dll
new file mode 100644
index 0000000..c5ba4e4
Binary files /dev/null and b/1.2/Assemblies/System.Runtime.CompilerServices.Unsafe.dll differ
diff --git a/1.3/Assemblies/0PrepatcherAPI.dll b/1.3/Assemblies/0PrepatcherAPI.dll
new file mode 100644
index 0000000..d869ece
Binary files /dev/null and b/1.3/Assemblies/0PrepatcherAPI.dll differ
diff --git a/1.3/Assemblies/1Fishery.dll b/1.3/Assemblies/1Fishery.dll
index 33a4226..3af3a7a 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
index 71148c8..1294fc7 100644
Binary files a/1.3/Assemblies/1Fishery.pdb and b/1.3/Assemblies/1Fishery.pdb differ
diff --git a/1.4/Assemblies/0PrepatcherAPI.dll b/1.4/Assemblies/0PrepatcherAPI.dll
new file mode 100644
index 0000000..d869ece
Binary files /dev/null and b/1.4/Assemblies/0PrepatcherAPI.dll differ
diff --git a/1.4/Assemblies/1Fishery.dll b/1.4/Assemblies/1Fishery.dll
index 7d5428b..560bdd8 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
index f0f2663..9a41571 100644
Binary files a/1.4/Assemblies/1Fishery.pdb and b/1.4/Assemblies/1Fishery.pdb differ
diff --git a/1.5/Assemblies/0PrepatcherAPI.dll b/1.5/Assemblies/0PrepatcherAPI.dll
new file mode 100644
index 0000000..d869ece
Binary files /dev/null and b/1.5/Assemblies/0PrepatcherAPI.dll differ
diff --git a/1.5/Assemblies/1Fishery.dll b/1.5/Assemblies/1Fishery.dll
new file mode 100644
index 0000000..4e70540
Binary files /dev/null and b/1.5/Assemblies/1Fishery.dll differ
diff --git a/1.5/Assemblies/1Fishery.pdb b/1.5/Assemblies/1Fishery.pdb
new file mode 100644
index 0000000..828348d
Binary files /dev/null and b/1.5/Assemblies/1Fishery.pdb differ
diff --git a/1.5/Assemblies/System.Runtime.CompilerServices.Unsafe.dll b/1.5/Assemblies/System.Runtime.CompilerServices.Unsafe.dll
new file mode 100644
index 0000000..c5ba4e4
Binary files /dev/null and b/1.5/Assemblies/System.Runtime.CompilerServices.Unsafe.dll differ
diff --git a/About/About.xml b/About/About.xml
index 53edef4..e8cd822 100644
--- a/About/About.xml
+++ b/About/About.xml
@@ -3,10 +3,13 @@
Fishery - Modding Library
bs.fishery
+ 1.2
1.3
1.4
+ 1.5
bradson
+ FisheryLib/ModIcon
https://github.com/bbradson/fishery
Ludeon.RimWorld
diff --git a/About/ModIcon.dds b/About/ModIcon.dds
new file mode 100644
index 0000000..0fdd3e5
Binary files /dev/null and b/About/ModIcon.dds differ
diff --git a/About/ModIcon.png b/About/ModIcon.png
new file mode 100644
index 0000000..0cc4e5f
Binary files /dev/null and b/About/ModIcon.png differ
diff --git a/Source/FisheryLib/.editorconfig b/Source/FisheryLib/.editorconfig
index 729881f..0e42c22 100644
--- a/Source/FisheryLib/.editorconfig
+++ b/Source/FisheryLib/.editorconfig
@@ -68,7 +68,7 @@ dotnet_naming_rule.public_or_internal_field_should_be_camel_case.style = camel_c
# Symbol specifications
dotnet_naming_symbols.constant.applicable_kinds = field, local
-dotnet_naming_symbols.constant.applicable_accessibilities = *
+dotnet_naming_symbols.constant.applicable_accessibilities = private, local
dotnet_naming_symbols.constant.required_modifiers = const
dotnet_naming_symbols.private_or_protected_field.applicable_kinds = field
diff --git a/Source/FisheryLib/1.2.csproj b/Source/FisheryLib/1.2.csproj
new file mode 100644
index 0000000..51ff8d0
--- /dev/null
+++ b/Source/FisheryLib/1.2.csproj
@@ -0,0 +1,18 @@
+
+
+
+ ..\..\1.2\Assemblies\
+ Copyright (c) 2024 Bradson
+
+
+ DEBUG;V1_2
+
+
+ V1_2
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/FisheryLib/1.3.csproj b/Source/FisheryLib/1.3.csproj
new file mode 100644
index 0000000..df5d68b
--- /dev/null
+++ b/Source/FisheryLib/1.3.csproj
@@ -0,0 +1,17 @@
+
+
+
+ ..\..\1.3\Assemblies\
+ Copyright (c) 2022 Bradson
+
+
+ DEBUG;V1_3
+
+
+ V1_3
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/FisheryLib/1.4.csproj b/Source/FisheryLib/1.4.csproj
new file mode 100644
index 0000000..fbfba2e
--- /dev/null
+++ b/Source/FisheryLib/1.4.csproj
@@ -0,0 +1,17 @@
+
+
+
+ ..\..\1.4\Assemblies\
+ Copyright (c) 2022 Bradson
+
+
+ DEBUG;V1_4
+
+
+ V1_4
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/FisheryLib/1.5.csproj b/Source/FisheryLib/1.5.csproj
new file mode 100644
index 0000000..c4c1819
--- /dev/null
+++ b/Source/FisheryLib/1.5.csproj
@@ -0,0 +1,17 @@
+
+
+
+ ..\..\1.5\Assemblies\
+ Copyright (c) 2024 Bradson
+
+
+ DEBUG;V1_5
+
+
+ V1_5
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/FisheryLib/Aliases.cs b/Source/FisheryLib/Aliases.cs
index bbc7b5f..93af238 100644
--- a/Source/FisheryLib/Aliases.cs
+++ b/Source/FisheryLib/Aliases.cs
@@ -5,7 +5,6 @@
global using static FisheryLib.Aliases;
using System.Runtime.CompilerServices;
-using JetBrains.Annotations;
namespace FisheryLib;
diff --git a/Source/FisheryLib/BoolExtensions.cs b/Source/FisheryLib/BoolExtensions.cs
index b25c288..396ca4a 100644
--- a/Source/FisheryLib/BoolExtensions.cs
+++ b/Source/FisheryLib/BoolExtensions.cs
@@ -5,7 +5,6 @@
// https://github.com/CommunityToolkit/dotnet/blob/main/CommunityToolkit.HighPerformance/Extensions/BoolExtensions.cs
using System.Runtime.CompilerServices;
-using JetBrains.Annotations;
namespace FisheryLib;
diff --git a/Source/FisheryLib/Cecil/ReflectionExtensions.cs b/Source/FisheryLib/Cecil/ReflectionExtensions.cs
index d00df79..afc256e 100644
--- a/Source/FisheryLib/Cecil/ReflectionExtensions.cs
+++ b/Source/FisheryLib/Cecil/ReflectionExtensions.cs
@@ -4,7 +4,6 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
using System.Diagnostics.CodeAnalysis;
-using JetBrains.Annotations;
using Mono.Cecil;
using Mono.Collections.Generic;
diff --git a/Source/FisheryLib/CollectionExtensions.cs b/Source/FisheryLib/CollectionExtensions.cs
index 1c32f8a..75591e9 100644
--- a/Source/FisheryLib/CollectionExtensions.cs
+++ b/Source/FisheryLib/CollectionExtensions.cs
@@ -9,7 +9,6 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using FisheryLib.Collections;
-using JetBrains.Annotations;
namespace FisheryLib;
@@ -77,8 +76,8 @@ public static class CollectionExtensions
return array;
}
- public static Telement[] ToArray(this Tenumerable enumerable, Telement[] destination)
- where Tenumerable : IList
+ public static TElement[] ToArray(this TEnumerable enumerable, TElement[] destination)
+ where TEnumerable : IList
{
Guard.IsNotNull(destination);
@@ -112,9 +111,9 @@ public static class CollectionExtensions
}
}
- public static void Fill(this Telement[] array, Tcollection collection, int startIndex = 0,
+ public static void Fill(this TElement[] array, TCollection collection, int startIndex = 0,
int count = -1)
- where Tcollection : IList
+ where TCollection : IList
{
Guard.IsNotNull(collection);
@@ -269,13 +268,13 @@ public static class CollectionExtensions
return default;
}
-
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TValue GetOrAddReference(this Dictionary dictionary, TKey key)
where TValue : new()
{
var keyCode = HashCode.Get(key) & int.MaxValue;
-
+
StartOfLookup:
if (dictionary.buckets != null)
{
@@ -293,7 +292,7 @@ public static class CollectionExtensions
dictionary.Add(key, Reflection.New());
goto StartOfLookup;
}
-
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TValue GetOrAddReference(this Dictionary dictionary, ref TKey key)
where TValue : new()
@@ -345,7 +344,7 @@ public static class CollectionExtensions
// dictionary.Add(key, Reflection.New());
// goto StartOfLookup;
// }
-
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TValue GetOrAdd(this Dictionary dictionary, TKey key)
where TValue : new()
@@ -369,7 +368,7 @@ public static class CollectionExtensions
dictionary.Add(key, Reflection.New());
goto StartOfLookup;
}
-
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TValue GetOrAdd(this Dictionary dictionary, ref TKey key)
where TValue : new()
@@ -393,7 +392,7 @@ public static class CollectionExtensions
dictionary.Add(key, Reflection.New());
goto StartOfLookup;
}
-
+
public static bool TryGetOrAddValue(this Dictionary dictionary, TKey key,
out TValue value)
{
@@ -431,10 +430,10 @@ public static class CollectionExtensions
var entries = dictionary.entries;
if (entries is null)
return 0;
-
+
var removedCount = 0;
var entriesCount = entries.Length;
-
+
for (var i = 0; i < entriesCount; i++)
{
ref var entry = ref entries[i];
@@ -442,8 +441,8 @@ public static class CollectionExtensions
continue;
// predicate might modify collection, turning a ref invalid
- var keyValuePair = new KeyValuePair(entry.key, entry.value);
-
+ var keyValuePair = new KeyValuePair(entry.key, entry.value);
+
if (!predicate(keyValuePair))
continue;
@@ -468,7 +467,7 @@ public static class CollectionExtensions
[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)
{
@@ -486,11 +485,11 @@ public static class CollectionExtensions
[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),
@@ -513,7 +512,7 @@ public static class CollectionExtensions
[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))
@@ -531,7 +530,7 @@ public static class CollectionExtensions
{
if (collectionCount < list._size)
list.RemoveRange(collectionCount, list._size - collectionCount);
-
+
collection.UnsafeCopyTo(list, 0);
}
else
@@ -544,11 +543,12 @@ public static class CollectionExtensions
list._version++;
}
- public static void ReplaceContentsWith(this List list, [AllowNull] V collection) where V : ICollection
+ public static void ReplaceContentsWith(this List list, [AllowNull] TOther collection)
+ where TOther : ICollection
{
- if (typeof(V) == typeof(List))
- list.ReplaceContentsWith(Unsafe.As>(collection));
-
+ if (typeof(TOther) == typeof(List))
+ list.ReplaceContentsWith(Unsafe.As>(collection));
+
if (ReferenceEquals(list, collection))
return;
@@ -568,7 +568,7 @@ public static class CollectionExtensions
}
else
{
- list._items = new T[collectionCount];
+ list._items = new TThis[collectionCount];
collection.CopyTo(list._items, 0);
list._size = collectionCount;
}
@@ -598,7 +598,7 @@ public static class CollectionExtensions
[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);
@@ -618,30 +618,30 @@ public static class CollectionExtensions
//
// 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;
@@ -651,13 +651,13 @@ public static class CollectionExtensions
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;
@@ -702,7 +702,7 @@ public static class CollectionExtensions
=> GetListExtensionMethods(type).AddRange(list, collection);
public static Array TryToArray(this IList list, Type type) => GetListExtensionMethods(type).ToArray(list);
-
+
public static void RemoveAtFastUnordered(this List list, int index)
{
ref var lastBucket = ref list._items[list._size - 1];
@@ -710,33 +710,33 @@ public static class CollectionExtensions
lastBucket = default!;
list._size--;
}
-
+
public static bool RemoveFastUnordered(this List list, T item)
{
var index = list.IndexOf(item);
if (index < 0)
return false;
-
+
list.RemoveAtFastUnordered(index);
return true;
}
public static void InsertFastUnordered(this List list, int index, T item)
{
- if ((uint) index > (uint) list._size)
+ if ((uint)index > (uint)list._size)
ThrowHelper.ThrowArgumentOutOfRangeException();
-
+
if (list._size == list._items.Length)
list.EnsureCapacity(list._size + 1);
ref var targetBucket = ref list._items[index];
-
+
list._items[list._size] = targetBucket;
targetBucket = item;
list._size++;
list._version++;
}
-
+
public static unsafe uint GetSizeEstimate(this Dictionary dictionary)
=> dictionary.GetType().ComputeManagedObjectSizeEstimate()
+ ((uint)sizeof(Dictionary.Entry) * (uint)(dictionary.entries?.Length ?? 0))
diff --git a/Source/FisheryLib/Collections/FishSet.cs b/Source/FisheryLib/Collections/FishSet.cs
index fd62cf1..07ed6df 100644
--- a/Source/FisheryLib/Collections/FishSet.cs
+++ b/Source/FisheryLib/Collections/FishSet.cs
@@ -7,6 +7,9 @@
// 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 V1_2
+extern alias nuget;
+#endif
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
@@ -14,7 +17,10 @@ using System.Runtime.InteropServices;
using FisheryLib.FunctionPointers;
using FisheryLib.Pools;
using FisheryLib.Utility;
-using JetBrains.Annotations;
+#if V1_2
+using CollectionAccessType = nuget::JetBrains.Annotations.CollectionAccessType;
+using CollectionAccessAttribute = nuget::JetBrains.Annotations.CollectionAccessAttribute;
+#endif
namespace FisheryLib.Collections;
@@ -42,6 +48,8 @@ public class FishSet : ISet, IReadOnlyCollection, ICollection
private float _maxLoadFactor = 0.5f;
+ private T _defaultValue = Internal.KeyUtility.Default;
+
public int Version
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -65,6 +73,22 @@ public class FishSet : ISet, IReadOnlyCollection, ICollection
public bool IsReadOnly => false;
+ ///
+ /// Empty FishSets have their entries set to a default value, causing fishSet.Contains(DefaultValue) to return
+ /// true. This property can be used for a check against this default, similar to a null. Unless set to a different
+ /// default through the FishSet constructor, for most reference types, the returned value is a static invalid
+ /// reference. Reference types implementing IEquatable or overriding Equals have null as default instead. Primitives
+ /// have all their bytes set to sbyte.MaxValue. Structs have their contents set according to previous rules.
+ ///
+ public T DefaultValue => _defaultValue;
+
+ public unsafe uint SizeEstimate
+ => GetType().ComputeManagedObjectSizeEstimate()
+ + ((uint)sizeof(T) * (uint)_buckets.Length)
+ + ((_tails.Length + 1u) >> 1);
+
+ public uint CollisionCount => (uint)GetTailingEntriesCount();
+
public event Action?
EntryAdded,
EntryRemoved;
@@ -112,6 +136,19 @@ public class FishSet : ISet, IReadOnlyCollection, ICollection
public FishSet(int minimumCapacity) => Initialize(minimumCapacity);
+ ///
+ /// Initialize a new FishSet
+ ///
+ /// The minimum capacity of the backing array
+ ///
+ /// The default value for empty entries. Contains() returns true for this value when empty
+ ///
+ public FishSet(int minimumCapacity, T defaultValue)
+ {
+ _defaultValue = defaultValue;
+ Initialize(minimumCapacity);
+ }
+
[MemberNotNull(nameof(_buckets))]
[MemberNotNull(nameof(_tails))]
private void Initialize(int minimumCapacity = 0)
@@ -120,6 +157,7 @@ public class FishSet : ISet, IReadOnlyCollection, ICollection
// Mathf.NextPowerOfTwo(minimumCapacity);
_buckets = new T?[minimumCapacity];
+ Array.Fill(_buckets, _defaultValue);
_tails = new((uint)minimumCapacity);
_bucketBitShift = 32 - FishMath.TrailingZeroCount((uint)_buckets.Length);
_wrapAroundMask = _buckets.Length - 1;
@@ -167,7 +205,7 @@ public class FishSet : ISet, IReadOnlyCollection, ICollection
private void SetBucketEmpty(int index)
{
- _buckets[index] = default;
+ _buckets[index] = _defaultValue;
_tails.SetEmpty(index);
}
@@ -356,7 +394,7 @@ public class FishSet : ISet, IReadOnlyCollection, ICollection
{
ref var tailingBucket = ref _buckets[tailIndex];
tailingEntriesList.Add(tailingBucket!);
- tailingBucket = default;
+ tailingBucket = _defaultValue;
if (HasTail(tailIndex))
{
@@ -409,6 +447,7 @@ public class FishSet : ISet, IReadOnlyCollection, ICollection
_tails = new((uint)_buckets.Length << 1);
_buckets = new T?[_buckets.Length << 1];
+ Array.Fill(_buckets, _defaultValue);
_bucketBitShift = 32 - FishMath.TrailingZeroCount((uint)_buckets.Length);
_wrapAroundMask = _buckets.Length - 1;
@@ -579,7 +618,7 @@ public class FishSet : ISet, IReadOnlyCollection, ICollection
if (_buckets is null)
Initialize();
- Array.Clear(_buckets, 0, _buckets.Length);
+ Array.Fill(_buckets, Internal.KeyUtility.Default);
_tails.Reset();
_count = 0;
@@ -660,6 +699,8 @@ public class FishSet : ISet, IReadOnlyCollection, ICollection
private NibbleArray _values = new(length);
+ public uint Length => _values.Length;
+
public uint this[uint index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/Source/FisheryLib/Collections/FishTable.cs b/Source/FisheryLib/Collections/FishTable.cs
index 4e0504f..8a33374 100644
--- a/Source/FisheryLib/Collections/FishTable.cs
+++ b/Source/FisheryLib/Collections/FishTable.cs
@@ -7,15 +7,22 @@
// 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 V1_2
+extern alias nuget;
+#endif
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
+using FisheryLib.Collections.Internal;
using FisheryLib.FunctionPointers;
using FisheryLib.Pools;
using FisheryLib.Utility;
-using JetBrains.Annotations;
+#if V1_2
+using CollectionAccessType = nuget::JetBrains.Annotations.CollectionAccessType;
+using CollectionAccessAttribute = nuget::JetBrains.Annotations.CollectionAccessAttribute;
+#endif
namespace FisheryLib.Collections;
@@ -45,6 +52,8 @@ public class FishTable : IDictionary, IDictionary, I
_version;
private float _maxLoadFactor = 0.5f;
+
+ private Entry _defaultEntry = Entry.Default;
public KeyCollection Keys => new(this);
@@ -65,6 +74,15 @@ public class FishTable : IDictionary, IDictionary, I
public bool IsReadOnly => false;
+ ///
+ /// Empty FishTables have their entries set to a default value, causing fishTable.ContainsKey(DefaultKey) to return
+ /// true. This property can be used for a check against this default, similar to a null. Unless set to a different
+ /// default through the FishTable constructor, for most reference types, the returned value is a static invalid
+ /// reference. Reference types implementing IEquatable or overriding Equals have null as default instead. Primitives
+ /// have all their bytes set to sbyte.MaxValue. Structs have their contents set according to previous rules.
+ ///
+ public TKey DefaultKey => _defaultEntry.Key;
+
public unsafe uint SizeEstimate
=> GetType().ComputeManagedObjectSizeEstimate()
+ ((uint)sizeof(Entry) * (uint)_buckets.Length)
@@ -227,6 +245,19 @@ public class FishTable : IDictionary, IDictionary, I
public FishTable(int minimumCapacity) => Initialize(minimumCapacity);
+ ///
+ /// Initialize a new FishTable
+ ///
+ /// The minimum capacity of the backing array
+ ///
+ /// The default key for empty entries. ContainsKey() returns true for this when empty
+ ///
+ public FishTable(int minimumCapacity, TKey defaultKey)
+ {
+ _defaultEntry.Key = defaultKey;
+ Initialize(minimumCapacity);
+ }
+
[MemberNotNull(nameof(_buckets))]
[MemberNotNull(nameof(_tails))]
private void Initialize(int minimumCapacity = 0)
@@ -235,6 +266,7 @@ public class FishTable : IDictionary, IDictionary, I
// Mathf.NextPowerOfTwo(minimumCapacity);
_buckets = new Entry[minimumCapacity];
+ Array.Fill(_buckets, _defaultEntry);
_tails = new((uint)minimumCapacity);
_bucketBitShift = 32 - FishMath.TrailingZeroCount((uint)_buckets.Length);
_wrapAroundMask = _buckets.Length - 1;
@@ -280,7 +312,7 @@ public class FishTable : IDictionary, IDictionary, I
private void SetBucketEmpty(int index)
{
- _buckets[index] = default;
+ _buckets[index] = _defaultEntry;
_tails.SetEmpty(index);
}
@@ -400,8 +432,11 @@ public class FishTable : IDictionary, IDictionary, I
{
ref var bucket = ref buckets[bucketIndex];
- if (!_keyEqualityComparer(bucket.Key, default))
+ if (!CompareBytes(bucket.Key, _defaultEntry.Key)
+ && !_keyEqualityComparer(bucket.Key, _defaultEntry.Key))
+ {
ThrowHelper.ThrowEmptyBucketKeyInvalidOperationException(bucketIndex, bucket);
+ }
if (bucket.Value.Equals(default))
return true;
@@ -412,6 +447,43 @@ public class FishTable : IDictionary, IDictionary, I
return false; // no good workaround without performance hit
}
+ private static unsafe bool CompareBytes(TKey a, TKey b)
+ {
+ var size = sizeof(TKey);
+ var iOffset = 0;
+ var i = 0;
+ do
+ {
+ var currentA = *((nint*)&a + iOffset);
+ var currentB = *((nint*)&b + iOffset);
+
+ if (size - i >= sizeof(nint))
+ {
+ if (currentA != currentB)
+ return false;
+
+ iOffset++;
+ i += sizeof(nint);
+ }
+ else
+ {
+ var remainingSize = size & sizeof(nint);
+ i = 0;
+ do
+ {
+ if ((byte)(currentA >> i) != (byte)(currentB >> i))
+ return false;
+ }
+ while (++i < remainingSize);
+
+ break;
+ }
+ }
+ while (i < size);
+
+ return size != 0;
+ }
+
private bool TryFindTailIndexAndReplace(in Entry entry, ref int bucketIndex, ReplaceBehaviour replaceBehaviour)
{
while (true)
@@ -498,7 +570,7 @@ public class FishTable : IDictionary, IDictionary, I
{
ref var tailingBucket = ref _buckets[tailIndex];
tailingEntriesList.Add(tailingBucket);
- tailingBucket = default;
+ tailingBucket = _defaultEntry;
if (HasTail(tailIndex))
{
@@ -585,6 +657,7 @@ public class FishTable : IDictionary, IDictionary, I
_tails = new((uint)newSize);
_buckets = new Entry[newSize];
+ Array.Fill(_buckets, _defaultEntry);
_bucketBitShift = 32 - FishMath.TrailingZeroCount((uint)newSize);
_wrapAroundMask = _buckets.Length - 1;
@@ -1046,7 +1119,7 @@ public class FishTable : IDictionary, IDictionary, I
private void EmplaceWithTailsBackward(int entryIndex)
{
if (IsBucketEmpty(entryIndex))
- Utility.Diagnostics.ThrowHelper.ThrowInvalidOperationException("Tried to emplace entry bucket");
+ Utility.Diagnostics.ThrowHelper.ThrowInvalidOperationException("Tried to emplace empty bucket");
while (true)
{
@@ -1225,7 +1298,7 @@ public class FishTable : IDictionary, IDictionary, I
if (_buckets is null)
Initialize();
- Array.Clear(_buckets, 0, _buckets.Length);
+ Array.Fill(_buckets, _defaultEntry);
_tails.Reset();
_count = 0;
@@ -1360,6 +1433,8 @@ public class FishTable : IDictionary, IDictionary, I
.Append(Value)
.Append(" }").ToString();
}
+
+ public static readonly Entry Default = new(KeyUtility.Default, default);
}
internal struct Tails(uint length)
@@ -1688,11 +1763,11 @@ public class FishTable : IDictionary, IDictionary, I
internal static void ThrowKeyNotFoundException(T key)
{
Guard.IsNotNull(key);
- ThrowKeyNotFoundException((object)key);
+ ThrowKeyNotFoundException((object?)key);
}
[DoesNotReturn]
- private static void ThrowKeyNotFoundException(object key)
+ private static void ThrowKeyNotFoundException(object? key)
=> throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary.");
[DoesNotReturn]
@@ -1715,7 +1790,7 @@ public class FishTable : IDictionary, IDictionary, I
internal static bool ThrowAddingDuplicateWithKeyArgumentException(T key)
{
Guard.IsNotNull(key);
- ThrowAddingDuplicateWithKeyArgumentException((object)key);
+ ThrowAddingDuplicateWithKeyArgumentException((object?)key);
return true;
}
@@ -1782,7 +1857,7 @@ public class FishTable : IDictionary, IDictionary, I
}
[DoesNotReturn]
- private static void ThrowAddingDuplicateWithKeyArgumentException(object key)
+ private static void ThrowAddingDuplicateWithKeyArgumentException(object? key)
=> throw new ArgumentException($"An item with the same key has already been added. Key: {key}");
}
}
diff --git a/Source/FisheryLib/Collections/IndexedFishMap.cs b/Source/FisheryLib/Collections/IndexedFishMap.cs
index 7791411..a47de24 100644
--- a/Source/FisheryLib/Collections/IndexedFishMap.cs
+++ b/Source/FisheryLib/Collections/IndexedFishMap.cs
@@ -6,7 +6,6 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
-using JetBrains.Annotations;
namespace FisheryLib.Collections;
diff --git a/Source/FisheryLib/Collections/IndexedFishSet.cs b/Source/FisheryLib/Collections/IndexedFishSet.cs
index 19c696c..9d1b106 100644
--- a/Source/FisheryLib/Collections/IndexedFishSet.cs
+++ b/Source/FisheryLib/Collections/IndexedFishSet.cs
@@ -5,7 +5,6 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
-using JetBrains.Annotations;
namespace FisheryLib.Collections;
diff --git a/Source/FisheryLib/Collections/Internal/KeyUtility.cs b/Source/FisheryLib/Collections/Internal/KeyUtility.cs
new file mode 100644
index 0000000..f6573b0
--- /dev/null
+++ b/Source/FisheryLib/Collections/Internal/KeyUtility.cs
@@ -0,0 +1,165 @@
+// Copyright (c) 2022 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.Collections.Concurrent;
+using System.Diagnostics;
+using System.Reflection.Emit;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+
+namespace FisheryLib.Collections.Internal;
+
+public static class KeyUtility
+{
+ public static readonly T Default = GetDefaultValue();
+
+ private static T GetDefaultValue()
+ {
+ var type = typeof(T);
+
+ var invalidKey = KeyUtility.GetDefaultValue(type);
+ if (!type.IsValueType)
+ return Unsafe.As