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>.Get.GetReference(new(map.uniqueID, (byte)request.group)); - if (cache.Things is null) - cache.Things = outThings.Copy(); - else - cache.Things.ReplaceContentsWith(outThings); - + cache.Things.ReplaceContentsWith(outThings); cache.ListsByGroupCount = map.listerThings.listsByGroup[(int)request.group]?.Count ?? -1; cache.ThingHolderCount = ThingHolderCount(map); cache.SetDirty(false, map.uniqueID + (int)request.group + cache.ListsByGroupCount + cache.ThingHolderCount); @@ -181,12 +177,12 @@ public sealed class ThingOwnerUtilityPreCaching : ClassWithFishPrepatches public bool Dirty => _nextRefreshTick < Current.Game.tickManager.TicksGame; }*/ -public record struct ThingRequestInfo where T : Thing +public record struct ThingRequestInfo() where T : Thing { - private int _nextUpdateTick; - public int ListsByGroupCount; - public int ThingHolderCount; - public List? Things; + private int _nextUpdateTick = -2; + public int ListsByGroupCount = -2; + public int ThingHolderCount = -2; + public readonly List Things = []; public void SetDirty(bool value, int offset) => _nextUpdateTick = value ? 0 : TickHelper.Add(3072, offset, 2048); diff --git a/Source/PerformanceFish/ThoughtWorker_Precept_AltarSharingOptimization.cs b/Source/PerformanceFish/ThoughtWorker_Precept_AltarSharingOptimization.cs index da8d806..86c152c 100644 --- a/Source/PerformanceFish/ThoughtWorker_Precept_AltarSharingOptimization.cs +++ b/Source/PerformanceFish/ThoughtWorker_Precept_AltarSharingOptimization.cs @@ -4,7 +4,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. using SharedAltarCache - = PerformanceFish.Cache.ByIndex; namespace PerformanceFish; @@ -13,6 +13,8 @@ public sealed class ThoughtWorker_Precept_AltarSharingOptimization : ClassWithFi { public sealed class SharedAltar_Patch : FirstPriorityFishPatch { + public override bool Enabled => base.Enabled && ModsConfig.IdeologyActive; + public override string Description { get; } = "Caches results of ideology's shared altar thoughtworker and throttles it to wait a minimum of 128 ticks " + "between refreshes. Quite impactful on performance"; @@ -20,17 +22,17 @@ public sealed class ThoughtWorker_Precept_AltarSharingOptimization : ClassWithFi public override MethodBase TargetMethodInfo { get; } = AccessTools.Method( typeof(ThoughtWorker_Precept_AltarSharing), nameof(ThoughtWorker_Precept_AltarSharing.SharedAltar)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Prefix(ThoughtWorker_Precept_AltarSharing __instance, Pawn pawn, ref Thing? __result, out bool __state) { - if (!pawn.IsSpawned()) + if (!pawn.IsSpawned() || pawn.Ideo is not { } ideo) { __result = null; return __state = false; } - ref var cache = ref SharedAltarCache.Get.GetReference(pawn.Ideo); - + ref var cache = ref SharedAltarCache.GetOrAddReference(ideo.id); if (cache.Dirty) return __state = true; @@ -38,22 +40,31 @@ public sealed class ThoughtWorker_Precept_AltarSharingOptimization : ClassWithFi return __state = false; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Postfix(Pawn pawn, Thing __result, bool __state) { if (!__state) return; - SharedAltarCache.Get[pawn.Ideo] = new(pawn, __result); + UpdateCache(pawn, __result); } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void UpdateCache(Pawn pawn, Thing __result) + => SharedAltarCache.GetExistingReference(pawn.Ideo.id) = new(pawn, __result); } public record struct SharedAltarCacheValue { public Thing? thing; - private int _nextLateRefreshTick; - private int _nextEarlyRefreshTick; - private int _allStructuresListVersion; - private ListerThings _lister; + private int _nextLateRefreshTick = -2; + private int _nextEarlyRefreshTick = -2; + private int _allStructuresListVersion = -2; + private ListerThings _lister = null!; + + public SharedAltarCacheValue() + { + } public SharedAltarCacheValue(Pawn pawn, Thing? result) { diff --git a/Source/PerformanceFish/TickManagerPatches.cs b/Source/PerformanceFish/TickManagerPatches.cs new file mode 100644 index 0000000..e3483ac --- /dev/null +++ b/Source/PerformanceFish/TickManagerPatches.cs @@ -0,0 +1,56 @@ +// 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/. + +#if unused +using PerformanceFish.Prepatching; + +namespace PerformanceFish; + +public sealed class TickManagerPatches : ClassWithFishPrepatches +{ + public sealed class SetCurTimeSpeedPatch : FishPrepatch + { + public override bool ShowSettings => false; + + public override MethodBase TargetMethodBase { get; } + = AccessTools.DeclaredPropertySetter(typeof(TickManager), nameof(TickManager.CurTimeSpeed)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Prefix(TickManager __instance, TimeSpeed value) => TimeSpeedChanging(__instance, value); + } + + public sealed class TogglePausedPatch : FishPrepatch + { + public override bool ShowSettings => false; + + public override MethodBase TargetMethodBase { get; } + = AccessTools.DeclaredMethod(typeof(TickManager), nameof(TickManager.TogglePaused)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Prefix(TickManager __instance) => TimeSpeedChanging(__instance, TimeSpeed.Paused); + } + + public static void TimeSpeedChanging(TickManager tickManager, TimeSpeed value) + { + if (value == tickManager.curTimeSpeed) + return; + + if (value == TimeSpeed.Paused) + GamePausing(); + else if (tickManager.curTimeSpeed == TimeSpeed.Paused) + GameResuming(); + } + + public static void GamePausing() + { + + } + + public static void GameResuming() + { + + } +} +#endif \ No newline at end of file diff --git a/Source/PerformanceFish/Utility/MiscExtensions.cs b/Source/PerformanceFish/Utility/MiscExtensions.cs index 640484a..39b9115 100644 --- a/Source/PerformanceFish/Utility/MiscExtensions.cs +++ b/Source/PerformanceFish/Utility/MiscExtensions.cs @@ -125,6 +125,9 @@ public static class MiscExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsSpawned(this Thing thing) => (uint)thing.mapIndexOrState < (uint)Current.Game.Maps.Count; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetLoadID(this Bill bill) => bill.loadID; + public static void InvokeWhenCellIndicesReady(this Map map, Action action) { if (map.cellIndices != null) diff --git a/Source/PerformanceFish/Utility/PooledArray.cs b/Source/PerformanceFish/Utility/PooledArray.cs index 93b6c18..546c9dd 100644 --- a/Source/PerformanceFish/Utility/PooledArray.cs +++ b/Source/PerformanceFish/Utility/PooledArray.cs @@ -3,15 +3,10 @@ // 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 System.Diagnostics.CodeAnalysis; - namespace PerformanceFish.Utility; public record struct PooledArray : IDisposable, IEnumerable { - private static T[]?[] _buffers = new T[]?[4]; - private static int _bufferCount; - private T[] _array; private int _length; @@ -19,6 +14,16 @@ public record struct PooledArray : IDisposable, IEnumerable public int Length => _length; public int Capacity => _array.Length; + [ThreadStatic] + private static T[]?[]? _buffersThreadStatic; + + [ThreadStatic] + private static int _bufferCountThreadStatic; + + private static object _lockObject = new(); + private static T[]?[] _buffersGlobal = new T[]?[16]; + private static int _bufferCountGlobal; + public T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -41,22 +46,21 @@ public record struct PooledArray : IDisposable, IEnumerable public PooledArray(int length) { - if (_bufferCount < 1) - { - NewBuffer(length); - } - else - { - ref var bucket = ref _buffers[--_bufferCount]; - _array = bucket!; - bucket = null; - if (_array.Length < length) - ResizeBuffer(length); - } + if (_bufferCountThreadStatic == 0) + FetchThroughLock(); + + ref var bucket = ref _buffersThreadStatic![--_bufferCountThreadStatic]; + _array = bucket!; + bucket = null; + if (_array.Length < length) + ResizeBuffer(length); _length = length; } + [MethodImpl(MethodImplOptions.NoInlining)] + private void ResizeBuffer(int length) => Array.Resize(ref _array, Mathf.NextPowerOfTwo(length)); + public PooledArray(ICollection collection) : this(collection.Count) => collection.CopyTo(_array, 0); [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -66,35 +70,78 @@ public record struct PooledArray : IDisposable, IEnumerable return ref _array[index]; } - [MethodImpl(MethodImplOptions.NoInlining)] - private void ResizeBuffer(int length) => Array.Resize(ref _array, Mathf.NextPowerOfTwo(length)); - - [MemberNotNull(nameof(_array))] - [MethodImpl(MethodImplOptions.NoInlining)] - private void NewBuffer(int length) => _array = new T[Mathf.NextPowerOfTwo(length)]; - public void Dispose() { - if (++_bufferCount > _buffers.Length) - ExpandBuffers(_bufferCount); - if (_length > 0) Array.Clear(_array, 0, _length); - _buffers[_bufferCount - 1] = _array; + var buffersThreadStatic = _buffersThreadStatic ??= InitializeBuffersThreadStatic(); + + if (_bufferCountThreadStatic == 16) + PushThroughLock(); + + buffersThreadStatic[_bufferCountThreadStatic++] = _array; } public void Sort(IComparer comparer) => _array.AsSpan()[.._length].Sort(comparer); - private static void ExpandBuffers(int minLength) + [MethodImpl(MethodImplOptions.NoInlining)] + private static void FetchThroughLock() { - var newBuffers = new T[]?[Mathf.NextPowerOfTwo(minLength)]; + var buffersThreadStatic = _buffersThreadStatic ??= InitializeBuffersThreadStatic(); + var createNew = false; + + lock (_lockObject) + { + if (_bufferCountGlobal != 0) + { + for (var i = 8; i-- > 0;) + { + buffersThreadStatic[i] = _buffersGlobal[--_bufferCountGlobal]; + _buffersGlobal[_bufferCountGlobal] = default; + } + } + else + { + createNew = true; + } + } - Array.Copy(_buffers, newBuffers, _buffers.Length); - - _buffers = newBuffers; + if (createNew) + Array.Fill(buffersThreadStatic, Array.Empty(), 0, 8); + + _bufferCountThreadStatic = 8; } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void PushThroughLock() + { + var buffersThreadStatic = _buffersThreadStatic!; + + lock (_lockObject) + { + if (_bufferCountGlobal + 8 > _buffersGlobal.Length) + ExpandGlobalBuffers(); + + for (var i = 16; i-- > 8;) + { + ref var bucket = ref buffersThreadStatic[i]; + var buffer = bucket; + bucket = default; + + _buffersGlobal[_bufferCountGlobal++] = buffer; + } + } + + _bufferCountThreadStatic = 8; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ExpandGlobalBuffers() => Array.Resize(ref _buffersGlobal, _buffersGlobal.Length << 1); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static T[]?[] InitializeBuffersThreadStatic() => new T[]?[16]; + public IEnumerator GetEnumerator() { for (var i = 0; i < _length; i++) diff --git a/Source/PerformanceFish/Utility/StringExtensions.cs b/Source/PerformanceFish/Utility/StringExtensions.cs index 430b6b4..b597f00 100644 --- a/Source/PerformanceFish/Utility/StringExtensions.cs +++ b/Source/PerformanceFish/Utility/StringExtensions.cs @@ -10,6 +10,18 @@ public static class StringExtensions public static bool NameEqualsCaseInsensitive(this MethodInfo methodInfo, string name) => methodInfo.Name.Equals(name, StringComparison.OrdinalIgnoreCase); + public static string AppendNewLineWhen(this string instance, bool predicate, string text) + => predicate ? instance.AppendNewLine(text) : instance; + + public static string AppendNewLineWhenNotNull(this string instance, object? value) + => value != null ? instance.AppendNewLine(value.ToString()) : instance; + + public static string AppendNewLine(this string instance, string text) + => string.Concat(instance, "\n", text); + public static string AppendWhen(this string instance, bool predicate, string text) => predicate ? instance + text : instance; + + public static string AppendWhenNotNull(this string instance, object? value) + => value != null ? instance + value : instance; } \ No newline at end of file diff --git a/Source/PerformanceFish/UtilityPatches.cs b/Source/PerformanceFish/UtilityPatches.cs index ad2e39b..9dedfcd 100644 --- a/Source/PerformanceFish/UtilityPatches.cs +++ b/Source/PerformanceFish/UtilityPatches.cs @@ -49,6 +49,7 @@ public sealed class UtilityPatches : ClassWithFishPatches public static void Postfix() { Cache.Utility.Clear(); + StatCaching.ResetAllStatsCaches(); ParallelNoAlloc.ClearAll(); foreach (var patch in PerformanceFishMod.AllPatchClasses!) {