Files
Performance-Fish/Source/PerformanceFish/Hediffs/HediffSetCaching.cs
T

247 lines
7.6 KiB
C#

// 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 Mono.Cecil;
using Mono.Cecil.Cil;
using PerformanceFish.Prepatching;
using HediffCache
= PerformanceFish.Cache.ByInt<Verse.HediffSet, Verse.HediffDef,
PerformanceFish.Hediffs.HediffSetCaching.HediffCacheValue>;
using VisibleHediffCache
= PerformanceFish.Cache.ByInt<Verse.HediffSet, Verse.HediffDef,
PerformanceFish.Hediffs.HediffSetCaching.VisibleHediffCacheValue>;
using NotMissingPartsCache
= PerformanceFish.Cache.ByInt<Verse.HediffSet,
PerformanceFish.Hediffs.HediffSetCaching.NotMissingPartsCacheValue>;
namespace PerformanceFish.Hediffs;
public sealed class HediffSetPrecaching : ClassWithFishPrepatches
{
public sealed class GetFirstHediffOfDef : FishPrepatch
{
public override string Description { get; }
= "Caches results of the HediffSet.GetFirstHediffOfDef method. Impact scales with hediff count.";
public override MethodBase TargetMethodBase { get; }
= SymbolExtensions.GetMethodInfo(static ()
=> default(HediffSet)!.GetFirstHediffOfDef(null, default));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(Replacement);
public static Hediff? Replacement(HediffSet instance, HediffDef? def, bool mustBeVisible)
{
var canCache = true;
if (instance.hediffs.Count >= 5
&& def is not null
// && def.GetType() == typeof(HediffDef) // different types have different indexing. Can't use those
)
{
ref var cache = ref GetCacheValue(instance, def, mustBeVisible);
if (!cache.Dirty)
return cache.Hediff;
}
else
{
canCache = false;
}
// original method, mostly left untouched in case of other mods patching in
Hediff? result;
var hediffs = instance.hediffs;
for (var i = 0; i < hediffs.Count; i++)
{
if (hediffs[i].def != def || (mustBeVisible && !hediffs[i].Visible))
continue;
result = hediffs[i];
goto Result;
}
result = null;
Result:
if (canCache)
UpdateCache(instance, def!, mustBeVisible, result);
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref HediffSetCaching.HediffCacheValue GetCacheValue(HediffSet instance, HediffDef def,
bool mustBeVisible)
{
var key = new HediffCache { First = instance.pawn.thingIDNumber, Second = def.shortHash };
ref var cache = ref mustBeVisible
? ref Unsafe.As<HediffSetCaching.VisibleHediffCacheValue, HediffSetCaching.HediffCacheValue>(
ref VisibleHediffCache.GetOrAddReference(Unsafe.As<HediffCache, VisibleHediffCache>(ref key)))
: ref HediffCache.GetOrAddReference(key);
return ref cache;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void UpdateCache(HediffSet __instance, HediffDef def, bool mustBeVisible, Hediff? __result)
{
if (mustBeVisible)
VisibleHediffCache.GetExistingReference(__instance, def).Update(__instance, __result);
else
HediffCache.GetExistingReference(__instance, def).Update(__instance, __result);
}
}
public sealed class HasHediff : FishPrepatch
{
public override string Description { get; }
= "Caches results of the HediffSet.HasHediff method. Impact scales with hediff count.";
public override MethodBase TargetMethodBase { get; }
= SymbolExtensions.GetMethodInfo(static ()
=> default(HediffSet)!.HasHediff(null, default));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(Replacement);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Replacement(HediffSet __instance, HediffDef def, bool mustBeVisible)
=> __instance.GetFirstHediffOfDef(def, mustBeVisible) != null;
}
public sealed class GetNotMissingParts : FishPrepatch
{
public override string? Description { get; }
= "Caches results of the HediffSet.GetNotMissingParts method. Impact scales with hediff count.";
public override MethodBase TargetMethodBase { get; }
= AccessTools.Method(typeof(HediffSet), nameof(HediffSet.GetNotMissingParts));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(HediffSet __instance, BodyPartHeight height, BodyPartDepth depth, BodyPartTagDef? tag,
BodyPartRecord? partParent, ref IEnumerable<BodyPartRecord> __result, out bool __state)
{
if (height != BodyPartHeight.Undefined
|| depth != BodyPartDepth.Undefined
|| tag != null
|| partParent != null)
{
__state = false;
return true;
}
ref var cache = ref NotMissingPartsCache.GetOrAddReference(__instance.pawn.thingIDNumber);
if (cache.Dirty)
return __state = true;
__result = cache.Parts;
return __state = false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(HediffSet __instance, bool __state, ref IEnumerable<BodyPartRecord> __result)
{
if (!__state)
return;
UpdateCache(__instance, ref __result);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void UpdateCache(HediffSet __instance, ref IEnumerable<BodyPartRecord> __result)
=> NotMissingPartsCache.GetExistingReference(__instance).Update(__instance, ref __result);
}
}
public sealed class HediffSetCaching : ClassWithFishPatches
{
public sealed class DirtyCache : FishPatch
{
public override string Description { get; }
= "Patched to trigger psylink cache clearing for the psychic entropy optimization.";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(HediffSet), nameof(HediffSet.DirtyCache));
public static void Postfix(HediffSet __instance)
{
if (__instance.pawn.psychicEntropy is { } entropy)
entropy.psylinkCachedForTick = 0;
}
}
public record struct NotMissingPartsCacheValue
{
private int _version = -1;
private List<Hediff> _hediffsInSet = [];
private int _nextRefreshTick;
public List<BodyPartRecord> Parts = [];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(HediffSet set, ref IEnumerable<BodyPartRecord> result)
{
_hediffsInSet = set.hediffs;
_version = set.hediffs._version;
_nextRefreshTick = TickHelper.Add(GenTicks.TickLongInterval, set.pawn.thingIDNumber);
var previousPartsVersion = Parts._version;
Parts.Clear();
Parts.AddRange(result);
Parts._version = previousPartsVersion;
result = Parts;
}
public bool Dirty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _version != _hediffsInSet._version || TickHelper.Past(_nextRefreshTick);
}
public NotMissingPartsCacheValue()
{
}
}
public record struct HediffCacheValue
{
private int _version = -1;
private List<Hediff> _hediffsInSet = [];
private int _nextRefreshTick;
public Hediff? Hediff;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(HediffSet set, Hediff? hediff)
{
_hediffsInSet = set.hediffs;
Hediff = hediff;
_version = _hediffsInSet._version;
_nextRefreshTick = TickHelper.Add(GenTicks.TickLongInterval, set.pawn.thingIDNumber);
}
public bool Dirty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _version != _hediffsInSet._version || TickHelper.Past(_nextRefreshTick);
}
public HediffCacheValue()
{
}
}
public record struct VisibleHediffCacheValue
{
public HediffCacheValue Value = new();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(HediffSet set, Hediff? hediff) => Value.Update(set, hediff);
public VisibleHediffCacheValue()
{
}
}
}