diff --git a/1.6/Assemblies/PerformanceFish.dll b/1.6/Assemblies/PerformanceFish.dll index 1798725..4ac7526 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 bdc70d0..6d57270 100644 Binary files a/1.6/Assemblies/PerformanceFish.pdb and b/1.6/Assemblies/PerformanceFish.pdb differ diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..95e879e --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + diff --git a/Source/PerformanceFish/1.6.csproj b/Source/PerformanceFish/1.6.csproj index 1478547..6b34b57 100644 --- a/Source/PerformanceFish/1.6.csproj +++ b/Source/PerformanceFish/1.6.csproj @@ -3,6 +3,7 @@ ..\..\1.6\Assemblies\ Copyright (c) 2026 Bradson + false $(MSBuildThisFileDirectory)..\..\..\Fishery $(MSBuildThisFileDirectory)..\..\..\Fishery-clean @@ -13,8 +14,13 @@ V1_5;V1_6 - + + + + + + $(FisheryRepoRoot)\1.6\Assemblies\1Fishery.dll False diff --git a/Source/PerformanceFish/Cache/Utility.cs b/Source/PerformanceFish/Cache/Utility.cs index 112d86a..51b6add 100644 --- a/Source/PerformanceFish/Cache/Utility.cs +++ b/Source/PerformanceFish/Cache/Utility.cs @@ -59,27 +59,33 @@ public static class Utility return; _gcInProgress = true; - - if (_cachesToProcessForGC is null || _currentGCProgress >= _cachesToProcessForGC.Length) + try { - _cachesToProcessForGC = All.ToArray(); - _gcRate = GenDate.TicksPerTwelfth / _cachesToProcessForGC.CountNonEmpty(); - _currentGCProgress = 0; - _destroyedThingCounter = 0; - } + if (_cachesToProcessForGC is null || _currentGCProgress >= _cachesToProcessForGC.Length) + { + _cachesToProcessForGC = All.ToArray(); + var nonEmptyCount = _cachesToProcessForGC.CountNonEmpty(); + _gcRate = nonEmptyCount > 0 ? GenDate.TicksPerTwelfth / nonEmptyCount : 1000; + _currentGCProgress = 0; + _destroyedThingCounter = 0; + if (nonEmptyCount == 0) + return; + } - while (_cachesToProcessForGC[_currentGCProgress].Count == 0 - && _cachesToProcessForGC.Length - _currentGCProgress > 1) + while (_currentGCProgress < _cachesToProcessForGC.Length + && _cachesToProcessForGC[_currentGCProgress].Count == 0) + { + _currentGCProgress++; + } + + if (_currentGCProgress < _cachesToProcessForGC.Length) + _cachesToProcessForGC[_currentGCProgress++].Clear(); + } + finally { - _currentGCProgress++; + _lastGCTick = TickHelper.TicksGame; + _gcInProgress = false; } - - if (_currentGCProgress < _cachesToProcessForGC.Length) - _cachesToProcessForGC[_currentGCProgress++].Clear(); - - _lastGCTick = TickHelper.TicksGame; - - _gcInProgress = false; } } @@ -414,4 +420,4 @@ public sealed class CollectionRef(AccessTools.FieldRef fieldRef) : ICleara } private delegate void ClearInvoker(ref T collectionRef); -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/GeneTrackerOptimization.cs b/Source/PerformanceFish/GeneTrackerOptimization.cs index 88ca881..b5702cb 100644 --- a/Source/PerformanceFish/GeneTrackerOptimization.cs +++ b/Source/PerformanceFish/GeneTrackerOptimization.cs @@ -61,7 +61,15 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Dirty(Pawn_GeneTracker instance, List genesToTick) +#if V1_6 + { + ref var versions = ref instance.GeneListVersions(); + return !versions.Initialized || versions.Xenogenes != instance.xenogenes._version + || versions.Endogenes != instance.endogenes._version; + } +#else => GetListVersion(instance) != genesToTick._version; +#endif [MethodImpl(MethodImplOptions.NoInlining)] public static void Update(Pawn_GeneTracker instance, List genesToTick) @@ -71,12 +79,21 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches AddTickableGenes(genesToTick, instance.xenogenes); AddTickableGenes(genesToTick, instance.endogenes); +#if V1_6 + ref var versions = ref instance.GeneListVersions(); + versions.Xenogenes = instance.xenogenes._version; + versions.Endogenes = instance.endogenes._version; + versions.Initialized = true; +#else genesToTick._version = GetListVersion(instance); +#endif } +#if !V1_6 [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int GetListVersion(Pawn_GeneTracker instance) => (instance.xenogenes._version << 16) | (instance.endogenes._version & 0b1111_1111_1111_1111); +#endif private static void AddTickableGenes(List genesToTick, List genes) { @@ -115,6 +132,15 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches } } +#if V1_6 + public struct GeneListVersionState + { + public int Xenogenes; + public int Endogenes; + public bool Initialized; + } +#endif + public sealed class GeneTrackerTickIntervalPatch : FishPrepatch { // i am iffy about this diff --git a/Source/PerformanceFish/GetCompCaching.cs b/Source/PerformanceFish/GetCompCaching.cs index 20ebb5a..1630d31 100644 --- a/Source/PerformanceFish/GetCompCaching.cs +++ b/Source/PerformanceFish/GetCompCaching.cs @@ -53,11 +53,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches public record struct CacheValue() where T : ThingComp { + private List? _listReference; private int _listVersion = -2; public T? Comp; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsDirty(List comps) => comps._version != _listVersion; + public bool IsDirty(List comps) + => !ReferenceEquals(comps, _listReference) || comps._version != _listVersion; [MethodImpl(MethodImplOptions.NoInlining)] public void Update(ThingWithComps thing, T? comp) @@ -65,6 +67,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches if (thing.thingIDNumber < 0) return; + _listReference = thing.comps; _listVersion = thing.comps._version; Comp = comp; } @@ -113,11 +116,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches public record struct CacheValue() where T : CompProperties { + private List? _listReference; private int _listVersion = -2; public T? Comp; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsDirty(List comps) => comps._version != _listVersion; + public bool IsDirty(List comps) + => !ReferenceEquals(comps, _listReference) || comps._version != _listVersion; [MethodImpl(MethodImplOptions.NoInlining)] public void Update(ThingDef thingDef, T? comp) @@ -125,6 +130,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches if (thingDef.shortHash == default) return; + _listReference = thingDef.comps; _listVersion = thingDef.comps._version; Comp = comp; } @@ -174,11 +180,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches public record struct CacheValue() { + private List? _listReference; private int _listVersion = -2; public bool HasComp; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsDirty(List comps) => comps._version != _listVersion; + public bool IsDirty(List comps) + => !ReferenceEquals(comps, _listReference) || comps._version != _listVersion; [MethodImpl(MethodImplOptions.NoInlining)] public void Update(ThingDef thingDef, bool hasComp) @@ -186,6 +194,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches if (thingDef.shortHash == default) return; + _listReference = thingDef.comps; _listVersion = thingDef.comps._version; HasComp = hasComp; } @@ -236,11 +245,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches public record struct CacheValue() where T : ThingComp { + private List? _listReference; private int _listVersion = -2; public bool HasComp; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsDirty(List comps) => comps._version != _listVersion; + public bool IsDirty(List comps) + => !ReferenceEquals(comps, _listReference) || comps._version != _listVersion; [MethodImpl(MethodImplOptions.NoInlining)] public void Update(ThingDef thingDef, bool hasComp) @@ -248,6 +259,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches if (thingDef.shortHash == default) return; + _listReference = thingDef.comps; _listVersion = thingDef.comps._version; HasComp = hasComp; } @@ -296,11 +308,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches public record struct CacheValue() where T : HediffComp { + private List? _listReference; private int _listVersion = -2; public T? Comp; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsDirty(List comps) => comps._version != _listVersion; + public bool IsDirty(List comps) + => !ReferenceEquals(comps, _listReference) || comps._version != _listVersion; [MethodImpl(MethodImplOptions.NoInlining)] public void Update(HediffWithComps hediff, T? comp) @@ -308,6 +322,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches if (hediff.loadID < 0) return; + _listReference = hediff.comps; _listVersion = hediff.comps._version; Comp = comp; } @@ -355,11 +370,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches public record struct CacheValue() where T : HediffCompProperties { + private List? _listReference; private int _listVersion = -2; public T? Comp; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsDirty(List comps) => comps._version != _listVersion; + public bool IsDirty(List comps) + => !ReferenceEquals(comps, _listReference) || comps._version != _listVersion; [MethodImpl(MethodImplOptions.NoInlining)] public void Update(HediffDef hediffDef, T? comp) @@ -367,6 +384,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches if (hediffDef.shortHash == 0) return; + _listReference = hediffDef.comps; _listVersion = hediffDef.comps._version; Comp = comp; } @@ -414,11 +432,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches public record struct CacheValue() where T : AbilityComp { + private List? _listReference; private int _listVersion = -2; public T? Comp; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsDirty(List comps) => comps._version != _listVersion; + public bool IsDirty(List comps) + => !ReferenceEquals(comps, _listReference) || comps._version != _listVersion; [MethodImpl(MethodImplOptions.NoInlining)] public void Update(Ability ability, T? comp) @@ -426,6 +446,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches if (ability.Id < 0) return; + _listReference = ability.comps; _listVersion = ability.comps._version; Comp = comp; } @@ -468,11 +489,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches public record struct CacheValue() where T : WorldObjectComp { + private List? _listReference; private int _listVersion = -2; public T? Comp; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsDirty(List comps) => comps._version != _listVersion; + public bool IsDirty(List comps) + => !ReferenceEquals(comps, _listReference) || comps._version != _listVersion; [MethodImpl(MethodImplOptions.NoInlining)] public void Update(WorldObject worldObject, T? comp) @@ -480,6 +503,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches if (worldObject.ID < 0) return; + _listReference = worldObject.comps; _listVersion = worldObject.comps._version; Comp = comp; } @@ -522,11 +546,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches public record struct CacheValue() where T : MapComponent { + private List? _listReference; private int _listVersion = -2; public T? Comp; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsDirty(List comps) => comps._version != _listVersion; + public bool IsDirty(List comps) + => !ReferenceEquals(comps, _listReference) || comps._version != _listVersion; [MethodImpl(MethodImplOptions.NoInlining)] public void Update(Map map, T? comp) @@ -534,6 +560,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches if (map.uniqueID < 0) return; + _listReference = map.components; _listVersion = map.components._version; Comp = comp; } @@ -641,4 +668,4 @@ public sealed class GetCompCaching : ClassWithFishPrepatches } } } -} \ No newline at end of file +} diff --git a/Source/PerformanceFish/NeedsTrackerCaching.cs b/Source/PerformanceFish/NeedsTrackerCaching.cs index df4f8ff..f7bac6f 100644 --- a/Source/PerformanceFish/NeedsTrackerCaching.cs +++ b/Source/PerformanceFish/NeedsTrackerCaching.cs @@ -16,6 +16,8 @@ public sealed class NeedsTrackerCaching : ClassWithFishPrepatches { public sealed class TryGetNeedPatch : FishPrepatch { + public override bool DefaultState => true; + public override string? Description { get; } = "Caches generic need lookups behind the needs list version."; @@ -30,6 +32,8 @@ public sealed class NeedsTrackerCaching : ClassWithFishPrepatches public sealed class TryGetNeedOutPatch : FishPrepatch { + public override bool DefaultState => true; + public override string? Description { get; } = "Caches generic need lookups behind the needs list version."; @@ -55,18 +59,18 @@ public sealed class NeedsTrackerCaching : ClassWithFishPrepatches return cache.Found; } - return UpdateCache(instance, typeof(T), out need, ref cache); + return UpdateCache(instance, out need, ref cache); } [MethodImpl(MethodImplOptions.NoInlining)] - private static bool UpdateCache(Pawn_NeedsTracker instance, Type needType, out T? need, ref CacheValue cache) + private static bool UpdateCache(Pawn_NeedsTracker instance, out T? need, ref CacheValue cache) where T : Need { var needs = instance.needs; for (var i = 0; i < needs.Count; i++) { var candidate = needs[i]; - if (candidate.GetType() != needType) + if (candidate.GetType() != typeof(T)) continue; need = (T)candidate; diff --git a/Source/PerformanceFish/OnAssembliesLoaded.cs b/Source/PerformanceFish/OnAssembliesLoaded.cs index 48d0b40..911cf03 100644 --- a/Source/PerformanceFish/OnAssembliesLoaded.cs +++ b/Source/PerformanceFish/OnAssembliesLoaded.cs @@ -39,7 +39,7 @@ public static class OnAssembliesLoaded PerformanceFishMod.AllPatchClasses = PerformanceFishMod.InitializeAllPatchClasses(); PerformanceFishMod.AllPrepatchClasses ??= PerformanceFishMod.InitializeAllPatchClasses(); - TryInitialize(AnalyzerFixes.Patch, true); + TryInitialize(AnalyzerFixes.Patch); _ = FishSettings.Instance; @@ -109,14 +109,11 @@ public static class OnAssembliesLoaded } } - private static async void TryInitialize(Action action, bool async = false) + private static void TryInitialize(Action action) { try { - if (async) - await Task.Run(action).ConfigureAwait(false); - else - action(); + action(); } catch (Exception ex) { diff --git a/Source/PerformanceFish/Patching/FishPatch.cs b/Source/PerformanceFish/Patching/FishPatch.cs index 84d3ae5..e6aa2e5 100644 --- a/Source/PerformanceFish/Patching/FishPatch.cs +++ b/Source/PerformanceFish/Patching/FishPatch.cs @@ -90,6 +90,7 @@ public abstract class FishPatch : SingletonFactory, IExposable, IHasD public List BenchmarkHarmonyMethodInfos { get; } = []; private bool Patched { get; set; } private bool ShouldBePatched { get; set; } + private readonly HashSet _patchedTargets = []; public virtual string? Name => null; @@ -190,15 +191,16 @@ public abstract class FishPatch : SingletonFactory, IExposable, IHasD protected virtual void ApplyPatch() { - Patched = true; - foreach (var method in TargetMethodInfos) + var targets = TargetMethodInfos.ToArray(); + foreach (var method in targets) { + if (_patchedTargets.Contains(method)) + continue; + try { if (IsBenchmarking) { - Patched = false; - if (AccessTools.EnumeratorMoveNext(method) is { } moveNext) { BenchmarkHarmonyMethodInfos.Add(PerformanceFishMod.Harmony!.Patch(moveNext, @@ -229,6 +231,7 @@ public abstract class FishPatch : SingletonFactory, IExposable, IHasD transpiler: TryMakeHarmonyMethod(TranspilerMethodInfo, TranspilerMethodPriority), finalizer: TryMakeHarmonyMethod(FinalizerMethodInfo, FinalizerMethodPriority))); } + _patchedTargets.Add(method); } } @@ -240,6 +243,7 @@ public abstract class FishPatch : SingletonFactory, IExposable, IHasD FinalizerMethodInfo.FullDescription()}:\n{e}"); } } + Patched = !IsBenchmarking && targets.All(_patchedTargets.Contains); } private static HarmonyMethod? TryMakeHarmonyMethod(MethodInfo? method, int priority = -1, string[]? before = null, @@ -249,7 +253,7 @@ public abstract class FishPatch : SingletonFactory, IExposable, IHasD public virtual void TryUnpatch() { ShouldBePatched = false; - if ( /*!Enabled &&*/ Patched) + if (Patched || _patchedTargets.Count != 0) ApplyUnpatching(); } @@ -269,6 +273,7 @@ public abstract class FishPatch : SingletonFactory, IExposable, IHasD }); } } + _patchedTargets.Clear(); } private static void TryUnpatchMethod(MethodBase targetMethod, MethodInfo? patchMethod) diff --git a/Source/PerformanceFish/Planet/WorldObjectsOptimization.cs b/Source/PerformanceFish/Planet/WorldObjectsOptimization.cs index eefb747..b746a5b 100644 --- a/Source/PerformanceFish/Planet/WorldObjectsOptimization.cs +++ b/Source/PerformanceFish/Planet/WorldObjectsOptimization.cs @@ -15,16 +15,21 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches { public sealed class WorldObjectsHolderTickPatch : FishPrepatch { + public override bool DefaultState => true; + private static FishTable _skippableCompTypes = null!; private static FishTable _skippableWorldObjectTypes = null!; private static readonly List _tmpThingHolders = []; + private static readonly List _skippedWorldObjects = []; + private static List? _cachedWorldObjectsReference; + private static List? _cachedMapsReference; + + private readonly record struct SkippedWorldObject(WorldObject WorldObject, + List Comps, int CompsVersion, bool HasEnterCooldownComp); public override string? Description { get; } - = "The world objects holder is responsible for ticking every world object. This includes settlements, " - + "caravans, outposts and any other object placed in the world, instead of maps. Normally it ticks " - + "everything equally, including many static objects that cannot possibly affect anything from a tick. " - + "This patch improves the world objects holder to determine objects that need ticking in advance, cache " - + "the list of them, and only tick those, skipping all the others."; + = "Caches the world objects that need ticking and skips objects with no active tick behavior. " + + "Objects with changing contents or components are rechecked before each tick."; public override MethodBase TargetMethodBase { get; } = AccessTools.Method(typeof(WorldObjectsHolder), nameof(WorldObjectsHolder.WorldObjectsHolderTick)); @@ -38,7 +43,7 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches UpdateCache(instance); var worldObjects = WorldObjectsHolder.tmpWorldObjects; - for (var i = worldObjects.Count; i-- > 0;) + for (var i = 0; i < worldObjects.Count; i++) #if V1_6 worldObjects[i].DoTick(); #else @@ -50,18 +55,23 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches { var staticWorldObjects = WorldObjectsHolder.tmpWorldObjects; staticWorldObjects.Clear(); + _skippedWorldObjects.Clear(); var instanceWorldObjects = instance.worldObjects; - for (var i = instanceWorldObjects.Count; i-- > 0;) + for (var i = 0; i < instanceWorldObjects.Count; i++) { var worldObject = instanceWorldObjects[i]; if (worldObject is not MapParent { HasMap: true } && CanSkipWorldObjectTick(worldObject) - && (worldObject is not Settlement settlement || settlement.trader?.stock is null) && CanSkipCompTick(worldObject)) { + var comps = worldObject.comps; + var hasEnterCooldownComp = false; + for (var j = 0; j < comps.Count; j++) + hasEnterCooldownComp |= comps[j] is EnterCooldownComp; + _skippedWorldObjects.Add(new(worldObject, comps, comps._version, hasEnterCooldownComp)); continue; } @@ -70,6 +80,8 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches CachedWorldObjectsVersion = instanceWorldObjects._version; CachedMapsVersion = Current.gameInt.maps._version; + _cachedWorldObjectsReference = instanceWorldObjects; + _cachedMapsReference = Current.gameInt.maps; } private static bool CanSkipWorldObjectTick(WorldObject worldObject) @@ -79,31 +91,30 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches private static bool HasTickableContents(WorldObject worldObject) { - if (worldObject is not IThingHolder holder) + if (worldObject is not IThingHolder holder + || worldObject is IThingHolderTickable { ShouldTickContents: false }) return false; lock (_tmpThingHolders) { _tmpThingHolders.Clear(); - _tmpThingHolders.Add(holder); - holder.GetChildHolders(_tmpThingHolders); - - for (var i = 0; i < _tmpThingHolders.Count; i++) + try { - var thingHolder = _tmpThingHolders[i]; - if (thingHolder is IThingHolderTickable tickable && !tickable.ShouldTickContents) - continue; - - var directlyHeldThings = thingHolder.GetDirectlyHeldThings(); - if (directlyHeldThings?.Owner is not null and not Map and not Caravan) + _tmpThingHolders.Add(holder); + holder.GetChildHolders(_tmpThingHolders); + for (var i = 0; i < _tmpThingHolders.Count; i++) { - _tmpThingHolders.Clear(); - return true; + var heldThings = _tmpThingHolders[i].GetDirectlyHeldThings(); + if (heldThings is not null && heldThings.Owner is not Map and not Caravan) + return true; } - } - _tmpThingHolders.Clear(); - return false; + return false; + } + finally + { + _tmpThingHolders.Clear(); + } } } @@ -137,10 +148,9 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches ], _whitelistedWorldObjectTypes = [ + // Settlement.TickInterval checks trader and defeat state even without a map. typeof(WorldObject), - typeof(MapParent), - typeof(Settlement), - ModCompatibility.Types.RealRuins.POIWorldObject + typeof(MapParent) ]; public static void AddCompToWhiteList(Type compType) @@ -211,8 +221,31 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches } public static bool CacheDirty(WorldObjectsHolder instance) - => CachedWorldObjectsVersion != instance.worldObjects._version - || CachedMapsVersion != Current.gameInt.maps._version; + { + if (!ReferenceEquals(_cachedWorldObjectsReference, instance.worldObjects) + || CachedWorldObjectsVersion != instance.worldObjects._version + || !ReferenceEquals(_cachedMapsReference, Current.gameInt.maps) + || CachedMapsVersion != Current.gameInt.maps._version) + return true; + + for (var i = 0; i < _skippedWorldObjects.Count; i++) + { + var skipped = _skippedWorldObjects[i]; + var worldObject = skipped.WorldObject; + var comps = worldObject.comps; + if (!ReferenceEquals(comps, skipped.Comps) || comps._version != skipped.CompsVersion + || worldObject is MapParent { HasMap: true } + || HasTickableContents(worldObject)) + return true; + + if (skipped.HasEnterCooldownComp) + for (var j = 0; j < comps.Count; j++) + if (comps[j] is EnterCooldownComp { Active: true }) + return true; + } + + return false; + } public static void SetDirty() => CachedWorldObjectsVersion = CachedMapsVersion = -2; diff --git a/Source/PerformanceFish/PrepatcherFields.cs b/Source/PerformanceFish/PrepatcherFields.cs index dd19b40..409f526 100644 --- a/Source/PerformanceFish/PrepatcherFields.cs +++ b/Source/PerformanceFish/PrepatcherFields.cs @@ -35,6 +35,12 @@ public static class PrepatcherFields [MethodImpl(MethodImplOptions.AggressiveInlining)] public static extern List GenesToTick(this Pawn_GeneTracker geneTracker); +#if V1_6 + [PrepatcherField] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static extern ref GeneTrackerOptimization.GeneListVersionState GeneListVersions(this Pawn_GeneTracker geneTracker); +#endif + [PrepatcherField] [ValueInitializer(nameof(CreateWorldPawnsCache))] [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Source/PerformanceFish/Rendering/PawnRenderingOptimizations.cs b/Source/PerformanceFish/Rendering/PawnRenderingOptimizations.cs index 7637478..e243764 100644 --- a/Source/PerformanceFish/Rendering/PawnRenderingOptimizations.cs +++ b/Source/PerformanceFish/Rendering/PawnRenderingOptimizations.cs @@ -15,6 +15,8 @@ public sealed class PawnRenderingOptimizations : ClassWithFishPrepatches public sealed class IsHiddenFromPlayerPatch : FishPrepatch { + public override bool DefaultState => true; + // not 100% on this even benefitting, bionics, scars, etc likely worsen performance here because of the extra check // probably better to just track if a hediff adds invisibility public override string? Description { get; } @@ -53,6 +55,8 @@ public sealed class PawnRenderingOptimizations : ClassWithFishPrepatches public sealed class PawnRenderTreeComputeMatrixPatch : FishPrepatch { + public override bool DefaultState => true; + public override string? Description { get; } = "Avoids Matrix4x4 inverse work when undoing a pivot translation in PawnRenderTree.ComputeMatrix."; @@ -85,6 +89,8 @@ public sealed class PawnRenderingOptimizations : ClassWithFishPrepatches public sealed class DamageFlasherCurColorPatch : FishPrepatch { + public override bool DefaultState => true; + private static readonly AccessTools.FieldRef _lastDamageTick = AccessTools.FieldRefAccess("lastDamageTick"); @@ -110,6 +116,8 @@ public sealed class PawnRenderingOptimizations : ClassWithFishPrepatches public sealed class DamageFlasherGetDamagedMatPatch : FishPrepatch { + public override bool DefaultState => true; + private static readonly AccessTools.FieldRef _lastDamageTick = AccessTools.FieldRefAccess("lastDamageTick"); diff --git a/Source/PerformanceFish/System/ReflectionCaching.cs b/Source/PerformanceFish/System/ReflectionCaching.cs index a725945..d929674 100644 --- a/Source/PerformanceFish/System/ReflectionCaching.cs +++ b/Source/PerformanceFish/System/ReflectionCaching.cs @@ -535,6 +535,13 @@ public sealed class ReflectionCaching : ClassWithFishPatches public static bool Prefix(ICustomAttributeProvider obj, Type attributeType, bool inherit, out object[]? __result, out CustomAttributeState __state) { + if (obj is null || attributeType is null) + { + __state = default; + __result = null; + return true; + } + var key = new CustomAttributeCache(obj, attributeType.TypeHandle, inherit); __state = new() { Key = key }; @@ -656,7 +663,9 @@ public sealed class ReflectionCaching : ClassWithFishPatches { var value = new CustomAttributeCacheValue { - Attributes = CopyCachedAttributes(result), + Attributes = result.Length == 0 + ? (object[])Array.CreateInstance(Type.GetTypeFromHandle(key.Second), 0) + : CopyCachedAttributes(result), Status = CustomAttributeCacheStatus.Completed }; @@ -693,7 +702,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches [MethodImpl(MethodImplOptions.AggressiveInlining)] private static object[] CopyCachedAttributes(object[] attributes) - => attributes.Length != 0 ? (object[])attributes.Clone() : Array.Empty(); + => attributes.Length != 0 ? (object[])attributes.Clone() : attributes; private static object _lock = new(); } @@ -1024,8 +1033,6 @@ public sealed class ReflectionCaching : ClassWithFishPatches [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Prefix(FieldInfo __instance, object? obj, ref object? __result) { - ValidateGetValueCall(__instance, obj); - ref var cache = ref FieldGetters.GetOrAddReference(__instance.FieldHandle); if (cache.Dirty @@ -1035,6 +1042,8 @@ public sealed class ReflectionCaching : ClassWithFishPatches return true; } + // Unsupported fields fall back to Mono's implementation, including its exception behavior. + ValidateGetValueCall(__instance, obj); __result = cache.Result!(obj); return false; }