// 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 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 object SyncLock = new(); private static 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 object _valueInitializerLock = new(); public static object SyncLock = new(); private static 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); [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(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 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(ByReference).FullName}\n{ex}"); } } [MethodImpl(MethodImplOptions.NoInlining)] private static FishTable InitializeNew() => Utility.AddNew(ValueInitializer); } [PublicAPI] [StructLayout(LayoutKind.Sequential, Pack = 1)] public record struct ByReference : IInitializable where T1 : notnull where T2 : notnull where TResult : new() { private static FishTable, TResult> _get = Utility.AddNew, TResult>(); [ThreadStatic] private static FishTable, TResult>? _getThreadStatic; public static FishTable, TResult> Get { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _getThreadStatic ??= Utility.AddNew, TResult>(); } 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(new() { 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 = new ByReference(first, 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 = new ByReference(first, 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 = new ByReference(key1, 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 = new ByReference(key1, 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 = new ByReference(first, 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 = new ByReference(key1, 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 = new ByReference(key1, 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 unsafe void Initialize() { try { _ = FisheryLib.FunctionPointers.Equals.Default; _ = FisheryLib.FunctionPointers.Equals.Default; _ = FisheryLib.FunctionPointers.Equals>.Default; _ = FisheryLib.FunctionPointers.GetHashCode.Default; _ = FisheryLib.FunctionPointers.GetHashCode.Default; _ = FisheryLib.FunctionPointers.GetHashCode>.Default; } catch (Exception ex) { Log.Error( $"Performance Fish encountered an exception while trying to initialize { typeof(ByReference).FullName}\n{ex}"); } } public T1 First; public T2 Second; [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); 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() { // TODO: Switch to FishTable. This is used by some of the reflection patches and currently crashes when not used // with standard dictionaries private static Dictionary, TResult> _get = Utility.AddNew, TResult>>(); [ThreadStatic] private static Dictionary, TResult>? _getThreadStatic; public static Dictionary, TResult> Get { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _getThreadStatic ??= Utility.AddNew, TResult>>(); } 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 = new ByReference { First = first, Second = second, Third = 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 = new ByReference(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 = new ByReference(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 = new ByReference(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 = new ByReference(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 = new ByReference(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 = new ByReference(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 = new ByReference(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)] public ByReference(T1 first, T2 second, T3 third) { First = first; Second = second; Third = third; _hashCode = HashCode.Combine(first, second, third); } public static unsafe void Initialize() { try { _ = FisheryLib.FunctionPointers.Equals.Default; _ = FisheryLib.FunctionPointers.Equals.Default; _ = FisheryLib.FunctionPointers.Equals.Default; _ = FisheryLib.FunctionPointers.Equals>.Default; _ = FisheryLib.FunctionPointers.GetHashCode.Default; _ = FisheryLib.FunctionPointers.GetHashCode.Default; _ = FisheryLib.FunctionPointers.GetHashCode.Default; _ = FisheryLib.FunctionPointers.GetHashCode>.Default; } catch (Exception ex) { Log.Error($"Performance Fish encountered an exception while trying to initialize { typeof(ByReference).FullName}\n{ex}"); } } public T1 First; public T2 Second; public T3 Third; private int _hashCode; [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; } [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() { private static FishTable, TResult> _get = Utility.AddNew, TResult>(); [ThreadStatic] private static FishTable, TResult>? _getThreadStatic; public static FishTable, TResult> Get { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _getThreadStatic ??= Utility.AddNew, TResult>(); } 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 = new ByReference { First = first, Second = second, Third = third, Fourth = 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 = new ByReference(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 = new ByReference(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 = new ByReference(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 = new ByReference(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 = new ByReference(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 = new ByReference(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 = new ByReference(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)] public ByReference(T1 first, T2 second, T3 third, T4 fourth) { First = first; Second = second; Third = third; Fourth = fourth; _hashCode = HashCode.Combine(first, second, third, fourth); } public static unsafe void Initialize() { try { _ = FisheryLib.FunctionPointers.Equals.Default; _ = FisheryLib.FunctionPointers.Equals.Default; _ = FisheryLib.FunctionPointers.Equals.Default; _ = FisheryLib.FunctionPointers.Equals.Default; _ = FisheryLib.FunctionPointers.Equals>.Default; _ = FisheryLib.FunctionPointers.GetHashCode.Default; _ = FisheryLib.FunctionPointers.GetHashCode.Default; _ = FisheryLib.FunctionPointers.GetHashCode.Default; _ = FisheryLib.FunctionPointers.GetHashCode.Default; _ = FisheryLib.FunctionPointers.GetHashCode>.Default; } catch (Exception ex) { Log.Error( $"Performance Fish encountered an exception while trying to initialize { typeof(ByReference).FullName}\n{ex}"); } } public T1 First; public T2 Second; public T3 Third; public T4 Fourth; private int _hashCode; [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; } #pragma warning restore CS9091