Added stat caching, fixed all the bugs

Added RitualObligationTriggers analyzer category,
Fixed defs from other mods with broken market value breaking fish's storage patches,
Fixed storage caches not updating for quality, rotting and similar changes to stored things,
Fixed storage caches not updating capacity when reinstalling storage,
Fixed prison labor compatibility,
Improved multiplayer compatibility,
Fixed ideology style dominance and altar sharing patches throwing errors instead of defaulting to disabled when not having ideology,
Fixed compatibility with RIMMSqol's world pawn GC patch by disabling that patch,
Fixed rare threading issues throwing errors on startup
This commit is contained in:
bbradson
2024-03-10 20:05:24 +01:00
parent a4ee95fde1
commit be3dae46bb
45 changed files with 1090 additions and 414 deletions
+78 -14
View File
@@ -25,6 +25,8 @@ public record struct ByInt<T, TResult> where T : notnull where TResult : new()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => Key;
private static List<FishTable<ByInt<T, TResult>, TResult>> _allCaches = [];
private static object _valueInitializerLock = new();
@@ -92,23 +94,65 @@ public record struct ByInt<T, TResult> where T : notnull where TResult : new()
public static ref TResult GetExistingReference(T key) => ref Get.GetReference(new(key));
[MethodImpl(MethodImplOptions.NoInlining)]
private static FishTable<ByInt<T, TResult>, TResult> InitializeNew() => Utility.AddNew(ValueInitializer);
private static FishTable<ByInt<T, TResult>, 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<T1, T2, TResult>
where T1 : notnull where T2 : notnull where TResult : new()
{
private static FishTable<ByInt<T1, T2, TResult>, TResult> _get
= Utility.AddNew<ByInt<T1, T2, TResult>, TResult>();
public int First, Second;
private static List<FishTable<ByInt<T1, T2, TResult>, TResult>> _allCaches = [];
private static object _valueInitializerLock = new();
private static FishTable<ByInt<T1, T2, TResult>, TResult> _get = InitializeNew();
[ThreadStatic]
private static FishTable<ByInt<T1, T2, TResult>, TResult>? _getThreadStatic;
private static Func<ByInt<T1, T2, TResult>, TResult>? _valueInitializer;
public static Func<ByInt<T1, T2, TResult>, TResult>? ValueInitializer
{
get
{
lock (_valueInitializerLock)
return _valueInitializer;
}
set
{
lock (_valueInitializerLock)
{
value ??= static _ => Reflection.New<TResult>();
_valueInitializer = value;
_get.ValueInitializer = value;
}
}
}
public static FishTable<ByInt<T1, T2, TResult>, TResult> Get
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= Utility.AddNew<ByInt<T1, T2, TResult>, TResult>();
get => _getThreadStatic ??= InitializeNew();
}
private static ref FishTable<ByInt<T1, T2, TResult>, TResult> GetCacheRef
@@ -135,10 +179,12 @@ public record struct ByInt<T1, T2, TResult>
public static ref TResult GetOrAddReference(in ByInt<T1, T2, TResult> 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<FishTable<ByInt<T1, T2, TResult>, TResult>, FishTable<long, TResult>>(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<T1, T2, TResult> 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<T1, T2, TResult>
First = first;
Second = second;
}
public int First, Second;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByInt<T1, T2, TResult> other)
@@ -170,12 +214,34 @@ public record struct ByInt<T1, T2, TResult>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(First, Second);
[MethodImpl(MethodImplOptions.NoInlining)]
private static FishTable<ByInt<T1, T2, TResult>, 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<T1, T2, T3, TResult>
where T1 : notnull where T2 : notnull where T3 : notnull where TResult : new()
{
public int First, Second, Third;
private static FishTable<ByInt<T1, T2, T3, TResult>, TResult> _get
= Utility.AddNew<ByInt<T1, T2, T3, TResult>, TResult>();
@@ -220,8 +286,6 @@ public record struct ByInt<T1, T2, T3, TResult>
Second = FunctionPointers.IndexGetter<T2>.Default(second);
Third = FunctionPointers.IndexGetter<T3>.Default(third);
}
public int First, Second, Third;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByInt<T1, T2, T3, TResult> other)
@@ -238,6 +302,8 @@ public record struct ByInt<T1, T2, T3, T4, TResult>
where T1 : notnull where T2 : notnull where T3 : notnull where T4 : notnull
where TResult : new()
{
public int First, Second, Third, Fourth;
private static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult> _get
= Utility.AddNew<ByInt<T1, T2, T3, T4, TResult>, TResult>();
@@ -287,8 +353,6 @@ public record struct ByInt<T1, T2, T3, T4, TResult>
Fourth = FunctionPointers.IndexGetter<T4>.Default(fourth);
}
public int First, Second, Third, Fourth;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByInt<T1, T2, T3, T4, TResult> other)
=> (Unsafe.As<ByInt<T1, T2, T3, T4, TResult>, long>(ref this)
+42
View File
@@ -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;
}
@@ -52,10 +52,10 @@ public sealed class RecipeDefCaching : ClassWithFishPatches
private static void UpdateCache(RecipeDef __instance, IEnumerable<ThingDef> __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<ThingDef> RecipeUsers;
@@ -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
}
}
@@ -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();
}
+17 -1
View File
@@ -11,7 +11,8 @@ public sealed class ThingEvents : ClassWithFishPrepatches
{
public static event Action<Thing>?
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);
}
@@ -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;
}
+18 -34
View File
@@ -51,9 +51,9 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
return null;
}
public record struct CacheValue<T> where T : ThingComp
public record struct CacheValue<T>() 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<T> where T : CompProperties
public record struct CacheValue<T>() 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<T> where T : HediffComp
public record struct CacheValue<T>() 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<T> where T : HediffCompProperties
public record struct CacheValue<T>() 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<T> where T : AbilityComp
public record struct CacheValue<T>() 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<T> where T : WorldObjectComp
public record struct CacheValue<T>() 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<T> where T : MapComponent
public record struct CacheValue<T>() 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<T> 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<T> where T : GameComponent
{
private static int _listVersion = -1;
private static int _listVersion = -2;
private static Game? _game;
public static T? Comp;
@@ -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()
{
}
}
@@ -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();
}
}
@@ -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<Action> 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<int, int> AllowedToAccept = new();
public FishTable<int, AllowedToAcceptCacheValue> AllowedToAccept = new();
private Thing? _parent;
@@ -129,15 +144,10 @@ public sealed class StorageSettingsPatches : ClassWithFishPatches
public List<Thing> 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
@@ -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<Haulables.TickPatch>().IsActive.AsInt()).AsBool();
public static class Debug
{
[Conditional("STORAGE_GROUP_DEBUG")]
@@ -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<BodyPartRecord> __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<Hediff> _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<Hediff> _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()
{
}
}
}
@@ -56,19 +56,10 @@ public sealed class ImmunityHandlerCaching : ClassWithFishPatches
public sealed class ImmunityRecordExists : FishPatch
{
public override List<Type> 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<TryAddImmunityRecord>().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<Type> 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<ImmunityRecordExists>().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<Hediff> _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;
}
}
+3 -3
View File
@@ -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)
{
@@ -14,7 +14,7 @@ using IngredientCacheList
bool Found)>;
using OpCodes = System.Reflection.Emit.OpCodes;
using RecipeIngredientCache
= PerformanceFish.Cache.ByIndex<RimWorld.Bill,
= PerformanceFish.Cache.ByInt<RimWorld.Bill,
PerformanceFish.JobSystem.WorkGiver_DoBillOptimization.RecipeIngredientCacheValue>;
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);
+1 -5
View File
@@ -225,7 +225,7 @@ public sealed class Buildings : ClassWithFishPrepatches
}
}
public readonly record struct Cache
public readonly record struct Cache()
{
public readonly FishTable<ThingDef, IndexedFishSet<Building>>
ColonistBuildingsByDef = new(),
@@ -240,9 +240,5 @@ public sealed class Buildings : ClassWithFishPrepatches
ValueInitializer = static type
=> (IList)Activator.CreateInstance(typeof(IndexedFishSet<>).MakeGenericType(type))
};
public Cache()
{
}
}
}
+27 -6
View File
@@ -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<string> 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<Thing> thingsQueuedToRemove)
{
foreach (var thing in thingsQueuedToRemove)
__instance.TryRemoveDirectly(thing);
thingsQueuedToRemove.Clear();
}
}
public record struct Cache
public record struct Cache()
{
public readonly FishSet<int> ContainedThings = [];
public readonly HashSet<Thing> ThingsQueuedToRemove = [];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CheckUpdate(List<Thing> haulables)
{
@@ -140,10 +165,6 @@ public sealed class Haulables : ClassWithFishPrepatches
for (var i = haulables.Count; i-- > 0;)
ContainedThings.Add(haulables[i].GetKey());
}
public Cache()
{
}
}
}
+15 -12
View File
@@ -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)
@@ -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)
@@ -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);
@@ -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";
}
@@ -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);
}
}
}
@@ -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
+2
View File
@@ -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
@@ -17,7 +17,7 @@
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<Configurations>v1.4</Configurations>
<Version>0.3.999.92</Version>
<Version>0.5.0.0</Version>
<NoWarn>
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 @@
</When -->
</Choose>
<ItemGroup>
<PackageReference Include="Lib.Harmony" Version="2.*" ExcludeAssets="runtime" />
<PackageReference Include="Lib.Harmony" Version="2.2.*" ExcludeAssets="runtime" />
<PackageReference Include="Zetrith.Prepatcher" Version="1.*" ExcludeAssets="runtime" />
<PackageReference Include="Krafs.Publicizer" Version="2.*" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.*" ExcludeAssets="runtime" /><!-- Version included in Unity is missing IsNullRef -->
+1 -1
View File
@@ -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();
@@ -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;
}
}
@@ -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<float>();
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<Action>());
+23 -37
View File
@@ -6,11 +6,11 @@
using System.Linq;
using System.Reflection.Emit;
using RoomOwnersCache
= PerformanceFish.Cache.ByIndex<Verse.Room, PerformanceFish.RoomOptimizations.Owners_Patch.CacheValue>;
= PerformanceFish.Cache.ByInt<Verse.Room, PerformanceFish.RoomOptimizations.Owners_Patch.CacheValue>;
using RoomRoleCache
= PerformanceFish.Cache.ByIndex<Verse.Room, PerformanceFish.RoomOptimizations.Role_Patch.CacheValue>;
= PerformanceFish.Cache.ByInt<Verse.Room, PerformanceFish.RoomOptimizations.Role_Patch.CacheValue>;
using ContainedAndAdjacentThingsCache
= PerformanceFish.Cache.ByIndex<Verse.Room,
= PerformanceFish.Cache.ByInt<Verse.Room,
PerformanceFish.RoomOptimizations.ContainedAndAdjacentThings_Patch.CacheValue>;
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<Pawn> __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<Pawn> __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<Pawn> Owners;
private int _nextRefreshTick = -2;
public readonly List<Pawn> Owners = [];
public void Update(Room room, IEnumerable<Pawn>? 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<int>? ListVersions;
public List<Thing> Things;
private int _nextRefreshTick = -2;
public List<int> ListVersions = [];
public List<Thing> Things = [];
public void SetDirty(bool value, Room room)
=> _nextRefreshTick = value ? 0 : TickHelper.Add(3072, room.ID, 2048);
+176
View File
@@ -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<uint, ushort, PerformanceFish.StatCaching.CachedStatValue>;
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<BuildableDef, StatDef, ThingDef, float>)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<StatDef>.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<StatDef>.DefCount];
ResetToDefaults(array);
return array;
}
public static void ResetToDefaults(float[] statsCache) => Array.Fill(statsCache, float.NaN);
public static void ResetAllStatsCaches()
{
var resetAllStatsCachesForDefTypeBaseMethod
= methodof(ResetAllStatsCachesForDefType<BuildableDef>).GetGenericMethodDefinition();
foreach (var defType in GenDefDatabase.AllDefTypesWithDatabases())
{
if (defType.IsAssignableTo(typeof(BuildableDef)))
resetAllStatsCachesForDefTypeBaseMethod.MakeGenericMethod(defType).Invoke(null, null);
}
StatsCache.Clear();
}
public static void ResetAllStatsCachesForDefType<T>() where T : BuildableDef
{
var defsList = DefDatabase<T>.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<StatDef>.SetIndices();
}
finally
{
Volatile.Write(ref _verifiedIndices, true);
}
}
}
private static bool _verifiedIndices;
private static object _verifyIndicesLock = new();
public static FishTable<StatDef, Func<BuildableDef, ThingDef?, Exception, float>> StatExceptionHandlers
= new() { ValueInitializer = static stat => (def, stuff, ex) => LogErrorInStat(def, stuff, ex, stat.label) };
public record struct CachedStatValue()
{
public float Value = float.NaN;
}
}
@@ -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
{
+1 -5
View File
@@ -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<DirectPawnRelation> _nullRelations = [];
private static readonly List<Pawn> _nullPawns = [];
public IdeoForbidsCacheValue()
{
}
}
}
+5 -60
View File
@@ -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<ThingFilter>, ICacheable<ThingFilter, int>
public record struct AllowedDefsListCacheValue() : ICacheable<ThingFilter>, ICacheable<ThingFilter, int>
{
public List<ThingDef> 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<float>();
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()
{
}
}
}
@@ -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<Map, ThingRequestGroup, ThingRequestInfo<T>>.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<T> where T : Thing
public record struct ThingRequestInfo<T>() where T : Thing
{
private int _nextUpdateTick;
public int ListsByGroupCount;
public int ThingHolderCount;
public List<T>? Things;
private int _nextUpdateTick = -2;
public int ListsByGroupCount = -2;
public int ThingHolderCount = -2;
public readonly List<T> Things = [];
public void SetDirty(bool value, int offset)
=> _nextUpdateTick = value ? 0 : TickHelper.Add(3072, offset, 2048);
@@ -4,7 +4,7 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
using SharedAltarCache
= PerformanceFish.Cache.ByIndex<RimWorld.Ideo,
= PerformanceFish.Cache.ByInt<RimWorld.Ideo,
PerformanceFish.ThoughtWorker_Precept_AltarSharingOptimization.SharedAltarCacheValue>;
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)
{
@@ -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
@@ -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<Map> action)
{
if (map.cellIndices != null)
+80 -33
View File
@@ -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<T> : IDisposable, IEnumerable<T>
{
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<T> : IDisposable, IEnumerable<T>
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<T> : IDisposable, IEnumerable<T>
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<T> collection) : this(collection.Count) => collection.CopyTo(_array, 0);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -66,35 +70,78 @@ public record struct PooledArray<T> : IDisposable, IEnumerable<T>
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<T> 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<T>(), 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<T> GetEnumerator()
{
for (var i = 0; i < _length; i++)
@@ -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;
}
+1
View File
@@ -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!)
{