Cache no-op component tick lists in RimWorld 1.6

This commit is contained in:
2026-10-02 11:28:13 -04:00
parent 9d31b36805
commit ff3119a810
4 changed files with 85 additions and 2 deletions
+81 -2
View File
@@ -11,6 +11,15 @@ namespace PerformanceFish;
public sealed class CompTickOptimization : ClassWithFishPrepatches
{
#if V1_6
public struct ThingTickCache
{
public List<ThingComp>? Source;
public int Version;
public bool AllTrivial;
}
#endif
private static readonly MethodInfo _thingCompTickMethod
= AccessTools.DeclaredMethod(typeof(ThingComp), nameof(ThingComp.CompTick));
@@ -39,8 +48,12 @@ public sealed class CompTickOptimization : ClassWithFishPrepatches
private static readonly bool _hediffCompPostTickIntervalBaseIsTrivial
= IsTrivialVoidMethod(_hediffCompPostTickIntervalMethod);
#if V1_6
private static int _canSkipInheritedBaseThingCompTickState;
#else
private static readonly Lazy<bool> _canSkipInheritedBaseThingCompTick
= new(static () => _thingCompTickBaseIsTrivial && !HasHarmonyPatches(_thingCompTickMethod));
#endif
private static readonly Lazy<bool> _canSkipInheritedBaseThingCompTickInterval
= new(static () => _thingCompTickIntervalBaseIsTrivial && !HasHarmonyPatches(_thingCompTickIntervalMethod));
@@ -89,10 +102,19 @@ public sealed class CompTickOptimization : ClassWithFishPrepatches
public sealed class ThingTickPatch : FishPrepatch
{
public override bool DefaultState => false;
public override bool DefaultState =>
#if V1_6
true;
#else
false;
#endif
public override string? Description { get; }
#if V1_6
= "Skips the component tick loop when every component inherits the unpatched, empty base CompTick method.";
#else
= "Skips inherited empty ThingComp.CompTick calls when the base method is still unpatched and trivial.";
#endif
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(ThingWithComps), "Tick");
@@ -102,16 +124,61 @@ public sealed class CompTickOptimization : ClassWithFishPrepatches
public static void ReplacementBody(ThingWithComps instance)
{
#if V1_6
var comps = instance.comps;
if (comps is null)
return;
#else
var comps = instance.AllComps;
#endif
var count = comps.Count;
#if V1_6
// Small lists cost less to dispatch normally than to validate a cache.
if (count >= 4 && CanSkipInheritedBaseThingCompTick())
{
ref var cache = ref instance.ThingTickCache();
if (!ReferenceEquals(cache.Source, comps) || cache.Version != comps._version)
UpdateThingTickCache(comps, ref cache);
if (cache.AllTrivial)
return;
}
#else
var canSkipInheritedBase = CanSkipInheritedBaseThingCompTick();
#endif
for (var i = 0; i < count; i++)
{
#if V1_6
instance.comps![i].CompTick();
#else
var comp = comps[i];
if (!canSkipInheritedBase || !_thingCompTickTypes.GetOrAdd(comp.GetType()))
comp.CompTick();
#endif
}
}
#if V1_6
[MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateThingTickCache(List<ThingComp> comps, ref ThingTickCache cache)
{
var allTrivial = true;
for (var i = 0; i < comps.Count; i++)
{
var comp = comps[i];
if (comp is null || !_thingCompTickTypes.GetOrAdd(comp.GetType()))
{
allTrivial = false;
break;
}
}
cache.Source = comps;
cache.Version = comps._version;
cache.AllTrivial = allTrivial;
}
#endif
}
public sealed class ThingTickIntervalPatch : FishPrepatch
@@ -267,7 +334,19 @@ public sealed class CompTickOptimization : ClassWithFishPrepatches
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CanSkipInheritedBaseThingCompTick()
=> _canSkipInheritedBaseThingCompTick.Value;
{
#if V1_6
var state = _canSkipInheritedBaseThingCompTickState;
if (state == 0)
{
state = _thingCompTickBaseIsTrivial && !HasHarmonyPatches(_thingCompTickMethod) ? 2 : 1;
_canSkipInheritedBaseThingCompTickState = state;
}
return state == 2;
#else
return _canSkipInheritedBaseThingCompTick.Value;
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CanSkipInheritedBaseThingCompTickInterval()
@@ -39,6 +39,10 @@ public static class PrepatcherFields
[PrepatcherField]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern ref GeneTrackerOptimization.GeneListVersionState GeneListVersions(this Pawn_GeneTracker geneTracker);
[PrepatcherField]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern ref CompTickOptimization.ThingTickCache ThingTickCache(this ThingWithComps thing);
#endif
[PrepatcherField]