Lots of new optimizations utilizing prepatcher

To start, this mod now requires Prepatcher and Fishery.
New optimizations include: gene ticking improvements, world object improvements, hediff optimizations, world pawn gc optimizations, better mothballing, dds support, ContentFinder caching, additions and changes to reflection caching, as well as various other additions. Multiplayer support has been improved too, but should be considered highly experimental. The WorkGiver_DoBill bug is fixed in this build. Multiple patches aiming to improve RimWorld's load time have been added too.
This commit is contained in:
bbradson
2023-06-03 20:24:38 +02:00
parent cb0a407425
commit ca9321add8
125 changed files with 11272 additions and 3681 deletions
@@ -0,0 +1,37 @@
// 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/.
#if false
using PerformanceFish.Prepatching;
namespace PerformanceFish.Hediffs;
public class HealthTrackerOptimization : ClassWithFishPrepatches
{
public static void Initialize()
{
var allHediffDefs = DefDatabase<HediffDef>.AllDefsListForReading;
for (var i = allHediffDefs.Count; i-- > 0;)
{
var hediffDef = allHediffDefs[i];
if (hediffDef.stages is { Count: > 1})
continue;
SkippableHediffs.Add(hediffDef);
}
}
public class HealthTickPatch : FishPrepatch
{
public override MethodBase TargetMethodBase { get; }
= AccessTools.Method(typeof(Pawn_HealthTracker), nameof(Pawn_HealthTracker.HealthTick));
}
public static HashSet<HediffDef> SkippableHediffs = new();
}
#endif
@@ -0,0 +1,60 @@
// 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.Linq;
using static System.Reflection.Emit.OpCodes;
namespace PerformanceFish.Hediffs;
public class HediffComp_PsychicHarmonizer_Bugfix : ClassWithFishPatches
{
public class CompPostTick_Patch : FishPatch
{
public override string Description { get; }
= "Fixes a bug in the psychic harmonizer tick method that was causing it to recalculate results on 149 "
+ "out of 150 ticks instead of once every 150 ticks. Large impact.";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(HediffComp_PsychicHarmonizer),
nameof(HediffComp_PsychicHarmonizer.CompPostTick));
public static CodeInstructions Transpiler(CodeInstructions codeInstructions)
{
var codes = codeInstructions.ToList();
var pawn_int_IsHashIntervalTick = FishTranspiler.Call<Func<Thing, int, bool>>(Gen.IsHashIntervalTick);
var success = false;
for (var i = 0; i < codes.Count; i++)
{
if (i + 2 < codes.Count
&& codes[i].opcode == Ldc_I4
&& codes[i + 1] == pawn_int_IsHashIntervalTick
&& codes[i + 2].opcode == Brfalse_S)
{
//codes[i].operand = 128;
yield return codes[i];
yield return codes[i + 1];
codes[i + 2].opcode = Brtrue_S;
yield return codes[i + 2];
success = true;
i += 2;
}
else
{
yield return codes[i];
}
}
if (!success)
{
Log.Warning("Performance Fish failed to insert its Psychic Harmonizer Tick bugfix. This is "
+ "probably harmless and can happen when toggling patches from mod settings. It could indicate "
+ "an actual failure to patch if settings were not toggled however, which would mean some other "
+ "patch or a vanilla change either messes with this or implements the same thing");
}
}
}
}
@@ -0,0 +1,247 @@
// 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 class HediffSetPrecaching : ClassWithFishPrepatches
{
public 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 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 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 class HediffSetCaching : ClassWithFishPatches
{
public 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 = new();
private int _nextRefreshTick;
public List<BodyPartRecord> Parts = new();
[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 = new();
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()
{
}
}
}
@@ -0,0 +1,146 @@
// 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 ImmunityInfoCache
= PerformanceFish.Cache.ByInt<Verse.Pawn,
PerformanceFish.Hediffs.ImmunityHandlerCaching.ImmunityInfoCacheValue>;
using ImmunityRecordExistsCache
= PerformanceFish.Cache.ByInt<Verse.Pawn, Verse.HediffDef,
PerformanceFish.Hediffs.ImmunityHandlerCaching.ImmunityRecordExistsCacheValue>;
namespace PerformanceFish.Hediffs;
public class ImmunityHandlerCaching : ClassWithFishPatches
{
public class NeededImmunitiesNow : FirstPriorityFishPatch
{
public override string Description { get; }
= "Caches results of the ImmunityHandler.NeededImmunitiesNow method. Impact scales with hediff quantity.";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(ImmunityHandler), nameof(ImmunityHandler.NeededImmunitiesNow));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(ImmunityHandler __instance, ref List<ImmunityHandler.ImmunityInfo> __result,
out bool __state)
{
ref var cache = ref ImmunityInfoCache.GetOrAddReference(__instance.pawn.thingIDNumber);
if (cache.Dirty)
return __state = true;
(__result = ImmunityHandler.tmpNeededImmunitiesNow).Clear();
__result.AddRangeFast(cache.Infos);
return __state = false;
}
// ImmunityHandler.tmpNeededImmunitiesNow, which is normally used for the result, is static
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(ImmunityHandler __instance, List<ImmunityHandler.ImmunityInfo> __result,
bool __state)
{
if (!__state)
return;
UpdateCache(__instance, __result);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateCache(ImmunityHandler __instance, List<ImmunityHandler.ImmunityInfo> __result)
=> ImmunityInfoCache.GetExistingReference(__instance.pawn)
.Update(__instance.pawn.health.hediffSet, __result);
}
public class ImmunityRecordExists : FishPatch
{
public override string? Description { get; } = "Required by the TryAddImmunityRecord optimization.";
public override bool Enabled
{
set
{
if (base.Enabled == value)
return;
Get<TryAddImmunityRecord>().Enabled = base.Enabled = value;
}
}
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(ImmunityHandler), nameof(ImmunityHandler.ImmunityRecordExists));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(ImmunityHandler __instance, HediffDef def, bool __result)
{
ref var cache
= ref ImmunityRecordExistsCache.GetOrAddReference(__instance.pawn.thingIDNumber, def.shortHash);
cache.Value = __result;
cache.ImmunityListVersion = __instance.immunityList._version;
}
}
public class TryAddImmunityRecord : FishPatch
{
public override string? Description { get; }
= "Optimization for this specific method to not run more checks than necessary.";
public override bool Enabled
{
set
{
if (base.Enabled == value)
return;
Get<ImmunityRecordExists>().Enabled = base.Enabled = value;
}
}
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(ImmunityHandler), nameof(ImmunityHandler.TryAddImmunityRecord));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(ImmunityHandler __instance, HediffDef def)
{
ref var cache
= ref ImmunityRecordExistsCache.GetOrAddReference(__instance.pawn.thingIDNumber, def.shortHash);
return !cache.Value || cache.ImmunityListVersion != __instance.immunityList._version;
}
}
public record struct ImmunityInfoCacheValue
{
private int _version = -1;
private List<Hediff> _hediffsInSet = new();
private int _nextRefreshTick;
public List<ImmunityHandler.ImmunityInfo> Infos = new();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(HediffSet set, List<ImmunityHandler.ImmunityInfo> infos)
{
Infos.ReplaceContentsWith(infos);
_hediffsInSet = set.hediffs;
_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 ImmunityInfoCacheValue()
{
}
}
public record struct ImmunityRecordExistsCacheValue
{
public bool Value;
public int ImmunityListVersion;
}
}
@@ -0,0 +1,31 @@
// 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/.
namespace PerformanceFish.Hediffs;
public class Pawn_PsychicEntropyTrackerOptimization : ClassWithFishPatches
{
public class Psylink_Patch : FirstPriorityFishPatch
{
public override string Description { get; } = "Caches/throttles the psylink method to only update every 32 ticks.";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.PropertyGetter(typeof(Pawn_PsychicEntropyTracker),
nameof(Pawn_PsychicEntropyTracker.Psylink));
public static CodeInstructions Transpiler(CodeInstructions codes) => Reflection.GetCodeInstructions(Psylink);
public static Hediff_Psylink Psylink(Pawn_PsychicEntropyTracker __instance)
{
if (__instance.psylinkCachedForTick <= TickHelper.TicksGame)
{
__instance.psylinkCached = __instance.pawn.GetMainPsylinkSource();
__instance.psylinkCachedForTick = TickHelper.TicksGame + 32;
}
return __instance.psylinkCached;
}
}
}