Lots of new optimizations utilizing prepatcher

To start, this mod now requires Prepatcher and Fishery.
New optimizations include: gene ticking improvements, world object improvements, hediff optimizations, world pawn gc optimizations, better mothballing, dds support, ContentFinder caching, additions and changes to reflection caching, as well as various other additions. Multiplayer support has been improved too, but should be considered highly experimental. The WorkGiver_DoBill bug is fixed in this build. Multiple patches aiming to improve RimWorld's load time have been added too.
This commit is contained in:
bbradson
2023-06-03 20:24:38 +02:00
parent cb0a407425
commit ca9321add8
125 changed files with 11272 additions and 3681 deletions
+32 -15
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2022 bradson
// 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/.
@@ -9,15 +9,21 @@ using System.Linq;
namespace PerformanceFish.Cache;
public class ByIndex<T_out> : IList<T_out?>, ICollection
{
private static ByIndex<T_out> _get = new();
private static ByIndex<T_out> _get = Utility.AddNew<ByIndex<T_out>>();
[ThreadStatic]
private static ByIndex<T_out>? _getThreadStatic;
public static ByIndex<T_out> Get
=> /*UnityData.IsInMainThread ? _get
:*/ _getThreadStatic ??= Utility.AddNew<ByIndex<T_out>>();
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= Utility.AddNew<ByIndex<T_out>>();
}
public static ByIndex<T_out> GetDirectly => _get;
public static ByIndex<T_out> GetDirectly
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _get;
}
static ByIndex() => Utility.All.Add(_get);
@@ -112,15 +118,21 @@ public class ByIndex<T_out> : IList<T_out?>, ICollection
public class ByIndex<T_in, T_out> : ByIndex<T_out>
where T_in : notnull
{
private static ByIndex<T_in, T_out> _get = new();
private static ByIndex<T_in, T_out> _get = Utility.AddNew<ByIndex<T_in, T_out>>();
[ThreadStatic]
private static ByIndex<T_in, T_out>? _getThreadStatic;
public new static ByIndex<T_in, T_out> Get
=> /*UnityData.IsInMainThread ? _get
:*/ _getThreadStatic ??= Utility.AddNew<ByIndex<T_in, T_out>>();
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= Utility.AddNew<ByIndex<T_in, T_out>>();
}
public new static ByIndex<T_in, T_out> GetDirectly => _get;
public new static ByIndex<T_in, T_out> GetDirectly
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _get;
}
static ByIndex() => Utility.All.Add(_get);
@@ -142,7 +154,6 @@ public class ByIndex<T_in, T_out> : ByIndex<T_out>
[return: MaybeNull]
public unsafe ref T_out GetReference(T_in key)
=> ref GetReference(FunctionPointers.IndexGetter<T_in>.Default(key))!;
}
/// <summary>
@@ -151,15 +162,21 @@ public class ByIndex<T_in, T_out> : ByIndex<T_out>
public class ByIndex<T_first, T_second, T_out> : ByIndex<T_first, ByIndex<T_second, T_out>>
where T_first : notnull where T_second : notnull
{
private static ByIndex<T_first, T_second, T_out> _get = new();
private static ByIndex<T_first, T_second, T_out> _get = Utility.AddNew<ByIndex<T_first, T_second, T_out>>();
[ThreadStatic]
private static ByIndex<T_first, T_second, T_out>? _getThreadStatic;
public new static ByIndex<T_first, T_second, T_out> Get
=> /*UnityData.IsInMainThread ? _get
:*/ _getThreadStatic ??= Utility.AddNew<ByIndex<T_first, T_second, T_out>>();
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= Utility.AddNew<ByIndex<T_first, T_second, T_out>>();
}
public new static ByIndex<T_first, T_second, T_out> GetDirectly => _get;
public new static ByIndex<T_first, T_second, T_out> GetDirectly
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _get;
}
static ByIndex() => Utility.All.Add(_get);
@@ -179,7 +196,7 @@ public class ByIndex<T_first, T_second, T_out> : ByIndex<T_first, ByIndex<T_seco
ref var inner = ref _items[firstIndex];
inner ??= new();
return inner!;
return inner;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
+198 -221
View File
@@ -1,291 +1,268 @@
// Copyright (c) 2022 bradson
// 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;
using JetBrains.Annotations;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
namespace PerformanceFish.Cache;
public struct ByInt<T_in, T_result>
where T_in : notnull
where T_result : IIsRefreshable<T_in, T_result>
#pragma warning disable CS9091
[PublicAPI]
public record struct ByInt<T, TResult> where T : notnull where TResult : new()
{
private static Dictionary<ByInt<T_in, T_result>, T_result> _get = new();
private static FishTable<ByInt<T, TResult>, TResult> _get
= Utility.AddNew<ByInt<T, TResult>, TResult>();
[ThreadStatic]
private static Dictionary<ByInt<T_in, T_result>, T_result>? _getThreadStatic;
private static FishTable<ByInt<T, TResult>, TResult>? _getThreadStatic;
public static Dictionary<ByInt<T_in, T_result>, T_result> Get
=> /*UnityData.IsInMainThread ? _get
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByInt<T_in, T_result>, T_result>>();
public static FishTable<ByInt<T, TResult>, TResult> Get
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= Utility.AddNew<ByInt<T, TResult>, TResult>();
}
public static Dictionary<ByInt<T_in, T_result>, T_result> GetDirectly => _get;
public static FishTable<ByInt<T, TResult>, TResult> GetDirectly
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _get;
}
static ByInt() => Utility.All.Add(_get);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(int key)
=> ref Get.GetOrAddReference(Unsafe.As<int, ByInt<T, TResult>>(ref key));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(ByInt<T, TResult> key) => ref Get.GetOrAddReference(key);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(ref ByInt<T, TResult> key) => ref Get.GetOrAddReference(ref key);
[MethodImpl(MethodImplOptions.NoInlining)]
public static ref TResult GetExistingReference(int key)
=> ref Get.GetReference(Unsafe.As<int, ByInt<T, TResult>>(ref key));
[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_in key) => Key = FunctionPointers.IndexGetter<T_in>.Default(key);
public unsafe ByInt(T key) => Key = FunctionPointers.IndexGetter<T>.Default(key);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T_result GetValue(T_in key)
{
ref var cache = ref Get.TryGetReferenceUnsafe(new(key));
if (Unsafe.IsNullRef(ref cache))
{
Get[new(key)] = Reflection.New<T_result>();
cache = ref Get.GetReference(new(key))!;
goto RefreshCache;
}
if (!cache.ShouldRefreshNow)
return ref cache!;
RefreshCache:
cache.ShouldRefreshNow = false;
Get[new(key)] = cache.SetNewValue(key);
return ref cache!;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static unsafe ref T_result TryGetReferenceUnsafe(int key)
=> ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_in, T_result>>(ref key))));
[MethodImpl(MethodImplOptions.NoInlining)]
public static unsafe ref T_result TryGetReferenceChecked(int key, out bool result)
{
ref var value = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_in, T_result>>(ref key))));
result = !Unsafe.IsNullRef(ref value) && !value.ShouldRefreshNow;
return ref value!;
}
public ByInt(int key) => Key = key;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetValue(T_in key, [MaybeNullWhen(false)] out T_result value)
=> Get.TryGetValue(new(key), out value)
&& !value.ShouldRefreshNow;
public bool Equals(ByInt<T, TResult> other) => Key == other.Key;
public static T_result GetValueAndRefreshNow(T_in key)
{
var cache = Get.TryGetValue(new(key));
cache ??= Reflection.New<T_result>();
cache.ShouldRefreshNow = false;
Get[new(key)] = cache.SetNewValue(key);
return cache;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => Key;
}
public struct ByIntRefreshable<T_first, T_second, T_result> : IEquatable<ByIntRefreshable<T_first, T_second, T_result>>
where T_first : notnull where T_second : notnull
where T_result : IIsRefreshable<ByIntRefreshable<T_first, T_second, T_result>, T_result>
[PublicAPI]
public record struct ByInt<T1, T2, TResult>
where T1 : notnull where T2 : notnull where TResult : new()
{
private static EqualityComparer<ByIntRefreshable<T_first, T_second, T_result>> _comparer = new Comparer();
private class Comparer : EqualityComparer<ByIntRefreshable<T_first, T_second, T_result>>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(ByIntRefreshable<T_first, T_second, T_result> x, ByIntRefreshable<T_first, T_second, T_result> y)
=> x.First == y.First
&& x.Second == y.Second;
private static FishTable<ByInt<T1, T2, TResult>, TResult> _get
= Utility.AddNew<ByInt<T1, T2, TResult>, TResult>();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode(ByIntRefreshable<T_first, T_second, T_result> obj)
=> HashCode.Combine(obj.First, obj.Second);
}
private static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result> _get = new(_comparer);
[ThreadStatic]
private static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result>? _getThreadStatic;
private static FishTable<ByInt<T1, T2, TResult>, TResult>? _getThreadStatic;
public static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result> Get
=> /*UnityData.IsInMainThread ? _get
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result>>(_comparer);
public static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result> GetDirectly => _get;
static ByIntRefreshable() => Utility.All.Add(_get);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref T_result GetValue(ref ByIntRefreshable<T_first, T_second, T_result> key)
public static FishTable<ByInt<T1, T2, TResult>, TResult> Get
{
// CS8347 without this crap
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= Utility.AddNew<ByInt<T1, T2, TResult>, TResult>();
}
if (Unsafe.IsNullRef(ref cache))
private static ref FishTable<ByInt<T1, T2, TResult>, TResult> GetCacheRef
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
Get[key] = Reflection.New<T_result>();
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
goto RefreshCache;
_getThreadStatic ??= Utility.AddNew<ByInt<T1, T2, TResult>, TResult>();
return ref _getThreadStatic!;
}
if (!cache.ShouldRefreshNow)
return ref cache!;
RefreshCache:
cache.ShouldRefreshNow = false;
Get[key] = cache.SetNewValue(key);
return ref cache!;
}
public static FishTable<ByInt<T1, T2, TResult>, TResult> GetDirectly
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _get;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(int first, int second)
=> ref Get.GetOrAddReference(new() { First = first, Second = second });
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(in ByInt<T1, T2, TResult> key)
=> ref Get.GetOrAddReference(ref Unsafe.AsRef(in key));
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public static ref TResult GetOrAddReference(long key) // different, worse, GetHashCode method
// => ref Unsafe.As<FishTable<ByInt<T1, T2, TResult>, TResult>, FishTable<long, TResult>>(ref GetCacheRef)
// .GetOrAddReference(key);
[MethodImpl(MethodImplOptions.NoInlining)]
public static unsafe ref T_result TryGetReferenceUnsafe(int second, int first)
=> ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByIntRefreshable<T_first, T_second, T_result>>(ref first))));
public static ref TResult GetExistingReference(int first, int second)
=> ref Get.GetReference(new() { First = first, Second = second });
[MethodImpl(MethodImplOptions.NoInlining)]
public static unsafe ref T_result TryGetReferenceChecked(int second, int first, out bool result)
public static ref TResult GetExistingReference(T1 first, T2 second)
=> ref Get.GetReference(new(first, second));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe ByInt(T1 first, T2 second)
{
ref var value = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByIntRefreshable<T_first, T_second, T_result>>(ref first))));
result = !Unsafe.IsNullRef(ref value) && !value.ShouldRefreshNow;
return ref value!;
First = FunctionPointers.IndexGetter<T1>.Default(first);
Second = FunctionPointers.IndexGetter<T2>.Default(second);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetValue(ByIntRefreshable<T_first, T_second, T_result> key, [MaybeNullWhen(false)] out T_result value)
=> Get.TryGetValue(key, out value)
&& !value.ShouldRefreshNow;
public static T_result GetValueAndRefreshNow(ByIntRefreshable<T_first, T_second, T_result> key)
{
var cache = Get.TryGetValue(ref key);
cache ??= Reflection.New<T_result>();
cache.ShouldRefreshNow = false;
Get[key] = cache.SetNewValue(key);
return cache;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe ByIntRefreshable(T_first first, T_second second)
{
First = FunctionPointers.IndexGetter<T_first>.Default(first);
Second = FunctionPointers.IndexGetter<T_second>.Default(second);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ByIntRefreshable(int first, int second)
public ByInt(int first, int second)
{
First = first;
Second = second;
}
public int First;
public int Second;
public int First, Second;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByIntRefreshable<T_first, T_second, T_result> other)
=> First == other.First
&& Second == other.Second;
public bool Equals(ByInt<T1, T2, TResult> other)
=> Unsafe.As<ByInt<T1, T2, TResult>, long>(ref this) == Unsafe.As<ByInt<T1, T2, TResult>, long>(ref other);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
=> HashCode.Combine(First, Second);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
=> obj is ByIntRefreshable<T_first, T_second, T_result> cache
&& Equals(cache);
public static bool operator ==(ByIntRefreshable<T_first, T_second, T_result> left, ByIntRefreshable<T_first, T_second, T_result> right)
=> left.Equals(right);
public static bool operator !=(ByIntRefreshable<T_first, T_second, T_result> left, ByIntRefreshable<T_first, T_second, T_result> right)
=> !(left == right);
public override int GetHashCode() => HashCode.Combine(First, Second);
}
public struct ByInt<T_first, T_second, T_third, T_result> : IEquatable<ByInt<T_first, T_second, T_third, T_result>>
where T_first : notnull where T_second : notnull where T_third : notnull
where T_result : IIsRefreshable<ByInt<T_first, T_second, T_third, T_result>, T_result>
[PublicAPI]
public record struct ByInt<T1, T2, T3, TResult>
where T1 : notnull where T2 : notnull where T3 : notnull where TResult : new()
{
private static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result> _get = new();
private static FishTable<ByInt<T1, T2, T3, TResult>, TResult> _get
= Utility.AddNew<ByInt<T1, T2, T3, TResult>, TResult>();
[ThreadStatic]
private static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result>? _getThreadStatic;
private static FishTable<ByInt<T1, T2, T3, TResult>, TResult>? _getThreadStatic;
public static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result> Get
=> /*UnityData.IsInMainThread ? _get
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result>>();
public static FishTable<ByInt<T1, T2, T3, TResult>, TResult> Get
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= Utility.AddNew<ByInt<T1, T2, T3, TResult>, TResult>();
}
public static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result> GetDirectly => _get;
static ByInt() => Utility.All.Add(_get);
public static FishTable<ByInt<T1, T2, T3, TResult>, TResult> GetDirectly
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _get;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref T_result GetValue(ByInt<T_first, T_second, T_third, T_result> key)
public static unsafe ref TResult GetOrAddReference(int first, int second, int third)
{
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
var key = new ByInt<T1, T2, T3, TResult> { First = first, Second = second, Third = third };
return ref Get.GetOrAddReference(ref key);
}
if (Unsafe.IsNullRef(ref cache))
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(in ByInt<T1, T2, T3, TResult> key)
=> ref Get.GetOrAddReference(ref Unsafe.AsRef(in key));
[MethodImpl(MethodImplOptions.NoInlining)]
public static ref TResult GetExistingReference(int first, int second, int third)
=> ref Get.GetReference(new() { First = first, Second = second, Third = third });
[MethodImpl(MethodImplOptions.NoInlining)]
public static ref TResult GetExistingReference(T1 first, T2 second, T3 third)
=> ref Get.GetReference(new(first, second, third));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe ByInt(T1 first, T2 second, T3 third)
{
First = FunctionPointers.IndexGetter<T1>.Default(first);
Second = FunctionPointers.IndexGetter<T2>.Default(second);
Third = FunctionPointers.IndexGetter<T3>.Default(third);
}
public int First, Second, Third;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByInt<T1, T2, T3, TResult> other)
=> (Unsafe.As<ByInt<T1, T2, T3, TResult>, long>(ref this)
== Unsafe.As<ByInt<T1, T2, T3, TResult>, long>(ref other))
& (Third == other.Third);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(First, Second, Third);
}
[PublicAPI]
public record struct ByInt<T1, T2, T3, T4, TResult>
where T1 : notnull where T2 : notnull where T3 : notnull where T4 : notnull
where TResult : new()
{
private static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult> _get
= Utility.AddNew<ByInt<T1, T2, T3, T4, TResult>, TResult>();
[ThreadStatic]
private static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult>? _getThreadStatic;
public static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult> Get
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= Utility.AddNew<ByInt<T1, T2, T3, T4, TResult>, TResult>();
}
public static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult> GetDirectly
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _get;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref TResult GetOrAddReference(int first, int second, int third, int fourth)
{
var key = new ByInt<T1, T2, T3, T4, TResult>
{
Get[key] = Reflection.New<T_result>();
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
goto RefreshCache;
}
if (!cache.ShouldRefreshNow)
return ref cache!;
RefreshCache:
cache.ShouldRefreshNow = false;
Get[key] = cache.SetNewValue(key);
return ref cache!;
First = first, Second = second, Third = third, Fourth = fourth
};
return ref Get.GetOrAddReference(ref key);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(in ByInt<T1, T2, T3, T4, TResult> key)
=> ref Get.GetOrAddReference(ref Unsafe.AsRef(in key));
[MethodImpl(MethodImplOptions.NoInlining)]
public static unsafe ref T_result TryGetReferenceUnsafe(int second, int first, int third)
=> ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_first, T_second, T_third, T_result>>(ref first))));
public static ref TResult GetExistingReference(int first, int second, int third, int fourth)
=> ref Get.GetReference(new() { First = first, Second = second, Third = third, Fourth = fourth });
[MethodImpl(MethodImplOptions.NoInlining)]
public static unsafe ref T_result TryGetReferenceChecked(int second, int first, out bool result)
public static ref TResult GetExistingReference(T1 first, T2 second, T3 third, T4 fourth)
=> ref Get.GetReference(new(first, second, third, fourth));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe ByInt(T1 first, T2 second, T3 third, T4 fourth)
{
ref var value = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_first, T_second, T_third, T_result>>(ref first))));
result = !Unsafe.IsNullRef(ref value) && !value.ShouldRefreshNow;
return ref value!;
First = FunctionPointers.IndexGetter<T1>.Default(first);
Second = FunctionPointers.IndexGetter<T2>.Default(second);
Third = FunctionPointers.IndexGetter<T3>.Default(third);
Fourth = FunctionPointers.IndexGetter<T4>.Default(fourth);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetValue(ByInt<T_first, T_second, T_third, T_result> key, out T_result value)
=> Get.TryGetValue(key, out value)
&& !value.ShouldRefreshNow;
public static T_result GetValueAndRefreshNow(ByInt<T_first, T_second, T_third, T_result> key)
{
var cache = Get.TryGetValue(ref key);
cache ??= Reflection.New<T_result>();
cache.ShouldRefreshNow = false;
Get[key] = cache.SetNewValue(key);
return cache;
}
public int First, Second, Third, Fourth;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe ByInt(T_first first, T_second second, T_third third)
{
First = FunctionPointers.IndexGetter<T_first>.Default(first);
Second = FunctionPointers.IndexGetter<T_second>.Default(second);
Third = FunctionPointers.IndexGetter<T_third>.Default(third);
}
public int First;
public int Second;
public int Third;
public bool Equals(ByInt<T1, T2, T3, T4, TResult> other)
=> (Unsafe.As<ByInt<T1, T2, T3, T4, TResult>, long>(ref this)
== Unsafe.As<ByInt<T1, T2, T3, T4, TResult>, long>(ref other))
& (Unsafe.As<int, long>(ref Third) == Unsafe.As<int, long>(ref other.Third));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByInt<T_first, T_second, T_third, T_result> other)
=> First == other.First
&& Second == other.Second
&& Third == other.Third;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode()
=> HashCode.Combine(First, Second, Third);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
=> obj is ByInt<T_first, T_second, T_third, T_result> cache
&& Equals(cache);
public static bool operator ==(ByInt<T_first, T_second, T_third, T_result> left, ByInt<T_first, T_second, T_third, T_result> right)
=> left.Equals(right);
public static bool operator !=(ByInt<T_first, T_second, T_third, T_result> left, ByInt<T_first, T_second, T_third, T_result> right)
=> !(left == right);
}
public override int GetHashCode() => HashCode.Combine(First, Second, Third, Fourth);
}
#pragma warning restore CS9091
+59 -15
View File
@@ -1,46 +1,90 @@
// Copyright (c) 2022 bradson
// 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 static class ByMap<T> where T : new()
{
private static List<(Map map, T value)> _cache = new();
private static List<(Map map, T value)> _cache = Utility.AddNew<List<(Map map, T value)>>();
[ThreadStatic]
private static List<(Map map, T value)>? _cacheThreadStatic;
public static List<(Map map, T value)> GetCache
=> UnityData.IsInMainThread ? _cache
: _cacheThreadStatic ??= Utility.AddNew<List<(Map map, T value)>>();
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _cacheThreadStatic ??= Utility.AddNew<List<(Map map, T value)>>();
}
public static List<(Map map, T value)> GetCacheDirectly => _cache;
public static List<(Map map, T value)> GetCacheDirectly
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _cache;
}
static ByMap() => Utility.All.Add(_cache);
private static Func<Map, T> Initialize { get; }
= typeof(MapComponent).IsAssignableFrom(typeof(T))
? AccessTools.MethodDelegate<Func<Map, T>>(AccessTools.Method(typeof(Map), nameof(Map.GetComponent), Array.Empty<Type>(), new[] { typeof(T) }))
// missing T constraints cause the compiler to complain when trying to just use map => map.GetComponent<T>
: _ => new();
? AccessTools.MethodDelegate<Func<Map, T>>(AccessTools.Method(typeof(Map), nameof(Map.GetComponent),
Type.EmptyTypes, new[] { typeof(T) }))
// missing T constraints cause the compiler to complain when trying to just use map => map.GetComponent<T>
: static _ => new();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T GetFor(Map map)
{
var cache = GetCache;
for (var i = 0; i < cache.Count; i++)
{
var cacheEntry = cache[i];
ref var cacheEntry = ref cache.GetReferenceUnverifiable(i);
if (cacheEntry.map == map)
return cacheEntry.value;
if (Find.Maps.Count <= i + 1
&& !Find.Maps.Contains(cacheEntry.map))
{
cache.RemoveAt(i);
i--;
}
if (EntryValid(i, cacheEntry.map))
continue;
cache.RemoveAt(i);
i--;
}
return AddEntry(map, cache);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetReferenceFor(Map map)
{
var cache = GetCache;
for (var i = 0; i < cache.Count; i++)
{
ref var cacheEntry = ref cache.GetReferenceUnverifiable(i);
if (cacheEntry.map == map)
return ref cacheEntry.value;
if (EntryValid(i, cacheEntry.map))
continue;
cache.RemoveAt(i);
i--;
}
return ref AddEntryByRef(map, cache);
}
private static bool EntryValid(int i, Map map)
=> Find.Maps.Count > i && Find.Maps.Contains(map);
private static T AddEntry(Map map, List<(Map map, T value)> cache)
{
cache.Add((map, Initialize(map)));
return cache[^1].value;
}
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;
}
}
File diff suppressed because it is too large Load Diff
@@ -1,218 +0,0 @@
// 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 PerformanceFish.Cache;
public static class ByReferenceRefreshable<T_first, T_result>
where T_first : notnull
where T_result : IIsRefreshable<T_first, T_result>
{
private static Dictionary<T_first, T_result> _get = new();
[ThreadStatic]
private static Dictionary<T_first, T_result>? _getThreadStatic;
public static Dictionary<T_first, T_result> Get
=> /*UnityData.IsInMainThread ? _get
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<T_first, T_result>>();
public static Dictionary<T_first, T_result> GetDirectly => _get;
static ByReferenceRefreshable() => Utility.All.Add(_get);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T_result GetValue(T_first key)
{
ref var cache = ref Get.TryGetReferenceUnsafe(key);
if (Unsafe.IsNullRef(ref cache))
{
Get[key] = Reflection.New<T_result>();
cache = ref Get.GetReference(key)!;
goto RefreshCache;
}
if (!cache.ShouldRefreshNow)
return ref cache!;
RefreshCache:
cache.ShouldRefreshNow = false;
Get[key] = cache.SetNewValue(key);
return ref cache!;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetValue(T_first key, out T_result value)
=> Get.TryGetValue(key, out value)
&& !value.ShouldRefreshNow;
public static T_result GetValueAndRefreshNow(T_first key)
{
var cache = Get.TryGetValue(key);
cache ??= Reflection.New<T_result>();
cache.ShouldRefreshNow = false;
Get[key] = cache.SetNewValue(key);
return cache;
}
}
public struct ByReferenceRefreshable<T_first, T_second, T_result> : IEquatable<ByReferenceRefreshable<T_first, T_second, T_result>>
where T_first : notnull where T_second : notnull
where T_result : IIsRefreshable<ByReferenceRefreshable<T_first, T_second, T_result>, T_result>
{
internal ByReference<T_first, T_second, T_result> _innerValue;
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result> _get = new();
[ThreadStatic]
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result>? _getThreadStatic;
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result> Get
=> /*UnityData.IsInMainThread ? _get
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result>>();
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result> GetDirectly => _get;
static ByReferenceRefreshable() => Utility.All.Add(_get);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref T_result GetValue(ref ByReferenceRefreshable<T_first, T_second, T_result> key)
{
// CS8347 without this crap
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
if (Unsafe.IsNullRef(ref cache))
{
Get[key] = Reflection.New<T_result>();
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
goto RefreshCache;
}
if (!cache.ShouldRefreshNow)
return ref cache!;
RefreshCache:
cache.ShouldRefreshNow = false;
Get[key] = cache.SetNewValue(key);
return ref cache!;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetValue(ByReferenceRefreshable<T_first, T_second, T_result> key, out T_result value)
=> Get.TryGetValue(key, out value)
&& !value.ShouldRefreshNow;
public static T_result GetValueAndRefreshNow(ByReferenceRefreshable<T_first, T_second, T_result> key)
{
var cache = Get.TryGetValue(ref key);
cache ??= Reflection.New<T_result>();
cache.ShouldRefreshNow = false;
Get[key] = cache.SetNewValue(key);
return cache;
}
public ByReferenceRefreshable(T_first first, T_second second) => _innerValue = new(first, second);
public T_first First => _innerValue.First;
public T_second Second => _innerValue.Second;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByReferenceRefreshable<T_first, T_second, T_result> other)
=> _innerValue.Equals(other._innerValue);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => _innerValue.GetHashCode();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
=> obj is ByReferenceRefreshable<T_first, T_second, T_result> cache
&& Equals(cache);
public static bool operator ==(ByReferenceRefreshable<T_first, T_second, T_result> left, ByReferenceRefreshable<T_first, T_second, T_result> right)
=> left.Equals(right);
public static bool operator !=(ByReferenceRefreshable<T_first, T_second, T_result> left, ByReferenceRefreshable<T_first, T_second, T_result> right)
=> !(left == right);
}
public struct ByReferenceRefreshable<T_first, T_second, T_third, T_result> : IEquatable<ByReferenceRefreshable<T_first, T_second, T_third, T_result>>
where T_first : notnull where T_second : notnull where T_third : notnull
where T_result : IIsRefreshable<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result>
{
private ByReference<T_first, T_second, T_third, T_result> _innerValue;
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result> _get = new();
[ThreadStatic]
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result>? _getThreadStatic;
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result> Get
=> /*UnityData.IsInMainThread ? _get
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result>>();
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result> GetDirectly => _get;
static ByReferenceRefreshable() => Utility.All.Add(_get);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref T_result GetValue(ref ByReferenceRefreshable<T_first, T_second, T_third, T_result> key)
{
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
if (Unsafe.IsNullRef(ref cache))
{
Get[key] = Activator.CreateInstance<T_result>();
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
goto RefreshCache;
}
if (!cache.ShouldRefreshNow)
return ref cache!;
RefreshCache:
cache.ShouldRefreshNow = false;
Get[key] = cache.SetNewValue(key);
return ref cache!;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetValue(ByReferenceRefreshable<T_first, T_second, T_third, T_result> key, out T_result value)
=> Get.TryGetValue(key, out value)
&& !value.ShouldRefreshNow;
public static T_result GetValueAndRefreshNow(ByReferenceRefreshable<T_first, T_second, T_third, T_result> key)
{
var cache = Get.TryGetValue(ref key);
cache ??= Activator.CreateInstance<T_result>();
cache.ShouldRefreshNow = false;
Get[key] = cache.SetNewValue(key);
return cache;
}
public ByReferenceRefreshable(T_first first, T_second second, T_third third)
=> _innerValue = new(first, second, third);
public T_first First => _innerValue.First;
public T_second Second => _innerValue.Second;
public T_third Third => _innerValue.Third;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByReferenceRefreshable<T_first, T_second, T_third, T_result> other)
=> _innerValue.Equals(other._innerValue);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => _innerValue.GetHashCode();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object obj)
=> obj is ByReferenceRefreshable<T_first, T_second, T_third, T_result> cache
&& Equals(cache);
public static bool operator ==(ByReferenceRefreshable<T_first, T_second, T_third, T_result> left, ByReferenceRefreshable<T_first, T_second, T_third, T_result> right)
=> left.Equals(right);
public static bool operator !=(ByReferenceRefreshable<T_first, T_second, T_third, T_result> left, ByReferenceRefreshable<T_first, T_second, T_third, T_result> right)
=> !(left == right);
}
+53
View File
@@ -0,0 +1,53 @@
// 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;
#pragma warning disable CS9091
public static class Database<TCache, TValue> where TCache : ICacheKeyable where TValue : new()
{
public static object SyncLock = new();
private static Dictionary<TCache, TValue> _get = Utility.AddNew<Dictionary<TCache, TValue>>();
[ThreadStatic]
private static Dictionary<TCache, TValue>? _getThreadStatic;
public static Dictionary<TCache, TValue> Get
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= Utility.AddNew<Dictionary<TCache, TValue>>();
}
public static Dictionary<TCache, TValue> GetDirectly
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _get;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TValue GetOrAddReference<VCache, T1, T2>(T1 first, T2 second)
where VCache : IMemberCount<T1, T2>, new() // new as generic constraint requires at least function pointers,
// and can't be inlined. Makes this slow, unfortunately
=> ref Unsafe.As<Dictionary<VCache, TValue>>(Get)
.GetOrAddReference(Reflection.New<VCache, T1, T2>(first, second));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TValue GetOrAddReference(in TCache key)
=> ref Get.GetOrAddReference(ref Unsafe.AsRef(in key));
[MethodImpl(MethodImplOptions.NoInlining)]
public static unsafe ref TValue GetExistingReference<VCache, T1, T2>(T1 first, T2 second)
where VCache : IMemberCount<T1, T2>, new()
{
var key = Reflection.New<VCache, T1, T2>(first, second);
return ref Get.GetReference(ref Unsafe.As<VCache, TCache>(ref key));
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static ref TValue GetExistingReference(in TCache key)
=> ref Get.GetReference(ref Unsafe.AsRef(in key));
}
#pragma warning restore CS9091
@@ -1,16 +1,11 @@
// Copyright (c) 2022 bradson
// 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 interface IIsRefreshable<T, V> where V : IIsRefreshable<T, V>
public static class Extensions
{
public bool ShouldRefreshNow
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
set;
}
public V SetNewValue(T key);
// This is where I'd put extension methods for my cache structs, if I had any
}
+140
View File
@@ -0,0 +1,140 @@
// 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.Threading.Tasks;
namespace PerformanceFish.Cache;
public interface ICacheKeyable
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int GetHashCode();
}
public interface IMemberCount<T1>
{
public T1 First
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set;
}
}
public interface IMemberCount<T1, T2> : IMemberCount<T1>
{
public T2 Second
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set;
}
}
public interface IMemberCount<T1, T2, T3> : IMemberCount<T1, T2>
{
public T3 Third
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set;
}
}
public interface IMemberCount<T1, T2, T3, T4> : IMemberCount<T1, T2, T3>
{
public T4 Fourth
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set;
}
}
public interface IInitializable<T> : IMemberCount<T>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Initialize(T key);
}
public interface IInitializable<T1, T2> : IMemberCount<T1, T2>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Initialize(T1 first, T2 second);
}
public interface IInitializable<T1, T2, T3> : IMemberCount<T1, T2, T3>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Initialize(T1 first, T2 second, T3 third);
}
public interface IInitializable<T1, T2, T3, T4> : IMemberCount<T1, T2, T3, T4>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Initialize(T1 first, T2 second, T3 third, T4 fourth);
}
public interface IDirtyable
{
public bool Dirty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
}
public interface ICacheable<TKey> : IDirtyable
{
public void Update(ref TKey key);
}
public interface ICacheable<TKey, in T2> : IDirtyable
{
public void Update(ref TKey key, T2 second);
}
public interface ICacheable<TKey, in T2, in T3> : IDirtyable
{
public void Update(ref TKey key, T2 second, T3 third);
}
public interface IAsyncCacheable<TResult> : IDirtyable
{
public Task<TResult>? Task
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set;
}
public TResult? Result
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set;
}
}
public interface IAsyncCacheable<in TKey, TResult> : IAsyncCacheable<TResult>
{
public TResult? MakeResultAsync(TKey key);
}
public interface IAsyncCacheable<in TKey, in T2, TResult> : IAsyncCacheable<TResult>
{
public ValueTask<TResult?> MakeResultAsync(TKey key, T2 second);
}
internal record Countdown(int Value)
{
public volatile int Value = Value;
}
+284 -13
View File
@@ -1,43 +1,314 @@
// Copyright (c) 2022 bradson
// 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.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection.Emit;
using JetBrains.Annotations;
namespace PerformanceFish.Cache;
public static class Utility
{
public static event Action<Thing>? ThingDestroyed; // TODO
private static volatile int _destroyedThingCounter, _lastGCTick, _gcRate = 1000;
private static int _currentGCProgress;
private static volatile bool _gcInProgress;
private static ICollection[]? _cachesToProcessForGC;
private static object _gcLock = new();
public static void NotifyThingDestroyed(Thing thing)
{
ThingDestroyed?.Invoke(thing);
if (Interlocked.Increment(ref _destroyedThingCounter) < 100
|| Current.programStateInt != ProgramState.Playing
|| TickHelper.TicksGame - _lastGCTick < _gcRate)
{
return;
}
ContinueGC();
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ContinueGC()
{
lock (_gcLock)
{
if (_gcInProgress)
return;
_gcInProgress = true;
if (_cachesToProcessForGC is null || _currentGCProgress >= _cachesToProcessForGC.Length)
{
_cachesToProcessForGC = All.ToArray();
_gcRate = GenDate.TicksPerTwelfth / _cachesToProcessForGC.CountNonEmpty();
_currentGCProgress = 0;
_destroyedThingCounter = 0;
}
while (_cachesToProcessForGC[_currentGCProgress].Count == 0
&& _cachesToProcessForGC.Length - _currentGCProgress > 1)
{
_currentGCProgress++;
}
if (_currentGCProgress < _cachesToProcessForGC.Length)
_cachesToProcessForGC[_currentGCProgress++].Clear();
_lastGCTick = TickHelper.TicksGame;
_gcInProgress = false;
}
}
private static int CountNonEmpty(this ICollection[] collection)
{
var count = 0;
for (var i = collection.Length; i-- > 0;)
{
if (collection.Length > 0)
count++;
}
return count;
}
public static ConcurrentBag<ICollection> All { get; } = new();
public static void Clear()
{
foreach (var cache in All)
cache.Clear();
}
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=============================================================");
}
private static string TryCalculateSize(Type type, ICollection[] allCaches)
=> (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")})"
: "";
private static void Clear(this ICollection cache)
{
switch (cache)
{
cache.GetType()
.GetMethods()
.Where(m
=> m.Name == nameof(Array.Clear)
&& m.GetParameters().Length == 0)
.First()
.Invoke(cache, null);
case IClearable clearable:
clearable.Clear();
break;
case IDictionary dictionary:
dictionary.Clear();
break;
case IList list:
list.Clear();
break;
default:
ref var clearMethod = ref ClearMethods.GetOrAddReference(cache.GetType().TypeHandle.Value);
if (clearMethod.Action is null)
AssignClearMethod(cache, out clearMethod.Action);
clearMethod.Action(cache);
break;
}
}
public static T AddNew<T>() where T : ICollection, new()
private static void AssignClearMethod(ICollection cache, out Action<ICollection> clearMethod)
=> clearMethod = Unsafe.As<Action<ICollection>>(Array.Find(cache.GetType().GetMethods(),
static m => m.Name == nameof(IList.Clear) && m.GetParameters().Length == 0)
.CreateDelegate(typeof(Action<>).MakeGenericType(cache.GetType())));
private static FishTable<IntPtr, ClearAction> ClearMethods => _clearMethods ??= new();
[ThreadStatic]
private static FishTable<IntPtr, ClearAction>? _clearMethods;
private record struct ClearAction
{
var newCollection = new T();
public Action<ICollection>? Action;
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static T AddNew<T>() where T : class, ICollection, new()
{
var newCollection = Reflection.New<T>();
All.Add(newCollection);
return newCollection;
}
public static T AddNew<T>(IEqualityComparer comparer) where T : ICollection, new()
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static FishTable<TKey, TValue> AddNew<TKey, TValue>(Action<KeyValuePair<TKey, TValue>>? onEntryAdded = null,
Action<KeyValuePair<TKey, TValue>>? onEntryRemoved = null)
{
var newCollection = (T)Activator.CreateInstance(typeof(T), comparer);
var newCollection = new FishTable<TKey, TValue>();
if (onEntryAdded != null)
newCollection.EntryAdded += onEntryAdded;
if (onEntryRemoved != null)
newCollection.EntryRemoved += onEntryRemoved;
All.Add(newCollection);
return newCollection;
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static T AddNewUnclearable<T>() where T : class, ICollection, new()
{
var newCollection = Reflection.New<T>();
return newCollection;
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static FishTable<TKey, TValue> AddNewUnclearable<TKey, TValue>(
Action<KeyValuePair<TKey, TValue>>? onEntryAdded = null,
Action<KeyValuePair<TKey, TValue>>? onEntryRemoved = null)
{
var newCollection = new FishTable<TKey, TValue>();
if (onEntryAdded != null)
newCollection.EntryAdded += onEntryAdded;
if (onEntryRemoved != null)
newCollection.EntryRemoved += onEntryRemoved;
return newCollection;
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static T AddNewWithRef<T>(AccessTools.FieldRef<T?> fieldRef) where T : ICollection, new()
{
var collectionRef = Reflection.New<CollectionRef<T>, AccessTools.FieldRef<T?>>(fieldRef);
All.Add(collectionRef);
return Reflection.New<T>();
}
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static T AddNew<T>(IEqualityComparer comparer) where T : ICollection, new()
{
var newCollection = (T)Activator.CreateInstance(typeof(T), comparer); // different comparer subclasses can
All.Add(newCollection); // require different constructors.
return newCollection; // Reflection.New<T> can't use arguments
} // for resolving method calls (yet)
}
public delegate int IndexGetter<in T>(T item);
public interface IClearable
{
public void Clear();
}
public interface IFishBool
{
public bool Value
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
}
public delegate int IndexGetter<T>(T item);
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
{
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 void Create() => Get = Reflection.New<T>();
public void Clear()
{
if (_clearInvoker is null)
ThrowForMissingClearMethod();
_clearInvoker(ref Get);
}
[DoesNotReturn]
private static void ThrowForMissingClearMethod()
=> throw new InvalidOperationException(
$"Tried to call Clear() on invalid collection of type {typeof(T).FullName}");
public IEnumerator GetEnumerator() => Get.GetEnumerator();
public void CopyTo(Array array, int index) => Get.CopyTo(array, index);
public int Count => Get.Count;
public object SyncRoot => Get.SyncRoot;
public bool IsSynchronized => Get.IsSynchronized;
private static ClearInvoker? CreateClearDelegate()
{
var clearMethod = AccessTools.Method(typeof(T), nameof(IList.Clear), Type.EmptyTypes);
if (clearMethod is null)
return null;
var dm = new DynamicMethod($"ClearInvoker_{typeof(T).FullName}", null,
new[] { typeof(T).MakeByRefType() }, typeof(CollectionRef<T>), true);
var il = dm.GetILGenerator();
il.Emit(FishTranspiler.Argument(0));
il.Emit(FishTranspiler.Call(clearMethod));
il.Emit(FishTranspiler.Return);
return (ClearInvoker)dm.CreateDelegate(typeof(ClearInvoker));
}
private delegate void ClearInvoker(ref T collectionRef);
}