diff --git a/1.6/Assemblies/PerformanceFish.dll b/1.6/Assemblies/PerformanceFish.dll index 1f8fb39..6137aa9 100644 Binary files a/1.6/Assemblies/PerformanceFish.dll and b/1.6/Assemblies/PerformanceFish.dll differ diff --git a/1.6/Assemblies/PerformanceFish.pdb b/1.6/Assemblies/PerformanceFish.pdb index 2b047e3..333a233 100644 Binary files a/1.6/Assemblies/PerformanceFish.pdb and b/1.6/Assemblies/PerformanceFish.pdb differ diff --git a/Source/PerformanceFish/CompTickOptimization.cs b/Source/PerformanceFish/CompTickOptimization.cs index 5fceebc..f949be1 100644 --- a/Source/PerformanceFish/CompTickOptimization.cs +++ b/Source/PerformanceFish/CompTickOptimization.cs @@ -11,6 +11,15 @@ namespace PerformanceFish; public sealed class CompTickOptimization : ClassWithFishPrepatches { +#if V1_6 + public struct ThingTickCache + { + public List? 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 _canSkipInheritedBaseThingCompTick = new(static () => _thingCompTickBaseIsTrivial && !HasHarmonyPatches(_thingCompTickMethod)); +#endif private static readonly Lazy _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 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() diff --git a/Source/PerformanceFish/PrepatcherFields.cs b/Source/PerformanceFish/PrepatcherFields.cs index 409f526..0f02687 100644 --- a/Source/PerformanceFish/PrepatcherFields.cs +++ b/Source/PerformanceFish/PrepatcherFields.cs @@ -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]