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(ref invalidKey); + + return (T)invalidKey!; + } +} + +public static class KeyUtility +{ + public static readonly object InvalidObject = new(); + + public const ulong InvalidValue = 0x7F7F_7F7F_7F7F_7F7FUL; + + public static readonly string InvalidString + = new(['\u7F7F', '\u7F7F', '\u7F7F', '\u7F7F', '\u7F7F', '\u7F7F', '\u7F7F', '\u7F7F']); + + private static readonly ConcurrentDictionary> _fieldSetters = []; + + private static readonly ConcurrentDictionary _defaultValues = []; + + private static readonly MethodInfo _genericMaxValueMethodDefinition + = methodof(GetMaxValue).GetGenericMethodDefinition(); + + private static readonly FieldInfo _invalidFieldInfo = typeof(KeyUtility).GetField(nameof(InvalidObject)); + + public static object? GetDefaultValue(Type type) + { + if (type == typeof(string)) + return InvalidString; + + if (_defaultValues.TryGetValue(type, out var result)) + return result; + + if (!type.IsValueType) + { + return !type.IsAssignableTo(typeof(IEquatable<>), type) + && type.GetMethod(nameof(object.Equals), + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, [typeof(object)], + Array.Empty())?.DeclaringType + == typeof(object) + ? InvalidObject + : null; + } + + result = FormatterServices.GetUninitializedObject(type); + + var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + + for (var i = 0; i < fields.Length; i++) + { + var field = fields[i]; + var fieldType = field.FieldType; + if (fieldType.IsValueType) + { + if (AccessTools.IsValue(fieldType)) + field.SetValue(result, GetMaxValue(fieldType)); + else + GetFieldInvalidator(field)(result); + } + else + { + if (fieldType != typeof(string)) + { + if (!fieldType.IsAssignableTo(typeof(IEquatable<>), fieldType)) + GetFieldInvalidator(field)(result); + else + field.SetValue(result, null); + } + else + { + field.SetValue(result, InvalidString); + } + } + + if (Unsafe.As(ref result) > 0xFFFF_FFFF_FFFF) + Log.Error($"Detected invalid pointer with a value > 48 bit\n{new StackTrace(true)}"); + } + + _defaultValues.TryAdd(type, result); + + return result; + } + + private static object GetMaxValue(Type type) + { + if (!AccessTools.IsValue(type)) + { + return ThrowHelper.ThrowArgumentException($"Invalid type passed to KeyUtility.GetMaxValue: '{ + type.FullDescription()}'. Only primitive structs are supported."); + } + + var result = _genericMaxValueMethodDefinition.MakeGenericMethod(type).Invoke(null, null); + _defaultValues.TryAdd(type, result); + return result; + } + + private static object GetMaxValue() where T : struct + { + var maxValue = InvalidValue; + return Unsafe.As(ref maxValue)!; + } + + private static Action GetFieldInvalidator(FieldInfo fieldInfo) + { + if (_fieldSetters.TryGetValue(fieldInfo, out var result)) + return result; + + var method = new DynamicMethod($"FisheryLib><::>UnsafeSetter<::><{fieldInfo.FullDescription()}", typeof(void), + [typeof(object)], typeof(KeyUtility), true); + var ilGenerator = method.GetILGenerator(); + var fieldType = fieldInfo.FieldType; + var declaringType = fieldInfo.DeclaringType; + + ilGenerator.Emit(FishTranspiler.Argument(0)); + if (declaringType?.IsValueType ?? false) + ilGenerator.Emit(FishTranspiler.UnboxAddress(declaringType)); + + if (!fieldType.IsValueType) + { + ilGenerator.Emit(FishTranspiler.Field(_invalidFieldInfo)); + ilGenerator.Emit(FishTranspiler.Call(methodof(Unsafe.As).GetGenericMethodDefinition() + .MakeGenericMethod(fieldType))); + } + else + { + ilGenerator.Emit(FishTranspiler.Token(fieldType)); + ilGenerator.Emit(FishTranspiler.Call(Type.GetTypeFromHandle)); + + ilGenerator.Emit(FishTranspiler.Call(AccessTools.IsValue(fieldType) + // ReSharper disable once RedundantCast + ? (Func)GetMaxValue + : GetDefaultValue)); + + ilGenerator.Emit(FishTranspiler.UnboxValue(fieldType)); + } + ilGenerator.Emit(FishTranspiler.StoreField(fieldInfo)); + ilGenerator.Emit(FishTranspiler.Return); + + result = (Action)method.CreateDelegate(typeof(Action)); + _fieldSetters.TryAdd(fieldInfo, result); + return result; + } +} \ No newline at end of file diff --git a/Source/FisheryLib/Collections/NibbleArray.cs b/Source/FisheryLib/Collections/NibbleArray.cs index 5a1b473..b4c5ae3 100644 --- a/Source/FisheryLib/Collections/NibbleArray.cs +++ b/Source/FisheryLib/Collections/NibbleArray.cs @@ -4,7 +4,6 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using System.Runtime.CompilerServices; -using JetBrains.Annotations; namespace FisheryLib.Collections; diff --git a/Source/FisheryLib/Diagnostics/Guard.String.cs b/Source/FisheryLib/Diagnostics/Guard.String.cs index b8b39d0..7cde828 100644 --- a/Source/FisheryLib/Diagnostics/Guard.String.cs +++ b/Source/FisheryLib/Diagnostics/Guard.String.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; #pragma warning disable CS8777 diff --git a/Source/FisheryLib/Diagnostics/Guard.cs b/Source/FisheryLib/Diagnostics/Guard.cs index b370723..2d782de 100644 --- a/Source/FisheryLib/Diagnostics/Guard.cs +++ b/Source/FisheryLib/Diagnostics/Guard.cs @@ -4,8 +4,9 @@ using System.Diagnostics; using System.Runtime.CompilerServices; +#if V1_2 using JetBrains.Annotations; -using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; +#endif namespace FisheryLib.Utility.Diagnostics; diff --git a/Source/FisheryLib/Diagnostics/ThrowHelper.cs b/Source/FisheryLib/Diagnostics/ThrowHelper.cs index 3204723..41e83d3 100644 --- a/Source/FisheryLib/Diagnostics/ThrowHelper.cs +++ b/Source/FisheryLib/Diagnostics/ThrowHelper.cs @@ -7,7 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.InteropServices; using System.Threading; -using JetBrains.Annotations; + // ReSharper disable InconsistentNaming namespace FisheryLib.Utility.Diagnostics; diff --git a/Source/FisheryLib/Directory.Build.Props b/Source/FisheryLib/Directory.Build.Props new file mode 100644 index 0000000..3bfedc8 --- /dev/null +++ b/Source/FisheryLib/Directory.Build.Props @@ -0,0 +1,93 @@ + + + + FisheryLib + 1Fishery + bradson + net48 + preview + AnyCPU + true + true + enable + True + preview-recommended + prompt + 5 + portable + false + false + false + CA1000;CA1051;CA1305;CA1310;CA1707;CA1708;CA1710;CA1711;CA1716;CA1725;CA1852;CA2201;CS8500 + obj\$(MSBuildProjectName)\$(Configuration)\ + obj\$(MSBuildProjectName)\$(Configuration)\ + $(DefaultItemExcludes);obj/**/*.* + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + + + + \ No newline at end of file diff --git a/Source/FisheryLib/FishTranspiler.cs b/Source/FisheryLib/FishTranspiler.cs index 3fd8737..72469dd 100644 --- a/Source/FisheryLib/FishTranspiler.cs +++ b/Source/FisheryLib/FishTranspiler.cs @@ -9,8 +9,8 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection.Emit; using System.Security; -using JetBrains.Annotations; using Verse; + // ReSharper disable PossibleMultipleEnumeration namespace FisheryLib; @@ -163,13 +163,19 @@ public static class FishTranspiler : null; public Type? GetTypeIn(MethodBase method) - => OpCode.LoadsArgument() ? method.GetParameters()[GetIndex()].ParameterType + => OpCode.LoadsArgument() + ? method.GetParameters()[GetIndex()].ParameterType : OpCode.LoadsLocalVariable() || OpCode.StoresLocalVariable() ? method.GetLocalVariables()[GetIndex()].LocalType - : OpCode.LoadsField() || OpCode.StoresField() ? (Operand as FieldInfo)?.FieldType - : OpCode == OpCodes.Call || OpCode == OpCodes.Callvirt ? (Operand as MethodInfo)?.ReturnType - : OpCode.LoadsConstant() ? Operand?.GetType() - : null; + : OpCode.LoadsField() || OpCode.StoresField() + ? (Operand as FieldInfo)?.FieldType + : OpCode == OpCodes.Call || OpCode == OpCodes.Callvirt + ? (Operand as MethodInfo)?.ReturnType + : OpCode.LoadsConstant() + ? Operand?.GetType() + : null; + + private bool Calls() => (OpCode == OpCodes.Call) | (OpCode == OpCodes.Callvirt); public override bool Equals(object? obj) => obj is CodeInstruction code @@ -186,13 +192,13 @@ public static class FishTranspiler : unchecked((((1009 * 9176) + OpCode.GetHashCode()) * 9176) + Operand.GetHashCode()); public static bool operator ==(Container lhs, Container rhs) - => lhs.OpCode == rhs.OpCode - && CompareOperands(lhs.Operand, rhs.Operand); + => OpCodeEquals(lhs.OpCode, rhs.OpCode) && CompareOperands(lhs.Operand, rhs.Operand); public static bool operator !=(Container lhs, Container rhs) => !(lhs == rhs); - public static bool operator ==(Container helper, CodeInstruction code) - => helper.OpCode == code.opcode + public static bool operator ==(Container helper, CodeInstruction? code) + => code != null + && OpCodeEquals(helper.OpCode, code.opcode) && CompareOperands(helper.Operand, code.operand); public static bool operator !=(Container helper, CodeInstruction code) => !(helper == code); @@ -201,6 +207,11 @@ public static class FishTranspiler public static bool operator !=(CodeInstruction code, Container helper) => !(helper == code); + private static bool OpCodeEquals(in OpCode lhs, in OpCode rhs) + => lhs == rhs || (IsCallOpcode(lhs) && IsCallOpcode(rhs)); + + private static bool IsCallOpcode(in OpCode opCode) => opCode == OpCodes.Call || opCode == OpCodes.Callvirt; + [SuppressMessage("Usage", "CA2225")] public static implicit operator CodeInstruction(Container helper) => helper.ToInstruction(); @@ -506,7 +517,7 @@ public static class FishTranspiler ? locals.First() : ThrowHelper.ThrowInvalidOperationException( $"No local variable found for predicate {predicate.Method.FullDescription()} and method { - method.FullDescription()}"); + method.FullDescription()}"); } /// @@ -782,7 +793,7 @@ public static class FishTranspiler => ThrowHelper.ThrowArgumentException( $"Tried using Enum {e.GetType().FullDescription()} with underlying Type { Enum.GetUnderlyingType(e.GetType()).FullDescription()} (TypeCode: { - typeCode}) for FishTranspiler.Constant."); + typeCode}) for FishTranspiler.Constant."); /// /// Calls the method indicated by the passed method group. @@ -828,8 +839,7 @@ public static class FishTranspiler Guard.IsNotNullOrEmpty(type); Guard.IsNotNullOrEmpty(name); - return Call( - Type.GetType($"{type}, {assembly}") + return Call(Type.GetType($"{type}, {assembly}") ?? ThrowHelper.ThrowArgumentException($"No type named {type} found in assembly {assembly}"), name, parameters, generics, forceNoCallvirt); } @@ -851,11 +861,10 @@ public static class FishTranspiler Guard.IsNotNull(type); Guard.IsNotNullOrEmpty(name); - return Call( - AccessTools.Method(type, name, parameters, generics) + return Call(AccessTools.Method(type, name, parameters, generics) ?? ThrowHelper.ThrowArgumentException($"No method found with type {type}, name { name}{(parameters != null ? $", parameters: {parameters.ToStringSafeEnumerable()}" : "")}{ - (generics != null ? $", generics: {generics.ToStringSafeEnumerable()}" : "")}"), + (generics != null ? $", generics: {generics.ToStringSafeEnumerable()}" : "")}"), forceNoCallvirt); } @@ -869,10 +878,9 @@ public static class FishTranspiler { Guard.IsNotNull(method); - return new( - method.IsStatic || (method.DeclaringType?.IsValueType ?? false) || forceNoCallvirt - ? OpCodes.Call - : OpCodes.Callvirt, method); + return new(method.IsStatic || (method.DeclaringType?.IsValueType ?? false) || forceNoCallvirt + ? OpCodes.Call + : OpCodes.Callvirt, method); } /// @@ -1488,7 +1496,6 @@ public static class FishTranspiler : ThrowHelper.ThrowArgumentException( $"Type ({type}) for FishTranspiler.Box must be a ValueType.", nameof(type)); } - /// /// Converts the boxed representation of a value type to its unboxed form, pushing an address to the box object's @@ -1983,9 +1990,9 @@ public static class FishTranspiler return new(!type.IsPrimitive || type == typeof(char) ? ThrowHelper.ThrowArgumentException("Convert expects a non-character primitive type", nameof(type)) - : ConvertOpCodesByType.TryGetValue(type, out var value) ? value - : ThrowHelper.ThrowArgumentException( - $"Type {type.FullDescription()} cannot be converted to")); + : ConvertOpCodesByType.TryGetValue(type, out var value) + ? value + : ThrowHelper.ThrowArgumentException($"Type {type.FullDescription()} cannot be converted to")); } public static readonly Dictionary ConvertOpCodesByType = new() @@ -2034,12 +2041,13 @@ public static class FishTranspiler : type == typeof(float) ? ThrowHelper.ThrowInvalidOperationException( "There is no operation for converting to a float with overflow checking") - : type == typeof(double) - ? ThrowHelper.ThrowInvalidOperationException( - "There is no operation for converting to a double with overflow checking") - : ConvertWithOverflowCheckOpCodesByType.TryGetValue(type, out var value) ? value - : ThrowHelper.ThrowArgumentException( - $"Type {type.FullDescription()} cannot be converted to")); + : type == typeof(double) + ? ThrowHelper.ThrowInvalidOperationException( + "There is no operation for converting to a double with overflow checking") + : ConvertWithOverflowCheckOpCodesByType.TryGetValue(type, out var value) + ? value + : ThrowHelper.ThrowArgumentException( + $"Type {type.FullDescription()} cannot be converted to")); } public static readonly Dictionary ConvertWithOverflowCheckOpCodesByType = new() @@ -2081,12 +2089,13 @@ public static class FishTranspiler : type == typeof(float) ? ThrowHelper.ThrowInvalidOperationException( "There is no operation for converting to a float with overflow checking") - : type == typeof(double) - ? ThrowHelper.ThrowInvalidOperationException( - "There is no operation for converting to a double with overflow checking") - : ConvertUnsignedWithOverflowCheckOpCodesByType.TryGetValue(type, out var value) ? value - : ThrowHelper.ThrowArgumentException( - $"Type {type.FullDescription()} cannot be converted to")); + : type == typeof(double) + ? ThrowHelper.ThrowInvalidOperationException( + "There is no operation for converting to a double with overflow checking") + : ConvertUnsignedWithOverflowCheckOpCodesByType.TryGetValue(type, out var value) + ? value + : ThrowHelper.ThrowArgumentException( + $"Type {type.FullDescription()} cannot be converted to")); } public static readonly Dictionary ConvertUnsignedWithOverflowCheckOpCodesByType = new() @@ -2191,8 +2200,8 @@ public static class FishTranspiler } } - public static CodeInstructions MethodReplacer(this CodeInstructions instructions, T from, V to, - bool throwOnFailure = true) where T : Delegate where V : Delegate + public static CodeInstructions MethodReplacer(this CodeInstructions instructions, TFrom from, TTo to, + bool throwOnFailure = true) where TFrom : Delegate where TTo : Delegate { Guard.IsNotNull(instructions); Guard.IsNotNull(from); @@ -2333,10 +2342,9 @@ public static class FishTranspiler if (throwOnFailure && !success) { - ThrowHelper.ThrowInvalidOperationException( - $"FishTranspiler.Replace couldn't find target instruction for { - predicate.Method.FullDescription()} of {UtilityF.GetCallingAssembly().GetName().Name}, Version { - UtilityF.GetCallingAssembly().GetName().Version}"); + ThrowHelper.ThrowInvalidOperationException($"FishTranspiler.Replace couldn't find target instruction for { + predicate.Method.FullDescription()} of {UtilityF.GetCallingAssembly().GetName().Name}, Version { + UtilityF.GetCallingAssembly().GetName().Version}"); } } @@ -2377,9 +2385,8 @@ public static class FishTranspiler if (throwOnFailure && !success) { - ThrowHelper.ThrowInvalidOperationException( - $"FishTranspiler.ReplaceAt couldn't find target instruction for { - position.Method.FullDescription()} of {UtilityF.GetCallingAssembly().GetName().Name}, Version { + ThrowHelper.ThrowInvalidOperationException($"FishTranspiler.ReplaceAt couldn't find target instruction for { + position.Method.FullDescription()} of {UtilityF.GetCallingAssembly().GetName().Name}, Version { UtilityF.GetCallingAssembly().GetName().Version}"); } @@ -2406,8 +2413,13 @@ public static class FishTranspiler return instruction; } - public static bool LoadsLocalVariable(this OpCode opcode) - => CodeInstructionExtensions.loadVarCodes.Contains(opcode); + public static bool LoadsLocalVariable(this OpCode opcode) => LoadLocalVariableOpCodes.Contains(opcode); + + public static readonly HashSet LoadLocalVariableOpCodes = + [ + OpCodes.Ldloca_S, OpCodes.Ldloca, OpCodes.Ldloc_0, OpCodes.Ldloc_1, OpCodes.Ldloc_2, OpCodes.Ldloc_3, + OpCodes.Ldloc_S, OpCodes.Ldloc + ]; public static OpCode ToLoadLocalVariable(this OpCode opcode) => opcode.LoadsLocalVariable() && opcode != OpCodes.Ldarga && opcode != OpCodes.Ldarga_S ? opcode @@ -2416,8 +2428,12 @@ public static class FishTranspiler : opcode == OpCodes.Stloc ? OpCodes.Ldloc : ThrowHelper.ThrowInvalidOperationException($"{opcode} cannot be cast to Ldloc."); - public static bool StoresLocalVariable(this OpCode opcode) - => CodeInstructionExtensions.storeVarCodes.Contains(opcode); + public static bool StoresLocalVariable(this OpCode opcode) => StoreLocalVariableOpCodes.Contains(opcode); + + public static readonly HashSet StoreLocalVariableOpCodes = + [ + OpCodes.Stloc_0, OpCodes.Stloc_1, OpCodes.Stloc_2, OpCodes.Stloc_3, OpCodes.Stloc_S, OpCodes.Stloc + ]; public static OpCode ToStoreLocalVariable(this OpCode opcode) => opcode.StoresLocalVariable() ? opcode @@ -2498,9 +2514,16 @@ public static class FishTranspiler public static bool LoadsString(this OpCode opcode) => opcode == OpCodes.Ldstr; - public static bool Branches(this OpCode opcode) - => CodeInstructionExtensions.branchCodes.Contains(opcode) - || opcode == OpCodes.Switch; + public static bool Branches(this OpCode opcode) => BranchingOpCodes.Contains(opcode); + + public static readonly HashSet BranchingOpCodes = + [ + OpCodes.Br_S, OpCodes.Brfalse_S, OpCodes.Brtrue_S, OpCodes.Beq_S, OpCodes.Bge_S, OpCodes.Bgt_S, + OpCodes.Ble_S, OpCodes.Blt_S, OpCodes.Bne_Un_S, OpCodes.Bge_Un_S, OpCodes.Bgt_Un_S, OpCodes.Ble_Un_S, + OpCodes.Blt_Un_S, OpCodes.Br, OpCodes.Brfalse, OpCodes.Brtrue, OpCodes.Beq, OpCodes.Bge, OpCodes.Bgt, + OpCodes.Ble, OpCodes.Blt, OpCodes.Bne_Un, OpCodes.Bge_Un, OpCodes.Bgt_Un, OpCodes.Ble_Un, OpCodes.Blt_Un, + OpCodes.Switch + ]; public static bool Compares(this OpCode opcode) => CompareOpCodes.Contains(opcode); @@ -2647,26 +2670,26 @@ public static class FishTranspiler { { OpCodes.Ldarg_0, 0 }, { OpCodes.Ldarg_1, 1 }, - { OpCodes.Ldarg_2, 2 }, - { OpCodes.Ldarg_3, 3 }, - { OpCodes.Ldloc_0, 0 }, - { OpCodes.Ldloc_1, 1 }, - { OpCodes.Ldloc_2, 2 }, - { OpCodes.Ldloc_3, 3 }, - { OpCodes.Stloc_0, 0 }, - { OpCodes.Stloc_1, 1 }, - { OpCodes.Stloc_2, 2 }, - { OpCodes.Stloc_3, 3 }, - { OpCodes.Ldc_I4_0, 0 }, - { OpCodes.Ldc_I4_1, 1 }, - { OpCodes.Ldc_I4_2, 2 }, - { OpCodes.Ldc_I4_3, 3 }, - { OpCodes.Ldc_I4_4, 4 }, - { OpCodes.Ldc_I4_5, 5 }, - { OpCodes.Ldc_I4_6, 6 }, - { OpCodes.Ldc_I4_7, 7 }, - { OpCodes.Ldc_I4_8, 8 }, - { OpCodes.Ldc_I4_M1, -1 } + { OpCodes.Ldarg_2, 2 }, + { OpCodes.Ldarg_3, 3 }, + { OpCodes.Ldloc_0, 0 }, + { OpCodes.Ldloc_1, 1 }, + { OpCodes.Ldloc_2, 2 }, + { OpCodes.Ldloc_3, 3 }, + { OpCodes.Stloc_0, 0 }, + { OpCodes.Stloc_1, 1 }, + { OpCodes.Stloc_2, 2 }, + { OpCodes.Stloc_3, 3 }, + { OpCodes.Ldc_I4_0, 0 }, + { OpCodes.Ldc_I4_1, 1 }, + { OpCodes.Ldc_I4_2, 2 }, + { OpCodes.Ldc_I4_3, 3 }, + { OpCodes.Ldc_I4_4, 4 }, + { OpCodes.Ldc_I4_5, 5 }, + { OpCodes.Ldc_I4_6, 6 }, + { OpCodes.Ldc_I4_7, 7 }, + { OpCodes.Ldc_I4_8, 8 }, + { OpCodes.Ldc_I4_M1, -1 } }; public static object? GetOperandFromIndex(int index) => index > 3 ? index : null; @@ -2686,7 +2709,7 @@ public static class FishTranspiler var methodBody = method.GetMethodBody(); if (methodBody is null) ThrowHelper.ThrowArgumentException($"Method {method.FullDescription()} has no body."); - + return methodBody.LocalVariables; } diff --git a/Source/FisheryLib/Fishery.cs b/Source/FisheryLib/Fishery.cs index 61087c7..df15a9b 100644 --- a/Source/FisheryLib/Fishery.cs +++ b/Source/FisheryLib/Fishery.cs @@ -10,11 +10,12 @@ global using System.Collections.Generic; global using System.Reflection; global using HarmonyLib; global using FisheryLib.Utility.Diagnostics; +global using JetBrains.Annotations; global using CodeInstructions = System.Collections.Generic.IEnumerable; +global using NotNullAttribute = System.Diagnostics.CodeAnalysis.NotNullAttribute; using System.Security; using System.Diagnostics; using System.Runtime.CompilerServices; -using JetBrains.Annotations; [assembly: AllowPartiallyTrustedCallers] [assembly: SecurityRules(SecurityRuleSet.Level2, SkipVerificationInFullTrust = true)] @@ -28,11 +29,11 @@ namespace FisheryLib; [PublicAPI] public static class FisheryLib { - public const decimal VERSION = 0.5M; + public const decimal VERSION = 0.51M; public static decimal CurrentlyLoadedVersion { get; } = VERSION; - public const string VERSION_STRING = "0.5.0.0"; + public const string VERSION_STRING = "0.5.1.0"; // public static string GetVersion() // { diff --git a/Source/FisheryLib/Fishery.csproj b/Source/FisheryLib/Fishery.csproj deleted file mode 100644 index 3dd3a22..0000000 --- a/Source/FisheryLib/Fishery.csproj +++ /dev/null @@ -1,81 +0,0 @@ - - - - FisheryLib - 1Fishery - net48 - preview - AnyCPU - true - false - bradson - Copyright (c) 2022 bradson - LICENSE - True - preview-all - true - portable - prompt - 5 - enable - True - v1.4;v1.3 - false - false - false - - - - 1701;1702;IL2121;CS8500;CS9082;CA1000;CA1002;CA1003;CA1707;CA1010;CA1715;CA1716;CA1724;CA1031;CA1033; - CA1034;CA1043;CA1051;CA1305;CA1310;CA1710;CA1711;CA1810;CA1812;CA1819;CA1851;CA2002;CA5394;CA1725;CA1062 - - - - - - ..\..\1.4\Assemblies\ - - - - - - - - ..\..\1.3\Assemblies\ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - - - - \ No newline at end of file diff --git a/Source/FisheryLib/Fishery.sln b/Source/FisheryLib/Fishery.sln index ff6c1e1..b55e133 100644 --- a/Source/FisheryLib/Fishery.sln +++ b/Source/FisheryLib/Fishery.sln @@ -3,18 +3,36 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.1.32120.378 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fishery", "Fishery.csproj", "{B5B38CBB-F8EE-451E-A05B-9ED9B03784D2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1.5", "1.5.csproj", "{B5B38CBB-F8EE-451E-A05B-9ED9B03784D2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1.4", "1.4.csproj", "{BE85EB31-71CD-443C-8C93-20E2B7F3F549}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1.3", "1.3.csproj", "{FC5FB1F4-626D-4895-AE5E-922DFE1AE120}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1.2", "1.2.csproj", "{E4269847-7C6E-404E-93C0-E47CF7459E81}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - v1.3|Any CPU = v1.3|Any CPU - v1.4|Any CPU = v1.4|Any CPU + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B5B38CBB-F8EE-451E-A05B-9ED9B03784D2}.v1.3|Any CPU.ActiveCfg = v1.3|Any CPU - {B5B38CBB-F8EE-451E-A05B-9ED9B03784D2}.v1.3|Any CPU.Build.0 = v1.3|Any CPU - {B5B38CBB-F8EE-451E-A05B-9ED9B03784D2}.v1.4|Any CPU.ActiveCfg = v1.4|Any CPU - {B5B38CBB-F8EE-451E-A05B-9ED9B03784D2}.v1.4|Any CPU.Build.0 = v1.4|Any CPU + {B5B38CBB-F8EE-451E-A05B-9ED9B03784D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5B38CBB-F8EE-451E-A05B-9ED9B03784D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5B38CBB-F8EE-451E-A05B-9ED9B03784D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5B38CBB-F8EE-451E-A05B-9ED9B03784D2}.Release|Any CPU.Build.0 = Release|Any CPU + {BE85EB31-71CD-443C-8C93-20E2B7F3F549}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE85EB31-71CD-443C-8C93-20E2B7F3F549}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE85EB31-71CD-443C-8C93-20E2B7F3F549}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE85EB31-71CD-443C-8C93-20E2B7F3F549}.Release|Any CPU.Build.0 = Release|Any CPU + {FC5FB1F4-626D-4895-AE5E-922DFE1AE120}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC5FB1F4-626D-4895-AE5E-922DFE1AE120}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC5FB1F4-626D-4895-AE5E-922DFE1AE120}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC5FB1F4-626D-4895-AE5E-922DFE1AE120}.Release|Any CPU.Build.0 = Release|Any CPU + {E4269847-7C6E-404E-93C0-E47CF7459E81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4269847-7C6E-404E-93C0-E47CF7459E81}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4269847-7C6E-404E-93C0-E47CF7459E81}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4269847-7C6E-404E-93C0-E47CF7459E81}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Source/FisheryLib/FunctionPointers/Equals.cs b/Source/FisheryLib/FunctionPointers/Equals.cs index c02661a..8b42cda 100644 --- a/Source/FisheryLib/FunctionPointers/Equals.cs +++ b/Source/FisheryLib/FunctionPointers/Equals.cs @@ -7,7 +7,6 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Security; -using JetBrains.Annotations; namespace FisheryLib.FunctionPointers; diff --git a/Source/FisheryLib/FunctionPointers/EqualsByRef.cs b/Source/FisheryLib/FunctionPointers/EqualsByRef.cs index 93d3ac6..f3ee7a7 100644 --- a/Source/FisheryLib/FunctionPointers/EqualsByRef.cs +++ b/Source/FisheryLib/FunctionPointers/EqualsByRef.cs @@ -7,7 +7,6 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Security; -using JetBrains.Annotations; namespace FisheryLib.FunctionPointers; diff --git a/Source/FisheryLib/FunctionPointers/GetHashCode.cs b/Source/FisheryLib/FunctionPointers/GetHashCode.cs index 05a8a3c..244fd14 100644 --- a/Source/FisheryLib/FunctionPointers/GetHashCode.cs +++ b/Source/FisheryLib/FunctionPointers/GetHashCode.cs @@ -5,7 +5,6 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using JetBrains.Annotations; namespace FisheryLib.FunctionPointers; diff --git a/Source/FisheryLib/FunctionPointers/GetHashCodeByRef.cs b/Source/FisheryLib/FunctionPointers/GetHashCodeByRef.cs index 7b19c92..99f77cb 100644 --- a/Source/FisheryLib/FunctionPointers/GetHashCodeByRef.cs +++ b/Source/FisheryLib/FunctionPointers/GetHashCodeByRef.cs @@ -5,7 +5,6 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using JetBrains.Annotations; namespace FisheryLib.FunctionPointers; diff --git a/Source/FisheryLib/GUIScope.cs b/Source/FisheryLib/GUIScope.cs index 6dfb3bb..c655efa 100644 --- a/Source/FisheryLib/GUIScope.cs +++ b/Source/FisheryLib/GUIScope.cs @@ -4,7 +4,6 @@ // You can obtain one at https://opensource.org/licenses/MIT/. using System.Runtime.CompilerServices; -using JetBrains.Annotations; using UnityEngine; using Verse; @@ -15,9 +14,19 @@ public static class GUIScope { public readonly record struct WidgetGroup : IDisposable { - public WidgetGroup(in Rect rect) => Widgets.BeginGroup(rect); + public WidgetGroup(in Rect rect) +#if !V1_2 + => Widgets.BeginGroup(rect); +#else + => GUI.BeginGroup(rect); +#endif - public void Dispose() => Widgets.EndGroup(); + public void Dispose() +#if !V1_2 + => Widgets.EndGroup(); +#else + => GUI.EndGroup(); +#endif } public readonly record struct ScrollView : IDisposable diff --git a/Source/FisheryLib/Log.cs b/Source/FisheryLib/Log.cs index f1d328e..e73a3b0 100644 --- a/Source/FisheryLib/Log.cs +++ b/Source/FisheryLib/Log.cs @@ -3,8 +3,6 @@ // 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 JetBrains.Annotations; - namespace FisheryLib; [PublicAPI] diff --git a/Source/FisheryLib/Pools/PooledStringBuilder.cs b/Source/FisheryLib/Pools/PooledStringBuilder.cs index 019b604..6900b47 100644 --- a/Source/FisheryLib/Pools/PooledStringBuilder.cs +++ b/Source/FisheryLib/Pools/PooledStringBuilder.cs @@ -5,7 +5,6 @@ using System.Security; using System.Text; -using JetBrains.Annotations; namespace FisheryLib.Pools; diff --git a/Source/FisheryLib/Pools/PooledStringBuilderHandler.cs b/Source/FisheryLib/Pools/PooledStringBuilderHandler.cs index 7d94643..b50ac3c 100644 --- a/Source/FisheryLib/Pools/PooledStringBuilderHandler.cs +++ b/Source/FisheryLib/Pools/PooledStringBuilderHandler.cs @@ -5,7 +5,6 @@ using System.Runtime.CompilerServices; using System.Text; -using JetBrains.Annotations; namespace FisheryLib.Pools; diff --git a/Source/FisheryLib/Pools/PooledStringHandler.cs b/Source/FisheryLib/Pools/PooledStringHandler.cs index 1f38bbf..dd840d5 100644 --- a/Source/FisheryLib/Pools/PooledStringHandler.cs +++ b/Source/FisheryLib/Pools/PooledStringHandler.cs @@ -4,7 +4,6 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using System.Runtime.CompilerServices; -using JetBrains.Annotations; namespace FisheryLib.Pools; diff --git a/Source/FisheryLib/ReadOnlySpanEnumerable.cs b/Source/FisheryLib/ReadOnlySpanEnumerable.cs index 6a9ba36..412f568 100644 --- a/Source/FisheryLib/ReadOnlySpanEnumerable.cs +++ b/Source/FisheryLib/ReadOnlySpanEnumerable.cs @@ -6,7 +6,6 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -using JetBrains.Annotations; namespace FisheryLib; diff --git a/Source/FisheryLib/ReadOnlySpanExtensions.cs b/Source/FisheryLib/ReadOnlySpanExtensions.cs index 1407116..5de0952 100644 --- a/Source/FisheryLib/ReadOnlySpanExtensions.cs +++ b/Source/FisheryLib/ReadOnlySpanExtensions.cs @@ -8,7 +8,6 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using JetBrains.Annotations; namespace FisheryLib; diff --git a/Source/FisheryLib/Reflection.cs b/Source/FisheryLib/Reflection.cs index f5560d3..9a7ce5d 100644 --- a/Source/FisheryLib/Reflection.cs +++ b/Source/FisheryLib/Reflection.cs @@ -13,7 +13,6 @@ using System.Security; using System.Text; using System.Threading.Tasks; using FisheryLib.Pools; -using JetBrains.Annotations; using Verse; namespace FisheryLib; diff --git a/Source/FisheryLib/SingletonFactory.cs b/Source/FisheryLib/SingletonFactory.cs index 39045a5..b30148e 100644 --- a/Source/FisheryLib/SingletonFactory.cs +++ b/Source/FisheryLib/SingletonFactory.cs @@ -4,7 +4,6 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using System.Collections.Concurrent; -using JetBrains.Annotations; namespace FisheryLib; diff --git a/Source/FisheryLib/SpanEnumerable.cs b/Source/FisheryLib/SpanEnumerable.cs index f521be9..2c4b9d2 100644 --- a/Source/FisheryLib/SpanEnumerable.cs +++ b/Source/FisheryLib/SpanEnumerable.cs @@ -6,7 +6,6 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -using JetBrains.Annotations; namespace FisheryLib; diff --git a/Source/FisheryLib/SpanExtensions.cs b/Source/FisheryLib/SpanExtensions.cs index 4a6f455..68288bf 100644 --- a/Source/FisheryLib/SpanExtensions.cs +++ b/Source/FisheryLib/SpanExtensions.cs @@ -8,7 +8,6 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using JetBrains.Annotations; namespace FisheryLib; diff --git a/Source/FisheryLib/StackList.cs b/Source/FisheryLib/StackList.cs index 84f07f2..9ad8600 100644 --- a/Source/FisheryLib/StackList.cs +++ b/Source/FisheryLib/StackList.cs @@ -4,7 +4,6 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using System.Runtime.CompilerServices; -using JetBrains.Annotations; namespace FisheryLib; diff --git a/Source/FisheryLib/StopwatchExtensions.cs b/Source/FisheryLib/StopwatchExtensions.cs index 91756f4..123a6c0 100644 --- a/Source/FisheryLib/StopwatchExtensions.cs +++ b/Source/FisheryLib/StopwatchExtensions.cs @@ -4,7 +4,6 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using System.Diagnostics; -using JetBrains.Annotations; namespace FisheryLib; diff --git a/Source/FisheryLib/Utility.cs b/Source/FisheryLib/Utility.cs index e7862d3..3d2c709 100644 --- a/Source/FisheryLib/Utility.cs +++ b/Source/FisheryLib/Utility.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; -using JetBrains.Annotations; namespace FisheryLib; diff --git a/Textures/FisheryLib/ModIcon.dds b/Textures/FisheryLib/ModIcon.dds new file mode 100644 index 0000000..0fdd3e5 Binary files /dev/null and b/Textures/FisheryLib/ModIcon.dds differ diff --git a/Textures/FisheryLib/ModIcon.png b/Textures/FisheryLib/ModIcon.png new file mode 100644 index 0000000..0cc4e5f Binary files /dev/null and b/Textures/FisheryLib/ModIcon.png differ