Files
Performance-Fish/Source/PerformanceFish/Hediffs/HediffSetCaching.cs
T
bbradson be3dae46bb Added stat caching, fixed all the bugs
Added RitualObligationTriggers analyzer category,
Fixed defs from other mods with broken market value breaking fish's storage patches,
Fixed storage caches not updating for quality, rotting and similar changes to stored things,
Fixed storage caches not updating capacity when reinstalling storage,
Fixed prison labor compatibility,
Improved multiplayer compatibility,
Fixed ideology style dominance and altar sharing patches throwing errors instead of defaulting to disabled when not having ideology,
Fixed compatibility with RIMMSqol's world pawn GC patch by disabling that patch,
Fixed rare threading issues throwing errors on startup
2024-03-10 20:05:24 +01:00

235 lines
7.4 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 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 record struct VisibleHediffCacheValue()
{
public HediffCacheValue Value = new();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(HediffSet set, Hediff? hediff) => Value.Update(set, hediff);
}
}