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
+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();
@@ -348,26 +351,40 @@ public static class CecilExtensions
var newMethodReference = module.ImportReference(replacement);
var newBody = newMethodReference.Resolve().Body;
newBody.SimplifyMacros();
MethodBodyRocks.ComputeOffsets(newBody);
oldBody.Instructions.Clear();
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
}));
}
MethodBodyRocks.ComputeOffsets(targetBody);
foreach (var instruction in targetInstructions)
{
if (instruction.Operand is Instruction operand)
instruction.Operand = targetInstructions.First(ti => ti.Offset == operand.Offset);
}
oldBody.Variables.Clear();
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();
+120 -186
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);
@@ -210,6 +163,12 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
_buckets[bucketIndex] = entry;
_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)
{
@@ -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, shifting: true);
if (hasTail)
{
for (var i = 0; i < tailingEntries!.Length; i++)
InsertEntry(tailingEntries[i], shifting: true);
}
InsertEntry(previousEntry, ReplaceBehaviour.Throw, 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,30 +267,36 @@ 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);
bucket = entry;
return true;
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,56 +324,55 @@ 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 = hasTail ? new T?[GetTailCount(bucketIndex)] : null;
if (hasTail)
{
var k = 0;
var tailIndex = GetTailIndex(bucketIndex);
while (true)
{
ref var tailingBucket = ref _buckets[tailIndex];
tailingEntries![k] = tailingBucket;
k++;
tailingBucket = default;
if (!_tails.IsSoloOrEmpty(tailIndex))
{
var nextTailIndex = GetTailIndex(tailIndex);
_tails.SetEmpty(tailIndex);
tailIndex = nextTailIndex;
}
else
{
_tails.SetEmpty(tailIndex);
break;
}
}
}
var tailingEntries = TryGetAndClearTailingEntries(bucketIndex);
SetParentTail(bucketIndex, Tails.SOLO);
SetEntryAsTail(bucketIndex, entry, parentIndex, offset);
InsertEntry(previousEntry!, shifting: true);
InsertEntry(previousEntry!, ReplaceBehaviour.Throw, true);
if (hasTail)
{
for (var k = 0; k < tailingEntries!.Length; k++)
InsertEntry(tailingEntries[k]!, shifting: true);
}
if (tailingEntries != null)
InsertEntries(tailingEntries, ReplaceBehaviour.Throw, true);
return true;
}
private T[]? TryGetAndClearTailingEntries(int bucketIndex)
{
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[i++] = tailingBucket!;
tailingBucket = default;
if (HasTail(tailIndex))
{
var nextTailIndex = GetTailIndex(tailIndex);
_tails.SetEmpty(tailIndex);
tailIndex = nextTailIndex;
}
else
{
_tails.SetEmpty(tailIndex);
break;
}
}
return tailingEntries;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void WarnForPossiblyExcessiveResizing(T entry)
=> Log.Warning($"FishTable is resizing from a large number of clashing hashCodes. Last inserted key: '{
@@ -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;
Span<int> tailIndices = stackalloc int[GetTailCount(entryIndex)];
if (HasTail(entryIndex))
if (IsBucketEmpty(entryIndex))
Utility.Diagnostics.ThrowHelper.ThrowInvalidOperationException("Tried to emplace entry bucket");
while (true)
{
tailIndices[0] = GetTailIndex(entryIndex);
for (var i = 1; i < tailIndices.Length; i++)
tailIndices[i] = GetTailIndex(tailIndices[i - 1]);
}
if (IsTail(entry!, entryIndex))
SetParentTail(entryIndex, Tails.SOLO);
bucket = default;
_tails.SetEmpty(entryIndex);
InsertEntry(entry!, shifting: true);
for (var i = 1; i < tailIndices.Length; i++)
{
var tailIndex = tailIndices[i];
ref var tailBucket = ref _buckets[tailIndex];
var tailEntry = tailBucket;
var tailIndex = HasTail(entryIndex) ? GetTailIndex(entryIndex) : -1;
var entry = _buckets[entryIndex];
tailBucket = default;
_tails.SetEmpty(tailIndex);
InsertEntry(tailEntry!, shifting: true);
SetBucketEmpty(entryIndex);
InsertEntry(entry!, ReplaceBehaviour.Throw, true);
if (tailIndex < 0)
return;
entryIndex = tailIndex;
}
}
@@ -621,20 +549,15 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
if (_equalityComparer(key, entry))
{
var tailIndex = -1;
var tailIndex = HasTail(bucketIndex) ? GetTailIndex(bucketIndex) : -1;
if (IsTail(entry!, bucketIndex))
{
if (HasTail(bucketIndex))
tailIndex = GetTailIndex(bucketIndex);
else
SetParentTail(bucketIndex, Tails.SOLO);
}
SetParentTail(bucketIndex, Tails.SOLO);
entry = default;
_tails.SetEmpty(bucketIndex);
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,8 +72,9 @@ internal static class Convert
}
catch (Exception ex)
{
throw new($"Failed to compile object converter from {from.FullDescription()} to {
to.FullDescription()}\n{ex}");
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);
@@ -232,16 +226,12 @@ public abstract class MethodFactory
throw;
}
}
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
};
];
}