// Copyright (c) 2023 bradson // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. using System.Runtime.InteropServices; using System.Security; using System.Threading.Tasks; using JetBrains.Annotations; // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract // ReSharper disable WithExpressionModifiesAllMembers namespace PerformanceFish.Cache; #pragma warning disable CS9091, 9082 [PublicAPI] public static class ByReferenceUnclearable where T : notnull where TResult : new() { public const int MINIMUM_CALL_COUNT_FOR_STARTING_TASK = 8; public static readonly object SyncLock = new(); private static readonly FishTable _get = Utility.AddNewUnclearable(); [ThreadStatic] private static FishTable? _getThreadStatic; public static FishTable Get { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _getThreadStatic ??= Utility.AddNewUnclearable(); } public static FishTable GetDirectly { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _get; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref TResult GetOrAddReference(T key) => ref Get.GetOrAddReference(key); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref TResult GetOrAddReference(ref T key) => ref Get.GetOrAddReference(ref key); [MethodImpl(MethodImplOptions.NoInlining)] public static ref TResult GetExistingReference(T key) => ref Get.GetReference(key); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Update(T key) where VResult : TResult, ICacheable => Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Update(T key, V2 second) where VResult : TResult, ICacheable => Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key, second); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Update(T key, V2 second, V3 third) where VResult : TResult, ICacheable => Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key, second, third); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref VResult GetAndCheck(T key) where VResult : TResult, ICacheable { ref var cache = ref Unsafe.As(ref GetOrAddReference(key)); if (cache.Dirty) cache.Update(ref key); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref VResult GetAndCheck(T key, V2 second) where VResult : TResult, ICacheable { ref var cache = ref Unsafe.As(ref GetOrAddReference(key)); if (cache.Dirty) cache.Update(ref key, second); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref VResult GetAndCheck(T key, V2 second, V3 third) where VResult : TResult, ICacheable { ref var cache = ref Unsafe.As(ref GetOrAddReference(key)); if (cache.Dirty) cache.Update(ref key, second, third); return ref cache!; } public static bool UpdateAsyncCache(ref TCacheValue cache, T key) where TCacheValue : TResult, IAsyncCacheable { if (cache.Task is null) return SyncWithCentralCache(ref cache, key); if (cache.Task.IsCompletedSuccessfully) cache.Result = cache.Task.Result; return !cache.Result.Equals(default); } private static bool SyncWithCentralCache(ref TCacheValue cache, T key) where TCacheValue : TResult, IAsyncCacheable { lock (SyncLock) { ref var centralCache = ref Unsafe.As(ref GetDirectly.GetOrAddReference(key)); var cacheCopy = centralCache; centralCache.Task ??= Task.Run(() => UpdateCacheAsync(key, cacheCopy)); cache.Task = centralCache.Task; cache.Result = centralCache.Result; return !cache.Result.Equals(default); } } private static TCacheResult UpdateCacheAsync(T key, TCacheValue cacheCopy) where TCacheValue : TResult, IAsyncCacheable { TCacheResult? result = default; try { result = cacheCopy.MakeResultAsync(key); lock (SyncLock) Unsafe.As(ref GetDirectly.GetOrAddReference(key)).Result = result; } catch (Exception e) { Log.Error($"{e}"); } return result ?? ThrowHelper.ThrowInvalidOperationException(); } [SecuritySafeCritical] public static Task? RequestFromCacheAsync(T key, TArgument2 second) where TCacheValue : TResult, IAsyncCacheable { TCacheValue cacheCopy; lock (SyncLock) { ref var centralCache = ref Unsafe.As(ref GetDirectly.GetOrAddReference(key)); cacheCopy = centralCache; if (centralCache.Task is null) { centralCache.Task = CreateTaskCountdown(); cacheCopy.Task = null; } // ReSharper disable once SuspiciousTypeConversion.Global // ReSharper disable once PatternNeverMatches else if (centralCache.Task is Countdown taskCountdown) { Interlocked.Decrement(ref taskCountdown.Value); if (taskCountdown.Value <= 0) { cacheCopy.Task = centralCache.Task = StartTask(key, second, cacheCopy); } else { cacheCopy.Task = null; } } else { cacheCopy.Task = centralCache.Task; } } return cacheCopy.Task; } private static Task StartTask(T key, TArgument2 second, TCacheValue cacheCopy) where TCacheValue : TResult, IAsyncCacheable => Task.Run(() => UpdateCacheAsync(key, cacheCopy, second)); [SecuritySafeCritical] private static Task CreateTaskCountdown() => Unsafe.As>(new Countdown(MINIMUM_CALL_COUNT_FOR_STARTING_TASK)); public static bool UpdateAsyncCache(ref TCacheValue cache, T key, TArgument2 second) where TCacheValue : TResult, IAsyncCacheable { cache.Task ??= RequestFromCacheAsync(key, second); if (cache.Task is null) return false; if (cache.Task.IsCompletedSuccessfully) cache.Result = cache.Task.Result; return !cache.Result.Equals(default); } private static async Task UpdateCacheAsync(T key, TCacheValue cacheCopy, TArgument2 second) where TCacheValue : TResult, IAsyncCacheable { TCacheResult? result = default; try { result = await cacheCopy.MakeResultAsync(key, second).ConfigureAwait(false); lock (SyncLock) Unsafe.As(ref GetDirectly.GetOrAddReference(key)).Result = result; } catch (Exception e) { Log.Error($"{e}"); } return result ?? ThrowHelper.ThrowInvalidOperationException(); } public static unsafe void Initialize() { try { _ = FisheryLib.FunctionPointers.Equals.Default; _ = FisheryLib.FunctionPointers.GetHashCode.Default; } catch (Exception ex) { Log.Error( $"Performance Fish encountered an exception while trying to initialize { typeof(ByReferenceUnclearable).FullName}\n{ex}"); } } } [PublicAPI] public static class ByReference where T : notnull where TResult : new() { private static readonly object _valueInitializerLock = new(); public static readonly object SyncLock = new(); private static readonly List _allCaches = []; private static readonly FishTable _get = InitializeNew(); [ThreadStatic] private static FishTable? _getThreadStatic; private static Func? _valueInitializer; public static Func? ValueInitializer { get { lock (_valueInitializerLock) return _valueInitializer; } set { lock (_valueInitializerLock) { value ??= static _ => Reflection.New(); _valueInitializer = value; _get.ValueInitializer = value; } } } public static FishTable Get { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _getThreadStatic ??= InitializeNew(); } public static FishTable GetDirectly { [MethodImpl(MethodImplOptions.AggressiveInlining)] 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); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref TResult GetOrAddReference(ref T key) => ref Get.GetOrAddReference(ref key); public static ref TResult GetExistingReference(T key) => ref Get.GetReference(key); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Update(T key) where VResult : TResult, ICacheable => Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Update(T key, V2 second) where VResult : TResult, ICacheable => Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key, second); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Update(T key, V2 second, V3 third) where VResult : TResult, ICacheable => Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key, second, third); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref VResult GetAndCheck(T key) where VResult : TResult, ICacheable { ref var cache = ref Unsafe.As(ref GetOrAddReference(key)); if (cache.Dirty) cache.Update(ref key); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref VResult GetAndCheck(T key, V2 second) where VResult : TResult, ICacheable { ref var cache = ref Unsafe.As(ref GetOrAddReference(key)); if (cache.Dirty) cache.Update(ref key, second); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref VResult GetAndCheck(T key, V2 second, V3 third) where VResult : TResult, ICacheable { ref var cache = ref Unsafe.As(ref GetOrAddReference(key)); if (cache.Dirty) cache.Update(ref key, second, third); return ref cache!; } public static bool UpdateAsyncCache(ref TCacheValue cache, T key) where TCacheValue : TResult, IAsyncCacheable { if (cache.Task is null) return SyncWithCentralCache(ref cache, key); if (cache.Task.IsCompletedSuccessfully) cache.Result = cache.Task.Result; return !cache.Result.Equals(default); } private static bool SyncWithCentralCache(ref TCacheValue cache, T key) where TCacheValue : TResult, IAsyncCacheable { lock (SyncLock) { ref var centralCache = ref Unsafe.As(ref GetDirectly.GetOrAddReference(key)); var cacheCopy = centralCache; centralCache.Task ??= Task.Run(() => UpdateCacheAsync(key, cacheCopy)); cache.Task = centralCache.Task; cache.Result = centralCache.Result; return !cache.Result.Equals(default); } } private static TCacheResult UpdateCacheAsync(T key, TCacheValue cacheCopy) where TCacheValue : TResult, IAsyncCacheable { TCacheResult? result = default; try { result = cacheCopy.MakeResultAsync(key); lock (SyncLock) Unsafe.As(ref GetDirectly.GetOrAddReference(key)).Result = result; } catch (Exception e) { Log.Error($"{e}"); } return result ?? ThrowHelper.ThrowInvalidOperationException(); } public static Task RequestFromCacheAsync(T key, TArgument2 second) where TCacheValue : TResult, IAsyncCacheable { TCacheValue cacheCopy; lock (SyncLock) { ref var centralCache = ref Unsafe.As(ref GetDirectly.GetOrAddReference(key)); cacheCopy = centralCache; centralCache.Task ??= Task.Run(async () => await UpdateCacheAsync(key, cacheCopy, second) .ConfigureAwait(false)); cacheCopy.Task = centralCache.Task; } return cacheCopy.Task; } public static bool UpdateAsyncCache(ref TCacheValue cache, T key, TArgument2 second) where TCacheValue : TResult, IAsyncCacheable { cache.Task ??= RequestFromCacheAsync(key, second); if (cache.Task.IsCompletedSuccessfully) cache.Result = cache.Task.Result; return !cache.Result.Equals(default); } private static async ValueTask UpdateCacheAsync(T key, TCacheValue cacheCopy, TArgument2 second) where TCacheValue : TResult, IAsyncCacheable { TCacheResult? result = default; try { result = await cacheCopy.MakeResultAsync(key, second).ConfigureAwait(false); lock (SyncLock) Unsafe.As(ref GetDirectly.GetOrAddReference(key)).Result = result; } catch (Exception e) { Log.Error($"{e}"); } return result ?? ThrowHelper.ThrowInvalidOperationException(); } public static void Initialize() => Utility.Internal.Initialize(); [MethodImpl(MethodImplOptions.NoInlining)] private static FishTable InitializeNew() => Utility.AddNew(_allCaches, ValueInitializer); public static void Remove(T key) => Utility.Internal.RemoveIn(_allCaches, key); public static void Clear() => Utility.Internal.ClearIn(_allCaches); } [PublicAPI] [StructLayout(LayoutKind.Sequential, Pack = 1)] public record struct ByReference : IInitializable where T1 : notnull where T2 : notnull where TResult : new() { public T1 First; public T2 Second; private static readonly object _valueInitializerLock = new(); private static readonly List _allCaches = []; private static readonly FishTable, TResult> _get = InitializeNew(); [ThreadStatic] private static FishTable, TResult>? _getThreadStatic; private static Func, TResult>? _valueInitializer; public static Func, TResult>? ValueInitializer { get { lock (_valueInitializerLock) return _valueInitializer; } set { lock (_valueInitializerLock) { value ??= static _ => Reflection.New(); _valueInitializer = value; _get.ValueInitializer = value; } } } public static FishTable, TResult> Get { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _getThreadStatic ??= InitializeNew(); } public static FishTable, TResult> GetDirectly { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _get; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref TResult GetOrAddReference(T1 first, T2 second) => ref Get.GetOrAddReference(default(ByReference) with { First = first, Second = second }); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref TResult GetOrAddReference(in ByReference key) => ref Get.GetOrAddReference(ref Unsafe.AsRef(in key)); [MethodImpl(MethodImplOptions.NoInlining)] public static unsafe ref TResult GetExistingReference(T1 first, T2 second) { var key = default(ByReference) with { First = first, Second = second }; return ref Get.GetReference(ref key); } [MethodImpl(MethodImplOptions.NoInlining)] public static ref TResult GetExistingReference(in ByReference key) => ref Get.GetReference(ref Unsafe.AsRef(in key)); [MethodImpl(MethodImplOptions.NoInlining)] public static void Update(T1 first, T2 second) where VResult : TResult, ICacheable> { var key = default(ByReference) with { First = first, Second = second }; Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key); } [MethodImpl(MethodImplOptions.NoInlining)] public static void Update(T1 key1, T2 key2, V2 second) where VResult : TResult, ICacheable, V2> { var key = default(ByReference) with { First = key1, Second = key2 }; Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key, second); } [MethodImpl(MethodImplOptions.NoInlining)] public static void Update(T1 key1, T2 key2, V2 second, V3 third) where VResult : TResult, ICacheable, V2, V3> { var key = default(ByReference) with { First = key1, Second = key2 }; Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key, second, third); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref VResult GetAndCheck(T1 first, T2 second) where VResult : TResult, ICacheable> { var key = default(ByReference) with { First = first, Second = second }; ref var cache = ref Unsafe.As(ref Get.GetOrAddReference(ref key)); if (cache.Dirty) cache.Update(ref key); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref VResult GetAndCheck(T1 key1, T2 key2, V2 second) where VResult : TResult, ICacheable, V2> { var key = default(ByReference) with { First = key1, Second = key2 }; ref var cache = ref Unsafe.As(ref Get.GetOrAddReference(ref key)); if (cache.Dirty) cache.Update(ref key, second); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref VResult GetAndCheck(T1 key1, T2 key2, V2 second, V3 third) where VResult : TResult, ICacheable, V2, V3> { var key = default(ByReference) with { First = key1, Second = key2 }; ref var cache = ref Unsafe.As(ref Get.GetOrAddReference(ref key)); if (cache.Dirty) cache.Update(ref key, second, third); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] // ReSharper disable once ConvertToPrimaryConstructor public ByReference(T1 first, T2 second) { First = first; Second = second; } public void Initialize(T1 first, T2 second) { First = first; Second = second; } public static void Initialize() { Utility.Internal.Initialize(); Utility.Internal.Initialize(); Utility.Internal.Initialize>(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ByReference other) => First.Equals(other.First) && Second.Equals(other.Second); [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() => HashCode.Combine(First, Second); [MethodImpl(MethodImplOptions.NoInlining)] private static FishTable, TResult> InitializeNew() => Utility.AddNew(_allCaches, ValueInitializer); public static void Remove(ByReference key) => Utility.Internal.RemoveIn(_allCaches, key); public static void Clear() => Utility.Internal.ClearIn(_allCaches); T1 IMemberCount.First { get => First; set => First = value; } T2 IMemberCount.Second { get => Second; set => Second = value; } } [PublicAPI] [StructLayout(LayoutKind.Sequential, Pack = 1)] public record struct ByReference where T1 : notnull where T2 : notnull where T3 : notnull where TResult : new() { public T1 First; public T2 Second; public T3 Third; public int HashCode; // TODO: Fix ReflectionCaching:CustomAttributeCache causing crashes when not used with standard dictionaries private static readonly object _valueInitializerLock = new(); private static readonly List _allCaches = []; private static readonly FishTable, TResult> _get = InitializeNew(); [ThreadStatic] private static FishTable, TResult>? _getThreadStatic; private static Func, TResult>? _valueInitializer; public static Func, TResult>? ValueInitializer { get { lock (_valueInitializerLock) return _valueInitializer; } set { lock (_valueInitializerLock) { value ??= static _ => Reflection.New(); _valueInitializer = value; _get.ValueInitializer = value; } } } public static FishTable, TResult> Get { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _getThreadStatic ??= InitializeNew(); } public static FishTable, TResult> GetDirectly { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _get; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref TResult GetOrAddReference(T1 first, T2 second, T3 third) { var key = Create(first, second, third); return ref Get.GetOrAddReference(ref key); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref TResult GetOrAddReference(in ByReference key) => ref Get.GetOrAddReference(ref Unsafe.AsRef(in key)); [MethodImpl(MethodImplOptions.NoInlining)] public static unsafe ref TResult GetExistingReference(T1 first, T2 second, T3 third) { var key = Create(first, second, third); return ref Get.GetReference(ref key); } [MethodImpl(MethodImplOptions.NoInlining)] public static ref TResult GetExistingReference(in ByReference key) => ref Get.GetReference(ref Unsafe.AsRef(in key)); [MethodImpl(MethodImplOptions.NoInlining)] public static void Update(T1 first, T2 second, T3 third) where VResult : TResult, ICacheable> { var key = Create(first, second, third); Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key); } [MethodImpl(MethodImplOptions.NoInlining)] public static void Update(T1 key1, T2 key2, T3 key3, V2 second) where VResult : TResult, ICacheable, V2> { var key = Create(key1, key2, key3); Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key, second); } [MethodImpl(MethodImplOptions.NoInlining)] public static void Update(T1 key1, T2 key2, T3 key3, V2 second, V3 third) where VResult : TResult, ICacheable, V2, V3> { var key = Create(key1, key2, key3); Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key, second, third); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref VResult GetAndCheck(T1 first, T2 second, T3 third) where VResult : TResult, ICacheable> { var key = Create(first, second, third); ref var cache = ref Unsafe.As(ref Get.GetOrAddReference(ref key)); if (cache.Dirty) cache.Update(ref key); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref VResult GetAndCheck(T1 key1, T2 key2, T3 key3, V2 second) where VResult : TResult, ICacheable, V2> { var key = Create(key1, key2, key3); ref var cache = ref Unsafe.As(ref Get.GetOrAddReference(ref key)); if (cache.Dirty) cache.Update(ref key, second); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref VResult GetAndCheck(T1 key1, T2 key2, T3 key3, V2 second, V3 third) where VResult : TResult, ICacheable, V2, V3> { var key = Create(key1, key2, key3); ref var cache = ref Unsafe.As(ref Get.GetOrAddReference(ref key)); if (cache.Dirty) cache.Update(ref key, second, third); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] // ReSharper disable once ConvertToPrimaryConstructor public ByReference(T1 first, T2 second, T3 third) { First = first; Second = second; Third = third; HashCode = FisheryLib.HashCode.Combine(first, second, third); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ByReference Create(T1 first, T2 second, T3 third) => default(ByReference) with { First = first, Second = second, Third = third, HashCode = FisheryLib.HashCode.Combine(first, second, third) }; public static void Initialize() { Utility.Internal.Initialize(); Utility.Internal.Initialize(); Utility.Internal.Initialize(); Utility.Internal.Initialize>(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ByReference other) => First.Equals(other.First) && Second.Equals(other.Second) && Third.Equals(other.Third); [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() => HashCode; [MethodImpl(MethodImplOptions.NoInlining)] private static FishTable, TResult> InitializeNew() => Utility.AddNew(_allCaches, ValueInitializer); public static void Remove(ByReference key) => Utility.Internal.RemoveIn(_allCaches, key); public static void Clear() => Utility.Internal.ClearIn(_allCaches); } [PublicAPI] [StructLayout(LayoutKind.Sequential, Pack = 1)] public record struct ByReference where T1 : notnull where T2 : notnull where T3 : notnull where T4 : notnull where TResult : new() { public T1 First; public T2 Second; public T3 Third; public T4 Fourth; public int HashCode; private static readonly object _valueInitializerLock = new(); private static readonly List _allCaches = []; private static readonly FishTable, TResult> _get = InitializeNew(); [ThreadStatic] private static FishTable, TResult>? _getThreadStatic; private static Func, TResult>? _valueInitializer; public static Func, TResult>? ValueInitializer { get { lock (_valueInitializerLock) return _valueInitializer; } set { lock (_valueInitializerLock) { value ??= static _ => Reflection.New(); _valueInitializer = value; _get.ValueInitializer = value; } } } public static FishTable, TResult> Get { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _getThreadStatic ??= InitializeNew(); } public static FishTable, TResult> GetDirectly { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _get; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref TResult GetOrAddReference(T1 first, T2 second, T3 third, T4 fourth) { var key = Create(first, second, third, fourth); return ref Get.GetOrAddReference(ref key); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref TResult GetOrAddReference(in ByReference key) => ref Get.GetOrAddReference(ref Unsafe.AsRef(in key)); [MethodImpl(MethodImplOptions.NoInlining)] public static unsafe ref TResult GetExistingReference(T1 first, T2 second, T3 third, T4 fourth) { var key = Create(first, second, third, fourth); return ref Get.GetReference(ref key); } [MethodImpl(MethodImplOptions.NoInlining)] public static ref TResult GetExistingReference(in ByReference key) => ref Get.GetReference(ref Unsafe.AsRef(in key)); [MethodImpl(MethodImplOptions.NoInlining)] public static void Update(T1 first, T2 second, T3 third, T4 fourth) where VResult : TResult, ICacheable> { var key = Create(first, second, third, fourth); Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key); } [MethodImpl(MethodImplOptions.NoInlining)] public static void Update(T1 key1, T2 key2, T3 key3, T4 key4, V2 second) where VResult : TResult, ICacheable, V2> { var key = Create(key1, key2, key3, key4); Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key, second); } [MethodImpl(MethodImplOptions.NoInlining)] public static void Update(T1 key1, T2 key2, T3 key3, T4 key4, V2 second, V3 third) where VResult : TResult, ICacheable, V2, V3> { var key = Create(key1, key2, key3, key4); Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key, second, third); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref VResult GetAndCheck(T1 first, T2 second, T3 third, T4 fourth) where VResult : TResult, ICacheable> { var key = Create(first, second, third, fourth); ref var cache = ref Unsafe.As(ref Get.GetOrAddReference(ref key)); if (cache.Dirty) cache.Update(ref key); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref VResult GetAndCheck(T1 key1, T2 key2, T3 key3, T4 key4, V2 second) where VResult : TResult, ICacheable, V2> { var key = Create(key1, key2, key3, key4); ref var cache = ref Unsafe.As(ref Get.GetOrAddReference(ref key)); if (cache.Dirty) cache.Update(ref key, second); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref VResult GetAndCheck(T1 key1, T2 key2, T3 key3, T4 key4, V2 second, V3 third) where VResult : TResult, ICacheable, V2, V3> { var key = Create(key1, key2, key3, key4); ref var cache = ref Unsafe.As(ref Get.GetOrAddReference(ref key)); if (cache.Dirty) cache.Update(ref key, second, third); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] // ReSharper disable once ConvertToPrimaryConstructor public ByReference(T1 first, T2 second, T3 third, T4 fourth) { First = first; Second = second; Third = third; Fourth = fourth; HashCode = FisheryLib.HashCode.Combine(first, second, third, fourth); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ByReference Create(T1 first, T2 second, T3 third, T4 fourth) => default(ByReference) with { First = first, Second = second, Third = third, Fourth = fourth, HashCode = FisheryLib.HashCode.Combine(first, second, third, fourth) }; public static void Initialize() { Utility.Internal.Initialize(); Utility.Internal.Initialize(); Utility.Internal.Initialize(); Utility.Internal.Initialize(); Utility.Internal.Initialize>(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ByReference other) => First.Equals(other.First) && Second.Equals(other.Second) && Third.Equals(other.Third) && Fourth.Equals(other.Fourth); [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() => HashCode; [MethodImpl(MethodImplOptions.NoInlining)] private static FishTable, TResult> InitializeNew() => Utility.AddNew(_allCaches, ValueInitializer); public static void Remove(ByReference key) => Utility.Internal.RemoveIn(_allCaches, key); public static void Clear() => Utility.Internal.ClearIn(_allCaches); } [PublicAPI] [StructLayout(LayoutKind.Sequential, Pack = 1)] public record struct ByReferenceClassic where T1 : notnull where T2 : notnull where T3 : notnull where TResult : new() { public T1 First; public T2 Second; public T3 Third; public int HashCode; // TODO: Figure out why this behaves differently compared to FishTables on ReflectionCaching:CustomAttributeCache private static readonly object _valueInitializerLock = new(); private static readonly List _allCaches = []; private static readonly Dictionary, TResult> _get = InitializeNew(); [ThreadStatic] private static Dictionary, TResult>? _getThreadStatic; public static Dictionary, TResult> Get { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _getThreadStatic ??= InitializeNew(); } public static Dictionary, TResult> GetDirectly { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _get; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref TResult GetOrAddReference(T1 first, T2 second, T3 third) { var key = Create(first, second, third); return ref Get.GetOrAddReference(ref key); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref TResult GetOrAddReference(in ByReferenceClassic key) => ref Get.GetOrAddReference(ref Unsafe.AsRef(in key)); [MethodImpl(MethodImplOptions.NoInlining)] public static unsafe ref TResult GetExistingReference(T1 first, T2 second, T3 third) { var key = Create(first, second, third); return ref Get.GetReference(ref key); } [MethodImpl(MethodImplOptions.NoInlining)] public static ref TResult GetExistingReference(in ByReferenceClassic key) => ref Get.GetReference(ref Unsafe.AsRef(in key)); [MethodImpl(MethodImplOptions.NoInlining)] public static void Update(T1 first, T2 second, T3 third) where VResult : TResult, ICacheable> { var key = Create(first, second, third); Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key); } [MethodImpl(MethodImplOptions.NoInlining)] public static void Update(T1 key1, T2 key2, T3 key3, V2 second) where VResult : TResult, ICacheable, V2> { var key = Create(key1, key2, key3); Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key, second); } [MethodImpl(MethodImplOptions.NoInlining)] public static void Update(T1 key1, T2 key2, T3 key3, V2 second, V3 third) where VResult : TResult, ICacheable, V2, V3> { var key = Create(key1, key2, key3); Unsafe.As(ref Get.GetOrAddReference(ref key)).Update(ref key, second, third); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref VResult GetAndCheck(T1 first, T2 second, T3 third) where VResult : TResult, ICacheable> { var key = Create(first, second, third); ref var cache = ref Unsafe.As(ref Get.GetOrAddReference(ref key)); if (cache.Dirty) cache.Update(ref key); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref VResult GetAndCheck(T1 key1, T2 key2, T3 key3, V2 second) where VResult : TResult, ICacheable, V2> { var key = Create(key1, key2, key3); ref var cache = ref Unsafe.As(ref Get.GetOrAddReference(ref key)); if (cache.Dirty) cache.Update(ref key, second); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static unsafe ref VResult GetAndCheck(T1 key1, T2 key2, T3 key3, V2 second, V3 third) where VResult : TResult, ICacheable, V2, V3> { var key = Create(key1, key2, key3); ref var cache = ref Unsafe.As(ref Get.GetOrAddReference(ref key)); if (cache.Dirty) cache.Update(ref key, second, third); return ref cache!; } [MethodImpl(MethodImplOptions.AggressiveInlining)] // ReSharper disable once ConvertToPrimaryConstructor public ByReferenceClassic(T1 first, T2 second, T3 third) { First = first; Second = second; Third = third; HashCode = FisheryLib.HashCode.Combine(first, second, third); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ByReferenceClassic Create(T1 first, T2 second, T3 third) => default(ByReferenceClassic) with { First = first, Second = second, Third = third, HashCode = FisheryLib.HashCode.Combine(first, second, third) }; public static void Initialize() { Utility.Internal.Initialize(); Utility.Internal.Initialize(); Utility.Internal.Initialize(); Utility.Internal.Initialize>(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(ByReferenceClassic other) => First.Equals(other.First) && Second.Equals(other.Second) && Third.Equals(other.Third); [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() => HashCode; [MethodImpl(MethodImplOptions.NoInlining)] private static Dictionary, TResult> InitializeNew() { var newCache = Utility.AddNew, TResult>>(); lock (_allCaches) _allCaches.Add(newCache); return newCache; } public static void Remove(ByReferenceClassic key) => Utility.Internal.RemoveIn(_allCaches, key); public static void Clear() => Utility.Internal.ClearIn(_allCaches); } #pragma warning restore CS9091