Compare commits
4 Commits
3199c1a85e
...
e1a79fc0b0
| Author | SHA1 | Date | |
|---|---|---|---|
|
e1a79fc0b0
|
|||
|
ff3119a810
|
|||
|
9d31b36805
|
|||
|
227dcd173f
|
Binary file not shown.
Binary file not shown.
+3
-3
@@ -7,9 +7,9 @@
|
||||
<li>1.5</li>
|
||||
<li>1.6</li>
|
||||
</supportedVersions>
|
||||
<author>bradson</author>
|
||||
<author>bradson, localavaster, Lionel Colaso, Riggs0</author>
|
||||
<modIconPath IgnoreIfNoMatchingField="True">PerformanceFish/ModIcon</modIconPath>
|
||||
<url>https://github.com/bbradson/performance-fish</url>
|
||||
<url>hhttps://git.hudsonriggs.systems/HRiggs/Performance-Fish</url>
|
||||
<loadBefore>
|
||||
<li>Dubwise.DubsPerformanceAnalyzer</li>
|
||||
<li>Dubwise.DubsPerformanceAnalyzerButchered</li>
|
||||
@@ -46,7 +46,7 @@
|
||||
<packageId>bs.fishery</packageId>
|
||||
<displayName>Fishery</displayName>
|
||||
<steamWorkshopUrl>n/a</steamWorkshopUrl>
|
||||
<downloadUrl>https://github.com/bbradson/Fishery</downloadUrl>
|
||||
<downloadUrl>https://git.hudsonriggs.systems/HRiggs/Fishery</downloadUrl>
|
||||
</li>
|
||||
</modDependencies>
|
||||
<incompatibleWith>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
@@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<OutDir>..\..\1.6\Assemblies\</OutDir>
|
||||
<Copyright>Copyright (c) 2026 Bradson</Copyright>
|
||||
<PolySharpUseEmbeddedAttributeForGeneratedTypes>false</PolySharpUseEmbeddedAttributeForGeneratedTypes>
|
||||
<FisheryRepoRoot Condition="Exists('$(MSBuildThisFileDirectory)..\..\..\Fishery\1.6\Assemblies\1Fishery.dll')">$(MSBuildThisFileDirectory)..\..\..\Fishery</FisheryRepoRoot>
|
||||
<FisheryRepoRoot Condition="'$(FisheryRepoRoot)' == '' and Exists('$(MSBuildThisFileDirectory)..\..\..\Fishery-clean\1.6\Assemblies\1Fishery.dll')">$(MSBuildThisFileDirectory)..\..\..\Fishery-clean</FisheryRepoRoot>
|
||||
</PropertyGroup>
|
||||
@@ -13,8 +14,13 @@
|
||||
<DefineConstants>V1_5;V1_6</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.6.*" />
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net48" Version="1.0.3" PrivateAssets="all" />
|
||||
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.6.4871" />
|
||||
<PackageReference Include="Lib.Harmony" Version="2.3.3" ExcludeAssets="runtime" />
|
||||
<PackageReference Update="Zetrith.Prepatcher" Version="1.2.0" />
|
||||
<PackageReference Update="PolySharp" Version="1.16.0" />
|
||||
<PackageReference Update="Krafs.Publicizer" Version="2.3.2" />
|
||||
<PackageReference Update="JetBrains.Annotations" Version="2023.3.0" />
|
||||
<Reference Include="1Fishery">
|
||||
<HintPath>$(FisheryRepoRoot)\1.6\Assemblies\1Fishery.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2026
|
||||
// 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 V1_6
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public sealed class AbilityTrackerTickOptimization : ClassWithFishPrepatches
|
||||
{
|
||||
public sealed class AbilitiesTickPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Reads the pawn ability list once per ability tick instead of twice, while still observing list changes between abilities.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(Pawn_AbilityTracker), nameof(Pawn_AbilityTracker.AbilitiesTick));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(AbilitiesTick);
|
||||
|
||||
public static void AbilitiesTick(Pawn_AbilityTracker instance)
|
||||
{
|
||||
for (var i = 0;; i++)
|
||||
{
|
||||
var abilities = instance.AllAbilitiesForReading;
|
||||
if (i >= abilities.Count)
|
||||
return;
|
||||
|
||||
abilities[i].AbilityTick();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -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<T>(AccessTools.FieldRef<T> fieldRef) : ICleara
|
||||
}
|
||||
|
||||
private delegate void ClearInvoker(ref T collectionRef);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -61,7 +61,15 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Dirty(Pawn_GeneTracker instance, List<Gene> 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<Gene> 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<Gene> genesToTick, List<Gene> 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
|
||||
|
||||
@@ -53,11 +53,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
||||
|
||||
public record struct CacheValue<T>() where T : ThingComp
|
||||
{
|
||||
private List<ThingComp>? _listReference;
|
||||
private int _listVersion = -2;
|
||||
public T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<ThingComp> comps) => comps._version != _listVersion;
|
||||
public bool IsDirty(List<ThingComp> 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<T>() where T : CompProperties
|
||||
{
|
||||
private List<CompProperties>? _listReference;
|
||||
private int _listVersion = -2;
|
||||
public T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<CompProperties> comps) => comps._version != _listVersion;
|
||||
public bool IsDirty(List<CompProperties> 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<CompProperties>? _listReference;
|
||||
private int _listVersion = -2;
|
||||
public bool HasComp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<CompProperties> comps) => comps._version != _listVersion;
|
||||
public bool IsDirty(List<CompProperties> 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<T>() where T : ThingComp
|
||||
{
|
||||
private List<CompProperties>? _listReference;
|
||||
private int _listVersion = -2;
|
||||
public bool HasComp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<CompProperties> comps) => comps._version != _listVersion;
|
||||
public bool IsDirty(List<CompProperties> 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<T>() where T : HediffComp
|
||||
{
|
||||
private List<HediffComp>? _listReference;
|
||||
private int _listVersion = -2;
|
||||
public T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<HediffComp> comps) => comps._version != _listVersion;
|
||||
public bool IsDirty(List<HediffComp> 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<T>() where T : HediffCompProperties
|
||||
{
|
||||
private List<HediffCompProperties>? _listReference;
|
||||
private int _listVersion = -2;
|
||||
public T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<HediffCompProperties> comps) => comps._version != _listVersion;
|
||||
public bool IsDirty(List<HediffCompProperties> 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<T>() where T : AbilityComp
|
||||
{
|
||||
private List<AbilityComp>? _listReference;
|
||||
private int _listVersion = -2;
|
||||
public T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<AbilityComp> comps) => comps._version != _listVersion;
|
||||
public bool IsDirty(List<AbilityComp> 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<T>() where T : WorldObjectComp
|
||||
{
|
||||
private List<WorldObjectComp>? _listReference;
|
||||
private int _listVersion = -2;
|
||||
public T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<WorldObjectComp> comps) => comps._version != _listVersion;
|
||||
public bool IsDirty(List<WorldObjectComp> 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<T>() where T : MapComponent
|
||||
{
|
||||
private List<MapComponent>? _listReference;
|
||||
private int _listVersion = -2;
|
||||
public T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<MapComponent> comps) => comps._version != _listVersion;
|
||||
public bool IsDirty(List<MapComponent> 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T>(Pawn_NeedsTracker instance, Type needType, out T? need, ref CacheValue cache)
|
||||
private static bool UpdateCache<T>(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;
|
||||
|
||||
@@ -39,7 +39,7 @@ public static class OnAssembliesLoaded
|
||||
PerformanceFishMod.AllPatchClasses = PerformanceFishMod.InitializeAllPatchClasses<IHasFishPatch>();
|
||||
PerformanceFishMod.AllPrepatchClasses ??= PerformanceFishMod.InitializeAllPatchClasses<ClassWithFishPrepatches>();
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -90,6 +90,7 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasD
|
||||
public List<MethodInfo> BenchmarkHarmonyMethodInfos { get; } = [];
|
||||
private bool Patched { get; set; }
|
||||
private bool ShouldBePatched { get; set; }
|
||||
private readonly HashSet<MethodBase> _patchedTargets = [];
|
||||
|
||||
public virtual string? Name => null;
|
||||
|
||||
@@ -190,15 +191,16 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, 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<FishPatch>, IExposable, IHasD
|
||||
transpiler: TryMakeHarmonyMethod(TranspilerMethodInfo, TranspilerMethodPriority),
|
||||
finalizer: TryMakeHarmonyMethod(FinalizerMethodInfo, FinalizerMethodPriority)));
|
||||
}
|
||||
_patchedTargets.Add(method);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -240,6 +243,7 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, 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<FishPatch>, 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<FishPatch>, IExposable, IHasD
|
||||
});
|
||||
}
|
||||
}
|
||||
_patchedTargets.Clear();
|
||||
}
|
||||
|
||||
private static void TryUnpatchMethod(MethodBase targetMethod, MethodInfo? patchMethod)
|
||||
|
||||
@@ -15,16 +15,21 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
|
||||
{
|
||||
public sealed class WorldObjectsHolderTickPatch : FishPrepatch
|
||||
{
|
||||
public override bool DefaultState => true;
|
||||
|
||||
private static FishTable<Type, bool> _skippableCompTypes = null!;
|
||||
private static FishTable<Type, bool> _skippableWorldObjectTypes = null!;
|
||||
private static readonly List<IThingHolder> _tmpThingHolders = [];
|
||||
private static readonly List<SkippedWorldObject> _skippedWorldObjects = [];
|
||||
private static List<WorldObject>? _cachedWorldObjectsReference;
|
||||
private static List<Map>? _cachedMapsReference;
|
||||
|
||||
private readonly record struct SkippedWorldObject(WorldObject WorldObject,
|
||||
List<WorldObjectComp> 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;
|
||||
|
||||
|
||||
@@ -35,6 +35,16 @@ public static class PrepatcherFields
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static extern List<Gene> GenesToTick(this Pawn_GeneTracker geneTracker);
|
||||
|
||||
#if V1_6
|
||||
[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]
|
||||
[ValueInitializer(nameof(CreateWorldPawnsCache))]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -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<DamageFlasher, int> _lastDamageTick
|
||||
= AccessTools.FieldRefAccess<DamageFlasher, int>("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<DamageFlasher, int> _lastDamageTick
|
||||
= AccessTools.FieldRefAccess<DamageFlasher, int>("lastDamageTick");
|
||||
|
||||
|
||||
@@ -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<object>();
|
||||
=> 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user