Added gas grid optimization, various despawn optimizations, new hauling optimizations utilizing events and regions, bugfixes for old patches and for vanilla functions too, as well as smaller additions. 1.3 files removed.
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
// 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/.
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
|
||||
public sealed class BitCellGrid
|
||||
{
|
||||
private long[] _innerArray;
|
||||
private CellIndices _cellIndices;
|
||||
|
||||
public BitCellGrid(Map map)
|
||||
{
|
||||
_innerArray = null!;
|
||||
_cellIndices = null!;
|
||||
map.InvokeWhenCellIndicesReady(Initialize);
|
||||
}
|
||||
|
||||
private void Initialize(Map map)
|
||||
{
|
||||
_cellIndices = map.cellIndices;
|
||||
_innerArray = new long[(Math.Max(0, _cellIndices.NumGridCells - 1) >> 6) + 1];
|
||||
}
|
||||
|
||||
public bool this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => (_innerArray[index >> 6] & (1L << (index & 63))) != 0L;
|
||||
set
|
||||
{
|
||||
ref var bucket = ref _innerArray[index >> 6];
|
||||
bucket ^= (-(long)value.AsInt() ^ bucket) & (1L << (index & 63));
|
||||
}
|
||||
}
|
||||
|
||||
public bool this[in IntVec3 c]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => this[c.CellToIndex(_cellIndices)];
|
||||
set => this[c.CellToIndex(_cellIndices)] = value;
|
||||
}
|
||||
}
|
||||
@@ -194,7 +194,7 @@ public class ByIndex<T_first, T_second, T_out> : ByIndex<T_first, ByIndex<T_seco
|
||||
ResizeIfNecessary(firstIndex);
|
||||
|
||||
ref var inner = ref _items[firstIndex];
|
||||
inner ??= new();
|
||||
inner ??= [];
|
||||
|
||||
return inner;
|
||||
}
|
||||
|
||||
@@ -12,16 +12,51 @@ namespace PerformanceFish.Cache;
|
||||
[PublicAPI]
|
||||
public record struct ByInt<T, TResult> where T : notnull where TResult : new()
|
||||
{
|
||||
private static FishTable<ByInt<T, TResult>, TResult> _get
|
||||
= Utility.AddNew<ByInt<T, TResult>, TResult>();
|
||||
public int Key;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ByInt(T key) => Key = FunctionPointers.IndexGetter<T>.Default(key);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ByInt(int key) => Key = key;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByInt<T, TResult> other) => Key == other.Key;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => Key;
|
||||
|
||||
private static object _valueInitializerLock = new();
|
||||
|
||||
private static FishTable<ByInt<T, TResult>, TResult> _get = InitializeNew();
|
||||
|
||||
[ThreadStatic]
|
||||
private static FishTable<ByInt<T, TResult>, TResult>? _getThreadStatic;
|
||||
|
||||
private static Func<ByInt<T, TResult>, TResult>? _valueInitializer;
|
||||
|
||||
public static Func<ByInt<T, TResult>, TResult>? ValueInitializer
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_valueInitializerLock)
|
||||
return _valueInitializer;
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (_valueInitializerLock)
|
||||
{
|
||||
value ??= static _ => Reflection.New<TResult>();
|
||||
_valueInitializer = value;
|
||||
_get.ValueInitializer = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static FishTable<ByInt<T, TResult>, TResult> Get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _getThreadStatic ??= Utility.AddNew<ByInt<T, TResult>, TResult>();
|
||||
get => _getThreadStatic ??= InitializeNew();
|
||||
}
|
||||
|
||||
public static FishTable<ByInt<T, TResult>, TResult> GetDirectly
|
||||
@@ -40,6 +75,15 @@ public record struct ByInt<T, TResult> where T : notnull where TResult : new()
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref TResult GetOrAddReference(ref ByInt<T, TResult> key) => ref Get.GetOrAddReference(ref key);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TResult GetOrAdd(int key) => Get.GetOrAdd(Unsafe.As<int, ByInt<T, TResult>>(ref key));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TResult GetOrAdd(ByInt<T, TResult> key) => Get.GetOrAdd(key);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TResult GetOrAdd(ref ByInt<T, TResult> key) => Get.GetOrAdd(ref key);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static ref TResult GetExistingReference(int key)
|
||||
=> ref Get.GetReference(Unsafe.As<int, ByInt<T, TResult>>(ref key));
|
||||
@@ -47,19 +91,8 @@ public record struct ByInt<T, TResult> where T : notnull where TResult : new()
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static ref TResult GetExistingReference(T key) => ref Get.GetReference(new(key));
|
||||
|
||||
public int Key;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ByInt(T key) => Key = FunctionPointers.IndexGetter<T>.Default(key);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ByInt(int key) => Key = key;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByInt<T, TResult> other) => Key == other.Key;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => Key;
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static FishTable<ByInt<T, TResult>, TResult> InitializeNew() => Utility.AddNew(ValueInitializer);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
|
||||
@@ -29,9 +29,9 @@ public static class ByMap<T> where T : new()
|
||||
private static Func<Map, T> Initialize { get; }
|
||||
= typeof(MapComponent).IsAssignableFrom(typeof(T))
|
||||
? AccessTools.MethodDelegate<Func<Map, T>>(AccessTools.Method(typeof(Map), nameof(Map.GetComponent),
|
||||
Type.EmptyTypes, new[] { typeof(T) }))
|
||||
Type.EmptyTypes, [typeof(T)]))
|
||||
// missing T constraints cause the compiler to complain when trying to just use map => map.GetComponent<T>
|
||||
: static _ => new();
|
||||
: static _ => Reflection.New<T>();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T GetFor(Map map)
|
||||
@@ -39,7 +39,7 @@ public static class ByMap<T> where T : new()
|
||||
var cache = GetCache;
|
||||
for (var i = 0; i < cache.Count; i++)
|
||||
{
|
||||
ref var cacheEntry = ref cache.GetReferenceUnverifiable(i);
|
||||
ref var cacheEntry = ref cache.GetReferenceUnsafe(i);
|
||||
if (cacheEntry.map == map)
|
||||
return cacheEntry.value;
|
||||
|
||||
@@ -59,7 +59,7 @@ public static class ByMap<T> where T : new()
|
||||
var cache = GetCache;
|
||||
for (var i = 0; i < cache.Count; i++)
|
||||
{
|
||||
ref var cacheEntry = ref cache.GetReferenceUnverifiable(i);
|
||||
ref var cacheEntry = ref cache.GetReferenceUnsafe(i);
|
||||
if (cacheEntry.map == map)
|
||||
return ref cacheEntry.value;
|
||||
|
||||
@@ -74,7 +74,10 @@ public static class ByMap<T> where T : new()
|
||||
}
|
||||
|
||||
private static bool EntryValid(int i, Map map)
|
||||
=> Find.Maps.Count > i && Find.Maps.Contains(map);
|
||||
{
|
||||
var maps = Current.Game.Maps;
|
||||
return maps.Count > i && maps.Contains(map);
|
||||
}
|
||||
|
||||
private static T AddEntry(Map map, List<(Map map, T value)> cache)
|
||||
{
|
||||
@@ -85,6 +88,6 @@ public static class ByMap<T> where T : new()
|
||||
private static ref T AddEntryByRef(Map map, List<(Map map, T value)> cache)
|
||||
{
|
||||
cache.Add((map, Initialize(map)));
|
||||
return ref cache.GetReferenceUnverifiable(cache.Count - 1).value;
|
||||
return ref cache.GetReferenceUnsafe(cache.Count - 1).value;
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ public static class ByReferenceUnclearable<T, TResult>
|
||||
Log.Error($"{e}");
|
||||
}
|
||||
|
||||
return result ?? throw new();
|
||||
return result ?? ThrowHelper.ThrowInvalidOperationException<TCacheResult>();
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
@@ -153,6 +153,7 @@ public static class ByReferenceUnclearable<T, TResult>
|
||||
cacheCopy.Task = null;
|
||||
}
|
||||
// ReSharper disable once SuspiciousTypeConversion.Global
|
||||
// ReSharper disable once PatternNeverMatches
|
||||
else if (centralCache.Task is Countdown taskCountdown)
|
||||
{
|
||||
Interlocked.Decrement(ref taskCountdown.Value);
|
||||
@@ -216,7 +217,7 @@ public static class ByReferenceUnclearable<T, TResult>
|
||||
Log.Error($"{e}");
|
||||
}
|
||||
|
||||
return result ?? throw new();
|
||||
return result ?? ThrowHelper.ThrowInvalidOperationException<TCacheResult>();
|
||||
}
|
||||
|
||||
public static unsafe void Initialize()
|
||||
@@ -239,17 +240,39 @@ public static class ByReferenceUnclearable<T, TResult>
|
||||
public static class ByReference<T, TResult>
|
||||
where T : notnull where TResult : new()
|
||||
{
|
||||
private static object _valueInitializerLock = new();
|
||||
|
||||
public static object SyncLock = new();
|
||||
|
||||
private static FishTable<T, TResult> _get = Utility.AddNew<T, TResult>();
|
||||
private static FishTable<T, TResult> _get = InitializeNew();
|
||||
|
||||
[ThreadStatic]
|
||||
private static FishTable<T, TResult>? _getThreadStatic;
|
||||
|
||||
private static Func<T, TResult>? _valueInitializer;
|
||||
|
||||
public static Func<T, TResult>? ValueInitializer
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_valueInitializerLock)
|
||||
return _valueInitializer;
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (_valueInitializerLock)
|
||||
{
|
||||
value ??= static _ => Reflection.New<TResult>();
|
||||
_valueInitializer = value;
|
||||
_get.ValueInitializer = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static FishTable<T, TResult> Get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _getThreadStatic ??= Utility.AddNew<T, TResult>();
|
||||
get => _getThreadStatic ??= InitializeNew();
|
||||
}
|
||||
|
||||
public static FishTable<T, TResult> GetDirectly
|
||||
@@ -258,6 +281,12 @@ public static class ByReference<T, TResult>
|
||||
get => _get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TResult GetOrAdd(T key) => Get.GetOrAdd(key);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TResult GetOrAdd(ref T key) => Get.GetOrAdd(ref key);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref TResult GetOrAddReference(T key) => ref Get.GetOrAddReference(key);
|
||||
|
||||
@@ -266,6 +295,12 @@ public static class ByReference<T, TResult>
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static ref TResult GetExistingReference(T key) => ref Get.GetReference(key);
|
||||
|
||||
public static void Remove(T key)
|
||||
{
|
||||
Get.Remove(key);
|
||||
GetDirectly.Remove(key);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Update<VResult>(T key) where VResult : TResult, ICacheable<T>
|
||||
@@ -355,7 +390,7 @@ public static class ByReference<T, TResult>
|
||||
Log.Error($"{e}");
|
||||
}
|
||||
|
||||
return result ?? throw new();
|
||||
return result ?? ThrowHelper.ThrowInvalidOperationException<TCacheResult>();
|
||||
}
|
||||
|
||||
public static Task<TCacheResult> RequestFromCacheAsync<TCacheValue, TArgument2, TCacheResult>(T key, TArgument2 second)
|
||||
@@ -406,7 +441,7 @@ public static class ByReference<T, TResult>
|
||||
Log.Error($"{e}");
|
||||
}
|
||||
|
||||
return result ?? throw new();
|
||||
return result ?? ThrowHelper.ThrowInvalidOperationException<TCacheResult>();
|
||||
}
|
||||
|
||||
public static unsafe void Initialize()
|
||||
@@ -423,6 +458,9 @@ public static class ByReference<T, TResult>
|
||||
typeof(ByReference<T, TResult>).FullName}\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static FishTable<T, TResult> InitializeNew() => Utility.AddNew(ValueInitializer);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
@@ -528,6 +566,7 @@ public record struct ByReference<T1, T2, TResult> : IInitializable<T1, T2>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// ReSharper disable once ConvertToPrimaryConstructor
|
||||
public ByReference(T1 first, T2 second)
|
||||
{
|
||||
First = first;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// 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.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
|
||||
public sealed class CellGrid<T>
|
||||
{
|
||||
private T[] _innerArray;
|
||||
private CellIndices _cellIndices;
|
||||
|
||||
public CellGrid(Map map)
|
||||
{
|
||||
_innerArray = null!;
|
||||
_cellIndices = null!;
|
||||
map.InvokeWhenCellIndicesReady(Initialize);
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(_innerArray), nameof(_cellIndices))]
|
||||
private void Initialize(Map map)
|
||||
{
|
||||
_cellIndices = map.cellIndices;
|
||||
_innerArray = new T[_cellIndices.NumGridCells];
|
||||
}
|
||||
|
||||
public ref T this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref _innerArray[index];
|
||||
}
|
||||
|
||||
public ref T this[in IntVec3 c]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref _innerArray[c.CellToIndex(_cellIndices)];
|
||||
}
|
||||
|
||||
public ref T this[CellIndex cellIndex]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref _innerArray[cellIndex.Value];
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ public interface IAsyncCacheable<in TKey, in T2, TResult> : IAsyncCacheable<TRes
|
||||
public ValueTask<TResult?> MakeResultAsync(TKey key, T2 second);
|
||||
}
|
||||
|
||||
internal record Countdown(int Value)
|
||||
internal sealed record Countdown(int Value)
|
||||
{
|
||||
public volatile int Value = Value;
|
||||
}
|
||||
@@ -5,16 +5,16 @@
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using JetBrains.Annotations;
|
||||
using PerformanceFish.Events;
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
|
||||
public static class Utility
|
||||
{
|
||||
public static event Action<Thing>? ThingDestroyed; // TODO
|
||||
|
||||
public static event Action? Cleared;
|
||||
|
||||
private static volatile int _destroyedThingCounter, _lastGCTick, _gcRate = 1000;
|
||||
@@ -24,11 +24,21 @@ public static class Utility
|
||||
private static ICollection[]? _cachesToProcessForGC;
|
||||
|
||||
private static object _gcLock = new();
|
||||
|
||||
public static ConcurrentBag<ICollection> All { get; } = [];
|
||||
|
||||
public static void NotifyThingDestroyed(Thing thing)
|
||||
public static void Clear()
|
||||
{
|
||||
ThingDestroyed?.Invoke(thing);
|
||||
foreach (var cache in All)
|
||||
cache.Clear();
|
||||
|
||||
Cleared?.Invoke();
|
||||
}
|
||||
|
||||
internal static void Initialize() => ThingEvents.Destroyed += TryContinueGC;
|
||||
|
||||
public static void TryContinueGC(Thing thing)
|
||||
{
|
||||
if (Interlocked.Increment(ref _destroyedThingCounter) < 100
|
||||
|| Current.programStateInt != ProgramState.Playing
|
||||
|| TickHelper.TicksGame - _lastGCTick < _gcRate)
|
||||
@@ -77,48 +87,59 @@ public static class Utility
|
||||
var count = 0;
|
||||
for (var i = collection.Length; i-- > 0;)
|
||||
{
|
||||
if (collection.Length > 0)
|
||||
if (collection[i].Count > 0)
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public static ConcurrentBag<ICollection> All { get; } = new();
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
foreach (var cache in All)
|
||||
cache.Clear();
|
||||
|
||||
Cleared?.Invoke();
|
||||
}
|
||||
|
||||
public static void LogCurrentCacheUtilization()
|
||||
{
|
||||
var allCaches = All.ToArray();
|
||||
Log.Message($"Total utilized caches: {allCaches.CountNonEmpty()}\n\n"
|
||||
+ $"Individual counts:\n{string.Join("\n",
|
||||
allCaches.Select(static cache => cache.GetType()).Distinct().Select(type
|
||||
=> $"{type.FullDescription()} :: {allCaches.Where(cache => cache.GetType() == type)
|
||||
.Select(static cache => cache.Count).Sum()}{TryCalculateSize(
|
||||
type, allCaches)}"))}\n=============================================================");
|
||||
var allCaches = All.Where(static c => c.Count > 0).ToArray();
|
||||
var allCacheTypes = allCaches.Select(static cache => cache.GetType()).Distinct();
|
||||
var cacheData = allCacheTypes.Select(type
|
||||
=>
|
||||
{
|
||||
var cachesOfType = allCaches.Where(cache => cache.GetType() == type).ToList();
|
||||
return (type, cachesOfType.Select(static cache => cache.Count).Sum(),
|
||||
TryCalculateSize(type, cachesOfType));
|
||||
}).ToArray();
|
||||
|
||||
Array.Sort(cacheData, static (x, y)
|
||||
=> y.Item3 is { } size ? size.CompareTo(x.Item3 ?? 0L)
|
||||
: y.Item2 != x.Item2 ? y.Item2.CompareTo(x.Item2)
|
||||
: string.Compare(x.Item1.ToString(), y.Item1.ToString(), StringComparison.Ordinal));
|
||||
|
||||
Log.Message($"Total utilized caches: {allCaches.Length} ({
|
||||
ToByteString(cacheData.Select(static tuple => tuple.Item3 ?? 0L).Sum())})\n\n"
|
||||
+ $"Individual counts:\n{string.Join("\n", cacheData.Select(static tuple
|
||||
=> $"{tuple.Item1.FullDescription()} :: {tuple.Item2.ToStringCached()}{
|
||||
(tuple.Item3 is { } size ? $" ({ToByteString(size)})"
|
||||
: "")}"))}\n=============================================================");
|
||||
}
|
||||
|
||||
private static string TryCalculateSize(Type type, ICollection[] allCaches)
|
||||
=> (type.GetProperty(nameof(FishTable<object, object>.SizeEstimate))?.GetMethod
|
||||
private static long? TryCalculateSize(Type type, IEnumerable<ICollection> cachesOfType)
|
||||
{
|
||||
var sizeGetter = type.GetProperty(nameof(FishTable<object, object>.SizeEstimate))?.GetMethod
|
||||
?? (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)
|
||||
? AccessTools.Method(typeof(FisheryLib.CollectionExtensions),
|
||||
nameof(FisheryLib.CollectionExtensions.GetSizeEstimate), generics: type.GetGenericArguments())
|
||||
: null)) is { } sizeGetter
|
||||
? $" ({(allCaches.Where(cache => cache.GetType() == type)
|
||||
.Select(cache => (long)(uint)sizeGetter.Invoke(sizeGetter.IsStatic ? null : cache,
|
||||
sizeGetter.IsStatic ? new object[] { cache } : null)).Sum()
|
||||
is var sizeInBytes
|
||||
&& sizeInBytes > 1024L * 1024L ? $"{Math.Round(sizeInBytes / (1024d * 1024d), 2)} MiB"
|
||||
: sizeInBytes > 1024L ? $"{Math.Round(sizeInBytes / 1024d, 2)} KiB"
|
||||
: $"{sizeInBytes} B")})"
|
||||
: "";
|
||||
: null);
|
||||
|
||||
return sizeGetter is null
|
||||
? null
|
||||
: cachesOfType.Select(cache
|
||||
=> (long)(uint)sizeGetter.Invoke(sizeGetter.IsStatic ? null : cache,
|
||||
sizeGetter.IsStatic ? [cache] : null)).Sum();
|
||||
}
|
||||
|
||||
private static string ToByteString(long sizeInBytes)
|
||||
=> sizeInBytes > 1024L * 1024L
|
||||
? $"{Math.Round(sizeInBytes / (1024d * 1024d), 2).ToString(CultureInfo.CurrentCulture)} MiB"
|
||||
: sizeInBytes > 1024L
|
||||
? $"{Math.Round(sizeInBytes / 1024d, 2).ToString(CultureInfo.CurrentCulture)} KiB"
|
||||
: $"{((int)sizeInBytes).ToStringCached()} B";
|
||||
|
||||
private static void Clear(this ICollection cache)
|
||||
{
|
||||
@@ -167,14 +188,29 @@ public static class Utility
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
||||
public static FishTable<TKey, TValue> AddNew<TKey, TValue>(Action<KeyValuePair<TKey, TValue>>? onEntryAdded = null,
|
||||
public static FishTable<TKey, TValue> AddNew<TKey, TValue>(Func<TKey, TValue>? valueInitializer = null,
|
||||
Action<KeyValuePair<TKey, TValue>>? onEntryAdded = null,
|
||||
Action<KeyValuePair<TKey, TValue>>? onEntryRemoved = null)
|
||||
{
|
||||
var newCollection = new FishTable<TKey, TValue>();
|
||||
|
||||
if (valueInitializer != null)
|
||||
newCollection.ValueInitializer = valueInitializer;
|
||||
|
||||
if (onEntryAdded != null)
|
||||
newCollection.EntryAdded += onEntryAdded;
|
||||
|
||||
if (typeof(TKey).IsAssignableTo(typeof(Thing)))
|
||||
{
|
||||
newCollection.EntryAdded
|
||||
+= entry =>
|
||||
{
|
||||
var data = new OnDestroyActionData<TKey, TValue>(entry.Key, newCollection);
|
||||
data.Action = data.Invoke;
|
||||
data.Thing.Events().Destroyed += data.Action;
|
||||
};
|
||||
}
|
||||
|
||||
if (onEntryRemoved != null)
|
||||
newCollection.EntryRemoved += onEntryRemoved;
|
||||
|
||||
@@ -182,6 +218,26 @@ public static class Utility
|
||||
return newCollection;
|
||||
}
|
||||
|
||||
private sealed record OnDestroyActionData<TKey, TValue>(TKey Key, FishTable<TKey, TValue> Collection)
|
||||
{
|
||||
public Action<Thing>? Action;
|
||||
public Thing Thing => Unsafe.As<Thing>(Key);
|
||||
|
||||
public void Invoke(Thing thing)
|
||||
{
|
||||
Collection.Remove(Key);
|
||||
Thing.Events().Destroyed -= Action;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
||||
public static FishTable<TKey, TValue> AddNew<TKey, TValue>(Func<FishTable<TKey, TValue>> collectionInitializer)
|
||||
{
|
||||
var newCollection = collectionInitializer();
|
||||
All.Add(newCollection);
|
||||
return newCollection;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
||||
public static T AddNewUnclearable<T>() where T : class, ICollection, new()
|
||||
{
|
||||
@@ -229,48 +285,13 @@ public interface IClearable
|
||||
public void Clear();
|
||||
}
|
||||
|
||||
public interface IFishBool
|
||||
{
|
||||
public bool Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
public struct FishBool : IFishBool
|
||||
{
|
||||
private byte _value;
|
||||
|
||||
public bool Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _value == 1;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set => _value = value ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
public struct True : IFishBool
|
||||
{
|
||||
public bool Value => true;
|
||||
}
|
||||
|
||||
public struct False : IFishBool
|
||||
{
|
||||
public bool Value => false;
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class CollectionRef<T> : IClearable, ICollection where T : ICollection
|
||||
public sealed class CollectionRef<T>(AccessTools.FieldRef<T> fieldRef) : IClearable, ICollection
|
||||
where T : ICollection
|
||||
{
|
||||
private readonly AccessTools.FieldRef<T> _fieldRef;
|
||||
private static readonly ClearInvoker? _clearInvoker = CreateClearDelegate();
|
||||
|
||||
public CollectionRef(AccessTools.FieldRef<T> fieldRef) => _fieldRef = fieldRef;
|
||||
|
||||
public ref T Get => ref _fieldRef();
|
||||
public ref T Get => ref fieldRef();
|
||||
|
||||
public void Create() => Get = Reflection.New<T>();
|
||||
|
||||
@@ -304,7 +325,7 @@ public class CollectionRef<T> : IClearable, ICollection where T : ICollection
|
||||
return null;
|
||||
|
||||
var dm = new DynamicMethod($"ClearInvoker_{typeof(T).FullName}", null,
|
||||
new[] { typeof(T).MakeByRefType() }, typeof(CollectionRef<T>), true);
|
||||
[typeof(T).MakeByRefType()], typeof(CollectionRef<T>), true);
|
||||
var il = dm.GetILGenerator();
|
||||
|
||||
il.Emit(FishTranspiler.Argument(0));
|
||||
|
||||
Reference in New Issue
Block a user