Added pools, IndexedFishMap, IndexedFishSet, fixed bugs related to FishTable.Remove, added Preview.png, various smaller additions, some refactoring too

This commit is contained in:
bbradson
2024-01-27 10:02:57 +01:00
parent cdf209eb02
commit af759661bd
38 changed files with 1879 additions and 642 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

+2
View File
@@ -1,4 +1,6 @@
# Fishery
![](About/Preview.png?raw=true)
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.
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.
+33 -11
View File
@@ -3,9 +3,12 @@
// 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.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using Mono.Collections.Generic;
using Verse;
using CallSite = Mono.Cecil.CallSite;
@@ -334,8 +337,8 @@ public static class CecilExtensions
public static void ReplaceBodyWith(this ILProcessor ilProcessor, MethodInfo replacement)
{
var oldBody = ilProcessor.Body;
var module = oldBody.Method.Module;
var targetBody = ilProcessor.Body;
var module = targetBody.Method.Module;
if (replacement.IsGenericMethod)
replacement = replacement.GetGenericMethodDefinition();
@@ -349,25 +352,39 @@ public static class CecilExtensions
var newMethodReference = module.ImportReference(replacement);
var newBody = newMethodReference.Resolve().Body;
oldBody.Instructions.Clear();
newBody.SimplifyMacros();
MethodBodyRocks.ComputeOffsets(newBody);
var targetInstructions = targetBody.instructions;
targetInstructions.Clear();
foreach (var instruction in newBody.instructions)
{
oldBody.Instructions.Add(new(instruction.OpCode, instruction.Operand switch
targetInstructions.Add(new(instruction.OpCode, instruction.Operand switch
{
FieldReference field => module.ImportReference(field, newMethodReference),
MethodReference method => module.ImportReference(method, newMethodReference),
TypeReference type => module.ImportReference(type, newMethodReference),
ParameterReference parameter => targetBody.GetParameter(parameter.Index),
_ => instruction.Operand
}));
}
oldBody.Variables.Clear();
MethodBodyRocks.ComputeOffsets(targetBody);
foreach (var instruction in targetInstructions)
{
if (instruction.Operand is Instruction operand)
instruction.Operand = targetInstructions.First(ti => ti.Offset == operand.Offset);
}
targetBody.Variables.Clear();
foreach (var variable in newBody.Variables)
oldBody.Variables.Add(new(module.ImportReference(variable.VariableType, newMethodReference)));
targetBody.Variables.Add(new(module.ImportReference(variable.VariableType, newMethodReference)));
oldBody.MaxStackSize = newBody.MaxStackSize;
targetBody.MaxStackSize = newBody.MaxStackSize;
oldBody.ExceptionHandlers.Clear();
targetBody.ExceptionHandlers.Clear();
foreach (var exceptionHandler in newBody.ExceptionHandlers)
{
var handler = new ExceptionHandler(exceptionHandler.HandlerType)
@@ -382,10 +399,13 @@ public static class CecilExtensions
if (exceptionHandler.CatchType is { } catchType)
handler.CatchType = module.ImportReference(catchType, newMethodReference);
oldBody.ExceptionHandlers.Add(handler);
targetBody.ExceptionHandlers.Add(handler);
}
oldBody.Method.ImplAttributes = newBody.Method.ImplAttributes;
targetBody.Method.ImplAttributes = newBody.Method.ImplAttributes;
targetBody.OptimizeMacros();
newBody.OptimizeMacros();
}
public static MethodDefinition GetMethod(this ILProcessor ilProcessor) => ilProcessor.Body.Method;
@@ -533,6 +553,7 @@ public static class CecilExtensions
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
$"Argument is of invalid type: {instruction?.GetType().FullName ?? "NULL"}");
[SuppressMessage("Maintainability", "CA1508")]
private static Instruction CreateInstruction(OpCode opCode, object operand)
=> operand switch
{
@@ -554,6 +575,7 @@ public static class CecilExtensions
ParameterDefinition parameter => Instruction.Create(opCode, parameter),
_ => ThrowHelper.ThrowArgumentException<Instruction>(nameof(operand),
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
$"Operand for OpCode {opCode} is of invalid type: {operand?.GetType().FullName ?? "NULL"}")
$"Operand for OpCode {opCode} is of invalid type: {
operand?.GetType().FullName ?? operand?.GetType().Name ?? "NULL"}")
};
}
@@ -0,0 +1,35 @@
// 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 FisheryLib.Collections;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace FisheryLib.Cecil;
public static class InstructionExtensions
{
public static bool LoadsArgument(this Instruction instruction, int index)
=> instruction.LoadsArgument() && GetLoadArgumentIndex(instruction) == index;
public static bool LoadsArgument(this Instruction instruction) => LoadArgumentOpcodes.Contains(instruction.OpCode);
private static int GetLoadArgumentIndex(Instruction instruction)
{
var opCode = instruction.OpCode;
return opCode == OpCodes.Ldarg_0 ? 0
: opCode == OpCodes.Ldarg_1 ? 1
: opCode == OpCodes.Ldarg_2 ? 2
: opCode == OpCodes.Ldarg_3 ? 3
: instruction.Operand is ParameterDefinition parameterDefinition ? parameterDefinition.Index
: Convert.Type<object, int>(instruction.Operand);
}
public static readonly FishSet<OpCode> LoadArgumentOpcodes =
[
OpCodes.Ldarg, OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldarg_3, OpCodes.Ldarg_S,
OpCodes.Ldarga, OpCodes.Ldarga_S
];
}
@@ -58,6 +58,8 @@ public static class ReflectionExtensions
private static T? TryGetNamed<T>(this IList<T> memberInfos, string name, Func<T, string> nameGetter)
where T : class
{
Guard.IsNotNull(memberInfos);
for (var i = 0; i < memberInfos.Count; i++)
{
if (nameGetter(memberInfos[i]) == name)
@@ -69,6 +71,8 @@ public static class ReflectionExtensions
public static TypeReference[] GetTypeReferences(this Type[] types, ModuleDefinition module)
{
Guard.IsNotNull(types);
var result = new TypeReference[types.Length];
for (var i = types.Length; i-- > 0;)
result[i] = module.ImportReference(types[i]);
@@ -77,8 +81,14 @@ public static class ReflectionExtensions
}
public static bool IsGeneric(this TypeReference type)
=> type.ContainsGenericParameter || type.HasGenericParameters || type.IsGenericInstance;
{
Guard.IsNotNull(type);
return type.ContainsGenericParameter || type.HasGenericParameters || type.IsGenericInstance;
}
public static bool IsGeneric(this Type type)
=> type.ContainsGenericParameters || type.IsGenericType || type.IsConstructedGenericType;
{
Guard.IsNotNull(type);
return type.ContainsGenericParameters || type.IsGenericType || type.IsConstructedGenericType;
}
}
+105 -14
View File
@@ -145,7 +145,8 @@ public static class CollectionExtensions
{
if (dictionary.buckets != null)
{
var keyCode = HashCode.Get(key) & 0x7FFFFFFF; // HashCode.Get can handle null, & 0x7FFFFFFF removes any sign
var keyCode = HashCode.Get(key) & int.MaxValue;
// HashCode.Get can handle null, & int.MaxValue removes any sign
var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
while (bucket >= 0)
@@ -165,7 +166,7 @@ public static class CollectionExtensions
{
if (dictionary.buckets != null)
{
var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
var keyCode = HashCode.Get(ref key) & int.MaxValue;
var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
while (bucket >= 0)
@@ -192,7 +193,7 @@ public static class CollectionExtensions
{
if (dictionary.buckets != null)
{
var keyCode = HashCode.Get(key) & 0x7FFFFFFF;
var keyCode = HashCode.Get(key) & int.MaxValue;
var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
while (bucket >= 0)
@@ -212,7 +213,7 @@ public static class CollectionExtensions
{
if (dictionary.buckets != null)
{
var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
var keyCode = HashCode.Get(ref key) & int.MaxValue;
var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
while (bucket >= 0)
@@ -232,7 +233,7 @@ public static class CollectionExtensions
{
if (dictionary.buckets != null)
{
var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
var keyCode = HashCode.Get(ref key) & int.MaxValue;
var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
while (bucket >= 0)
@@ -253,7 +254,7 @@ public static class CollectionExtensions
{
if (dictionary.buckets != null)
{
var keyCode = HashCode.Get(key) & 0x7FFFFFFF;
var keyCode = HashCode.Get(key) & int.MaxValue;
var bucket = dictionary.buckets[keyCode % dictionary.buckets.Length];
while (bucket >= 0)
@@ -273,7 +274,7 @@ public static class CollectionExtensions
public static ref TValue GetOrAddReference<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
where TValue : new()
{
var keyCode = HashCode.Get(key) & 0x7FFFFFFF;
var keyCode = HashCode.Get(key) & int.MaxValue;
StartOfLookup:
if (dictionary.buckets != null)
@@ -297,7 +298,7 @@ public static class CollectionExtensions
public static ref TValue GetOrAddReference<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, ref TKey key)
where TValue : new()
{
var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
var keyCode = HashCode.Get(ref key) & int.MaxValue;
StartOfLookup:
if (dictionary.buckets != null)
@@ -320,7 +321,7 @@ public static class CollectionExtensions
// public static bool TryGetOrAddReference<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key,
// ref TValue reference)
// {
// var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
// var keyCode = HashCode.Get(ref key) & int.MaxValue;
// var result = true;
//
// StartOfLookup:
@@ -349,7 +350,7 @@ public static class CollectionExtensions
public static TValue GetOrAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key)
where TValue : new()
{
var keyCode = HashCode.Get(key) & 0x7FFFFFFF;
var keyCode = HashCode.Get(key) & int.MaxValue;
StartOfLookup:
if (dictionary.buckets != null)
@@ -373,7 +374,7 @@ public static class CollectionExtensions
public static TValue GetOrAdd<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, ref TKey key)
where TValue : new()
{
var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
var keyCode = HashCode.Get(ref key) & int.MaxValue;
StartOfLookup:
if (dictionary.buckets != null)
@@ -396,7 +397,7 @@ public static class CollectionExtensions
public static bool TryGetOrAddValue<TKey, TValue>(this Dictionary<TKey, TValue> dictionary, TKey key,
out TValue value)
{
var keyCode = HashCode.Get(ref key) & 0x7FFFFFFF;
var keyCode = HashCode.Get(ref key) & int.MaxValue;
var result = true;
StartOfLookup:
@@ -421,6 +422,38 @@ public static class CollectionExtensions
goto StartOfLookup;
}
public static int RemoveWhere<TKey, TValue>(this Dictionary<TKey, TValue> dictionary,
Predicate<KeyValuePair<TKey, TValue>> predicate)
{
Guard.IsNotNull(dictionary);
Guard.IsNotNull(predicate);
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];
if (entry.hashCode < 0) // valid entries are stored with & int.MaxValue on hashCode
continue;
// predicate might modify collection, turning a ref invalid
var keyValuePair = new KeyValuePair<TKey,TValue>(entry.key, entry.value);
if (!predicate(keyValuePair))
continue;
if (dictionary.Remove(keyValuePair.Key))
removedCount++;
}
return removedCount;
}
[DoesNotReturn]
private static ref V ThrowKeyNotFoundException<T, V>(T key)
{
@@ -640,7 +673,28 @@ public static class CollectionExtensions
public static StringBuilder Append(this StringBuilder builder, List<char> value)
=> builder.Append(value._items, 0, value._size);
public static ref T GetReferenceUnverifiable<T>(this List<T> list, int index) => ref list._items[index];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static StringBuilder Append<T>(this StringBuilder builder, T? value)
// ReSharper disable once RedundantToStringCallForValueType
// ReSharper disable once CompareNonConstrainedGenericWithNull
// ReSharper disable once HeapView.PossibleBoxingAllocation
=> value == null ? builder : builder.Append(value.ToString());
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetReference<T>(this List<T> list, int index)
{
Guard.IsInRange(index, 0, list.Count);
list._version++;
return ref list._items[index];
}
[Obsolete("Renamed to GetReferenceUnsafe")]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetReferenceUnverifiable<T>(this List<T> list, int index) => ref list.GetReferenceUnsafe(index);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetReferenceUnsafe<T>(this List<T> list, int index) => ref list._items[index];
public static IList TryNew(IEnumerable enumerable, Type type) => GetListExtensionMethods(type).New(enumerable);
@@ -649,6 +703,40 @@ public static class CollectionExtensions
public static Array TryToArray(this IList list, Type type) => GetListExtensionMethods(type).ToArray(list);
public static void RemoveAtFastUnordered<T>(this List<T> list, int index)
{
ref var lastBucket = ref list._items[list._size - 1];
list[index] = lastBucket;
lastBucket = default!;
list._size--;
}
public static bool RemoveFastUnordered<T>(this List<T> list, T item)
{
var index = list.IndexOf(item);
if (index < 0)
return false;
list.RemoveAtFastUnordered(index);
return true;
}
public static void InsertFastUnordered<T>(this List<T> list, int index, T item)
{
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<TKey, TValue>(this Dictionary<TKey, TValue> dictionary)
=> dictionary.GetType().ComputeManagedObjectSizeEstimate()
+ ((uint)sizeof(Dictionary<TKey, TValue>.Entry) * (uint)(dictionary.entries?.Length ?? 0))
@@ -657,7 +745,8 @@ public static class CollectionExtensions
internal static unsafe uint ComputeManagedObjectSizeEstimate(this Type type)
{
var objectHeader = (uint)sizeof(void*) * 2u;
ref var cachedSize = ref CachedManagedObjectSizes.GetOrAddReference(type.TypeHandle.Value);
var cachedSize = CachedManagedObjectSizes.GetOrAdd(type.TypeHandle.Value);
// can't use GetOrAddReference here due to recursion
if (cachedSize.Size == uint.MaxValue)
UpdateCachedManagedObjectSize(type, ref cachedSize);
@@ -689,6 +778,8 @@ public static class CollectionExtensions
var remainder = cachedSize.Size % (uint)IntPtr.Size;
if (remainder != 0u)
cachedSize.Size += (uint)IntPtr.Size - remainder;
CachedManagedObjectSizes[type.TypeHandle.Value] = cachedSize;
}
private static FishTable<IntPtr, ManagedObjectSize> CachedManagedObjectSizes => _cachedManagedObjectSizes ??= new();
+100 -166
View File
@@ -58,9 +58,9 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
get => _count;
}
public object SyncRoot => throw new NotImplementedException();
object ICollection.SyncRoot => this;
public bool IsSynchronized => throw new NotImplementedException();
bool ICollection.IsSynchronized => false;
public bool IsReadOnly => false;
@@ -96,7 +96,13 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
get => Contains(key);
[CollectionAccess(CollectionAccessType.UpdatedContent)]
set => InsertEntry(key, value);
set
{
if (value)
InsertEntry(key, ReplaceBehaviour.Fail);
else
Remove(key);
}
}
public FishSet() : this(0)
@@ -121,7 +127,7 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
public FishSet(IEnumerable<T> entries) : this(0)
{
foreach (var entry in entries)
InsertEntry(entry, false);
InsertEntry(entry, ReplaceBehaviour.Throw);
}
private int GetTailIndex(int entryIndex) => GetIndexForTail(entryIndex, _tails[(uint)entryIndex]);
@@ -129,56 +135,6 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
[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
{
@@ -191,9 +147,6 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
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);
@@ -211,6 +164,12 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
_tails.SetSolo(bucketIndex);
}
private void SetBucketEmpty(int index)
{
_buckets[index] = default;
_tails.SetEmpty(index);
}
private void SetEntryAsTail(int bucketIndex, T entry, int parentIndex, uint offset)
{
SetEntryWithoutTail(bucketIndex, entry);
@@ -220,9 +179,9 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
private void SetParentTail(int entryIndex, uint newTail)
=> _tails[(uint)GetParentBucketIndex(entryIndex)] = newTail;
private bool InsertEntry(T entry, bool allowReplace = true, bool shifting = false)
private bool InsertEntry(T entry, ReplaceBehaviour replaceBehaviour, bool shifting = false)
{
var addedNewEntry = InsertEntryInternal(entry, allowReplace);
var addedNewEntry = InsertEntryInternal(entry, replaceBehaviour);
if (shifting)
return addedNewEntry;
@@ -247,7 +206,7 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
/// 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.
/// </summary>
private bool InsertEntryInternal(T entry, bool allowReplace = true)
private bool InsertEntryInternal(T entry, ReplaceBehaviour replaceBehaviour)
{
StartOfMethod:
var bucketIndex = GetBucketIndex(entry);
@@ -259,7 +218,7 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
return true;
}
if (TryReplaceValueOfMatchingKey(entry, allowReplace, ref bucket!))
if (TryReplaceValueOfMatchingKey(entry, replaceBehaviour, ref bucket!))
return false;
if (CheckResize())
@@ -268,53 +227,30 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
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;
}
}
}
var tailingEntries = TryGetAndClearTailingEntries(bucketIndex);
SetParentTail(bucketIndex, Tails.SOLO);
SetEntryWithoutTail(bucketIndex, entry);
InsertEntry(previousEntry, ReplaceBehaviour.Throw, true);
InsertEntry(previousEntry, shifting: true);
if (hasTail)
{
for (var i = 0; i < tailingEntries!.Length; i++)
InsertEntry(tailingEntries[i], shifting: true);
}
if (tailingEntries != null)
InsertEntries(tailingEntries, ReplaceBehaviour.Throw, true);
return true;
}
else
{
return !TryFindTailIndexAndReplace(entry, ref bucketIndex, allowReplace)
return !TryFindTailIndexAndReplace(entry, ref bucketIndex, replaceBehaviour)
&& InsertAsTail(entry, bucketIndex);
}
}
private void InsertEntries(T[] tailingEntries, ReplaceBehaviour replaceBehaviour, bool shifting = false)
{
for (var i = 0; i < tailingEntries.Length; i++)
InsertEntry(tailingEntries[i], replaceBehaviour, shifting);
}
private int GetTailCount(int index)
{
var tailCount = 0;
@@ -331,31 +267,37 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
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 static bool IsBucketEmpty(int bucketIndex, Tails tails, T?[] buckets) => tails.IsEmpty(bucketIndex);
private bool TryFindTailIndexAndReplace(T entry, ref int bucketIndex, bool allowReplace)
private bool TryFindTailIndexAndReplace(T entry, ref int bucketIndex, ReplaceBehaviour replaceBehaviour)
{
while (true)
{
if (!TryContinueWithTail(ref bucketIndex))
return false;
if (TryReplaceValueOfMatchingKey(entry, allowReplace, ref _buckets[bucketIndex]!))
if (TryReplaceValueOfMatchingKey(entry, replaceBehaviour, ref _buckets[bucketIndex]!))
return true;
}
}
private unsafe bool TryReplaceValueOfMatchingKey(T entry, bool allowReplace, ref T bucket)
private unsafe bool TryReplaceValueOfMatchingKey(T entry, ReplaceBehaviour replaceBehaviour, ref T bucket)
{
if (!_equalityComparer(bucket, entry))
return false;
if (!allowReplace)
ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(entry);
switch (replaceBehaviour)
{
case ReplaceBehaviour.Throw:
return ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(entry);
case ReplaceBehaviour.Replace:
bucket = entry;
goto default;
case ReplaceBehaviour.Fail:
default:
return true;
}
}
private bool InsertAsTail(T entry, int bucketIndex)
{
@@ -382,29 +324,40 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
WarnForPossiblyExcessiveResizing(entry);
Resize();
InsertEntry(entry, false, true);
InsertEntry(entry, ReplaceBehaviour.Throw, true);
return true;
}
var previousEntry = _buckets[bucketIndex];
var hasTail = !_tails.IsSolo(bucketIndex);
var tailingEntries = TryGetAndClearTailingEntries(bucketIndex);
var tailingEntries = hasTail ? new T?[GetTailCount(bucketIndex)] : null;
SetParentTail(bucketIndex, Tails.SOLO);
SetEntryAsTail(bucketIndex, entry, parentIndex, offset);
if (hasTail)
InsertEntry(previousEntry!, ReplaceBehaviour.Throw, true);
if (tailingEntries != null)
InsertEntries(tailingEntries, ReplaceBehaviour.Throw, true);
return true;
}
private T[]? TryGetAndClearTailingEntries(int bucketIndex)
{
var k = 0;
if (!HasTail(bucketIndex))
return null;
var tailingEntries = new T[GetTailCount(bucketIndex)];
var i = 0;
var tailIndex = GetTailIndex(bucketIndex);
while (true)
{
ref var tailingBucket = ref _buckets[tailIndex];
tailingEntries![k] = tailingBucket;
k++;
tailingEntries[i++] = tailingBucket!;
tailingBucket = default;
if (!_tails.IsSoloOrEmpty(tailIndex))
if (HasTail(tailIndex))
{
var nextTailIndex = GetTailIndex(tailIndex);
_tails.SetEmpty(tailIndex);
@@ -416,20 +369,8 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
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;
return tailingEntries;
}
[MethodImpl(MethodImplOptions.NoInlining)]
@@ -474,7 +415,7 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
for (var i = 0; i < oldBuckets.Length; i++)
{
if (!IsBucketEmpty(i, oldTails, oldBuckets))
InsertEntry(oldBuckets[i]!, false, true);
InsertEntry(oldBuckets[i]!, ReplaceBehaviour.Throw, true);
}
}
@@ -545,7 +486,7 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
public bool Add(T key)
{
Guard.IsNotNull(key);
return InsertEntry(key, false);
return InsertEntry(key, ReplaceBehaviour.Fail);
}
public void UnionWith(IEnumerable<T> other) => throw new NotImplementedException();
@@ -578,36 +519,23 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
return true;
}
private void EmplaceWithTails(int entryIndex)
private void EmplaceWithTailsBackward(int entryIndex)
{
ref var bucket = ref _buckets[entryIndex];
var entry = bucket;
if (IsBucketEmpty(entryIndex))
Utility.Diagnostics.ThrowHelper.ThrowInvalidOperationException("Tried to emplace entry bucket");
Span<int> tailIndices = stackalloc int[GetTailCount(entryIndex)];
if (HasTail(entryIndex))
while (true)
{
tailIndices[0] = GetTailIndex(entryIndex);
for (var i = 1; i < tailIndices.Length; i++)
tailIndices[i] = GetTailIndex(tailIndices[i - 1]);
}
var tailIndex = HasTail(entryIndex) ? GetTailIndex(entryIndex) : -1;
var entry = _buckets[entryIndex];
if (IsTail(entry!, entryIndex))
SetParentTail(entryIndex, Tails.SOLO);
SetBucketEmpty(entryIndex);
InsertEntry(entry!, ReplaceBehaviour.Throw, true);
bucket = default;
_tails.SetEmpty(entryIndex);
InsertEntry(entry!, shifting: true);
if (tailIndex < 0)
return;
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);
entryIndex = tailIndex;
}
}
@@ -621,20 +549,15 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
if (_equalityComparer(key, entry))
{
var tailIndex = -1;
if (IsTail(entry!, bucketIndex))
{
if (HasTail(bucketIndex))
tailIndex = GetTailIndex(bucketIndex);
else
SetParentTail(bucketIndex, Tails.SOLO);
}
var tailIndex = HasTail(bucketIndex) ? GetTailIndex(bucketIndex) : -1;
entry = default;
_tails.SetEmpty(bucketIndex);
if (IsTail(entry!, bucketIndex))
SetParentTail(bucketIndex, Tails.SOLO);
SetBucketEmpty(bucketIndex);
if (tailIndex != -1)
EmplaceWithTails(tailIndex);
EmplaceWithTailsBackward(tailIndex);
return true;
}
@@ -714,17 +637,27 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
#endregion
#region enums
private enum ReplaceBehaviour
{
Replace,
Throw,
Fail
}
#endregion
#region Structs
internal struct Tails
internal struct Tails(uint length)
{
public const uint
EMPTY = 0u,
SOLO = 1u;
private NibbleArray _values;
public Tails(uint length) => _values = new(length);
private NibbleArray _values = new(length);
public uint this[uint index]
{
@@ -837,10 +770,11 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
}
[DoesNotReturn]
internal static void ThrowAddingDuplicateWithKeyArgumentException(T key)
internal static bool ThrowAddingDuplicateWithKeyArgumentException(T key)
{
Guard.IsNotNull(key);
ThrowAddingDuplicateWithKeyArgumentException((object)key);
return true;
}
[DoesNotReturn]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,260 @@
// 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;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
namespace FisheryLib.Collections;
/// <summary>
/// Indexable collection similar to a list, but with constant time Contains, Remove, Insert and IndexOf methods for any
/// argument type. Order of elements is not preserved on inserting and removing however. Displacement happens between
/// the target index and the last slot. X and Y are individually enforced to be unique
/// </summary>
[PublicAPI]
public class IndexedFishMap<TX, TY> : IList<(TX X, TY Y)>, IList, IReadOnlyList<(TX X, TY Y)>
// IDictionary<TX, TY>, IDictionary<TY, TX>
{
private readonly List<(TX X, TY Y)> _items = [];
private readonly FishTable<TX, int> _xIndices = new();
private readonly FishTable<TY, int> _yIndices = new();
public (TX X, TY Y) this[int index]
{
get => _items[index];
set
{
var previousValue = _items[index];
_items[index] = value;
_xIndices.Remove(previousValue.X);
_xIndices.Add(value.X, index);
_yIndices.Remove(previousValue.Y);
_yIndices.Add(value.Y, index);
}
}
public TY this[TX x]
{
get => _items[_xIndices[x]].Y;
set
{
Start:
if (!_xIndices.TryGetValue(x, out var index))
index = _items.Count;
if (!_yIndices.TryAdd(value, index))
{
if (RemoveIfNotEqual(_yIndices, value, index))
goto Start;
else
return;
}
if (index == _items.Count)
{
_xIndices[x] = index;
_items.Add((x, value));
}
else
{
_items[index] = (x, value);
}
}
}
public TX this[TY y]
{
get => _items[_yIndices[y]].X;
set
{
Start:
if (!_yIndices.TryGetValue(y, out var index))
index = _items.Count;
if (!_xIndices.TryAdd(value, index))
{
if (RemoveIfNotEqual(_xIndices, value, index))
goto Start;
else
return;
}
if (index == _items.Count)
{
_yIndices[y] = index;
_items.Add((value, y));
}
else
{
_items[index] = (value, y);
}
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool RemoveIfNotEqual<TKey>(FishTable<TKey, int> fishTable, TKey key, int index)
=> fishTable[key] != index && fishTable.Remove(key);
public List<(TX X, TY Y)> ReadOnlyList => _items;
public List<(TX X, TY Y)>.Enumerator GetEnumerator() => _items.GetEnumerator();
IEnumerator<(TX X, TY Y)> IEnumerable<(TX X, TY Y)>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Add((TX X, TY Y) item)
{
if (_xIndices.ContainsKey(item.X) || !_yIndices.TryAdd(item.Y, _items.Count))
ThrowHelper.ThrowInvalidOperationException();
_xIndices.Add(item.X, _items.Count);
_items.Add(item);
}
public int Add(object? value)
{
Add(EnsureCorrectType(value));
return _items.Count - 1;
}
public bool TryAdd((TX X, TY Y) item)
{
if (_xIndices.ContainsKey(item.X) || !_yIndices.TryAdd(item.Y, _items.Count))
return false;
_xIndices.Add(item.X, _items.Count);
_items.Add(item);
return true;
}
public bool Contains(object? value) => IsCompatibleObject(value) && Contains(((TX X, TY Y))value);
public void Clear()
{
_xIndices.Clear();
_items.Clear();
}
public int IndexOf(object value) => IsCompatibleObject(value) ? IndexOf(((TX X, TY Y))value) : -1;
public void Insert(int index, object value) => Insert(index, EnsureCorrectType(value));
public void Remove(object value)
{
if (IsCompatibleObject(value))
Remove(((TX X, TY Y))value);
}
public bool Contains((TX X, TY Y) item) => _xIndices.ContainsKey(item.X);
public bool Contains(TX x) => _xIndices.ContainsKey(x);
public bool Contains(TY y) => _yIndices.ContainsKey(y);
public void CopyTo((TX X, TY Y)[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex);
public bool Remove((TX X, TY Y) item)
{
if (!_xIndices.ContainsKey(item.X) || !_yIndices.Remove(item.Y))
return false;
_xIndices.Remove(item.X, out var index);
_items.RemoveAtFastUnordered(index);
if (index < _items.Count)
_xIndices[_items[index].X] = _yIndices[_items[index].Y] = index;
return true;
}
public bool Remove(TX x)
{
if (!_xIndices.Remove(x, out var index))
return false;
_yIndices.Remove(_items[index].Y);
_items.RemoveAtFastUnordered(index);
if (index < _items.Count)
_xIndices[_items[index].X] = _yIndices[_items[index].Y] = index;
return true;
}
public bool Remove(TY y)
{
if (!_yIndices.Remove(y, out var index))
return false;
_xIndices.Remove(_items[index].X);
_items.RemoveAtFastUnordered(index);
if (index < _items.Count)
_xIndices[_items[index].X] = _yIndices[_items[index].Y] = index;
return true;
}
public void CopyTo(Array array, int index) => ((IList)_items).CopyTo(array, index);
public int Count => _items.Count;
public object SyncRoot => this;
public bool IsSynchronized => false;
public bool IsReadOnly => false;
public bool IsFixedSize => false;
public int IndexOf((TX X, TY Y) item) => _xIndices.TryGetValue(item.X, out var index) ? index : -1;
public int IndexOf(TX x) => _xIndices.TryGetValue(x, out var index) ? index : -1;
public int IndexOf(TY y) => _yIndices.TryGetValue(y, out var index) ? index : -1;
public void Insert(int index, (TX X, TY Y) item)
{
if (_xIndices.ContainsKey(item.X) || !_yIndices.TryAdd(item.Y, index))
ThrowHelper.ThrowInvalidOperationException();
_xIndices.Add(item.X, index);
_items.InsertFastUnordered(index, item);
var lastIndex = _items.Count - 1;
_xIndices[_items[lastIndex].X] = _yIndices[_items[lastIndex].Y] = lastIndex;
}
public void RemoveAt(int index)
{
var item = _items[index];
_items.RemoveAtFastUnordered(index);
_xIndices.Remove(item.X);
_yIndices.Remove(item.Y);
if (index < _items.Count)
_xIndices[_items[index].X] = _yIndices[_items[index].Y] = index;
}
object IList.this[int index]
{
get => this[index];
set => this[index] = EnsureCorrectType(value);
}
private static bool IsCompatibleObject([NotNullWhen(true)] object? value)
=> value is (TX, TY) || (value == null) & (default(TX) == null) & (default(TY) == null);
private static (TX X, TY Y) EnsureCorrectType([NotNullWhen(true)] object? item)
=> item is ValueTuple<TX, TY> t ? t : ThrowHelper.ThrowArgumentException<(TX X, TY Y)>();
}
@@ -0,0 +1,146 @@
// 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;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
namespace FisheryLib.Collections;
/// <summary>
/// Indexable collection similar to a list, but with constant time Contains, Remove, Insert and IndexOf methods. Order
/// of elements is not preserved on inserting and removing however. Displacement happens between the target index and
/// the last slot
/// </summary>
[PublicAPI]
public class IndexedFishSet<T> : IList<T>, IList, IReadOnlyList<T>
{
private readonly List<T> _items = [];
private readonly FishTable<T, int> _indices = new();
public T this[int index]
{
get => _items[index];
set
{
var previousValue = _items[index];
_items[index] = value;
_indices.Remove(previousValue);
_indices.Add(value, index);
}
}
public List<T> ReadOnlyList => _items;
public List<T>.Enumerator GetEnumerator() => _items.GetEnumerator();
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void Add(T item)
{
if (!_indices.TryAdd(item, _items.Count))
ThrowHelper.ThrowInvalidOperationException();
_items.Add(item);
}
public int Add(object? value)
{
Add(EnsureCorrectType(value));
return _items.Count - 1;
}
public bool TryAdd(T item)
{
if (!_indices.TryAdd(item, _items.Count))
return false;
_items.Add(item);
return true;
}
public bool Contains(object? value) => IsCompatibleObject(value) && Contains((T)value);
public void Clear()
{
_indices.Clear();
_items.Clear();
}
public int IndexOf(object value) => IsCompatibleObject(value) ? IndexOf((T)value) : -1;
public void Insert(int index, object value) => Insert(index, EnsureCorrectType(value));
public void Remove(object value)
{
if (IsCompatibleObject(value))
Remove((T)value);
}
public bool Contains(T item) => _indices.ContainsKey(item);
public void CopyTo(T[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex);
public bool Remove(T item)
{
if (!_indices.Remove(item, out var index))
return false;
_items.RemoveAtFastUnordered(index);
if (index < _items.Count)
_indices[_items[index]] = index;
return true;
}
public void CopyTo(Array array, int index) => ((IList)_items).CopyTo(array, index);
public int Count => _items.Count;
public object SyncRoot => this;
public bool IsSynchronized => false;
public bool IsReadOnly => false;
public bool IsFixedSize => false;
public int IndexOf(T item) => _indices.TryGetValue(item, out var index) ? index : -1;
public void Insert(int index, T item)
{
if (!_indices.TryAdd(item, index))
ThrowHelper.ThrowInvalidOperationException();
_items.InsertFastUnordered(index, item);
var lastIndex = _items.Count - 1;
_indices[_items[lastIndex]] = lastIndex;
}
public void RemoveAt(int index)
{
var item = _items[index];
_items.RemoveAtFastUnordered(index);
_indices.Remove(item);
if (index < _items.Count)
_indices[_items[index]] = index;
}
object IList.this[int index]
{
get => this[index]!;
set => this[index] = EnsureCorrectType(value);
}
private static bool IsCompatibleObject([NotNullWhen(true)] object? value)
=> value is T || (value == null && default(T) == null);
private static T EnsureCorrectType([NotNullWhen(true)] object? item)
=> item is T t ? t : ThrowHelper.ThrowArgumentException<T>();
}
+1 -1
View File
@@ -9,7 +9,7 @@ using JetBrains.Annotations;
namespace FisheryLib.Collections;
[PublicAPI]
public struct NibbleArray
public record struct NibbleArray
{
private uint _length;
private byte[] _data;
@@ -2,6 +2,7 @@
// 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.Linq;
using System.Runtime.CompilerServices;
@@ -15,7 +16,8 @@ public static class TypeExtensions
/// <summary>
/// The mapping of built-in types to their simple representation.
/// </summary>
private static readonly IReadOnlyDictionary<Type, string> BuiltInTypesMap = new Dictionary<Type, string>
[SuppressMessage("Performance", "CA1859")]
private static readonly IReadOnlyDictionary<Type, string> _builtInTypesMap = new Dictionary<Type, string>
{
[typeof(bool)] = "bool",
[typeof(byte)] = "byte",
@@ -38,7 +40,7 @@ public static class TypeExtensions
/// <summary>
/// A thread-safe mapping of precomputed string representation of types.
/// </summary>
private static readonly ConditionalWeakTable<Type, string> DisplayNames = new();
private static readonly ConditionalWeakTable<Type, string> _displayNames = new();
/// <summary>
/// Returns a simple string representation of a type.
@@ -51,7 +53,7 @@ public static class TypeExtensions
static string FormatDisplayString(Type type, int genericTypeOffset, ReadOnlySpan<Type> typeArguments)
{
// Primitive types use the keyword name
if (BuiltInTypesMap.TryGetValue(type, out var typeName))
if (_builtInTypesMap.TryGetValue(type, out var typeName))
return typeName!;
// Array types are displayed as Foo[]
@@ -131,7 +133,7 @@ public static class TypeExtensions
}
// Atomically get or build the display string for the current type.
return DisplayNames.GetValue(type, static t =>
return _displayNames.GetValue(type, static t =>
{
// By-ref types are displayed as T&
if (t.IsByRef)
@@ -7,12 +7,16 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using JetBrains.Annotations;
// ReSharper disable InconsistentNaming
namespace FisheryLib.Utility.Diagnostics;
/// <summary>
/// Helper methods to efficiently throw exceptions.
/// </summary>
[PublicAPI]
[SuppressMessage("Usage", "CA2201")]
public static partial class ThrowHelper
{
/// <summary>
+53 -147
View File
@@ -3,6 +3,7 @@
// 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.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
@@ -200,6 +201,7 @@ public static class FishTranspiler
public static bool operator !=(CodeInstruction code, Container helper) => !(helper == code);
[SuppressMessage("Usage", "CA2225")]
public static implicit operator CodeInstruction(Container helper) => helper.ToInstruction();
//public static implicit operator Container(CodeInstruction instruction)
@@ -701,13 +703,15 @@ public static class FishTranspiler
/// Pushes a supplied value of type int32 onto the evaluation stack as an int32.
/// </summary>
/// <param name="integer">The value to push onto the evaluation stack</param>
public static Container Constant(int integer) => new(GetLoadConstantOpCode(integer), GetOperandOfConstant(integer));
public static Container Constant([SuppressMessage("Naming", "CA1720")] int integer)
=> new(GetLoadConstantOpCode(integer), GetOperandOfConstant(integer));
/// <summary>
/// Pushes a supplied value of type int64 onto the evaluation stack as an int64.
/// </summary>
/// <param name="integer">The value to push onto the evaluation stack</param>
public static Container Constant(long integer) => new(OpCodes.Ldc_I8, integer);
public static Container Constant([SuppressMessage("Naming", "CA1720")] long integer)
=> new(OpCodes.Ldc_I8, integer);
/// <summary>
/// Pushes a supplied value of type float32 onto the evaluation stack as type F (float).
@@ -1129,6 +1133,7 @@ public static class FishTranspiler
/// Pushes a new object reference to a string literal stored in the metadata.
/// </summary>
/// <param name="text">The string literal</param>
[SuppressMessage("Naming", "CA1720")]
public static Container String(string text)
{
Guard.IsNotNull(text);
@@ -2425,72 +2430,34 @@ public static class FishTranspiler
public static bool StoresElement(this OpCode opcode) => StoreElementOpCodes.Contains(opcode);
public static readonly HashSet<OpCode> LoadElementOpCodes
= new()
{
OpCodes.Ldelem,
OpCodes.Ldelema,
OpCodes.Ldelem_I,
OpCodes.Ldelem_I1,
OpCodes.Ldelem_I2,
OpCodes.Ldelem_I4,
OpCodes.Ldelem_I8,
OpCodes.Ldelem_R4,
OpCodes.Ldelem_R8,
OpCodes.Ldelem_Ref,
OpCodes.Ldelem_U1,
OpCodes.Ldelem_U2,
OpCodes.Ldelem_U4
};
public static readonly HashSet<OpCode> LoadElementOpCodes =
[
OpCodes.Ldelem, OpCodes.Ldelema, OpCodes.Ldelem_I, OpCodes.Ldelem_I1, OpCodes.Ldelem_I2, OpCodes.Ldelem_I4,
OpCodes.Ldelem_I8, OpCodes.Ldelem_R4, OpCodes.Ldelem_R8, OpCodes.Ldelem_Ref, OpCodes.Ldelem_U1,
OpCodes.Ldelem_U2, OpCodes.Ldelem_U4
];
public static readonly HashSet<OpCode> StoreElementOpCodes
= new()
{
OpCodes.Stelem,
OpCodes.Stelem_I,
OpCodes.Stelem_I1,
OpCodes.Stelem_I2,
OpCodes.Stelem_I4,
OpCodes.Stelem_I8,
OpCodes.Stelem_R4,
OpCodes.Stelem_R8,
OpCodes.Stelem_Ref
};
public static readonly HashSet<OpCode> StoreElementOpCodes =
[
OpCodes.Stelem, OpCodes.Stelem_I, OpCodes.Stelem_I1, OpCodes.Stelem_I2, OpCodes.Stelem_I4,
OpCodes.Stelem_I8, OpCodes.Stelem_R4, OpCodes.Stelem_R8, OpCodes.Stelem_Ref
];
public static bool LoadsByRef(this OpCode opcode) => LoadByRefOpCodes.Contains(opcode);
public static bool StoresByRef(this OpCode opcode) => StoreByRefOpCodes.Contains(opcode);
public static readonly HashSet<OpCode> LoadByRefOpCodes
= new()
{
OpCodes.Ldobj,
OpCodes.Ldind_I,
OpCodes.Ldind_I1,
OpCodes.Ldind_I2,
OpCodes.Ldind_I4,
OpCodes.Ldind_I8,
OpCodes.Ldind_R4,
OpCodes.Ldind_R8,
OpCodes.Ldind_Ref,
OpCodes.Ldind_U1,
OpCodes.Ldind_U2,
OpCodes.Ldind_U4
};
public static readonly HashSet<OpCode> LoadByRefOpCodes =
[
OpCodes.Ldobj, OpCodes.Ldind_I, OpCodes.Ldind_I1, OpCodes.Ldind_I2, OpCodes.Ldind_I4, OpCodes.Ldind_I8,
OpCodes.Ldind_R4, OpCodes.Ldind_R8, OpCodes.Ldind_Ref, OpCodes.Ldind_U1, OpCodes.Ldind_U2, OpCodes.Ldind_U4
];
public static readonly HashSet<OpCode> StoreByRefOpCodes
= new()
{
OpCodes.Stobj,
OpCodes.Stind_I,
OpCodes.Stind_I1,
OpCodes.Stind_I2,
OpCodes.Stind_I4,
OpCodes.Stind_I8,
OpCodes.Stind_R4,
OpCodes.Stind_R8,
OpCodes.Stind_Ref
};
public static readonly HashSet<OpCode> StoreByRefOpCodes =
[
OpCodes.Stobj, OpCodes.Stind_I, OpCodes.Stind_I1, OpCodes.Stind_I2, OpCodes.Stind_I4, OpCodes.Stind_I8,
OpCodes.Stind_R4, OpCodes.Stind_R8, OpCodes.Stind_Ref
];
public static bool LoadsField(this OpCode opcode) => opcode.LoadsInstanceField() || opcode.LoadsStaticField();
@@ -2538,100 +2505,38 @@ public static class FishTranspiler
public static bool Compares(this OpCode opcode) => CompareOpCodes.Contains(opcode);
public static readonly HashSet<OpCode> CompareOpCodes
= new()
{
OpCodes.Ceq,
OpCodes.Cgt,
OpCodes.Cgt_Un,
OpCodes.Clt,
OpCodes.Clt_Un
};
= [OpCodes.Ceq, OpCodes.Cgt, OpCodes.Cgt_Un, OpCodes.Clt, OpCodes.Clt_Un];
public static bool Computes(this OpCode opcode) => ComputeOpCodes.Contains(opcode);
public static readonly HashSet<OpCode> ComputeOpCodes
= new()
{
OpCodes.Add,
OpCodes.Add_Ovf,
OpCodes.Add_Ovf_Un,
OpCodes.Sub,
OpCodes.Sub_Ovf,
OpCodes.Sub_Ovf_Un,
OpCodes.Mul,
OpCodes.Mul_Ovf,
OpCodes.Mul_Ovf_Un,
OpCodes.Div,
OpCodes.Div_Un,
OpCodes.Rem,
OpCodes.Rem_Un,
OpCodes.And,
OpCodes.Or,
OpCodes.Xor,
OpCodes.Shl,
OpCodes.Shr,
OpCodes.Shr_Un,
OpCodes.Not,
OpCodes.Neg
};
public static readonly HashSet<OpCode> ComputeOpCodes =
[
OpCodes.Add, OpCodes.Add_Ovf, OpCodes.Add_Ovf_Un, OpCodes.Sub, OpCodes.Sub_Ovf, OpCodes.Sub_Ovf_Un,
OpCodes.Mul, OpCodes.Mul_Ovf, OpCodes.Mul_Ovf_Un, OpCodes.Div, OpCodes.Div_Un, OpCodes.Rem, OpCodes.Rem_Un,
OpCodes.And, OpCodes.Or, OpCodes.Xor, OpCodes.Shl, OpCodes.Shr, OpCodes.Shr_Un, OpCodes.Not, OpCodes.Neg
];
public static bool Converts(this OpCode opcode) => ConvertOpCodes.Contains(opcode);
public static readonly HashSet<OpCode> ConvertOpCodes
= new()
{
OpCodes.Conv_I,
OpCodes.Conv_I1,
OpCodes.Conv_I2,
OpCodes.Conv_I4,
OpCodes.Conv_I8,
OpCodes.Conv_Ovf_I,
OpCodes.Conv_Ovf_I1,
OpCodes.Conv_Ovf_I1_Un,
OpCodes.Conv_Ovf_I2,
OpCodes.Conv_Ovf_I2_Un,
OpCodes.Conv_Ovf_I4,
OpCodes.Conv_Ovf_I4_Un,
OpCodes.Conv_Ovf_I8,
OpCodes.Conv_Ovf_I8_Un,
OpCodes.Conv_Ovf_I_Un,
OpCodes.Conv_Ovf_U,
OpCodes.Conv_Ovf_U1,
OpCodes.Conv_Ovf_U1_Un,
OpCodes.Conv_Ovf_U2,
OpCodes.Conv_Ovf_U2_Un,
OpCodes.Conv_Ovf_U4,
OpCodes.Conv_Ovf_U4_Un,
OpCodes.Conv_Ovf_U8,
OpCodes.Conv_Ovf_U8_Un,
OpCodes.Conv_Ovf_U_Un,
OpCodes.Conv_R4,
OpCodes.Conv_R8,
OpCodes.Conv_R_Un,
OpCodes.Conv_U,
OpCodes.Conv_U1,
OpCodes.Conv_U2,
OpCodes.Conv_U4,
OpCodes.Conv_U8,
OpCodes.Box,
OpCodes.Unbox,
OpCodes.Unbox_Any
};
public static readonly HashSet<OpCode> ConvertOpCodes =
[
OpCodes.Conv_I, OpCodes.Conv_I1, OpCodes.Conv_I2, OpCodes.Conv_I4, OpCodes.Conv_I8, OpCodes.Conv_Ovf_I,
OpCodes.Conv_Ovf_I1, OpCodes.Conv_Ovf_I1_Un, OpCodes.Conv_Ovf_I2, OpCodes.Conv_Ovf_I2_Un,
OpCodes.Conv_Ovf_I4, OpCodes.Conv_Ovf_I4_Un, OpCodes.Conv_Ovf_I8, OpCodes.Conv_Ovf_I8_Un,
OpCodes.Conv_Ovf_I_Un, OpCodes.Conv_Ovf_U, OpCodes.Conv_Ovf_U1, OpCodes.Conv_Ovf_U1_Un, OpCodes.Conv_Ovf_U2,
OpCodes.Conv_Ovf_U2_Un, OpCodes.Conv_Ovf_U4, OpCodes.Conv_Ovf_U4_Un, OpCodes.Conv_Ovf_U8,
OpCodes.Conv_Ovf_U8_Un, OpCodes.Conv_Ovf_U_Un, OpCodes.Conv_R4, OpCodes.Conv_R8, OpCodes.Conv_R_Un,
OpCodes.Conv_U, OpCodes.Conv_U1, OpCodes.Conv_U2, OpCodes.Conv_U4, OpCodes.Conv_U8, OpCodes.Box,
OpCodes.Unbox, OpCodes.Unbox_Any
];
public static bool LoadsArgument(this OpCode opcode) => LoadArgumentOpCodes.Contains(opcode);
public static readonly HashSet<OpCode> LoadArgumentOpCodes
= new()
{
OpCodes.Ldarg,
OpCodes.Ldarga,
OpCodes.Ldarga_S,
OpCodes.Ldarg_0,
OpCodes.Ldarg_1,
OpCodes.Ldarg_2,
OpCodes.Ldarg_3,
OpCodes.Ldarg_S
};
public static readonly HashSet<OpCode> LoadArgumentOpCodes =
[
OpCodes.Ldarg, OpCodes.Ldarga, OpCodes.Ldarga_S, OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2,
OpCodes.Ldarg_3, OpCodes.Ldarg_S
];
public static OpCode ToStoreArgument(this OpCode opcode)
=> opcode.StoresArgument() ? opcode
@@ -2766,7 +2671,8 @@ public static class FishTranspiler
public static object? GetOperandFromIndex(int index) => index > 3 ? index : null;
public static object? GetOperandOfConstant(int integer) => integer is < 9 and > -2 ? null : integer;
public static object? GetOperandOfConstant([SuppressMessage("Naming", "CA1720")] int integer)
=> integer is < 9 and > -2 ? null : integer;
public static object? GetOperandFromBuilder(LocalBuilder builder)
{
+19 -1
View File
@@ -14,20 +14,38 @@ global using CodeInstructions = System.Collections.Generic.IEnumerable<HarmonyLi
using System.Security;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
[assembly: AllowPartiallyTrustedCallers]
[assembly: SecurityRules(SecurityRuleSet.Level2, SkipVerificationInFullTrust = true)]
[assembly: Debuggable(false, false)]
[assembly: AssemblyVersion(FisheryLib.FisheryLib.VERSION_STRING)]
[module: SkipLocalsInit]
namespace FisheryLib;
[PublicAPI]
public static class FisheryLib
{
public const decimal VERSION = 0.380085M;
public const decimal VERSION = 0.39995M;
public static decimal CurrentlyLoadedVersion { get; } = VERSION;
public const string VERSION_STRING = "0.3.999.5";
// public static string GetVersion()
// {
// var text = File.ReadAllText("Fishery.cs");
// const string VERSION_FIELD_NAME = "VERSION_STRING = \"";
// var versionIndex = text.IndexOf(VERSION_FIELD_NAME, StringComparison.InvariantCulture);
// versionIndex += VERSION_FIELD_NAME.Length;
// var versionEndIndex = versionIndex;
// while (text[versionEndIndex] != '"')
// versionEndIndex++;
//
// return text.Substring(versionIndex, versionEndIndex - versionIndex);
// }
}
/*namespace FisheryLib;
+9 -2
View File
@@ -20,8 +20,15 @@
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<Configurations>v1.4;v1.3</Configurations>
<Version>0.3.800.85</Version>
<NoWarn>1701;1702;IL2121;CS8500;CS9082</NoWarn>
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
<!-- Version>0.3.800.85</Version -->
<!-- Deterministic>False</Deterministic -->
<NoWarn>
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
</NoWarn>
</PropertyGroup>
<Choose>
<When Condition="'$(Configuration)' == 'v1.4'">
@@ -51,7 +51,7 @@ internal static class Convert
try
{
var dm = new DynamicMethod($"ObjectConverter_{from}_{to}", to,
new[] { typeof(object), typeof(IFormatProvider) }, typeof(FromObjectTo<TTo>), true);
[typeof(object), typeof(IFormatProvider)], typeof(FromObjectTo<TTo>), true);
var il = dm.GetILGenerator();
il.Emit(FishTranspiler.This);
@@ -59,7 +59,7 @@ internal static class Convert
if (from.IsValueType)
{
il.Emit(FishTranspiler.Call(typeof(FromObjectTo<TTo>), nameof(UnboxSafely),
generics: new[] { from }));
generics: [from]));
}
il.Emit(FishTranspiler.Argument(1));
@@ -72,7 +72,8 @@ internal static class Convert
}
catch (Exception ex)
{
throw new($"Failed to compile object converter from {from.FullDescription()} to {
return ThrowHelper.ThrowInvalidOperationException<Func<object, IFormatProvider, TTo>>(
$"Failed to compile object converter from {from.FullDescription()} to {
to.FullDescription()}\n{ex}");
}
}
@@ -83,7 +84,7 @@ internal static class Convert
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SecuritySafeCritical]
private static ref T UnboxSafely<T>(object obj) where T : struct
=> ref Unsafe.Unbox<T>(obj is T ? obj : default);
=> ref Unsafe.Unbox<T>(obj is T ? obj : default(T));
private record struct FuncWrapper
{
@@ -163,7 +164,7 @@ internal static class Convert
private static MethodInfo GenerateWrapperMethod(Type from, Type to, MethodBase method)
{
var dm = new DynamicMethod($"Wrapper_op_{from.Name}_{to.Name}", to,
new[] { from.IsValueType ? from.MakeByRefType() : from, typeof(IFormatProvider) }, from, true);
[from.IsValueType ? from.MakeByRefType() : from, typeof(IFormatProvider)], from, true);
var il = dm.GetILGenerator();
il.Emit(FishTranspiler.Argument(0));
@@ -3,6 +3,7 @@
// 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.Diagnostics.CodeAnalysis;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Security;
@@ -16,6 +17,7 @@ public static class Equals<T>
= (delegate*<T, T, bool>)FunctionPointers.Equals.EqualsMethods.GetFunctionPointer(typeof(T));
}
[SuppressMessage("Naming", "CA1720")]
public static class Equals
{
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
@@ -37,6 +39,8 @@ public static class Equals
public static bool IntPtr(IntPtr x, IntPtr y) => x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SuppressMessage("Globalization", "CA1307")]
[SuppressMessage("Globalization", "CA1309")]
public static bool String(string x, string y) => string.Equals(x, y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -3,6 +3,7 @@
// 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.Diagnostics.CodeAnalysis;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Security;
@@ -16,6 +17,7 @@ public static class EqualsByRef<T>
= (delegate*<ref T, ref T, bool>)EqualsByRef.EqualsMethods.GetFunctionPointer(typeof(T));
}
[SuppressMessage("Naming", "CA1720")]
public static class EqualsByRef
{
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
@@ -37,6 +39,8 @@ public static class EqualsByRef
public static bool IntPtr(ref IntPtr x, ref IntPtr y) => x == y;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SuppressMessage("Globalization", "CA1307")]
[SuppressMessage("Globalization", "CA1309")]
public static bool String(ref string x, ref string y) => string.Equals(x, y);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -3,6 +3,7 @@
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
@@ -14,6 +15,7 @@ public static class GetHashCode<T>
= (delegate*<T, int>)FunctionPointers.GetHashCode.HashCodeMethods.GetFunctionPointer(typeof(T));
}
[SuppressMessage("Naming", "CA1720")]
public static class GetHashCode
{
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
@@ -3,6 +3,7 @@
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
@@ -14,6 +15,7 @@ public static class GetHashCodeByRef<T>
= (delegate*<ref T, int>)GetHashCodeByRef.HashCodeMethods.GetFunctionPointer(typeof(T));
}
[SuppressMessage("Naming", "CA1720")]
public static class GetHashCodeByRef
{
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
@@ -11,15 +11,15 @@ using System.Security;
namespace FisheryLib.FunctionPointers;
public abstract class MethodFactory
public abstract class MethodFactory(MethodInfo[] methods, string namePrefix, bool byRef)
{
protected readonly MethodInfo[] _methods;
protected readonly MethodInfo[] _methods = methods;
protected readonly ConcurrentDictionary<Type, MethodInfo> _methodsByType = new();
protected readonly string _namePrefix;
protected readonly string _namePrefix = namePrefix;
protected readonly bool _byRef;
protected readonly bool _byRef = byRef;
public MethodInfo GetGeneric(string name, params Type[] typeArguments)
=> GetNamed(name).MakeGenericMethod(typeArguments);
@@ -64,14 +64,8 @@ public abstract class MethodFactory
=> type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly)
.Where(predicate).ToArray();
protected MethodFactory(MethodInfo[] methods, string namePrefix, bool byRef)
{
_byRef = byRef;
_methods = methods;
_namePrefix = namePrefix;
}
public new class Equals : MethodFactory
public new class Equals(Type type, string dynamicMethodNamePrefix, bool byRef)
: MethodFactory(GetMethodInfoArraySafely(type), dynamicMethodNamePrefix, byRef)
{
protected override MethodInfo GetOrMakeMethodForType(Type type)
=> GetSpecializedMethod(type)
@@ -160,7 +154,7 @@ public abstract class MethodFactory
try
{
var dm = new DynamicMethod($"{_namePrefix}{type.Name}", typeof(bool),
_byRef ? new[] { type.MakeByRefType(), type.MakeByRefType() } : new[] { type, type }, type, true);
_byRef ? [type.MakeByRefType(), type.MakeByRefType()] : [type, type], type, true);
var il = dm.GetILGenerator();
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
@@ -233,15 +227,11 @@ public abstract class MethodFactory
}
}
public Equals(Type type, string dynamicMethodNamePrefix, bool byRef)
: base(GetMethodInfoArraySafely(type), dynamicMethodNamePrefix, byRef)
{
}
private delegate TResult ByRefFunc<T1, T2, out TResult>(ref T1 arg1, ref T2 arg2);
}
public class HashCode : MethodFactory
public class HashCode(Type type, string dynamicMethodNamePrefix, bool byRef)
: MethodFactory(GetMethodInfoArraySafely(type), dynamicMethodNamePrefix, byRef)
{
protected override MethodInfo GetOrMakeMethodForType(Type type)
=> GetSpecializedMethod(type)
@@ -325,7 +315,7 @@ public abstract class MethodFactory
try
{
var dm = new DynamicMethod($"{_namePrefix}{type.Name}", typeof(int),
_byRef ? new[] { type.MakeByRefType() } : new[] { type }, type, true);
_byRef ? [type.MakeByRefType()] : [type], type, true);
var il = dm.GetILGenerator();
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
@@ -382,11 +372,6 @@ public abstract class MethodFactory
}
}
public HashCode(Type type, string dynamicMethodNamePrefix, bool byRef)
: base(GetMethodInfoArraySafely(type), dynamicMethodNamePrefix, byRef)
{
}
private delegate int ByRefHashCodeGetter<T>(ref T arg);
private delegate int HashCodeGetter<in T>(T arg);
}
+201
View File
@@ -0,0 +1,201 @@
// Copyright (c) 2023 bradson
// This Source Code Form is subject to the terms of the MIT license.
// If a copy of the license was not distributed with this file,
// You can obtain one at https://opensource.org/licenses/MIT/.
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using UnityEngine;
using Verse;
namespace FisheryLib;
[PublicAPI]
public static class GUIScope
{
public readonly record struct WidgetGroup : IDisposable
{
public WidgetGroup(in Rect rect) => Widgets.BeginGroup(rect);
public void Dispose() => Widgets.EndGroup();
}
public readonly record struct ScrollView : IDisposable
{
private const float SCROLL_BAR_WIDTH = 16f;
private readonly ScrollViewStatus _scrollViewStatus;
private readonly float _outRectHeight;
public readonly Vector2 ViewSize;
public ref float Height
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ref _scrollViewStatus.Height;
}
public Vector2 Position
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _scrollViewStatus.Position;
}
public ScrollView(in Rect outRect, ScrollViewStatus scrollViewStatus, bool showScrollbars = true)
{
_scrollViewStatus = scrollViewStatus;
_outRectHeight = outRect.height;
var viewRect = new Rect(0f, 0f, outRect.width, Math.Max(Height, _outRectHeight));
if (Height - 0.1f >= outRect.height)
viewRect.width -= SCROLL_BAR_WIDTH;
ViewSize = viewRect.size;
Height = 0f;
Widgets.BeginScrollView(outRect, ref _scrollViewStatus.Position, viewRect, showScrollbars);
}
public void Dispose() => Widgets.EndScrollView();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanCull(float entryHeight, float entryY)
=> entryY + entryHeight < Position.y
|| entryY > Position.y + _outRectHeight;
}
public class ScrollViewStatus
{
public Vector2 Position;
public float Height;
}
public readonly record struct ListingStandard : IDisposable
{
public Listing_Standard Listing
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
public ListingStandard(in Rect rect, Listing_Standard? listing = null)
=> (Listing = listing ?? new()).Begin(rect);
public void Dispose() => Listing.End();
}
public readonly record struct ScrollableListingStandard : IDisposable
{
private readonly ListingStandard _listingStandard;
private readonly ScrollView _scrollView;
public Listing_Standard Listing
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _listingStandard.Listing;
}
public Vector2 ScrollViewSize
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _scrollView.ViewSize;
}
public Vector2 ScrollViewPosition
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _scrollView.Position;
}
public ScrollableListingStandard(Rect outRect, ScrollViewStatus scrollViewStatus,
Listing_Standard? listing = null, bool showScrollbars = true)
{
_scrollView = new(outRect, scrollViewStatus, showScrollbars);
_listingStandard = new(new(0f, 0f, ScrollViewSize.x, float.PositiveInfinity), listing);
}
public void Dispose()
{
_scrollView.Height = Listing.CurHeight + 12f;
_listingStandard.Dispose();
_scrollView.Dispose();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryCull(float entryHeight)
{
var canCull = _scrollView.CanCull(entryHeight, Listing.curY);
if (canCull)
Listing.curY += entryHeight;
return canCull;
}
}
public readonly record struct TextAnchor : IDisposable
{
private readonly UnityEngine.TextAnchor _default;
public TextAnchor(UnityEngine.TextAnchor anchor)
{
_default = Text.Anchor;
Text.Anchor = anchor;
}
public void Dispose() => Text.Anchor = _default;
}
public readonly record struct WordWrap : IDisposable
{
private readonly bool _default;
public WordWrap(bool wordWrap)
{
_default = Text.WordWrap;
Text.WordWrap = wordWrap;
}
public void Dispose() => Text.WordWrap = _default;
}
public readonly record struct Color : IDisposable
{
private readonly UnityEngine.Color _default;
public Color(UnityEngine.Color color)
{
_default = GUI.color;
GUI.color = color;
}
public void Dispose() => GUI.color = _default;
}
public readonly record struct Font : IDisposable
{
private readonly GameFont _default;
public Font(GameFont font)
{
_default = Text.Font;
Text.Font = font;
}
public void Dispose() => Text.Font = _default;
}
public readonly record struct FontSize : IDisposable
{
private readonly int _default;
public FontSize(int size)
{
var curFontStyle = Text.CurFontStyle;
_default = curFontStyle.fontSize;
curFontStyle.fontSize = size;
}
public void Dispose() => Text.CurFontStyle.fontSize = _default;
}
}
+37
View File
@@ -0,0 +1,37 @@
// 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;
namespace FisheryLib.Pools;
public static class FishPool<T> where T : IFishPoolable
{
private static T?[] _freeItems = new T[4];
private static int _freeItemCount;
public static T Get()
{
if (_freeItemCount == 0)
return Reflection.New<T>();
var freeItem = _freeItems[--_freeItemCount];
_freeItems[_freeItemCount] = default;
return freeItem!;
}
public static void Return(T item)
{
item.Reset();
if (++_freeItemCount >= _freeItems.Length)
ExpandFreeItems();
_freeItems[_freeItemCount - 1] = item;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ExpandFreeItems() => Array.Resize(ref _freeItems, _freeItems.Length << 1);
}
+11
View File
@@ -0,0 +1,11 @@
// 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/.
namespace FisheryLib.Pools;
public interface IFishPoolable
{
public void Reset();
}
+49
View File
@@ -0,0 +1,49 @@
// 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.Collections;
using System.Runtime.CompilerServices;
namespace FisheryLib.Pools;
public record struct PooledIList<T>() : IDisposable where T : IList, new()
{
public T List = Get();
private static T?[] _freeItems = new T[4];
private static int _freeItemCount;
public void Dispose()
{
if (List == null)
return;
Return(List);
List = default!;
}
public static T Get()
{
if (_freeItemCount == 0)
return Reflection.New<T>();
var freeItem = _freeItems[--_freeItemCount];
_freeItems[_freeItemCount] = default;
return freeItem!;
}
public static void Return(T item)
{
item.Clear();
if (++_freeItemCount >= _freeItems.Length)
ExpandFreeItems();
_freeItems[_freeItemCount - 1] = item;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ExpandFreeItems() => Array.Resize(ref _freeItems, _freeItems.Length << 1);
}
@@ -0,0 +1,62 @@
// 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.Security;
using System.Text;
using JetBrains.Annotations;
namespace FisheryLib.Pools;
public record struct PooledStringBuilder : IDisposable
{
private FishPoolable _fishPoolable;
public StringBuilder Builder => _fishPoolable.Builder;
public StringBuilder Append(string? value) => Builder.Append(value);
public StringBuilder Append(char value) => Builder.Append(value);
public StringBuilder Append(char[] value) => Builder.Append(value);
public StringBuilder Append(PooledStringBuilderHandler value) => value.AppendToStringBuilder(Builder);
public StringBuilder AppendLine(string? value) => Builder.AppendLine(value);
public StringBuilder Insert(int index, string? value) => Builder.Insert(index, value);
public StringBuilder Insert(int index, char value) => Builder.Insert(index, value);
public StringBuilder Remove(int startIndex, int length) => Builder.Remove(startIndex, length);
public void Clear() => Builder.Clear();
public override string ToString() => Builder.ToString();
[SecuritySafeCritical]
public PooledStringBuilder() => _fishPoolable = FishPool<FishPoolable>.Get();
public PooledStringBuilder(int minimumSize) : this()
{
if (_fishPoolable.Builder.Capacity < minimumSize)
_fishPoolable.Builder.Capacity = minimumSize;
}
[SecuritySafeCritical]
public void Dispose()
{
if (_fishPoolable.Builder == null!)
return;
FishPool<FishPoolable>.Return(_fishPoolable);
_fishPoolable.Builder = null!;
}
private struct FishPoolable : IFishPoolable
{
public StringBuilder Builder = new();
public void Reset() => Builder.Clear();
[UsedImplicitly]
public FishPoolable() {}
}
}
@@ -0,0 +1,40 @@
// 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 System.Text;
using JetBrains.Annotations;
namespace FisheryLib.Pools;
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
[InterpolatedStringHandler]
public record struct PooledStringBuilderHandler : IDisposable
{
private PooledIList<List<char>> _chars;
public PooledStringBuilderHandler(int literalLength, int formattedCount)
{
_chars = new();
_chars.List.EnsureCapacity(literalLength + (formattedCount * 8));
}
public void AppendLiteral(string s) => _chars.List.Add(s);
public void AppendFormatted<T>(T t)
{
if (t != null)
_chars.List.Add(t.ToString());
}
public StringBuilder AppendToStringBuilder(StringBuilder destination)
{
destination.Append(_chars.List);
Dispose();
return destination;
}
public void Dispose() => _chars.Dispose();
}
@@ -0,0 +1,38 @@
// 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 JetBrains.Annotations;
namespace FisheryLib.Pools;
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
[InterpolatedStringHandler]
public record struct PooledStringHandler : IDisposable
{
private PooledStringBuilder _builder;
public PooledStringHandler(int literalLength, int formattedCount)
=> _builder = new(literalLength + (formattedCount * 8));
public void AppendLiteral(string s) => _builder.Append(s);
public void AppendFormatted<T>(T t)
{
if (typeof(T) == typeof(PooledStringBuilderHandler))
_builder.Append(Unsafe.As<T, PooledStringBuilderHandler>(ref t));
else
_builder.Append(t?.ToString());
}
public string GetFormattedText()
{
var result = _builder.ToString();
Dispose();
return result;
}
public void Dispose() => _builder.Dispose();
}
+38 -3
View File
@@ -12,6 +12,7 @@ using System.Runtime.CompilerServices;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using FisheryLib.Pools;
using JetBrains.Annotations;
using Verse;
@@ -20,6 +21,9 @@ namespace FisheryLib;
[PublicAPI]
public static class Reflection
{
private static Assembly[] _allAssemblies = Array.Empty<Assembly>();
private static object _allAssembliesLock = new();
public static CodeInstructions GetCodeInstructions(Delegate method, ILGenerator? generator = null)
=> GetCodeInstructions(method.Method, generator);
@@ -43,7 +47,37 @@ public static class Reflection
yield return FishTranspiler.Return;
}
public static Type? Type(string assembly, string type) => System.Type.GetType($"{type}, {assembly}");
public static Assembly[] AllAssemblies
{
get
{
lock (_allAssembliesLock)
{
return _allAssemblies.Length == AppDomain.CurrentDomain.GetAssemblies().Length
? _allAssemblies
: _allAssemblies = AccessTools.AllAssemblies().ToArray();
}
}
}
public static Type? Type(string assembly, string typeFullName)
=> System.Type.GetType(string.Concat(typeFullName, ", ", assembly));
public static Type? Type(string assembly, string @namespace, string type)
=> System.Type.GetType(StringHelper.Resolve($"{@namespace}.{type}, {assembly}"));
public static Type? Type(string fullName)
{
var assemblies = AllAssemblies;
for (var i = assemblies.Length; i-- > 0;)
{
if (assemblies[i].GetType(fullName) is { } type)
return type;
}
return null;
}
public static MethodInfo MethodInfo<T>(T method) where T : Delegate => method.Method;
@@ -323,7 +357,8 @@ public static class Reflection
if (memberInfo is null)
return "NULL";
var stringBuilder = new StringBuilder();
using var pooledStringBuilder = new PooledStringBuilder();
var stringBuilder = pooledStringBuilder.Builder;
switch (memberInfo)
{
@@ -346,7 +381,7 @@ public static class Reflection
if (memberInfo is PropertyInfo propertyInfo1)
AppendPropertyPostfix(stringBuilder, propertyInfo1);
return stringBuilder.ToString();
return pooledStringBuilder.ToString();
}
private static void AppendFieldPrefix(FieldInfo fieldInfo, StringBuilder stringBuilder)
+13
View File
@@ -0,0 +1,13 @@
// 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 FisheryLib.Pools;
namespace FisheryLib;
public static class StringHelper
{
public static string Resolve(PooledStringHandler interpolation) => interpolation.GetFormattedText();
}
+3 -2
View File
@@ -23,6 +23,7 @@ internal static class UtilityF
}
[DoesNotReturn]
[SuppressMessage("Usage", "CA2201")]
internal static void Throw(string message) => throw new(message);
[DoesNotReturn]
@@ -37,10 +38,10 @@ internal static class UtilityF
for (var i = 0; i < stacktrace.FrameCount; i++)
{
var assembly = stacktrace.GetFrame(i).GetMethod().ReflectedType.Assembly;
var assembly = stacktrace.GetFrame(i).GetMethod().ReflectedType?.Assembly;
fishAssembly ??= assembly;
if (assembly != fishAssembly)
if (assembly != fishAssembly && assembly != null)
return assembly;
}
+3 -2
View File
@@ -35,10 +35,11 @@ public static class FishMath
}
private static byte[] _trailingZeroCountDeBruijn
= {
=
[
0, 1, 28, 2, 29, 14, 24, 3, 30, 22,
20, 15, 25, 17, 4, 8, 31, 27, 13, 23,
21, 19, 16, 7, 26, 12, 18, 6, 11, 5,
10, 9
};
];
}