Added 1.2 and 1.5 support, mod icon, DefaultValue/Key for FishSet/Table
This commit is contained in:
@@ -7,6 +7,9 @@
|
||||
// which itself is based on
|
||||
// https://probablydance.com/2018/05/28/a-new-fast-hash-table-in-response-to-googles-new-fast-hash-table/
|
||||
|
||||
#if V1_2
|
||||
extern alias nuget;
|
||||
#endif
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -14,7 +17,10 @@ using System.Runtime.InteropServices;
|
||||
using FisheryLib.FunctionPointers;
|
||||
using FisheryLib.Pools;
|
||||
using FisheryLib.Utility;
|
||||
using JetBrains.Annotations;
|
||||
#if V1_2
|
||||
using CollectionAccessType = nuget::JetBrains.Annotations.CollectionAccessType;
|
||||
using CollectionAccessAttribute = nuget::JetBrains.Annotations.CollectionAccessAttribute;
|
||||
#endif
|
||||
|
||||
namespace FisheryLib.Collections;
|
||||
|
||||
@@ -42,6 +48,8 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
|
||||
|
||||
private float _maxLoadFactor = 0.5f;
|
||||
|
||||
private T _defaultValue = Internal.KeyUtility<T>.Default;
|
||||
|
||||
public int Version
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -65,6 +73,22 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
/// <summary>
|
||||
/// Empty FishSets have their entries set to a default value, causing fishSet.Contains(DefaultValue) to return
|
||||
/// true. This property can be used for a check against this default, similar to a null. Unless set to a different
|
||||
/// default through the FishSet constructor, for most reference types, the returned value is a static invalid
|
||||
/// reference. Reference types implementing IEquatable or overriding Equals have null as default instead. Primitives
|
||||
/// have all their bytes set to sbyte.MaxValue. Structs have their contents set according to previous rules.
|
||||
/// </summary>
|
||||
public T DefaultValue => _defaultValue;
|
||||
|
||||
public unsafe uint SizeEstimate
|
||||
=> GetType().ComputeManagedObjectSizeEstimate()
|
||||
+ ((uint)sizeof(T) * (uint)_buckets.Length)
|
||||
+ ((_tails.Length + 1u) >> 1);
|
||||
|
||||
public uint CollisionCount => (uint)GetTailingEntriesCount();
|
||||
|
||||
public event Action<T>?
|
||||
EntryAdded,
|
||||
EntryRemoved;
|
||||
@@ -112,6 +136,19 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
|
||||
|
||||
public FishSet(int minimumCapacity) => Initialize(minimumCapacity);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new FishSet
|
||||
/// </summary>
|
||||
/// <param name="minimumCapacity">The minimum capacity of the backing array</param>
|
||||
/// <param name="defaultValue">
|
||||
/// The default value for empty entries. Contains() returns true for this value when empty
|
||||
/// </param>
|
||||
public FishSet(int minimumCapacity, T defaultValue)
|
||||
{
|
||||
_defaultValue = defaultValue;
|
||||
Initialize(minimumCapacity);
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(_buckets))]
|
||||
[MemberNotNull(nameof(_tails))]
|
||||
private void Initialize(int minimumCapacity = 0)
|
||||
@@ -120,6 +157,7 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
|
||||
// Mathf.NextPowerOfTwo(minimumCapacity);
|
||||
|
||||
_buckets = new T?[minimumCapacity];
|
||||
Array.Fill(_buckets, _defaultValue);
|
||||
_tails = new((uint)minimumCapacity);
|
||||
_bucketBitShift = 32 - FishMath.TrailingZeroCount((uint)_buckets.Length);
|
||||
_wrapAroundMask = _buckets.Length - 1;
|
||||
@@ -167,7 +205,7 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
|
||||
|
||||
private void SetBucketEmpty(int index)
|
||||
{
|
||||
_buckets[index] = default;
|
||||
_buckets[index] = _defaultValue;
|
||||
_tails.SetEmpty(index);
|
||||
}
|
||||
|
||||
@@ -356,7 +394,7 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
|
||||
{
|
||||
ref var tailingBucket = ref _buckets[tailIndex];
|
||||
tailingEntriesList.Add(tailingBucket!);
|
||||
tailingBucket = default;
|
||||
tailingBucket = _defaultValue;
|
||||
|
||||
if (HasTail(tailIndex))
|
||||
{
|
||||
@@ -409,6 +447,7 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
|
||||
|
||||
_tails = new((uint)_buckets.Length << 1);
|
||||
_buckets = new T?[_buckets.Length << 1];
|
||||
Array.Fill(_buckets, _defaultValue);
|
||||
|
||||
_bucketBitShift = 32 - FishMath.TrailingZeroCount((uint)_buckets.Length);
|
||||
_wrapAroundMask = _buckets.Length - 1;
|
||||
@@ -579,7 +618,7 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
|
||||
if (_buckets is null)
|
||||
Initialize();
|
||||
|
||||
Array.Clear(_buckets, 0, _buckets.Length);
|
||||
Array.Fill(_buckets, Internal.KeyUtility<T>.Default);
|
||||
_tails.Reset();
|
||||
|
||||
_count = 0;
|
||||
@@ -660,6 +699,8 @@ public class FishSet<T> : ISet<T>, IReadOnlyCollection<T>, ICollection
|
||||
|
||||
private NibbleArray _values = new(length);
|
||||
|
||||
public uint Length => _values.Length;
|
||||
|
||||
public uint this[uint index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -7,15 +7,22 @@
|
||||
// which itself is based on
|
||||
// https://probablydance.com/2018/05/28/a-new-fast-hash-table-in-response-to-googles-new-fast-hash-table/
|
||||
|
||||
#if V1_2
|
||||
extern alias nuget;
|
||||
#endif
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using FisheryLib.Collections.Internal;
|
||||
using FisheryLib.FunctionPointers;
|
||||
using FisheryLib.Pools;
|
||||
using FisheryLib.Utility;
|
||||
using JetBrains.Annotations;
|
||||
#if V1_2
|
||||
using CollectionAccessType = nuget::JetBrains.Annotations.CollectionAccessType;
|
||||
using CollectionAccessAttribute = nuget::JetBrains.Annotations.CollectionAccessAttribute;
|
||||
#endif
|
||||
|
||||
namespace FisheryLib.Collections;
|
||||
|
||||
@@ -45,6 +52,8 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
_version;
|
||||
|
||||
private float _maxLoadFactor = 0.5f;
|
||||
|
||||
private Entry _defaultEntry = Entry.Default;
|
||||
|
||||
public KeyCollection Keys => new(this);
|
||||
|
||||
@@ -65,6 +74,15 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
/// <summary>
|
||||
/// Empty FishTables have their entries set to a default value, causing fishTable.ContainsKey(DefaultKey) to return
|
||||
/// true. This property can be used for a check against this default, similar to a null. Unless set to a different
|
||||
/// default through the FishTable constructor, for most reference types, the returned value is a static invalid
|
||||
/// reference. Reference types implementing IEquatable or overriding Equals have null as default instead. Primitives
|
||||
/// have all their bytes set to sbyte.MaxValue. Structs have their contents set according to previous rules.
|
||||
/// </summary>
|
||||
public TKey DefaultKey => _defaultEntry.Key;
|
||||
|
||||
public unsafe uint SizeEstimate
|
||||
=> GetType().ComputeManagedObjectSizeEstimate()
|
||||
+ ((uint)sizeof(Entry) * (uint)_buckets.Length)
|
||||
@@ -227,6 +245,19 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
|
||||
public FishTable(int minimumCapacity) => Initialize(minimumCapacity);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a new FishTable
|
||||
/// </summary>
|
||||
/// <param name="minimumCapacity">The minimum capacity of the backing array</param>
|
||||
/// <param name="defaultKey">
|
||||
/// The default key for empty entries. ContainsKey() returns true for this when empty
|
||||
/// </param>
|
||||
public FishTable(int minimumCapacity, TKey defaultKey)
|
||||
{
|
||||
_defaultEntry.Key = defaultKey;
|
||||
Initialize(minimumCapacity);
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(_buckets))]
|
||||
[MemberNotNull(nameof(_tails))]
|
||||
private void Initialize(int minimumCapacity = 0)
|
||||
@@ -235,6 +266,7 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
// Mathf.NextPowerOfTwo(minimumCapacity);
|
||||
|
||||
_buckets = new Entry[minimumCapacity];
|
||||
Array.Fill(_buckets, _defaultEntry);
|
||||
_tails = new((uint)minimumCapacity);
|
||||
_bucketBitShift = 32 - FishMath.TrailingZeroCount((uint)_buckets.Length);
|
||||
_wrapAroundMask = _buckets.Length - 1;
|
||||
@@ -280,7 +312,7 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
|
||||
private void SetBucketEmpty(int index)
|
||||
{
|
||||
_buckets[index] = default;
|
||||
_buckets[index] = _defaultEntry;
|
||||
_tails.SetEmpty(index);
|
||||
}
|
||||
|
||||
@@ -400,8 +432,11 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
{
|
||||
ref var bucket = ref buckets[bucketIndex];
|
||||
|
||||
if (!_keyEqualityComparer(bucket.Key, default))
|
||||
if (!CompareBytes(bucket.Key, _defaultEntry.Key)
|
||||
&& !_keyEqualityComparer(bucket.Key, _defaultEntry.Key))
|
||||
{
|
||||
ThrowHelper.ThrowEmptyBucketKeyInvalidOperationException(bucketIndex, bucket);
|
||||
}
|
||||
|
||||
if (bucket.Value.Equals<TValue>(default))
|
||||
return true;
|
||||
@@ -412,6 +447,43 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
return false; // no good workaround without performance hit
|
||||
}
|
||||
|
||||
private static unsafe bool CompareBytes(TKey a, TKey b)
|
||||
{
|
||||
var size = sizeof(TKey);
|
||||
var iOffset = 0;
|
||||
var i = 0;
|
||||
do
|
||||
{
|
||||
var currentA = *((nint*)&a + iOffset);
|
||||
var currentB = *((nint*)&b + iOffset);
|
||||
|
||||
if (size - i >= sizeof(nint))
|
||||
{
|
||||
if (currentA != currentB)
|
||||
return false;
|
||||
|
||||
iOffset++;
|
||||
i += sizeof(nint);
|
||||
}
|
||||
else
|
||||
{
|
||||
var remainingSize = size & sizeof(nint);
|
||||
i = 0;
|
||||
do
|
||||
{
|
||||
if ((byte)(currentA >> i) != (byte)(currentB >> i))
|
||||
return false;
|
||||
}
|
||||
while (++i < remainingSize);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (i < size);
|
||||
|
||||
return size != 0;
|
||||
}
|
||||
|
||||
private bool TryFindTailIndexAndReplace(in Entry entry, ref int bucketIndex, ReplaceBehaviour replaceBehaviour)
|
||||
{
|
||||
while (true)
|
||||
@@ -498,7 +570,7 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
{
|
||||
ref var tailingBucket = ref _buckets[tailIndex];
|
||||
tailingEntriesList.Add(tailingBucket);
|
||||
tailingBucket = default;
|
||||
tailingBucket = _defaultEntry;
|
||||
|
||||
if (HasTail(tailIndex))
|
||||
{
|
||||
@@ -585,6 +657,7 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
|
||||
_tails = new((uint)newSize);
|
||||
_buckets = new Entry[newSize];
|
||||
Array.Fill(_buckets, _defaultEntry);
|
||||
|
||||
_bucketBitShift = 32 - FishMath.TrailingZeroCount((uint)newSize);
|
||||
_wrapAroundMask = _buckets.Length - 1;
|
||||
@@ -1046,7 +1119,7 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
private void EmplaceWithTailsBackward(int entryIndex)
|
||||
{
|
||||
if (IsBucketEmpty(entryIndex))
|
||||
Utility.Diagnostics.ThrowHelper.ThrowInvalidOperationException("Tried to emplace entry bucket");
|
||||
Utility.Diagnostics.ThrowHelper.ThrowInvalidOperationException("Tried to emplace empty bucket");
|
||||
|
||||
while (true)
|
||||
{
|
||||
@@ -1225,7 +1298,7 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
if (_buckets is null)
|
||||
Initialize();
|
||||
|
||||
Array.Clear(_buckets, 0, _buckets.Length);
|
||||
Array.Fill(_buckets, _defaultEntry);
|
||||
_tails.Reset();
|
||||
|
||||
_count = 0;
|
||||
@@ -1360,6 +1433,8 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
.Append<TValue>(Value)
|
||||
.Append(" }").ToString();
|
||||
}
|
||||
|
||||
public static readonly Entry Default = new(KeyUtility<TKey>.Default, default);
|
||||
}
|
||||
|
||||
internal struct Tails(uint length)
|
||||
@@ -1688,11 +1763,11 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
internal static void ThrowKeyNotFoundException<T>(T key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
ThrowKeyNotFoundException((object)key);
|
||||
ThrowKeyNotFoundException((object?)key);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
private static void ThrowKeyNotFoundException(object key)
|
||||
private static void ThrowKeyNotFoundException(object? key)
|
||||
=> throw new KeyNotFoundException($"The given key '{key}' was not present in the dictionary.");
|
||||
|
||||
[DoesNotReturn]
|
||||
@@ -1715,7 +1790,7 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
internal static bool ThrowAddingDuplicateWithKeyArgumentException<T>(T key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
ThrowAddingDuplicateWithKeyArgumentException((object)key);
|
||||
ThrowAddingDuplicateWithKeyArgumentException((object?)key);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1782,7 +1857,7 @@ public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, I
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
private static void ThrowAddingDuplicateWithKeyArgumentException(object key)
|
||||
private static void ThrowAddingDuplicateWithKeyArgumentException(object? key)
|
||||
=> throw new ArgumentException($"An item with the same key has already been added. Key: {key}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace FisheryLib.Collections;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
using System.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace FisheryLib.Collections;
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v.2.0.If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace FisheryLib.Collections.Internal;
|
||||
|
||||
public static class KeyUtility<T>
|
||||
{
|
||||
public static readonly T Default = GetDefaultValue();
|
||||
|
||||
private static T GetDefaultValue()
|
||||
{
|
||||
var type = typeof(T);
|
||||
|
||||
var invalidKey = KeyUtility.GetDefaultValue(type);
|
||||
if (!type.IsValueType)
|
||||
return Unsafe.As<object?, T>(ref invalidKey);
|
||||
|
||||
return (T)invalidKey!;
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeyUtility
|
||||
{
|
||||
public static readonly object InvalidObject = new();
|
||||
|
||||
public const ulong InvalidValue = 0x7F7F_7F7F_7F7F_7F7FUL;
|
||||
|
||||
public static readonly string InvalidString
|
||||
= new(['\u7F7F', '\u7F7F', '\u7F7F', '\u7F7F', '\u7F7F', '\u7F7F', '\u7F7F', '\u7F7F']);
|
||||
|
||||
private static readonly ConcurrentDictionary<FieldInfo, Action<object>> _fieldSetters = [];
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, object> _defaultValues = [];
|
||||
|
||||
private static readonly MethodInfo _genericMaxValueMethodDefinition
|
||||
= methodof(GetMaxValue<ulong>).GetGenericMethodDefinition();
|
||||
|
||||
private static readonly FieldInfo _invalidFieldInfo = typeof(KeyUtility).GetField(nameof(InvalidObject));
|
||||
|
||||
public static object? GetDefaultValue(Type type)
|
||||
{
|
||||
if (type == typeof(string))
|
||||
return InvalidString;
|
||||
|
||||
if (_defaultValues.TryGetValue(type, out var result))
|
||||
return result;
|
||||
|
||||
if (!type.IsValueType)
|
||||
{
|
||||
return !type.IsAssignableTo(typeof(IEquatable<>), type)
|
||||
&& type.GetMethod(nameof(object.Equals),
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, [typeof(object)],
|
||||
Array.Empty<ParameterModifier>())?.DeclaringType
|
||||
== typeof(object)
|
||||
? InvalidObject
|
||||
: null;
|
||||
}
|
||||
|
||||
result = FormatterServices.GetUninitializedObject(type);
|
||||
|
||||
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
for (var i = 0; i < fields.Length; i++)
|
||||
{
|
||||
var field = fields[i];
|
||||
var fieldType = field.FieldType;
|
||||
if (fieldType.IsValueType)
|
||||
{
|
||||
if (AccessTools.IsValue(fieldType))
|
||||
field.SetValue(result, GetMaxValue(fieldType));
|
||||
else
|
||||
GetFieldInvalidator(field)(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fieldType != typeof(string))
|
||||
{
|
||||
if (!fieldType.IsAssignableTo(typeof(IEquatable<>), fieldType))
|
||||
GetFieldInvalidator(field)(result);
|
||||
else
|
||||
field.SetValue(result, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
field.SetValue(result, InvalidString);
|
||||
}
|
||||
}
|
||||
|
||||
if (Unsafe.As<object, ulong>(ref result) > 0xFFFF_FFFF_FFFF)
|
||||
Log.Error($"Detected invalid pointer with a value > 48 bit\n{new StackTrace(true)}");
|
||||
}
|
||||
|
||||
_defaultValues.TryAdd(type, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static object GetMaxValue(Type type)
|
||||
{
|
||||
if (!AccessTools.IsValue(type))
|
||||
{
|
||||
return ThrowHelper.ThrowArgumentException<object>($"Invalid type passed to KeyUtility.GetMaxValue: '{
|
||||
type.FullDescription()}'. Only primitive structs are supported.");
|
||||
}
|
||||
|
||||
var result = _genericMaxValueMethodDefinition.MakeGenericMethod(type).Invoke(null, null);
|
||||
_defaultValues.TryAdd(type, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static object GetMaxValue<T>() where T : struct
|
||||
{
|
||||
var maxValue = InvalidValue;
|
||||
return Unsafe.As<ulong, T>(ref maxValue)!;
|
||||
}
|
||||
|
||||
private static Action<object> GetFieldInvalidator(FieldInfo fieldInfo)
|
||||
{
|
||||
if (_fieldSetters.TryGetValue(fieldInfo, out var result))
|
||||
return result;
|
||||
|
||||
var method = new DynamicMethod($"FisheryLib><::>UnsafeSetter<::><{fieldInfo.FullDescription()}", typeof(void),
|
||||
[typeof(object)], typeof(KeyUtility), true);
|
||||
var ilGenerator = method.GetILGenerator();
|
||||
var fieldType = fieldInfo.FieldType;
|
||||
var declaringType = fieldInfo.DeclaringType;
|
||||
|
||||
ilGenerator.Emit(FishTranspiler.Argument(0));
|
||||
if (declaringType?.IsValueType ?? false)
|
||||
ilGenerator.Emit(FishTranspiler.UnboxAddress(declaringType));
|
||||
|
||||
if (!fieldType.IsValueType)
|
||||
{
|
||||
ilGenerator.Emit(FishTranspiler.Field(_invalidFieldInfo));
|
||||
ilGenerator.Emit(FishTranspiler.Call(methodof(Unsafe.As<object>).GetGenericMethodDefinition()
|
||||
.MakeGenericMethod(fieldType)));
|
||||
}
|
||||
else
|
||||
{
|
||||
ilGenerator.Emit(FishTranspiler.Token(fieldType));
|
||||
ilGenerator.Emit(FishTranspiler.Call(Type.GetTypeFromHandle));
|
||||
|
||||
ilGenerator.Emit(FishTranspiler.Call(AccessTools.IsValue(fieldType)
|
||||
// ReSharper disable once RedundantCast
|
||||
? (Func<Type, object?>)GetMaxValue
|
||||
: GetDefaultValue));
|
||||
|
||||
ilGenerator.Emit(FishTranspiler.UnboxValue(fieldType));
|
||||
}
|
||||
ilGenerator.Emit(FishTranspiler.StoreField(fieldInfo));
|
||||
ilGenerator.Emit(FishTranspiler.Return);
|
||||
|
||||
result = (Action<object>)method.CreateDelegate(typeof(Action<object>));
|
||||
_fieldSetters.TryAdd(fieldInfo, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace FisheryLib.Collections;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user