// Copyright (c) 2022 bradson // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. using PerformanceFish.Collections; using HediffCache = PerformanceFish.Cache.ByIntRefreshable; using VisibleHediffCache = PerformanceFish.Cache.ByIntRefreshable; namespace PerformanceFish; public class HediffSetCaching : ClassWithFishPatches { public class HasHediff_Patch : FirstPriorityFishPatch { public override string Description => "Caches results of the HediffSet.HasHediff method. This scales with the number of hediffs pawns have and can make a decent difference in transhumanist playthroughs."; public override Expression TargetMethod => () => default(HediffSet)!.HasHediff(null, default); public static CodeInstructions Transpiler(CodeInstructions codes) => Reflection.GetCodeInstructions(Replacement); public static bool Replacement(HediffSet __instance, HediffDef def, bool mustBeVisible) => __instance.GetFirstHediffOfDef(def, mustBeVisible) != null; } public class GetFirstHediffOfDef_Patch : FirstPriorityFishPatch { public override string Description => "Caches results of the HediffSet.GetFirstHediffOfDef method. This scales with the number of hediffs pawns have and can make a decent difference in transhumanist playthroughs."; public override Expression TargetMethod => () => default(HediffSet)!.GetFirstHediffOfDef(null, default); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Prefix(HediffSet __instance, HediffDef def, bool mustBeVisible, ref Hediff? __result, out bool __state) { if (__instance.hediffs.Count < 5 || def is null || def.GetType() != typeof(HediffDef)) // different types have different indexing. Can't use those { __state = false; return true; } var key = new HediffCache() { First = __instance.pawn.thingIDNumber, Second = def.index }; ref var cache = ref mustBeVisible ? ref Unsafe.As(ref VisibleHediffCache.Get.TryGetReferenceUnsafe(ref Unsafe.As(ref key))) : ref HediffCache.Get.TryGetReferenceUnsafe(ref key); if (Unsafe.IsNullRef(ref cache) || cache.ShouldRefreshNow) return __state = true; __result = cache.Hediff; return __state = false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Postfix(HediffSet __instance, HediffDef def, bool mustBeVisible, Hediff? __result, bool __state) { if (!__state) return; if (mustBeVisible) VisibleHediffCache.Get[new(__instance, def)] = new(__instance, __result); else HediffCache.Get[new(__instance, def)] = new(__instance, __result); } } public class HediffSet_DirtyCache_Patch : FishPatch { public override string? Description => "Patched to trigger Psylink cache clearing for the psychic entropy optimization."; public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(HediffSet), nameof(HediffSet.DirtyCache)); public static void Postfix(HediffSet __instance) { if (__instance.pawn.psychicEntropy is { } entropy) entropy.psylinkCachedForTick = 0; } } public struct HediffCacheValue : Cache.IIsRefreshable { private int _nextRefreshTick; private int _version; private HediffSet _set; public Hediff? Hediff; [MethodImpl(MethodImplOptions.AggressiveInlining)] public HediffCacheValue(HediffSet set, Hediff? hediff) { _set = set; Hediff = hediff; _version = set.hediffs._version; _nextRefreshTick = Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048); } public bool ShouldRefreshNow { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _nextRefreshTick < Current.Game.tickManager.ticksGameInt || _version != _set.hediffs._version; set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048); } public HediffCacheValue SetNewValue(HediffCache key) => throw new NotImplementedException(); } public struct VisibleHediffCacheValue : Cache.IIsRefreshable { public HediffCacheValue Value; [MethodImpl(MethodImplOptions.AggressiveInlining)] public VisibleHediffCacheValue(HediffSet set, Hediff? hediff) => Value = new(set, hediff); public bool ShouldRefreshNow { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Value.ShouldRefreshNow; set => Value.ShouldRefreshNow = value; } public VisibleHediffCacheValue SetNewValue(VisibleHediffCache key) => throw new NotImplementedException(); } }