Added pools, IndexedFishMap, IndexedFishSet, fixed bugs related to FishTable.Remove, added Preview.png, various smaller additions, some refactoring too
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user