diff --git a/1.4/Assemblies/PerformanceFish.dll b/1.4/Assemblies/PerformanceFish.dll
index 369aadb..ef9e5bc 100644
Binary files a/1.4/Assemblies/PerformanceFish.dll and b/1.4/Assemblies/PerformanceFish.dll differ
diff --git a/1.4/Assemblies/PerformanceFish.pdb b/1.4/Assemblies/PerformanceFish.pdb
index 0967816..6f1bd9b 100644
Binary files a/1.4/Assemblies/PerformanceFish.pdb and b/1.4/Assemblies/PerformanceFish.pdb differ
diff --git a/DubsAnalyzer.xml b/DubsAnalyzer.xml
index be4f39f..ac34f3e 100644
--- a/DubsAnalyzer.xml
+++ b/DubsAnalyzer.xml
@@ -118,36 +118,6 @@
-
- Tick methods of comps attached to Things. Any ThingWithComps can have these, and any mod can add new ones
- Tick
-
- Verse.ThingComp:CompTick
- Verse.ThingComp:CompTickRare
- Verse.ThingComp:CompTickLong
-
-
-
-
- Update methods of comps attached to Things. Any ThingWithComps can have these, and any mod can add new ones
- Update
-
- Verse.ThingComp:DrawGUIOverlay
- Verse.ThingComp:PostDraw
- Verse.ThingComp:PostDrawExtraSelectionOverlays
- Verse.ThingComp:PostPrintOnto
-
-
-
-
- Ticking health stuff ond other things implanted in pawn bodies
- Tick
-
- Verse.Hediff:Tick
- Verse.Hediff:PostTick
-
-
-
Ticking methods of abilities. Cooldown, effects, combat
Tick
@@ -157,16 +127,16 @@
-
- Ticking methods of precepts. Ideology stuff.
+
+ Comps attached to abilities
Tick
- RimWorld.Precept:Tick
+ RimWorld.AbilityComp:CompTick
-
+
- Ticking methods of genes. Yes, all the genes tick.
+ Ticking methods of genes. Often empty when not having any ticking genes
Tick
Verse.Gene:Tick
@@ -181,45 +151,14 @@
-
- Comps attached to abilities
+
+ Ticking health stuff ond other things implanted in pawn bodies
Tick
- RimWorld.AbilityComp:CompTick
+ Verse.Hediff:Tick
+ Verse.Hediff:PostTick
-
-
-
- Settlements, caravans etc
- Tick
-
- RimWorld.Planet.WorldObject:Tick
-
-
-
-
- Comps attached to settlements, caravans etc
- Tick
-
- RimWorld.Planet.WorldObjectComp:CompTick
-
-
-
-
- Tick methods of comps with one instance attached to the game world
- Tick
-
- RimWorld.Planet.WorldComponent:WorldComponentTick
-
-
-
-
- Update methods of comps with one instance attached to the game world
- Update
-
- RimWorld.Planet.WorldComponent:WorldComponentUpdate
-
-
+
Tick methods of mental states like berserk, tantrum etc
@@ -229,4 +168,73 @@
+
+ Ticking methods of precepts. Ideology stuff.
+ Tick
+
+ RimWorld.Precept:Tick
+
+
+
+
+ Tick methods ideology ritual triggers. Can scale quite badly when having a lot of precepts loaded
+ Tick
+
+ RimWorld.RitualObligationTrigger:Tick
+
+
+
+
+ Tick methods of comps attached to Things. Any ThingWithComps can have these, and any mod can add new ones
+ Tick
+
+ Verse.ThingComp:CompTick
+ Verse.ThingComp:CompTickRare
+ Verse.ThingComp:CompTickLong
+
+
+
+
+ Tick methods of comps with one instance attached to the game world
+ Tick
+
+ RimWorld.Planet.WorldComponent:WorldComponentTick
+
+
+
+
+ Comps attached to settlements, caravans etc
+ Tick
+
+ RimWorld.Planet.WorldObjectComp:CompTick
+
+
+
+
+ Settlements, caravans etc
+ Tick
+
+ RimWorld.Planet.WorldObject:Tick
+
+
+
+
+ Update methods of comps attached to Things. Any ThingWithComps can have these, and any mod can add new ones
+ Update
+
+ Verse.ThingComp:DrawGUIOverlay
+ Verse.ThingComp:PostDraw
+ Verse.ThingComp:PostDrawExtraSelectionOverlays
+ Verse.ThingComp:PostPrintOnto
+
+
+
+
+ Update methods of comps with one instance attached to the game world
+ Update
+
+ RimWorld.Planet.WorldComponent:WorldComponentUpdate
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 74dc654..ab887a5 100644
--- a/README.md
+++ b/README.md
@@ -9,8 +9,8 @@ Performance Fish attempts to improve overall framerates and tick times by patchi
A settings menu includes short descriptions for every patch, and each of them can be freely toggled in there. They get entirely unpatched in a disabled state, immediately. Nothing is stored in savefiles of specific game-sessions, ensuring that no errors get thrown when removing this mod.
Special patches exist to add new entries and features to Dub's Performance Analyzer, including a right-click function allowing to profile overrides of functions.
-Almost all mods, including Combat Extended, Vanilla Expanded, RocketMan and Performance Optimizer, are compatible.
-Multiplayer needs testing.
+Almost all mods, including Combat Extended, Multiplayer, Vanilla Expanded, RocketMan and Performance Optimizer, are compatible.
+
RimThreaded, RimWorld Rick, Oskar Obnoxious, No Laggy Beds and Better GC are currently marked as incompatible.
Licensed under [MPL-2.0](https://tldrlegal.com/license/mozilla-public-license-2.0-(mpl-2))
\ No newline at end of file
diff --git a/Source/PerformanceFish/Cache/ByInt.cs b/Source/PerformanceFish/Cache/ByInt.cs
index 165589d..9aca693 100644
--- a/Source/PerformanceFish/Cache/ByInt.cs
+++ b/Source/PerformanceFish/Cache/ByInt.cs
@@ -25,6 +25,8 @@ public record struct ByInt where T : notnull where TResult : new()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => Key;
+
+ private static List, TResult>> _allCaches = [];
private static object _valueInitializerLock = new();
@@ -92,23 +94,65 @@ public record struct ByInt where T : notnull where TResult : new()
public static ref TResult GetExistingReference(T key) => ref Get.GetReference(new(key));
[MethodImpl(MethodImplOptions.NoInlining)]
- private static FishTable, TResult> InitializeNew() => Utility.AddNew(ValueInitializer);
+ private static FishTable, TResult> InitializeNew()
+ {
+ var newCache = Utility.AddNew(ValueInitializer);
+
+ lock (_allCaches)
+ _allCaches.Add(newCache);
+
+ return newCache;
+ }
+
+ public static void Clear()
+ {
+ lock (_allCaches)
+ {
+ foreach (var cache in _allCaches)
+ cache.Clear();
+ }
+ }
}
[PublicAPI]
public record struct ByInt
where T1 : notnull where T2 : notnull where TResult : new()
{
- private static FishTable, TResult> _get
- = Utility.AddNew, TResult>();
+ public int First, Second;
+
+ private static List, TResult>> _allCaches = [];
+
+ private static object _valueInitializerLock = new();
+
+ private static FishTable, TResult> _get = InitializeNew();
[ThreadStatic]
private static FishTable, TResult>? _getThreadStatic;
+ private static Func, TResult>? _valueInitializer;
+
+ public static Func, TResult>? ValueInitializer
+ {
+ get
+ {
+ lock (_valueInitializerLock)
+ return _valueInitializer;
+ }
+ set
+ {
+ lock (_valueInitializerLock)
+ {
+ value ??= static _ => Reflection.New();
+ _valueInitializer = value;
+ _get.ValueInitializer = value;
+ }
+ }
+ }
+
public static FishTable, TResult> Get
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _getThreadStatic ??= Utility.AddNew, TResult>();
+ get => _getThreadStatic ??= InitializeNew();
}
private static ref FishTable, TResult> GetCacheRef
@@ -135,10 +179,12 @@ public record struct ByInt
public static ref TResult GetOrAddReference(in ByInt key)
=> ref Get.GetOrAddReference(ref Unsafe.AsRef(in key));
- // [MethodImpl(MethodImplOptions.AggressiveInlining)]
- // public static ref TResult GetOrAddReference(long key) // different, worse, GetHashCode method
- // => ref Unsafe.As, TResult>, FishTable>(ref GetCacheRef)
- // .GetOrAddReference(key);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TResult GetOrAdd(int first, int second)
+ => Get.GetOrAdd(new() { First = first, Second = second });
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TResult GetOrAdd(in ByInt key) => Get.GetOrAdd(ref Unsafe.AsRef(in key));
[MethodImpl(MethodImplOptions.NoInlining)]
public static ref TResult GetExistingReference(int first, int second)
@@ -161,8 +207,6 @@ public record struct ByInt
First = first;
Second = second;
}
-
- public int First, Second;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByInt other)
@@ -170,12 +214,34 @@ public record struct ByInt
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(First, Second);
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static FishTable, TResult> InitializeNew()
+ {
+ var newCache = Utility.AddNew(ValueInitializer);
+
+ lock (_allCaches)
+ _allCaches.Add(newCache);
+
+ return newCache;
+ }
+
+ public static void Clear()
+ {
+ lock (_allCaches)
+ {
+ foreach (var cache in _allCaches)
+ cache.Clear();
+ }
+ }
}
[PublicAPI]
public record struct ByInt
where T1 : notnull where T2 : notnull where T3 : notnull where TResult : new()
{
+ public int First, Second, Third;
+
private static FishTable, TResult> _get
= Utility.AddNew, TResult>();
@@ -220,8 +286,6 @@ public record struct ByInt
Second = FunctionPointers.IndexGetter.Default(second);
Third = FunctionPointers.IndexGetter.Default(third);
}
-
- public int First, Second, Third;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByInt other)
@@ -238,6 +302,8 @@ public record struct ByInt
where T1 : notnull where T2 : notnull where T3 : notnull where T4 : notnull
where TResult : new()
{
+ public int First, Second, Third, Fourth;
+
private static FishTable, TResult> _get
= Utility.AddNew, TResult>();
@@ -287,8 +353,6 @@ public record struct ByInt
Fourth = FunctionPointers.IndexGetter.Default(fourth);
}
- public int First, Second, Third, Fourth;
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByInt other)
=> (Unsafe.As, long>(ref this)
diff --git a/Source/PerformanceFish/DefPair.cs b/Source/PerformanceFish/DefPair.cs
new file mode 100644
index 0000000..b5d8e7b
--- /dev/null
+++ b/Source/PerformanceFish/DefPair.cs
@@ -0,0 +1,42 @@
+// 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 JetBrains.Annotations;
+
+namespace PerformanceFish;
+
+[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
+public record struct DefPair
+{
+ private uint _data;
+
+ public ushort First
+ {
+ get => (ushort)(_data >> 16);
+ set => _data = (_data & 0x00_00_FF_FF) | ((uint)value << 16);
+ }
+
+ public ushort Second
+ {
+ get => (ushort)_data;
+ set => _data = (_data & 0xFF_FF_00_00) | value;
+ }
+
+ public DefPair(Def first, Def second) : this(first.shortHash, second.shortHash)
+ {
+ }
+
+ public DefPair(ushort first, ushort second) => _data = Create(first, second);
+
+ public bool Equals(DefPair other) => _data == other._data;
+
+ public override int GetHashCode() => unchecked((int)_data);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint Create(Def first, Def second) => Create(first.shortHash, second.shortHash);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static uint Create(ushort first, ushort second) => ((uint)first << 16) | second;
+}
\ No newline at end of file
diff --git a/Source/PerformanceFish/Defs/RecipeDefCaching.cs b/Source/PerformanceFish/Defs/RecipeDefCaching.cs
index 0692266..868047e 100644
--- a/Source/PerformanceFish/Defs/RecipeDefCaching.cs
+++ b/Source/PerformanceFish/Defs/RecipeDefCaching.cs
@@ -52,10 +52,10 @@ public sealed class RecipeDefCaching : ClassWithFishPatches
private static void UpdateCache(RecipeDef __instance, IEnumerable __result)
=> RecipeUsersCache.GetExistingReference(__instance).Update(__instance, __result);
- public record struct RecipeUsersCacheValue
+ public record struct RecipeUsersCacheValue()
{
- private int _defDatabaseVersion;
- private int _defRecipeUsersVersion;
+ private int _defDatabaseVersion = -2;
+ private int _defRecipeUsersVersion = -2;
private RecipeDef _def;
public List RecipeUsers;
diff --git a/Source/PerformanceFish/Defs/ThingDefPatches.cs b/Source/PerformanceFish/Defs/ThingDefPatches.cs
index 2ff4c6d..ffc0c67 100644
--- a/Source/PerformanceFish/Defs/ThingDefPatches.cs
+++ b/Source/PerformanceFish/Defs/ThingDefPatches.cs
@@ -7,6 +7,7 @@ using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
+using PerformanceFish.Events;
using PerformanceFish.Prepatching;
using FieldAttributes = Mono.Cecil.FieldAttributes;
using MethodImplAttributes = Mono.Cecil.MethodImplAttributes;
@@ -109,4 +110,189 @@ public sealed class ThingDefPatches : ClassWithFishPrepatches
}
}
}
+
+ public sealed class BaseMarketValuePatch : FishPrepatch
+ {
+ public override string? Description { get; }
+ = "Caches the frequently accessed and never changing ThingDef.BaseMarketValue property. Also adds proper "
+ + "exception handling in case of errors in the market value calculation.";
+
+ public override MethodBase TargetMethodBase { get; }
+ = AccessTools.DeclaredPropertyGetter(typeof(ThingDef), nameof(ThingDef.BaseMarketValue));
+
+ public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
+ => ilProcessor.ReplaceBodyWith(ReplacementBody);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ReplacementBody(ThingDef instance)
+ {
+ var cacheValue = instance.BaseStatsCache()[(int)Stats.MarketValue];
+
+ return !float.IsNaN(cacheValue) ? cacheValue : GetUpdatedValue(instance);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static float GetUpdatedValue(ThingDef instance)
+ => Current.Game is null
+ ? GetValueSafely(instance)
+ : instance.BaseStatsCache()[(int)Stats.MarketValue] = GetValueSafely(instance);
+
+ public static float GetValueSafely(ThingDef instance)
+ => ThingDefPatches.GetValueSafely(instance, StatDefOf.MarketValue);
+
+ public static float LogAndFixMarketValue(BuildableDef def, ThingDef? stuff, Exception exception)
+ {
+ Guard.IsNotNull(def);
+ Log.Error($"Exception thrown while calculating {StatDefOf.MarketValue.label} for def '{
+ def.defName}' from mod '{
+ def.modContentPack?.Name ?? "null"}'. Attempting to fix this now so it doesn't happen again.\n{
+ exception}");
+
+ RecipeDef? recipe;
+ try
+ {
+ recipe = StatWorker_MarketValue.CalculableRecipe(def);
+ // this is slow, but gets cached by perf optimizer. Might be worth replicating
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"Exception thrown while calculating recipe for def '{def.defName}' from mod '{
+ def.modContentPack?.Name ?? "null"}'.\n{ex}");
+ recipe = null;
+ }
+
+ if (recipe?.ingredients is { } ingredients)
+ ingredients.RemoveAll(static ingredientCount => ingredientCount is null);
+
+ if (def.CostList is { } costList)
+ costList.RemoveAll(static thingDefCountClass => thingDefCountClass?.thingDef is null);
+
+ try
+ {
+ return def.GetStatValueAbstract(StatDefOf.MarketValue, stuff);
+ }
+ catch (Exception ex)
+ {
+ Log.Error($"Exception thrown while calculating market value for def '{def.defName}' from mod '{
+ def.modContentPack?.Name ?? "null"}' again. Fix failed, F.\n{ex}");
+
+ return 0f;
+ }
+ }
+
+ static BaseMarketValuePatch()
+ => StaticEvents.StaticConstructorOnStartupCalled
+ += static () => StatCaching.StatExceptionHandlers[StatDefOf.MarketValue] = LogAndFixMarketValue;
+ }
+
+ public sealed class BaseMassPatch : FishPrepatch
+ {
+ public override string? Description { get; }
+ = "Caches the frequently accessed and never changing ThingDef.BaseMass property. Also adds basic "
+ + "exception handling in case of errors in the mass calculation.";
+
+ public override MethodBase TargetMethodBase { get; }
+ = AccessTools.DeclaredPropertyGetter(typeof(ThingDef), nameof(ThingDef.BaseMass));
+
+ public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
+ => ilProcessor.ReplaceBodyWith(ReplacementBody);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ReplacementBody(ThingDef instance)
+ {
+ var cacheValue = instance.BaseStatsCache()[(int)Stats.Mass];
+
+ return !float.IsNaN(cacheValue) ? cacheValue : GetUpdatedValue(instance);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static float GetUpdatedValue(ThingDef instance)
+ => Current.Game is null
+ ? GetValueSafely(instance)
+ : instance.BaseStatsCache()[(int)Stats.Mass] = GetValueSafely(instance);
+
+ public static float GetValueSafely(ThingDef instance)
+ => ThingDefPatches.GetValueSafely(instance, StatDefOf.Mass);
+ }
+
+ public sealed class BaseFlammabilityPatch : FishPrepatch
+ {
+ public override string? Description { get; }
+ = "Caches the frequently accessed and never changing ThingDef.BaseFlammability property. Also adds basic "
+ + "exception handling in case of errors in the mass calculation.";
+
+ public override MethodBase TargetMethodBase { get; }
+ = AccessTools.DeclaredPropertyGetter(typeof(ThingDef), nameof(ThingDef.BaseFlammability));
+
+ public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
+ => ilProcessor.ReplaceBodyWith(ReplacementBody);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ReplacementBody(ThingDef instance)
+ {
+ var cacheValue = instance.BaseStatsCache()[(int)Stats.Flammability];
+
+ return !float.IsNaN(cacheValue) ? cacheValue : GetUpdatedValue(instance);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static float GetUpdatedValue(ThingDef instance)
+ => Current.Game is null
+ ? GetValueSafely(instance)
+ : instance.BaseStatsCache()[(int)Stats.Flammability] = GetValueSafely(instance);
+
+ public static float GetValueSafely(ThingDef instance)
+ => ThingDefPatches.GetValueSafely(instance, StatDefOf.Flammability);
+ }
+
+ public sealed class BaseMaxHitPointsPatch : FishPrepatch
+ {
+ public override string? Description { get; }
+ = "Caches the frequently accessed and never changing ThingDef.BaseMaxHitPoints property. Also adds basic "
+ + "exception handling in case of errors in the mass calculation.";
+
+ public override MethodBase TargetMethodBase { get; }
+ = AccessTools.DeclaredPropertyGetter(typeof(ThingDef), nameof(ThingDef.BaseMaxHitPoints));
+
+ public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
+ => ilProcessor.ReplaceBodyWith(ReplacementBody);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int ReplacementBody(ThingDef instance)
+ {
+ var cacheValue = instance.BaseStatsCache()[(int)Stats.MaxHitPoints];
+
+ return Mathf.RoundToInt(!float.IsNaN(cacheValue) ? cacheValue : GetUpdatedValue(instance));
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static float GetUpdatedValue(ThingDef instance)
+ => Current.Game is null
+ ? GetValueSafely(instance)
+ : instance.BaseStatsCache()[(int)Stats.MaxHitPoints] = GetValueSafely(instance);
+
+ public static float GetValueSafely(ThingDef instance)
+ => ThingDefPatches.GetValueSafely(instance, StatDefOf.MaxHitPoints);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static float GetValueSafely(ThingDef instance, StatDef stat)
+ {
+ try
+ {
+ return instance.GetStatValueAbstract(stat);
+ }
+ catch (Exception ex)
+ {
+ return StatCaching.StatExceptionHandlers.GetOrAdd(stat)(instance, null, ex);
+ }
+ }
+
+ public enum Stats
+ {
+ MarketValue,
+ Mass,
+ Flammability,
+ MaxHitPoints
+ }
}
\ No newline at end of file
diff --git a/Source/PerformanceFish/Events/StaticEvents.cs b/Source/PerformanceFish/Events/StaticEvents.cs
index 3ea8dc1..9fa9dbf 100644
--- a/Source/PerformanceFish/Events/StaticEvents.cs
+++ b/Source/PerformanceFish/Events/StaticEvents.cs
@@ -12,4 +12,8 @@ public static class StaticEvents
add => Cache.Utility.Cleared += value;
remove => Cache.Utility.Cleared -= value;
}
+
+ public static event Action? StaticConstructorOnStartupCalled;
+
+ internal static void OnStaticConstructorOnStartupCalled() => StaticConstructorOnStartupCalled?.Invoke();
}
\ No newline at end of file
diff --git a/Source/PerformanceFish/Events/ThingEvents.cs b/Source/PerformanceFish/Events/ThingEvents.cs
index 3f9c301..18d8754 100644
--- a/Source/PerformanceFish/Events/ThingEvents.cs
+++ b/Source/PerformanceFish/Events/ThingEvents.cs
@@ -11,7 +11,8 @@ public sealed class ThingEvents : ClassWithFishPrepatches
{
public static event Action?
Initialized,
- Destroyed;
+ Destroyed,
+ QualityChanging;
public sealed class Instanced
{
@@ -163,5 +164,20 @@ public sealed class ThingEvents : ClassWithFishPrepatches
public static void Postfix(ThingWithComps __instance) => OnThingInitialized(__instance);
}
+ public sealed class SetQualityPatch : FishPrepatch
+ {
+ public override bool ShowSettings => false;
+
+ public override MethodBase TargetMethodBase { get; }
+ = AccessTools.DeclaredMethod(typeof(CompQuality), nameof(CompQuality.SetQuality));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Prefix(CompQuality __instance, QualityCategory q)
+ {
+ if (q != __instance.qualityInt)
+ QualityChanging?.Invoke(__instance.parent);
+ }
+ }
+
private static void OnThingInitialized(Thing thing) => Initialized?.Invoke(thing);
}
\ No newline at end of file
diff --git a/Source/PerformanceFish/GenLocalDateCaching.cs b/Source/PerformanceFish/GenLocalDateCaching.cs
index 0fcfb04..50680d8 100644
--- a/Source/PerformanceFish/GenLocalDateCaching.cs
+++ b/Source/PerformanceFish/GenLocalDateCaching.cs
@@ -98,6 +98,6 @@ public sealed class GenLocalDateCaching : ClassWithFishPatches
}
public static Map? SavedMap;
- public static int SavedTick;
- public static int DayTick;
+ public static int SavedTick = -2;
+ public static int DayTick = -2;
}
\ No newline at end of file
diff --git a/Source/PerformanceFish/GetCompCaching.cs b/Source/PerformanceFish/GetCompCaching.cs
index 5bbe788..bae9f99 100644
--- a/Source/PerformanceFish/GetCompCaching.cs
+++ b/Source/PerformanceFish/GetCompCaching.cs
@@ -51,9 +51,9 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
return null;
}
- public record struct CacheValue where T : ThingComp
+ public record struct CacheValue() where T : ThingComp
{
- private int _listVersion = -1;
+ private int _listVersion = -2;
public T? Comp;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -68,8 +68,6 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
_listVersion = thing.comps._version;
Comp = comp;
}
-
- public CacheValue() {}
}
}
@@ -113,9 +111,9 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
return null;
}
- public record struct CacheValue where T : CompProperties
+ public record struct CacheValue() where T : CompProperties
{
- private int _listVersion = -1;
+ private int _listVersion = -2;
public T? Comp;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -130,8 +128,6 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
_listVersion = thingDef.comps._version;
Comp = comp;
}
-
- public CacheValue() {}
}
}
@@ -176,9 +172,9 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
return false;
}
- public record struct CacheValue
+ public record struct CacheValue()
{
- private int _listVersion = -1;
+ private int _listVersion = -2;
public bool HasComp;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -193,8 +189,6 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
_listVersion = thingDef.comps._version;
HasComp = hasComp;
}
-
- public CacheValue() {}
}
}
@@ -237,9 +231,9 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
return null;
}
- public record struct CacheValue where T : HediffComp
+ public record struct CacheValue() where T : HediffComp
{
- private int _listVersion = -1;
+ private int _listVersion = -2;
public T? Comp;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -254,8 +248,6 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
_listVersion = hediff.comps._version;
Comp = comp;
}
-
- public CacheValue() {}
}
}
@@ -298,9 +290,9 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
return null;
}
- public record struct CacheValue where T : HediffCompProperties
+ public record struct CacheValue() where T : HediffCompProperties
{
- private int _listVersion = -1;
+ private int _listVersion = -2;
public T? Comp;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -315,8 +307,6 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
_listVersion = hediffDef.comps._version;
Comp = comp;
}
-
- public CacheValue() {}
}
}
@@ -359,9 +349,9 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
return null;
}
- public record struct CacheValue where T : AbilityComp
+ public record struct CacheValue() where T : AbilityComp
{
- private int _listVersion = -1;
+ private int _listVersion = -2;
public T? Comp;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -376,8 +366,6 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
_listVersion = ability.comps._version;
Comp = comp;
}
-
- public CacheValue() {}
}
}
@@ -415,9 +403,9 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
return null;
}
- public record struct CacheValue where T : WorldObjectComp
+ public record struct CacheValue() where T : WorldObjectComp
{
- private int _listVersion = -1;
+ private int _listVersion = -2;
public T? Comp;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -432,8 +420,6 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
_listVersion = worldObject.comps._version;
Comp = comp;
}
-
- public CacheValue() {}
}
}
@@ -471,9 +457,9 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
return null;
}
- public record struct CacheValue where T : MapComponent
+ public record struct CacheValue() where T : MapComponent
{
- private int _listVersion = -1;
+ private int _listVersion = -2;
public T? Comp;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -488,8 +474,6 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
_listVersion = map.components._version;
Comp = comp;
}
-
- public CacheValue() {}
}
}
@@ -527,7 +511,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
public static class Cache where T : WorldComponent
{
- private static int _listVersion = -1;
+ private static int _listVersion = -2;
private static World? _world;
public static T? Comp;
@@ -578,7 +562,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
public static class Cache where T : GameComponent
{
- private static int _listVersion = -1;
+ private static int _listVersion = -2;
private static Game? _game;
public static T? Comp;
diff --git a/Source/PerformanceFish/Hauling/HaulDestinationManagerCache.cs b/Source/PerformanceFish/Hauling/HaulDestinationManagerCache.cs
index 1c67060..5f32c30 100644
--- a/Source/PerformanceFish/Hauling/HaulDestinationManagerCache.cs
+++ b/Source/PerformanceFish/Hauling/HaulDestinationManagerCache.cs
@@ -5,7 +5,7 @@
namespace PerformanceFish.Hauling;
-public record struct HaulDestinationManagerCache
+public record struct HaulDestinationManagerCache()
{
public static readonly int StoragePriorityCount = Enum.GetValues(typeof(StoragePriority)).Length;
@@ -24,8 +24,4 @@ public record struct HaulDestinationManagerCache
PriorityChanged?.Invoke(manager);
}
-
- public HaulDestinationManagerCache()
- {
- }
}
\ No newline at end of file
diff --git a/Source/PerformanceFish/Hauling/HaulDestinationManagerPatches.cs b/Source/PerformanceFish/Hauling/HaulDestinationManagerPatches.cs
index 50aff3a..2ec951e 100644
--- a/Source/PerformanceFish/Hauling/HaulDestinationManagerPatches.cs
+++ b/Source/PerformanceFish/Hauling/HaulDestinationManagerPatches.cs
@@ -31,8 +31,11 @@ public sealed class HaulDestinationManagerPatches : ClassWithFishPrepatches
public static void Postfix(HaulDestinationManager __instance, IHaulDestination haulDestination)
{
- if (haulDestination is ISlotGroupParent)
- __instance.Cache().OnPriorityChanged(__instance);
+ if (haulDestination is not ISlotGroupParent slotGroupParent)
+ return;
+
+ __instance.Cache().OnPriorityChanged(__instance);
+ slotGroupParent.GetSlotGroup().NotifyCellCountChanged();
}
}
@@ -46,8 +49,11 @@ public sealed class HaulDestinationManagerPatches : ClassWithFishPrepatches
public static void Postfix(HaulDestinationManager __instance, IHaulDestination haulDestination)
{
- if (haulDestination is ISlotGroupParent)
- __instance.Cache().OnPriorityChanged(__instance);
+ if (haulDestination is not ISlotGroupParent slotGroupParent)
+ return;
+
+ __instance.Cache().OnPriorityChanged(__instance);
+ slotGroupParent.GetSlotGroup().ResetDistricts();
}
}
diff --git a/Source/PerformanceFish/Hauling/StorageSettingsPatches.cs b/Source/PerformanceFish/Hauling/StorageSettingsPatches.cs
index 9d91a84..2a23bdc 100644
--- a/Source/PerformanceFish/Hauling/StorageSettingsPatches.cs
+++ b/Source/PerformanceFish/Hauling/StorageSettingsPatches.cs
@@ -18,18 +18,32 @@ public sealed class StorageSettingsPatches : ClassWithFishPatches
public override string Description { get; }
= "Caches results of storage settings checks for things when hauling";
+ protected internal override void OnPatchingCompleted()
+ => Events.ThingEvents.QualityChanging += NotifyQualityChanging;
+
+ public static void NotifyQualityChanging(Thing thing)
+ {
+ if (!thing.IsItem() || thing.TryGetMap() is not { } map)
+ return;
+
+ var haulDestinations = map.haulDestinationManager.AllHaulDestinationsListForReading;
+ for (var i = haulDestinations.Count; i-- > 0;)
+ haulDestinations[i].GetStoreSettings().Cache().AllowedToAccept.Remove(thing.thingIDNumber);
+ }
+
public override Expression TargetMethod { get; }
= static () => default(StorageSettings)!.AllowedToAccept(default(Thing));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(StorageSettings __instance, Thing t, ref bool __result, out bool __state)
{
- var cachedAllowedToAcceptValue = __instance.Cache().AllowedToAccept.GetOrAdd(t.thingIDNumber);
+ ref var allowedToAcceptCacheValue
+ = ref __instance.Cache().AllowedToAccept.GetOrAddReference(t.thingIDNumber);
- if (StorageSettingsCache.IsDirty(cachedAllowedToAcceptValue))
+ if (allowedToAcceptCacheValue.Dirty)
return __state = true;
- __result = cachedAllowedToAcceptValue.AsBool() /*&& CapacityAllows(__instance, t)*/;
+ __result = allowedToAcceptCacheValue.Value;
return __state = false;
}
@@ -45,7 +59,8 @@ public sealed class StorageSettingsPatches : ClassWithFishPatches
[MethodImpl(MethodImplOptions.NoInlining)]
public static void UpdateCache(StorageSettings __instance, Thing t, bool __result)
- => __instance.Cache().Update(t.thingIDNumber, __result && t is { def: not null, thingIDNumber: not -1 });
+ => __instance.Cache().AllowedToAccept.GetOrAddReference(t.thingIDNumber)
+ .Update(t, __result && t is { def: not null, thingIDNumber: not -1 });
}
public sealed class TryNotifyChanged_Patch : FishPatch
@@ -107,9 +122,9 @@ public sealed class StorageSettingsPatches : ClassWithFishPatches
}
}
- public record struct StorageSettingsCache
+ public record struct StorageSettingsCache()
{
- public FishTable AllowedToAccept = new();
+ public FishTable AllowedToAccept = new();
private Thing? _parent;
@@ -129,15 +144,10 @@ public sealed class StorageSettingsPatches : ClassWithFishPatches
public List StoredThingsOfDef(ThingDef def) => _storedThingsByDef.GetOrAdd(def.shortHash);
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool IsDirty(int allowedToAcceptValue) => allowedToAcceptValue == int.MaxValue;
-
- public void Update(int thingIDNumber, bool allowedToAccept)
- => AllowedToAccept[thingIDNumber] = allowedToAccept.AsInt();
-
public void Initialize(Thing? slotGroupParent)
{
_parent = slotGroupParent;
+ _storedThingCount = 0;
_storedThingsByDef.Clear();
AllowedToAccept.Clear();
_lwmProps = slotGroupParent?.TryGetLwmCompProperties();
@@ -177,8 +187,27 @@ public sealed class StorageSettingsPatches : ClassWithFishPatches
SettingsChanged?.Invoke(storageSettings);
}
+ }
- public StorageSettingsCache() => AllowedToAccept.ValueInitializer = static _ => int.MaxValue;
+ public record struct AllowedToAcceptCacheValue()
+ {
+ private int
+ _value = int.MaxValue,
+ _nextUpdateTick = -2;
+
+ public bool Value => _value.AsBool();
+
+ public bool Dirty
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => (_value == int.MaxValue) | TickHelper.Past(_nextUpdateTick);
+ }
+
+ public void Update(Thing thing, bool allowedToAccept)
+ {
+ _value = allowedToAccept.AsInt();
+ _nextUpdateTick = TickHelper.Add(512, thing.thingIDNumber, 128);
+ }
}
public static class Debug
diff --git a/Source/PerformanceFish/Hauling/StoreUtilityPrepatches.cs b/Source/PerformanceFish/Hauling/StoreUtilityPrepatches.cs
index 43efe3c..4afb413 100644
--- a/Source/PerformanceFish/Hauling/StoreUtilityPrepatches.cs
+++ b/Source/PerformanceFish/Hauling/StoreUtilityPrepatches.cs
@@ -113,11 +113,7 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches
var thingEvents = thing.Events();
if (thing is ISlotGroupParent)
- {
- thingEvents.Spawned += NotifySlotGroupCellCountChanged;
thingEvents.Spawned += StorageSettingsPatches.InitializeSlotGroupParent;
- thingEvents.DeSpawned += NotifySlotGroupCellCountChanged;
- }
if (!thing.IsItem())
return;
@@ -126,12 +122,6 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches
thingEvents.DeregisteredAtThingGrid += TryNotifyLostThing;
}
- public static void NotifySlotGroupCellCountChanged(Thing slotGroupParent, Map map)
- => NotifySlotGroupCellCountChanged(slotGroupParent);
-
- public static void NotifySlotGroupCellCountChanged(Thing slotGroupParent)
- => slotGroupParent.GetSlotGroup()?.NotifyCellCountChanged();
-
public static void TryNotifyReceivedThing(Thing thing, Map map, in IntVec3 cell)
{
if (cell.GetSlotGroupParent(map) is { } parent and Thing)
@@ -370,13 +360,27 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches
[MethodImpl(MethodImplOptions.NoInlining)]
public static void TryRemoveFromListerHaulables(Thing t, StoragePriority currentPriority)
{
+ if (ModCompatibility.ActiveMods.Multiplayer | !HaulablesTickPatchActive)
+ return;
+
if (t.IsInAnyStorage() /*t.IsInValidStorage()*/ && t.TryGetMapHeld() is { } map)
- map.listerHaulables.TryRemoveDirectly(t);
-
+ map.listerHaulables.Cache().ThingsQueuedToRemove.Add(t);
+
// remove for any storage instead of only valid storage to prevent further haul attempts until the next
// automatic ListerHaulablesTick cycle
}
+ private static int _haulablesTickPatchActive = int.MaxValue;
+
+ private static bool HaulablesTickPatchActive
+ => _haulablesTickPatchActive != int.MaxValue
+ ? _haulablesTickPatchActive.AsBool()
+ : UpdateHaulablesTickPatchActive();
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static bool UpdateHaulablesTickPatchActive()
+ => (_haulablesTickPatchActive = Get().IsActive.AsInt()).AsBool();
+
public static class Debug
{
[Conditional("STORAGE_GROUP_DEBUG")]
diff --git a/Source/PerformanceFish/Hediffs/HediffSetCaching.cs b/Source/PerformanceFish/Hediffs/HediffSetCaching.cs
index ce06620..ab6cfdd 100644
--- a/Source/PerformanceFish/Hediffs/HediffSetCaching.cs
+++ b/Source/PerformanceFish/Hediffs/HediffSetCaching.cs
@@ -123,10 +123,10 @@ public sealed class HediffSetPrecaching : ClassWithFishPrepatches
public static bool Prefix(HediffSet __instance, BodyPartHeight height, BodyPartDepth depth, BodyPartTagDef? tag,
BodyPartRecord? partParent, ref IEnumerable __result, out bool __state)
{
- if (height != BodyPartHeight.Undefined
- || depth != BodyPartDepth.Undefined
- || tag != null
- || partParent != null)
+ if ((height != BodyPartHeight.Undefined)
+ | (depth != BodyPartDepth.Undefined)
+ | (tag != null)
+ | (partParent != null))
{
__state = false;
return true;
@@ -173,7 +173,7 @@ public sealed class HediffSetCaching : ClassWithFishPatches
}
}
- public record struct NotMissingPartsCacheValue
+ public record struct NotMissingPartsCacheValue()
{
private int _version = -1;
private List _hediffsInSet = [];
@@ -200,13 +200,9 @@ public sealed class HediffSetCaching : ClassWithFishPatches
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _version != _hediffsInSet._version || TickHelper.Past(_nextRefreshTick);
}
-
- public NotMissingPartsCacheValue()
- {
- }
}
- public record struct HediffCacheValue
+ public record struct HediffCacheValue()
{
private int _version = -1;
private List _hediffsInSet = [];
@@ -227,21 +223,13 @@ public sealed class HediffSetCaching : ClassWithFishPatches
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _version != _hediffsInSet._version || TickHelper.Past(_nextRefreshTick);
}
-
- public HediffCacheValue()
- {
- }
}
- public record struct VisibleHediffCacheValue
+ public record struct VisibleHediffCacheValue()
{
public HediffCacheValue Value = new();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(HediffSet set, Hediff? hediff) => Value.Update(set, hediff);
-
- public VisibleHediffCacheValue()
- {
- }
}
}
\ No newline at end of file
diff --git a/Source/PerformanceFish/Hediffs/ImmunityHandlerCaching.cs b/Source/PerformanceFish/Hediffs/ImmunityHandlerCaching.cs
index 2cb4a84..70f58a0 100644
--- a/Source/PerformanceFish/Hediffs/ImmunityHandlerCaching.cs
+++ b/Source/PerformanceFish/Hediffs/ImmunityHandlerCaching.cs
@@ -56,19 +56,10 @@ public sealed class ImmunityHandlerCaching : ClassWithFishPatches
public sealed class ImmunityRecordExists : FishPatch
{
+ public override List LinkedPatches { get; } = [typeof(TryAddImmunityRecord)];
+
public override string? Description { get; } = "Required by the TryAddImmunityRecord optimization.";
- public override bool Enabled
- {
- set
- {
- if (base.Enabled == value)
- return;
-
- Get().Enabled = base.Enabled = value;
- }
- }
-
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(ImmunityHandler), nameof(ImmunityHandler.ImmunityRecordExists));
@@ -84,19 +75,10 @@ public sealed class ImmunityHandlerCaching : ClassWithFishPatches
public sealed class TryAddImmunityRecord : FishPatch
{
+ public override List LinkedPatches { get; } = [typeof(ImmunityRecordExists)];
+
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().Enabled = base.Enabled = value;
- }
- }
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(ImmunityHandler), nameof(ImmunityHandler.TryAddImmunityRecord));
@@ -110,7 +92,7 @@ public sealed class ImmunityHandlerCaching : ClassWithFishPatches
}
}
- public record struct ImmunityInfoCacheValue
+ public record struct ImmunityInfoCacheValue()
{
private int _version = -1;
private List _hediffsInSet = [];
@@ -132,15 +114,11 @@ public sealed class ImmunityHandlerCaching : ClassWithFishPatches
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _version != _hediffsInSet._version || TickHelper.Past(_nextRefreshTick);
}
-
- public ImmunityInfoCacheValue()
- {
- }
}
- public record struct ImmunityRecordExistsCacheValue
+ public record struct ImmunityRecordExistsCacheValue()
{
public bool Value;
- public int ImmunityListVersion;
+ public int ImmunityListVersion = -2;
}
}
\ No newline at end of file
diff --git a/Source/PerformanceFish/IdeoUtilityCaching.cs b/Source/PerformanceFish/IdeoUtilityCaching.cs
index 79f1b31..cd7c083 100644
--- a/Source/PerformanceFish/IdeoUtilityCaching.cs
+++ b/Source/PerformanceFish/IdeoUtilityCaching.cs
@@ -22,7 +22,7 @@ public sealed class IdeoUtilityCaching : ClassWithFishPrepatches
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Thing? t, Ideo? ideo, ref float __result, out bool __state)
{
- if (t is null || ideo is null || t.thingIDNumber < 0)
+ if (t is null || t.thingIDNumber < 0 || ideo is null)
{
__state = false;
return true;
@@ -51,10 +51,10 @@ public sealed class IdeoUtilityCaching : ClassWithFishPrepatches
=> StyleDominanceCache.GetExistingReference(t, ideo).Update(__result, t);
}
- public record struct StyleDominanceCacheValue
+ public record struct StyleDominanceCacheValue()
{
public float Value;
- private int _nextRefreshTick;
+ private int _nextRefreshTick = -2;
public void Update(float value, Thing thing)
{
diff --git a/Source/PerformanceFish/JobSystem/WorkGiver_DoBillOptimization.cs b/Source/PerformanceFish/JobSystem/WorkGiver_DoBillOptimization.cs
index 329c4bf..de9e112 100644
--- a/Source/PerformanceFish/JobSystem/WorkGiver_DoBillOptimization.cs
+++ b/Source/PerformanceFish/JobSystem/WorkGiver_DoBillOptimization.cs
@@ -14,7 +14,7 @@ using IngredientCacheList
bool Found)>;
using OpCodes = System.Reflection.Emit.OpCodes;
using RecipeIngredientCache
- = PerformanceFish.Cache.ByIndex;
namespace PerformanceFish.JobSystem;
@@ -96,7 +96,7 @@ public sealed class WorkGiver_DoBillPrepatches : ClassWithFishPrepatches
}
else
{
- ref var cache = ref RecipeIngredientCache.Get.GetReference(bill);
+ ref var cache = ref RecipeIngredientCache.GetOrAddReference(bill.GetLoadID());
if (cache.thingDefs is null || cache.Dirty)
{
cache.thingDefs = UpdateCache(bill, r, cache.thingDefs);
@@ -501,7 +501,7 @@ public sealed class WorkGiver_DoBillOptimization : ClassWithFishPatches
if (thingValidatorInstance is null)
return;
- var cache = RecipeIngredientCache.Get[thingValidatorInstance.Bill].thingDefs;
+ var cache = RecipeIngredientCache.GetOrAdd(thingValidatorInstance.Bill.loadID).thingDefs;
if (cache is null)
return;
@@ -583,7 +583,7 @@ public sealed class WorkGiver_DoBillOptimization : ClassWithFishPatches
return;
}
- var cache = RecipeIngredientCache.Get[bill].thingDefs;
+ var cache = RecipeIngredientCache.GetOrAdd(bill.loadID).thingDefs;
if (cache is null)
return;
@@ -599,10 +599,10 @@ public sealed class WorkGiver_DoBillOptimization : ClassWithFishPatches
}
}
- public record struct RecipeIngredientCacheValue
+ public record struct RecipeIngredientCacheValue()
{
public IngredientCacheList? thingDefs;
- private int _nextRefreshTick;
+ private int _nextRefreshTick = -2;
public void Update(Bill bill)
=> _nextRefreshTick = TickHelper.Add(GenTicks.TickRareInterval * 2, bill.loadID);
diff --git a/Source/PerformanceFish/Listers/Buildings.cs b/Source/PerformanceFish/Listers/Buildings.cs
index e69a416..723423d 100644
--- a/Source/PerformanceFish/Listers/Buildings.cs
+++ b/Source/PerformanceFish/Listers/Buildings.cs
@@ -225,7 +225,7 @@ public sealed class Buildings : ClassWithFishPrepatches
}
}
- public readonly record struct Cache
+ public readonly record struct Cache()
{
public readonly FishTable>
ColonistBuildingsByDef = new(),
@@ -240,9 +240,5 @@ public sealed class Buildings : ClassWithFishPrepatches
ValueInitializer = static type
=> (IList)Activator.CreateInstance(typeof(IndexedFishSet<>).MakeGenericType(type))
};
-
- public Cache()
- {
- }
}
}
\ No newline at end of file
diff --git a/Source/PerformanceFish/Listers/Haulables.cs b/Source/PerformanceFish/Listers/Haulables.cs
index fb819d3..8cfdd9f 100644
--- a/Source/PerformanceFish/Listers/Haulables.cs
+++ b/Source/PerformanceFish/Listers/Haulables.cs
@@ -6,6 +6,7 @@
using Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod.Utils;
+using PerformanceFish.ModCompatibility;
using PerformanceFish.Prepatching;
namespace PerformanceFish.Listers;
@@ -14,6 +15,8 @@ public sealed class Haulables : ClassWithFishPrepatches
{
public sealed class CheckPatch : FishPrepatch
{
+ public override List IncompatibleModIDs { get; } = [PackageIDs.MULTIPLAYER];
+
public override string? Description { get; }
= "Optimizes the ListerHaulables by making frequent checks that happen every tick to only check for adding "
+ "new haulables, skipping things that are already inside the lister. This generally results in them "
@@ -95,7 +98,8 @@ public sealed class Haulables : ClassWithFishPrepatches
{
public override string? Description { get; }
= "Essentially fixes a bug that was causing the method to tick cells multiple times for storages with less "
- + "than 4 cells. Not ticking multiple times leads to a performance improvement";
+ + "than 4 cells. Not ticking multiple times leads to a performance improvement. Also handles things queued "
+ + "for removal by the StoreUtilityPrepatches:TryFindBestBetterStoreCellFor patch";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(ListerHaulables), nameof(ListerHaulables.ListerHaulablesTick));
@@ -116,12 +120,33 @@ public sealed class Haulables : ClassWithFishPrepatches
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetCellCountToTick(int original, SlotGroup slotGroup)
=> Math.Min(original, slotGroup.CellsList.Count);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Postfix(ListerHaulables __instance)
+ {
+ var thingsQueuedToRemove = __instance.Cache().ThingsQueuedToRemove;
+ if (thingsQueuedToRemove.Count == 0)
+ return;
+
+ RemoveQueuedThings(__instance, thingsQueuedToRemove);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void RemoveQueuedThings(ListerHaulables __instance, HashSet thingsQueuedToRemove)
+ {
+ foreach (var thing in thingsQueuedToRemove)
+ __instance.TryRemoveDirectly(thing);
+
+ thingsQueuedToRemove.Clear();
+ }
}
- public record struct Cache
+ public record struct Cache()
{
public readonly FishSet ContainedThings = [];
+ public readonly HashSet ThingsQueuedToRemove = [];
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CheckUpdate(List haulables)
{
@@ -140,10 +165,6 @@ public sealed class Haulables : ClassWithFishPrepatches
for (var i = haulables.Count; i-- > 0;)
ContainedThings.Add(haulables[i].GetKey());
}
-
- public Cache()
- {
- }
}
}
diff --git a/Source/PerformanceFish/MassUtilityCaching.cs b/Source/PerformanceFish/MassUtilityCaching.cs
index db204df..2281352 100644
--- a/Source/PerformanceFish/MassUtilityCaching.cs
+++ b/Source/PerformanceFish/MassUtilityCaching.cs
@@ -71,11 +71,11 @@ public sealed class MassUtilityCaching : ClassWithFishPatches
=> InventoryMassCache.GetExistingReference(p).Update(__result, p);
}
- public record struct GearMassCacheValue
+ public record struct GearMassCacheValue()
{
public float Mass;
- private int _nextRefreshTick;
- private int _equipmentListsState;
+ private int _nextRefreshTick = -2;
+ private int _equipmentListsState = -69123;
public void Update(float mass, Pawn p)
{
@@ -87,9 +87,13 @@ public sealed class MassUtilityCaching : ClassWithFishPatches
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetEquipmentListsState(Pawn p)
{
- var result = p.apparel != null ? p.apparel.WornApparel._version : 0;
+ var result = p.apparel != null ? p.apparel.WornApparel._version : -1;
if (p.equipment != null)
- result += p.equipment.AllEquipmentListForReading._version;
+ {
+ var equipmentListVersion = (uint)p.equipment.AllEquipmentListForReading._version;
+ result += (int)((equipmentListVersion << 16) | (equipmentListVersion >> 16));
+ }
+
return result;
}
@@ -99,24 +103,23 @@ public sealed class MassUtilityCaching : ClassWithFishPatches
|| _equipmentListsState != GetEquipmentListsState(p);
}
- public record struct InventoryMassCacheValue
+ public record struct InventoryMassCacheValue()
{
public float Mass;
- private int _nextRefreshTick;
- private int _inventoryListsState;
+ private int _nextRefreshTick = -2;
+ private int _inventoryListsState = -2;
public void Update(float mass, Pawn p)
{
Mass = mass;
+ //shorter refresh interval because of stack sizes not being considered by the listVersion check
_nextRefreshTick = TickHelper.Add(GenTicks.TickRareInterval, p.thingIDNumber);
- _inventoryListsState
- = GetInventoryListsState(
- p); //shorter refresh interval because of stack sizes not being considered by the listVersion check
+ _inventoryListsState = GetInventoryListsState(p);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetInventoryListsState(Pawn p)
- => p.inventory?.innerContainer is { } container ? container.innerList._version : 0;
+ => p.inventory?.innerContainer is { } container ? container.innerList._version : -42;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsDirty(Pawn p)
diff --git a/Source/PerformanceFish/MeditationUtilityCaching.cs b/Source/PerformanceFish/MeditationUtilityCaching.cs
index 1fa4a09..4dc3c61 100644
--- a/Source/PerformanceFish/MeditationUtilityCaching.cs
+++ b/Source/PerformanceFish/MeditationUtilityCaching.cs
@@ -46,9 +46,9 @@ public sealed class MeditationUtilityCaching : ClassWithFishPatches
.Update(__result, pawn);
}
- public record struct PsyfocusCacheValue
+ public record struct PsyfocusCacheValue()
{
- private int _nextRefreshTick;
+ private int _nextRefreshTick = -2;
public float PsyfocusGain;
public void Update(float psyfocusGain, Pawn pawn)
diff --git a/Source/PerformanceFish/ModCompatibility/ActiveMods.cs b/Source/PerformanceFish/ModCompatibility/ActiveMods.cs
index f67e6c0..f6d8ce1 100644
--- a/Source/PerformanceFish/ModCompatibility/ActiveMods.cs
+++ b/Source/PerformanceFish/ModCompatibility/ActiveMods.cs
@@ -16,6 +16,8 @@ public static class ActiveMods
Multiplayer = Contains(PackageIDs.MULTIPLAYER),
PerformanceOptimizer = Contains(PackageIDs.PERFORMANCE_OPTIMIZER),
Prepatcher = Contains(PackageIDs.PREPATCHER),
+ PrisonLabor = Contains(PackageIDs.PRISON_LABOR),
+ RIMMSqol = Contains(PackageIDs.RIMMSQOL),
UseBestMaterials = Contains(PackageIDs.USE_BEST_MATERIALS),
VanillaExpandedFramework = Contains(PackageIDs.VANILLA_EXPANDED_FRAMEWORK);
diff --git a/Source/PerformanceFish/ModCompatibility/PackageIDs.cs b/Source/PerformanceFish/ModCompatibility/PackageIDs.cs
index df087af..d14e01b 100644
--- a/Source/PerformanceFish/ModCompatibility/PackageIDs.cs
+++ b/Source/PerformanceFish/ModCompatibility/PackageIDs.cs
@@ -18,6 +18,8 @@ public static class PackageIDs
PERFORMANCE_FISH = "bs.performance",
PERFORMANCE_OPTIMIZER = "Taranchuk.PerformanceOptimizer",
PREPATCHER = "zetrith.prepatcher",
+ PRISON_LABOR = "avius.prisonlabor",
+ RIMMSQOL = "MalteSchulze.RIMMSqol",
USE_BEST_MATERIALS = "kathanon.UseBestMaterials",
VANILLA_EXPANDED_FRAMEWORK = "OskarPotocki.VanillaFactionsExpanded.Core";
}
\ No newline at end of file
diff --git a/Source/PerformanceFish/ModCompatibility/RIMMSqol.cs b/Source/PerformanceFish/ModCompatibility/RIMMSqol.cs
new file mode 100644
index 0000000..465f6ff
--- /dev/null
+++ b/Source/PerformanceFish/ModCompatibility/RIMMSqol.cs
@@ -0,0 +1,78 @@
+// 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.ModCompatibility;
+
+public static class RIMMSqol
+{
+ private const string ASSEMBLY_NAME = "RIMMSqol";
+
+ public static bool Active => ActiveMods.RIMMSqol;
+
+ public static string[] BlockedPatchNamespaces { get; } = ["WorldPawnGC"];
+
+ public static ModContentPack? ModContentPack { get; }
+ = ActiveMods.TryGetModContentPack(PackageIDs.RIMMSQOL);
+
+ public static Type? HarmonyPatchNamespaceType { get; }
+
+ public static MethodInfo? RegisterAllMethod { get; }
+
+ static RIMMSqol()
+ {
+ if (ModContentPack is null)
+ return;
+
+ if (ModContentPack.TryGetAssembly(ASSEMBLY_NAME) is not { } assembly)
+ {
+ LogFailureToFind("assembly");
+ return;
+ }
+
+ if ((HarmonyPatchNamespaceType = assembly.GetType("RIMMSqol.HarmonyPatchNamespace")) is null)
+ {
+ LogFailureToFind("harmony patch namespace type");
+ return;
+ }
+
+ if ((RegisterAllMethod = AccessTools.DeclaredMethod(HarmonyPatchNamespaceType, "registerAll")) is null)
+ LogFailureToFind("registerAll method");
+ }
+
+ private static void LogFailureToFind(string text)
+ => Log.Error($"Performance Fish failed to find RIMMSqol's {
+ text}. This is likely to cause issues.");
+
+ public static bool TryPatch()
+ => RegisterAllMethod is { } registerAllMethod
+ && PerformanceFishMod.Harmony.Patch(registerAllMethod, postfix: new(methodof(RegisterAllPostfix))) != null;
+
+ public static void RegisterAllPostfix()
+ {
+ if (AccessTools.Field(HarmonyPatchNamespaceType, "namespaces").GetValue(null) is not IDictionary namespaces)
+ {
+ LogFailureToFind("harmony patch namespaces field");
+ return;
+ }
+
+ foreach (var blockedPatchNamespace in BlockedPatchNamespaces)
+ {
+ if (!namespaces.Contains(blockedPatchNamespace))
+ {
+ LogFailureToFind(blockedPatchNamespace);
+ continue;
+ }
+
+ var properties = namespaces[blockedPatchNamespace];
+ if (AccessTools.Field(properties.GetType(), "active") is not { } activeField)
+ {
+ LogFailureToFind("harmony patch active field");
+ return;
+ }
+
+ activeField.SetValue(properties, false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/PerformanceFish/OnAssembliesLoaded.cs b/Source/PerformanceFish/OnAssembliesLoaded.cs
index ad0c1c0..8f66d69 100644
--- a/Source/PerformanceFish/OnAssembliesLoaded.cs
+++ b/Source/PerformanceFish/OnAssembliesLoaded.cs
@@ -23,6 +23,7 @@ public static class OnAssembliesLoaded
TryPatchGraphicsSetter();
TryPatchPerformanceOptimizer();
+ TryPatchRIMMSqol();
TryInitialize(EqualityComparerOptimization.Optimization.Initialize);
@@ -72,6 +73,31 @@ public static class OnAssembliesLoaded
}
}
+ // ReSharper disable once InconsistentNaming
+ private static void TryPatchRIMMSqol()
+ {
+ if (!ActiveMods.RIMMSqol)
+ return;
+
+ var success = false;
+ Exception? exception = null;
+
+ try
+ {
+ success = RIMMSqol.TryPatch();
+ }
+ catch (Exception ex)
+ {
+ exception = ex;
+ }
+
+ if (!success)
+ {
+ Log.Error($"Failed to apply compatibility patch for {
+ RIMMSqol.ModContentPack?.Name ?? "RIMMSqol"}.".AppendNewLineWhenNotNull(exception));
+ }
+ }
+
private static async void TryInitialize(Action action, bool async = false)
{
try
diff --git a/Source/PerformanceFish/OnStartup.cs b/Source/PerformanceFish/OnStartup.cs
index b71df6b..6e834ca 100644
--- a/Source/PerformanceFish/OnStartup.cs
+++ b/Source/PerformanceFish/OnStartup.cs
@@ -4,6 +4,7 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
using JetBrains.Annotations;
+using PerformanceFish.Events;
namespace PerformanceFish;
@@ -20,6 +21,7 @@ public static class OnStartup
// => !MothballOptimization.AllowedDefs.Contains(def)).ToStringSafeEnumerable()}");
Fields.Initialized = true;
+ StaticEvents.OnStaticConstructorOnStartupCalled();
}
public static class State
diff --git a/Source/PerformanceFish/PerformanceFish.csproj b/Source/PerformanceFish/PerformanceFish.csproj
index 749a533..09b117a 100644
--- a/Source/PerformanceFish/PerformanceFish.csproj
+++ b/Source/PerformanceFish/PerformanceFish.csproj
@@ -17,7 +17,7 @@
enable
True
v1.4
- 0.3.999.92
+ 0.5.0.0
1701;1702;IL2121;CS8500;CS9082;CA1000;CA1002;CA1003;CA1707;CA1010;CA1715;CA1716;CA1724;CA1031;CA1033;
CA1034;CA1043;CA1051;CA1305;CA1310;CA1710;CA1711;CA1810;CA1812;CA1819;CA1851;CA2002;CA5394;CA1725;CA1062
@@ -50,7 +50,7 @@
-
+
diff --git a/Source/PerformanceFish/PerformanceFishMod.cs b/Source/PerformanceFish/PerformanceFishMod.cs
index 184afa5..e10b73a 100644
--- a/Source/PerformanceFish/PerformanceFishMod.cs
+++ b/Source/PerformanceFish/PerformanceFishMod.cs
@@ -48,7 +48,7 @@ public sealed class PerformanceFishMod : Mod
PACKAGE_ID = PackageIDs.PERFORMANCE_FISH;
[Obsolete("I am not incrementing this version number")]
- public const decimal VERSION = 0.4M;
+ public const decimal VERSION = 0.5M;
public static Version LoadedVersion => typeof(PerformanceFishMod).Assembly.GetLoadedVersion();
diff --git a/Source/PerformanceFish/Planet/WorldPawnsOptimization.cs b/Source/PerformanceFish/Planet/WorldPawnsOptimization.cs
index 89e4602..4dc53fb 100644
--- a/Source/PerformanceFish/Planet/WorldPawnsOptimization.cs
+++ b/Source/PerformanceFish/Planet/WorldPawnsOptimization.cs
@@ -96,16 +96,12 @@ public sealed class WorldPawnsOptimization : ClassWithFishPrepatches
|| allPawnsDead._version != cache.AllPawnsDeadVersion;
}
- public record struct Cache
+ public record struct Cache()
{
public int
- PawnsAliveVersion = -1,
- PawnsMothballedVersion = -1,
- AllPawnsAliveVersion = -1,
- AllPawnsDeadVersion = -1;
-
- public Cache()
- {
- }
+ PawnsAliveVersion = -2,
+ PawnsMothballedVersion = -2,
+ AllPawnsAliveVersion = -2,
+ AllPawnsDeadVersion = -2;
}
}
\ No newline at end of file
diff --git a/Source/PerformanceFish/PrepatcherFields.cs b/Source/PerformanceFish/PrepatcherFields.cs
index 1664455..2b547f8 100644
--- a/Source/PerformanceFish/PrepatcherFields.cs
+++ b/Source/PerformanceFish/PrepatcherFields.cs
@@ -112,6 +112,16 @@ public static class PrepatcherFields
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern ref HaulDestinationManagerCache Cache(this HaulDestinationManager manager);
+ [PrepatcherField]
+ [ValueInitializer(nameof(CreateBaseStatsCache))]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static extern float[] BaseStatsCache(this ThingDef thingDef);
+
+ [PrepatcherField]
+ [ValueInitializer(nameof(GetEmptyArray))]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static extern ref float[] StatsCache(this BuildableDef buildableDef);
+
[PrepatcherField]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern ref object MonitorObject(this GasGrid gasGrid);
@@ -155,6 +165,14 @@ public static class PrepatcherFields
public static Listers.Buildings.Cache CreateListerBuildingsCache() => new();
public static Listers.Haulables.Cache CreateListerHaulablesCache() => new();
public static HaulDestinationManagerCache CreateHaulDestinationManagerCache() => new();
+ public static float[] GetEmptyArray() => Array.Empty();
+
+ public static float[] CreateBaseStatsCache()
+ {
+ var array = new float[Enum.GetNames(typeof(Defs.ThingDefPatches.Stats)).Length];
+ StatCaching.ResetToDefaults(array);
+ return array;
+ }
public static object CreateGroupMonitorObject()
=> ParallelNoAlloc.RegisterBackgroundWaitingWorkers(Array.Empty());
diff --git a/Source/PerformanceFish/RoomOptimizations.cs b/Source/PerformanceFish/RoomOptimizations.cs
index 876cce2..a8c516e 100644
--- a/Source/PerformanceFish/RoomOptimizations.cs
+++ b/Source/PerformanceFish/RoomOptimizations.cs
@@ -6,11 +6,11 @@
using System.Linq;
using System.Reflection.Emit;
using RoomOwnersCache
- = PerformanceFish.Cache.ByIndex;
+ = PerformanceFish.Cache.ByInt;
using RoomRoleCache
- = PerformanceFish.Cache.ByIndex;
+ = PerformanceFish.Cache.ByInt;
using ContainedAndAdjacentThingsCache
- = PerformanceFish.Cache.ByIndex;
using OpCodes = System.Reflection.Emit.OpCodes;
@@ -58,7 +58,7 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
public static bool ShouldUpdate(Room __instance)
{
if (__instance.role is not null
- || !RoomRoleCache.Get[__instance.ID].Dirty)
+ || !RoomRoleCache.GetOrAddReference(__instance.ID).Dirty)
{
return false;
}
@@ -69,11 +69,11 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
}
private static void UpdateCache(Room __instance)
- => RoomRoleCache.Get.GetReference(__instance).SetDirty(__instance);
+ => RoomRoleCache.GetOrAddReference(__instance.ID).SetDirty(__instance);
- public record struct CacheValue
+ public record struct CacheValue()
{
- private int _nextUpdateTick;
+ private int _nextUpdateTick = -2;
public bool Dirty
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -97,7 +97,7 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Room __instance, ref IEnumerable __result, out bool __state)
{
- ref var cache = ref RoomOwnersCache.Get.GetReference(__instance.ID);
+ ref var cache = ref RoomOwnersCache.GetOrAddReference(__instance.ID);
if (cache.Dirty)
{
@@ -127,36 +127,24 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
[MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateCache(Room __instance, ref IEnumerable __result)
{
- ref var cache = ref RoomOwnersCache.Get.GetReference(__instance);
+ ref var cache = ref RoomOwnersCache.GetOrAddReference(__instance.ID);
cache.Update(__instance, __result);
__result = cache.Owners;
}
- public record struct CacheValue
+ public record struct CacheValue()
{
- private int _nextRefreshTick;
- public List Owners;
+ private int _nextRefreshTick = -2;
+ public readonly List Owners = [];
public void Update(Room room, IEnumerable? result)
{
- if (result is null)
- {
- (Owners ??= []).Clear();
- }
- else
- {
- if (Owners != null)
- {
- Owners.Clear();
- Owners.AddRange(result);
- }
- else
- {
- Owners = [..result];
- }
- }
+ Owners.Clear();
+
+ if (result != null)
+ Owners.AddRange(result);
_nextRefreshTick = TickHelper.Add(384, room.ID, 256);
}
@@ -243,11 +231,9 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
var regions = instance.Regions;
- ref var cache = ref ContainedAndAdjacentThingsCache.Get.GetReference(instance.ID);
+ ref var cache = ref ContainedAndAdjacentThingsCache.GetOrAddReference(instance.ID);
- if (cache.ListVersions != null
- && cache.ListVersions.Count == regions.Count
- && !cache.Dirty)
+ if (cache.ListVersions.Count == regions.Count && !cache.Dirty)
{
var cacheIsAccurate = true;
for (var i = 0; i < regions.Count; i++)
@@ -280,7 +266,7 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
var uniqueContainedThings = instance.uniqueContainedThings;
var uniqueContainedThingsSet = instance.uniqueContainedThingsSet;
uniqueContainedThingsSet.Clear();
- (cache.ListVersions ??= []).Clear();
+ cache.ListVersions.Clear();
for (var i = 0; i < regions.Count; i++)
{
@@ -360,11 +346,11 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
return uniqueContainedThings;
}*/
- public record struct CacheValue
+ public record struct CacheValue()
{
- private int _nextRefreshTick;
- public List? ListVersions;
- public List Things;
+ private int _nextRefreshTick = -2;
+ public List ListVersions = [];
+ public List Things = [];
public void SetDirty(bool value, Room room)
=> _nextRefreshTick = value ? 0 : TickHelper.Add(3072, room.ID, 2048);
diff --git a/Source/PerformanceFish/StatCaching.cs b/Source/PerformanceFish/StatCaching.cs
new file mode 100644
index 0000000..b506195
--- /dev/null
+++ b/Source/PerformanceFish/StatCaching.cs
@@ -0,0 +1,176 @@
+// 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 StatsCache = PerformanceFish.Cache.ByInt;
+
+namespace PerformanceFish;
+
+public sealed class StatCaching : ClassWithFishPrepatches
+{
+ public sealed class SetStatBaseValuePatch : FishPrepatch
+ {
+ public override bool ShowSettings => false;
+
+ public override MethodBase TargetMethodBase { get; } = methodof(StatExtension.SetStatBaseValue);
+
+ public static void Postfix(BuildableDef def, StatDef stat)
+ {
+ if (def is ThingDef thingDef)
+ ResetToDefaults(thingDef.BaseStatsCache());
+
+ ResetToDefaults(def.StatsCache());
+ StatsCache.Clear();
+ }
+ }
+
+ public sealed class GetStatValueAbstractPatch : FishPrepatch
+ {
+ public override string? Description { get; }
+ = "Caching for stats calculated from defs, which can be expected to not change during regular gameplay. "
+ + "Also adds basic exception handling in case of errors in their calculation.";
+
+ public override MethodBase TargetMethodBase { get; }
+ = methodof((Func)StatExtension.GetStatValueAbstract);
+
+ public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
+ => ilProcessor.ReplaceBodyWith(ReplacementBody);
+
+ public static float ReplacementBody(BuildableDef def, StatDef stat, ThingDef? stuff = null)
+ {
+ var statsCache = def.StatsCache();
+ if (statsCache.Length > stat.index)
+ {
+ var cacheValue = stuff is null ? statsCache[stat.index] : GetStuffed(def, stat, stuff);
+
+ if (!float.IsNaN(cacheValue))
+ return cacheValue;
+ }
+
+ return TryGetUpdatedValue(def, stat, stuff);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static float GetStuffed(BuildableDef def, StatDef stat, ThingDef stuff)
+ => StatsCache.GetOrAdd(unchecked((int)DefPair.Create(def, stat)), stuff.shortHash).Value;
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static float TryGetUpdatedValue(BuildableDef def, StatDef stat, ThingDef? stuff)
+ => Current.Game is null ? GetValueSafely(def, stat, stuff) : GetUpdatedValue(def, stat, stuff);
+
+ private static float GetUpdatedValue(BuildableDef def, StatDef stat, ThingDef? stuff)
+ {
+ ref var statsCache = ref def.StatsCache();
+ if (statsCache.Length != DefDatabase.DefCount)
+ statsCache = InitializeBuildableDefStatCache();
+
+ ref var statBucket = ref stuff is null
+ ? ref statsCache[stat.index]
+ : ref StatsCache.GetOrAddReference(unchecked((int)DefPair.Create(def, stat)), stuff.shortHash).Value;
+
+ return statBucket = GetValueSafely(def, stat, stuff);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static float GetValueSafely(BuildableDef def, StatDef stat, ThingDef? stuff = null)
+ {
+ try
+ {
+ return stat.Worker.GetValueAbstract(def, stuff);
+ }
+ catch (Exception ex)
+ {
+ return StatExceptionHandlers.GetOrAdd(stat)(def, stuff, ex);
+ }
+ }
+
+ public static float LogErrorInStat(BuildableDef def, ThingDef? stuff, Exception exception, string name)
+ {
+ Guard.IsNotNull(def);
+ Log.Error($"Exception thrown while calculating {name} for def '{def.defName}' from mod '{
+ def.modContentPack?.Name ?? "null"}'{(stuff != null ? $" with stuff '{stuff.defName}' from mod '{
+ stuff.modContentPack?.Name ?? "null"}'" : "")}.\n{exception}");
+
+ return 0f;
+ }
+
+ public static float[] InitializeBuildableDefStatCache()
+ {
+ TryVerifyIndices();
+ var array = new float[DefDatabase.DefCount];
+ ResetToDefaults(array);
+ return array;
+ }
+
+ public static void ResetToDefaults(float[] statsCache) => Array.Fill(statsCache, float.NaN);
+
+ public static void ResetAllStatsCaches()
+ {
+ var resetAllStatsCachesForDefTypeBaseMethod
+ = methodof(ResetAllStatsCachesForDefType).GetGenericMethodDefinition();
+
+ foreach (var defType in GenDefDatabase.AllDefTypesWithDatabases())
+ {
+ if (defType.IsAssignableTo(typeof(BuildableDef)))
+ resetAllStatsCachesForDefTypeBaseMethod.MakeGenericMethod(defType).Invoke(null, null);
+ }
+
+ StatsCache.Clear();
+ }
+
+ public static void ResetAllStatsCachesForDefType() where T : BuildableDef
+ {
+ var defsList = DefDatabase.AllDefsListForReading;
+ for (var i = 0; i < defsList.Count; i++)
+ {
+ var def = defsList[i];
+ ResetToDefaults(def.StatsCache());
+
+ if (def is ThingDef thingDef)
+ ResetToDefaults(thingDef.BaseStatsCache());
+ }
+ }
+
+ private static void TryVerifyIndices()
+ {
+ if (_verifiedIndices)
+ return;
+
+ VerifyIndices();
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void VerifyIndices()
+ {
+ lock (_verifyIndicesLock)
+ {
+ if (Volatile.Read(ref _verifiedIndices))
+ return;
+
+ try
+ {
+ DefDatabase.SetIndices();
+ }
+ finally
+ {
+ Volatile.Write(ref _verifiedIndices, true);
+ }
+ }
+ }
+
+ private static bool _verifiedIndices;
+ private static object _verifyIndicesLock = new();
+
+ public static FishTable> StatExceptionHandlers
+ = new() { ValueInitializer = static stat => (def, stuff, ex) => LogErrorInStat(def, stuff, ex, stat.label) };
+
+ public record struct CachedStatValue()
+ {
+ public float Value = float.NaN;
+ }
+}
\ No newline at end of file
diff --git a/Source/PerformanceFish/System/ReflectionCaching.cs b/Source/PerformanceFish/System/ReflectionCaching.cs
index 28b453a..846a73b 100644
--- a/Source/PerformanceFish/System/ReflectionCaching.cs
+++ b/Source/PerformanceFish/System/ReflectionCaching.cs
@@ -415,11 +415,9 @@ public sealed class ReflectionCaching : ClassWithFishPatches
public PropertyInfo? Info;
}
- public record struct TypeFullNameCacheValue
+ public record struct TypeFullNameCacheValue()
{
- private object? _value;
-
- public TypeFullNameCacheValue() => _value = typeof(void);
+ private object? _value = typeof(void);
public string? Name
{
diff --git a/Source/PerformanceFish/ThingCompCaching.cs b/Source/PerformanceFish/ThingCompCaching.cs
index 2cb7e74..12a152e 100644
--- a/Source/PerformanceFish/ThingCompCaching.cs
+++ b/Source/PerformanceFish/ThingCompCaching.cs
@@ -55,7 +55,7 @@ public sealed class ThingCompCaching : ClassWithFishPatches
=> IdeoForbidsCache.GetExistingReference(__instance, pawn).Update(__instance, pawn, __result);
}
- public record struct IdeoForbidsCacheValue
+ public record struct IdeoForbidsCacheValue()
{
public bool Result;
private int _nextRefreshTick = -2;
@@ -85,9 +85,5 @@ public sealed class ThingCompCaching : ClassWithFishPatches
private static readonly List _nullRelations = [];
private static readonly List _nullPawns = [];
-
- public IdeoForbidsCacheValue()
- {
- }
}
}
\ No newline at end of file
diff --git a/Source/PerformanceFish/ThingFilterPatches.cs b/Source/PerformanceFish/ThingFilterPatches.cs
index 1ed9a5a..d84db9d 100644
--- a/Source/PerformanceFish/ThingFilterPatches.cs
+++ b/Source/PerformanceFish/ThingFilterPatches.cs
@@ -205,7 +205,7 @@ public sealed class ThingFilterPatches : ClassWithFishPatches
public static void ForceSynchronizeCache(ThingFilter filter)
=> AllowedDefsListCache.GetOrAddReference(filter).Update(ref filter);
- public record struct AllowedDefsListCacheValue : ICacheable, ICacheable
+ public record struct AllowedDefsListCacheValue() : ICacheable, ICacheable
{
public List Defs = [];
private int _nextRefreshTick = -2;
@@ -229,7 +229,7 @@ public sealed class ThingFilterPatches : ClassWithFishPatches
Defs.ReplaceContentsWith(allowedDefs);
- SortDefs(Defs); // why, actually? I don't remember
+ SortDefs(Defs); // TODO: figure out what this was meant for
SetDirty(false, tickOffset + Defs.Count + (key.disallowedSpecialFilters?.Count ?? 0));
}
@@ -240,11 +240,11 @@ public sealed class ThingFilterPatches : ClassWithFishPatches
var defArray = defs._items;
var marketValues = GetTempArrayForSorting(defCount);
for (var i = 0; i < defCount; i++)
- marketValues[i] = GetMarketValueSafely(defArray[i]);
+ marketValues[i] = defArray[i].BaseMarketValue;
- //Array.Sort(marketValues, defs); would require an array of perfect length, generating garbage
+ // Array.Sort(marketValues, defs); has no count argument
- defArray.AsSpan()[..defCount].Sort(marketValues.AsSpan()[..defCount], null);
+ defArray.AsSpan()[..defCount].Sort(marketValues.AsSpan()[..defCount]);
defs._version++;
}
@@ -254,60 +254,5 @@ public sealed class ThingFilterPatches : ClassWithFishPatches
: _tempArrayForSorting = new float[defCount];
private static float[] _tempArrayForSorting = Array.Empty();
-
- private static float GetMarketValueSafely(ThingDef def)
- {
- try
- {
- return def.BaseMarketValue;
- }
- catch (Exception ex)
- {
- return LogAndFixMarketValue(def, ex);
- }
- }
-
- private static float LogAndFixMarketValue(ThingDef def, Exception exception)
- {
- Guard.IsNotNull(def);
- Log.Warning($"Exception thrown while calculating market value for def {def.defName} from mod {
- def.modContentPack?.Name ?? "null"}. Attempting to fix this now so it doesn't happen again.\n{
- exception}");
-
- RecipeDef? recipe;
- try
- {
- recipe = StatWorker_MarketValue.CalculableRecipe(def);
- // this is slow, but gets cached by perf optimizer. Might be worth replicating
- }
- catch (Exception ex)
- {
- Log.Error($"Exception thrown while calculating recipe for def {def.defName} from mod {
- def.modContentPack?.Name ?? "null"}.\n{ex}");
- recipe = null;
- }
-
- if (recipe?.ingredients is { } ingredients)
- ingredients.RemoveAll(static ingredientCount => ingredientCount is null);
-
- if (def.CostList is { } costList)
- costList.RemoveAll(static thingDefCountClass => thingDefCountClass?.thingDef is null);
-
- try
- {
- return def.BaseMarketValue;
- }
- catch (Exception ex)
- {
- Log.Error($"Exception thrown while calculating market value for def {def.defName} from mod {
- def.modContentPack?.Name ?? "null"} again. Fix failed, F.\n{ex}");
-
- return 0f;
- }
- }
-
- public AllowedDefsListCacheValue()
- {
- }
}
}
\ No newline at end of file
diff --git a/Source/PerformanceFish/ThingOwnerUtilityCaching.cs b/Source/PerformanceFish/ThingOwnerUtilityCaching.cs
index 64a36f4..98ed897 100644
--- a/Source/PerformanceFish/ThingOwnerUtilityCaching.cs
+++ b/Source/PerformanceFish/ThingOwnerUtilityCaching.cs
@@ -60,7 +60,7 @@ public sealed class ThingOwnerUtilityPreCaching : ClassWithFishPrepatches
return __state = true;
}
- outThings.ReplaceContentsWith(cache.Things!);
+ outThings.ReplaceContentsWith(cache.Things);
return __state = false;
}
@@ -73,11 +73,7 @@ public sealed class ThingOwnerUtilityPreCaching : ClassWithFishPrepatches
ref var cache = ref Cache.ByInt