diff --git a/1.5/Assemblies/PerformanceFish.dll b/1.5/Assemblies/PerformanceFish.dll
index 24304e8..eacfdc0 100644
Binary files a/1.5/Assemblies/PerformanceFish.dll and b/1.5/Assemblies/PerformanceFish.dll differ
diff --git a/1.5/Assemblies/PerformanceFish.pdb b/1.5/Assemblies/PerformanceFish.pdb
index 312e985..4771ea9 100644
Binary files a/1.5/Assemblies/PerformanceFish.pdb and b/1.5/Assemblies/PerformanceFish.pdb differ
diff --git a/1.6/Assemblies/PerformanceFish.dll b/1.6/Assemblies/PerformanceFish.dll
new file mode 100644
index 0000000..6c995e0
Binary files /dev/null and b/1.6/Assemblies/PerformanceFish.dll differ
diff --git a/1.6/Assemblies/PerformanceFish.pdb b/1.6/Assemblies/PerformanceFish.pdb
new file mode 100644
index 0000000..b83ab76
Binary files /dev/null and b/1.6/Assemblies/PerformanceFish.pdb differ
diff --git a/About/About.xml b/About/About.xml
index 4817d54..78aa9e3 100644
--- a/About/About.xml
+++ b/About/About.xml
@@ -5,6 +5,7 @@
1.4
1.5
+ 1.6
bradson
PerformanceFish/ModIcon
@@ -56,4 +57,4 @@
vanillaexpanded.storytellers.rick
download more tps
-
\ No newline at end of file
+
diff --git a/Source/PerformanceFish/1.5.csproj b/Source/PerformanceFish/1.5.csproj
index baf5a13..7379b8e 100644
--- a/Source/PerformanceFish/1.5.csproj
+++ b/Source/PerformanceFish/1.5.csproj
@@ -3,6 +3,8 @@
..\..\1.5\Assemblies\
Copyright (c) 2024 Bradson
+ $(MSBuildThisFileDirectory)..\..\..\Fishery
+ $(MSBuildThisFileDirectory)..\..\..\Fishery-clean
DEBUG;V1_5
@@ -14,7 +16,7 @@
- ..\..\..\Fishery\1.5\Assemblies\1Fishery.dll
+ $(FisheryRepoRoot)\1.5\Assemblies\1Fishery.dll
False
diff --git a/Source/PerformanceFish/1.6.csproj b/Source/PerformanceFish/1.6.csproj
new file mode 100644
index 0000000..1478547
--- /dev/null
+++ b/Source/PerformanceFish/1.6.csproj
@@ -0,0 +1,23 @@
+
+
+
+ ..\..\1.6\Assemblies\
+ Copyright (c) 2026 Bradson
+ $(MSBuildThisFileDirectory)..\..\..\Fishery
+ $(MSBuildThisFileDirectory)..\..\..\Fishery-clean
+
+
+ DEBUG;V1_5;V1_6
+
+
+ V1_5;V1_6
+
+
+
+
+
+ $(FisheryRepoRoot)\1.6\Assemblies\1Fishery.dll
+ False
+
+
+
diff --git a/Source/PerformanceFish/Cache/BitCellGrid.cs b/Source/PerformanceFish/Cache/BitCellGrid.cs
index 9e99ea6..1027cc4 100644
--- a/Source/PerformanceFish/Cache/BitCellGrid.cs
+++ b/Source/PerformanceFish/Cache/BitCellGrid.cs
@@ -7,13 +7,15 @@ namespace PerformanceFish.Cache;
public sealed class BitCellGrid
{
+ private readonly Map _map;
private long[] _innerArray;
private CellIndices _cellIndices;
public BitCellGrid(Map map)
{
- _innerArray = null!;
- _cellIndices = null!;
+ _map = map;
+ _innerArray = Array.Empty();
+ _cellIndices = default;
map.InvokeWhenCellIndicesReady(Initialize);
}
@@ -23,12 +25,26 @@ public sealed class BitCellGrid
_innerArray = new long[(Math.Max(0, _cellIndices.NumGridCells - 1) >> 6) + 1];
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void EnsureInitialized()
+ {
+ if (_cellIndices.NumGridCells != _map.cellIndices.NumGridCells)
+ Initialize(_map);
+ }
+
public bool this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => (_innerArray[index >> 6] & (1L << (index & 63))) != 0L;
+ get
+ {
+ EnsureInitialized();
+ ValidateIndex(index);
+ return (_innerArray[index >> 6] & (1L << (index & 63))) != 0L;
+ }
set
{
+ EnsureInitialized();
+ ValidateIndex(index);
ref var bucket = ref _innerArray[index >> 6];
bucket ^= (-(long)value.AsInt() ^ bucket) & (1L << (index & 63));
}
@@ -37,7 +53,24 @@ public sealed class BitCellGrid
public bool this[in IntVec3 c]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => this[c.CellToIndex(_cellIndices)];
- set => this[c.CellToIndex(_cellIndices)] = value;
+ get
+ {
+ EnsureInitialized();
+ return this[c.CellToIndex(_cellIndices)];
+ }
+ set
+ {
+ EnsureInitialized();
+ this[c.CellToIndex(_cellIndices)] = value;
+ }
}
-}
\ No newline at end of file
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void ValidateIndex(int index)
+ {
+ if ((uint)index >= (uint)_cellIndices.NumGridCells)
+ {
+ throw new IndexOutOfRangeException($"BitCellGrid index {index} out of bounds for {_cellIndices.NumGridCells} cells on map size {_map.Size}.");
+ }
+ }
+}
diff --git a/Source/PerformanceFish/Cache/CellGrid.cs b/Source/PerformanceFish/Cache/CellGrid.cs
index d83ad3f..9d964e5 100644
--- a/Source/PerformanceFish/Cache/CellGrid.cs
+++ b/Source/PerformanceFish/Cache/CellGrid.cs
@@ -9,38 +9,60 @@ namespace PerformanceFish.Cache;
public sealed class CellGrid
{
+ private readonly Map _map;
private T[] _innerArray;
private CellIndices _cellIndices;
public CellGrid(Map map)
{
- _innerArray = null!;
- _cellIndices = null!;
+ _map = map;
+ _innerArray = Array.Empty();
+ _cellIndices = default;
map.InvokeWhenCellIndicesReady(Initialize);
}
- [MemberNotNull(nameof(_innerArray), nameof(_cellIndices))]
+ [MemberNotNull(nameof(_innerArray))]
private void Initialize(Map map)
{
_cellIndices = map.cellIndices;
_innerArray = new T[_cellIndices.NumGridCells];
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void EnsureInitialized()
+ {
+ if (_cellIndices.NumGridCells != _map.cellIndices.NumGridCells)
+ Initialize(_map);
+ }
+
public ref T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => ref _innerArray[index];
+ get
+ {
+ EnsureInitialized();
+ return ref _innerArray[index];
+ }
}
public ref T this[in IntVec3 c]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => ref _innerArray[c.CellToIndex(_cellIndices)];
+ get
+ {
+ EnsureInitialized();
+ var index = c.CellToIndex(_cellIndices);
+ return ref _innerArray[index];
+ }
}
public ref T this[CellIndex cellIndex]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => ref _innerArray[cellIndex.Value];
+ get
+ {
+ EnsureInitialized();
+ return ref _innerArray[cellIndex.Value];
+ }
}
-}
\ No newline at end of file
+}
diff --git a/Source/PerformanceFish/CellIndex.cs b/Source/PerformanceFish/CellIndex.cs
index 731b6c4..e2f544f 100644
--- a/Source/PerformanceFish/CellIndex.cs
+++ b/Source/PerformanceFish/CellIndex.cs
@@ -10,20 +10,25 @@ public record struct CellIndex(int Value)
public int Value = Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public CellIndex(IntVec3 cell, Map map) : this(cell, map.cellIndices.mapSizeX) {}
+ public CellIndex(IntVec3 cell, Map map) : this(cell, map.Size.x) {}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CellIndex(IntVec3 cell, int mapSizeX) : this(cell.CellToIndex(mapSizeX)) {}
- public IntVec3 ToCell(Map map) => ToCell(map.cellIndices.mapSizeX);
+ public IntVec3 ToCell(Map map) => ToCell(map.Size.x);
public IntVec3 ToCell(int mapSizeX) => CellIndicesUtility.IndexToCell(Value, mapSizeX);
public List GetThingList(Map map) => map.thingGrid.thingGrid[Value];
- public bool IsFogged(Map map) => map.fogGrid.fogGrid[Value];
+ public bool IsFogged(Map map)
+#if V1_6
+ => map.fogGrid.fogGrid.IsSet(Value);
+#else
+ => map.fogGrid.fogGrid[Value];
+#endif
public float GetSnowDepth(Map map) => map.snowGrid.depthGrid[Value];
public Building? GetEdifice(Map map) => map.edificeGrid[Value];
-}
\ No newline at end of file
+}
diff --git a/Source/PerformanceFish/Defs/ThingDefPatches.cs b/Source/PerformanceFish/Defs/ThingDefPatches.cs
index ffc0c67..ac98f4d 100644
--- a/Source/PerformanceFish/Defs/ThingDefPatches.cs
+++ b/Source/PerformanceFish/Defs/ThingDefPatches.cs
@@ -21,13 +21,25 @@ public sealed class ThingDefPatches : ClassWithFishPrepatches
public sealed class GetHashCodePatch : FishClassPrepatch
{
public override string? Description { get; }
+#if V1_6
+ = "Disabled on 1.6 because Verse.Def now already caches defNameHash and overrides GetHashCode itself.";
+#else
= "Def.GetHashCode is overridden to return defName.GetHashCode, which is rather slow as that's a string. "
+ "This patch caches the hashcode in a field added through prepatcher and simply returns that instead.";
+#endif
- public override Type Type { get; } = typeof(BuildableDef);
+ public override Type Type { get; } =
+#if V1_6
+ typeof(Def);
+#else
+ typeof(BuildableDef);
+#endif
public override void FreePatch(TypeDefinition typeDefinition)
{
+#if V1_6
+ return;
+#else
var module = typeDefinition.Module;
var fishHashField = new FieldDefinition(FISH_HASH_FIELD_NAME, FieldAttributes.Private,
@@ -74,6 +86,7 @@ public sealed class ThingDefPatches : ClassWithFishPrepatches
getHashCodeBody.MaxStackSize = 2;
getHashCodeBody.Method.ImplAttributes |= MethodImplAttributes.AggressiveInlining;
getHashCodeBody.OptimizeMacros();
+#endif
}
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -295,4 +308,4 @@ public sealed class ThingDefPatches : ClassWithFishPrepatches
Flammability,
MaxHitPoints
}
-}
\ No newline at end of file
+}
diff --git a/Source/PerformanceFish/Directory.Build.props b/Source/PerformanceFish/Directory.Build.props
index cc90b52..84db8c9 100644
--- a/Source/PerformanceFish/Directory.Build.props
+++ b/Source/PerformanceFish/Directory.Build.props
@@ -29,12 +29,13 @@
-
+
+
@@ -93,4 +94,4 @@
-
\ No newline at end of file
+
diff --git a/Source/PerformanceFish/GasGridOptimization.cs b/Source/PerformanceFish/GasGridOptimization.cs
index 3b5e3bf..a69bd8c 100644
--- a/Source/PerformanceFish/GasGridOptimization.cs
+++ b/Source/PerformanceFish/GasGridOptimization.cs
@@ -41,7 +41,8 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
= "Required by the gas grid optimization";
public override MethodBase TargetMethodBase { get; }
- = AccessTools.DeclaredMethod(typeof(GasGrid), nameof(GasGrid.SetDirect));
+ = AccessTools.DeclaredMethod(typeof(GasGrid), nameof(GasGrid.SetDirect),
+ [typeof(int), typeof(byte), typeof(byte), typeof(byte), typeof(byte)])!;
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
@@ -415,7 +416,7 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static int GetMapSizeX(Map map) => map.cellIndices.mapSizeX;
+ public static int GetMapSizeX(Map map) => map.Size.x;
}
public sealed class ExposeDataPatch : FishPrepatch
@@ -1075,7 +1076,7 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private IntVec3 IndexToCell(int index)
- => CellIndicesUtility.IndexToCell(index, Map.cellIndices.mapSizeX);
+ => CellIndicesUtility.IndexToCell(index, Map.Size.x);
///
/// For modders. Performance Fish doesn't automatically scribe added custom defs.
@@ -1136,4 +1137,4 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
}
// ReSharper restore UnusedMember.Local
}
-}
\ No newline at end of file
+}
diff --git a/Source/PerformanceFish/GenTypesPatches.cs b/Source/PerformanceFish/GenTypesPatches.cs
index 105cc95..e4f48f2 100644
--- a/Source/PerformanceFish/GenTypesPatches.cs
+++ b/Source/PerformanceFish/GenTypesPatches.cs
@@ -43,14 +43,15 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches
public static List ReplacementBody() where TAttr : Attribute
{
+ var attributeType = typeof(TAttr);
lock (Lock)
{
- var typesWithAttribute = GenTypes.cachedTypesWithAttribute.TryGetValue(typeof(TAttr));
+ var typesWithAttribute = GenTypes.cachedTypesWithAttribute.TryGetValue(attributeType);
if (typesWithAttribute is null)
{
- GenTypes.cachedTypesWithAttribute.Add(typeof(TAttr),
- typesWithAttribute = GenTypes.AllTypes.AsParallel()
+ GenTypes.cachedTypesWithAttribute.Add(attributeType,
+ typesWithAttribute = GenTypes.AllTypes
.Where(Predicate()).ToList());
}
@@ -82,23 +83,14 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches
if (subclasses is null)
{
- ((PredicateClass)Predicate.Target).BaseType = baseType;
GenTypes.cachedSubclasses.Add(baseType,
- subclasses = GenTypes.AllTypes.AsParallel()
- .Where(Predicate).ToList());
+ subclasses = GenTypes.AllTypes
+ .Where(type => type.IsSubclassOf(baseType)).ToList());
}
return subclasses;
}
}
-
- public static Func Predicate = new PredicateClass().Invoke;
-
- public sealed class PredicateClass
- {
- public Type? BaseType;
- public bool Invoke(Type type) => type.IsSubclassOf(BaseType!);
- }
}
public sealed class AllSubclassesNonAbstract : FishPrepatch
@@ -123,7 +115,7 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches
if (subclassesNonAbstract is null)
{
GenTypes.cachedSubclassesNonAbstract.Add(baseType,
- subclassesNonAbstract = baseType.AllSubclasses().AsParallel()
+ subclassesNonAbstract = baseType.AllSubclasses()
.Where(Predicate).ToList());
}
@@ -133,4 +125,4 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches
public static Func Predicate = static subClass => !subClass.IsAbstract;
}
-}
\ No newline at end of file
+}
diff --git a/Source/PerformanceFish/GeneTrackerOptimization.cs b/Source/PerformanceFish/GeneTrackerOptimization.cs
index 242358d..87d88d8 100644
--- a/Source/PerformanceFish/GeneTrackerOptimization.cs
+++ b/Source/PerformanceFish/GeneTrackerOptimization.cs
@@ -14,6 +14,16 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
{
public sealed class GeneTrackerTickPatch : FishPrepatch
{
+ private static readonly FishTable _skippableTypes;
+
+ static GeneTrackerTickPatch()
+ {
+ _skippableTypes = new()
+ {
+ ValueInitializer = static type => ComputeIsSkippableType(type)
+ };
+ }
+
public override string? Description { get; }
= "Every pawn has a gene tracker, which is responsible for ticking each of their genes. Normally it ticks "
+ "all of them equally, including those don't change or affect anything through ticking, like skin colors "
@@ -74,7 +84,7 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
for (var i = 0; i < count; i++)
{
var gene = genes[i];
- if (SkippableTypes.Contains(gene.GetType())
+ if (IsSkippableType(gene.GetType())
&& (gene.def.mentalBreakMtbDays <= 0f || gene.def.mentalBreakDef == null))
{
continue;
@@ -83,8 +93,15 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
genesToTick.Add(gene);
}
}
-
- public static HashSet SkippableTypes
- = typeof(Gene).SubclassesWithNoMethodOverrideAndSelf(nameof(Gene.Tick)).ToHashSet();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsSkippableType(Type type) => _skippableTypes.GetOrAdd(type);
+
+ private static bool ComputeIsSkippableType(Type type)
+ => type == typeof(Gene)
+ || type.GetMethod(nameof(Gene.Tick), global::System.Reflection.BindingFlags.Instance
+ | global::System.Reflection.BindingFlags.Public
+ | global::System.Reflection.BindingFlags.NonPublic
+ | global::System.Reflection.BindingFlags.DeclaredOnly, null, Type.EmptyTypes, null) == null;
}
-}
\ No newline at end of file
+}
diff --git a/Source/PerformanceFish/GlobalUsings.cs b/Source/PerformanceFish/GlobalUsings.cs
new file mode 100644
index 0000000..ee3aa06
--- /dev/null
+++ b/Source/PerformanceFish/GlobalUsings.cs
@@ -0,0 +1 @@
+global using HashCode = FisheryLib.HashCode;
diff --git a/Source/PerformanceFish/Listers/Buildings.cs b/Source/PerformanceFish/Listers/Buildings.cs
index 32cae14..bba1cdd 100644
--- a/Source/PerformanceFish/Listers/Buildings.cs
+++ b/Source/PerformanceFish/Listers/Buildings.cs
@@ -237,6 +237,11 @@ public sealed class Buildings : ClassWithFishPrepatches
if (def.building is { isNaturalRock: true })
return;
+ UpdateCacheOnAdd(__instance, b, def);
+ }
+
+ private static void UpdateCacheOnAdd(ListerBuildings __instance, Building b, ThingDef def)
+ {
var cache = __instance.Cache();
if (b.Faction == Faction.OfPlayer)
@@ -290,6 +295,11 @@ public sealed class Buildings : ClassWithFishPrepatches
if (def.building is { isNaturalRock: true })
return;
+ UpdateCacheOnRemove(__instance, b, def);
+ }
+
+ private static void UpdateCacheOnRemove(ListerBuildings __instance, Building b, ThingDef def)
+ {
var cache = __instance.Cache();
if (b.Faction == Faction.OfPlayer)
@@ -334,4 +344,4 @@ public sealed class Buildings : ClassWithFishPrepatches
=> (IList)Activator.CreateInstance(typeof(IndexedFishSet<>).MakeGenericType(type))
};
}
-}
\ No newline at end of file
+}
diff --git a/Source/PerformanceFish/Listers/Haulables.cs b/Source/PerformanceFish/Listers/Haulables.cs
index 6fd8c4f..384c511 100644
--- a/Source/PerformanceFish/Listers/Haulables.cs
+++ b/Source/PerformanceFish/Listers/Haulables.cs
@@ -183,22 +183,22 @@ public sealed class Haulables : ClassWithFishPrepatches
public readonly HashSet ThingsQueuedToRemove = [];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void CheckUpdate(List haulables)
+ public void CheckUpdate(ICollection haulables)
{
if (IsDirty(haulables))
Update(haulables);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool IsDirty(List haulables) => haulables.Count != ContainedThings.Count;
+ public bool IsDirty(ICollection haulables) => haulables.Count != ContainedThings.Count;
[MethodImpl(MethodImplOptions.NoInlining)]
- public void Update(List haulables)
+ public void Update(ICollection haulables)
{
ContainedThings.Clear();
- for (var i = haulables.Count; i-- > 0;)
- ContainedThings.Add(haulables[i].GetKey());
+ foreach (var haulable in haulables)
+ ContainedThings.Add(haulable.GetKey());
}
}
}
@@ -232,4 +232,4 @@ public static class ListerHaulablesExtensions
if (cache.ContainedThings.Remove(t.GetKey()))
haulables.Remove(t);
}
-}
\ No newline at end of file
+}
diff --git a/Source/PerformanceFish/Listers/Things.cs b/Source/PerformanceFish/Listers/Things.cs
index 60fc1bb..968a7d7 100644
--- a/Source/PerformanceFish/Listers/Things.cs
+++ b/Source/PerformanceFish/Listers/Things.cs
@@ -568,9 +568,25 @@ public sealed class Things : ClassWithFishPatches
public static unsafe IndexedFishSet InitializeThingsList() where T : Thing
#pragma warning disable CS8622
- => new(static () => new(0, keyHashCodeByRefGetter: &GetKeyByRef, keyHashCodeGetter: &ThingHelper.GetKey));
+ => new(static () => new(0,
+ keyHashCodeByRefGetter: &GetKeyByRef,
+ keyHashCodeGetter: &ThingHelper.GetKey,
+ keyEqualityByRefComparer: &EqualsByKeyByRef,
+ keyEqualityComparer: &EqualsByKey));
#pragma warning restore CS8622
private static int GetKeyByRef(ref T thing) where T : Thing => thing.GetKey();
+
+ private static bool EqualsByKey(T? left, T? right) where T : Thing
+ => ReferenceEquals(left, right)
+ || left is not null
+ && right is not null
+ && left.GetKey() == right.GetKey();
+
+ private static bool EqualsByKeyByRef(ref T? left, ref T? right) where T : Thing
+ => ReferenceEquals(left, right)
+ || left is not null
+ && right is not null
+ && left.GetKey() == right.GetKey();
}
-}
\ No newline at end of file
+}
diff --git a/Source/PerformanceFish/MiscOptimizations.cs b/Source/PerformanceFish/MiscOptimizations.cs
index bde9c27..2b7287e 100644
--- a/Source/PerformanceFish/MiscOptimizations.cs
+++ b/Source/PerformanceFish/MiscOptimizations.cs
@@ -114,17 +114,30 @@ public sealed class MiscOptimizations : ClassWithFishPatches
= "Throttles rotting on misconfigured defs to only recalculate every 256 ticks, instead of constantly.";
public override MethodBase TargetMethodInfo { get; }
+#if V1_6
+ = AccessTools.DeclaredMethod(typeof(RimWorld.CompRottable), nameof(RimWorld.CompRottable.CompTickInterval),
+ [typeof(int)])!;
+#else
= AccessTools.Method(typeof(RimWorld.CompRottable), nameof(RimWorld.CompRottable.CompTick));
+#endif
public static CodeInstructions Transpiler(CodeInstructions codes, ILGenerator generator)
=> Reflection.GetCodeInstructions(Replacement, generator);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
+#if V1_6
+ public static void Replacement(RimWorld.CompRottable instance, int delta)
+ {
+ if ((TickHelper.TicksGame & 255) == (instance.parent.thingIDNumber & 255))
+ instance.TickInterval(256);
+ }
+#else
public static void Replacement(RimWorld.CompRottable instance)
{
if ((TickHelper.TicksGame & 255) == (instance.parent.thingIDNumber & 255))
instance.Tick(256);
}
+#endif
}
public sealed class RitualObligationTrigger_Date : FirstPriorityFishPatch
@@ -344,4 +357,4 @@ public sealed class MiscOptimizations : ClassWithFishPatches
}
#endif
}
-}
\ No newline at end of file
+}
diff --git a/Source/PerformanceFish/ModsConfigPatches.cs b/Source/PerformanceFish/ModsConfigPatches.cs
index b6c3ad8..f6d1432 100644
--- a/Source/PerformanceFish/ModsConfigPatches.cs
+++ b/Source/PerformanceFish/ModsConfigPatches.cs
@@ -17,7 +17,8 @@ public sealed class ModsConfigPatches : ClassWithFishPrepatches
= "Fixes a MayRequire bug causing it to normally not recognize steam versions of mods if another local "
+ "copy exists.";
- public override MethodBase TargetMethodBase { get; } = methodof(ModsConfig.AreAllActive);
+ public override MethodBase TargetMethodBase { get; }
+ = AccessTools.DeclaredMethod(typeof(ModsConfig), nameof(ModsConfig.AreAllActive), [typeof(string)])!;
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> IsActiveFix(ilProcessor, module);
@@ -29,7 +30,9 @@ public sealed class ModsConfigPatches : ClassWithFishPrepatches
= "Fixes a MayRequireAnyOf bug causing it to normally not recognize steam versions of mods if another "
+ "local copy exists, as well as its inability to strip whitespace.";
- public override MethodBase TargetMethodBase { get; } = methodof(ModsConfig.IsAnyActiveOrEmpty);
+ public override MethodBase TargetMethodBase { get; }
+ = AccessTools.DeclaredMethod(typeof(ModsConfig), nameof(ModsConfig.IsAnyActiveOrEmpty),
+ [typeof(IEnumerable), typeof(bool)])!;
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> IsActiveFix(ilProcessor, module);
@@ -59,4 +62,4 @@ public sealed class ModsConfigPatches : ClassWithFishPrepatches
}
public static bool IsActiveReplacement(string id) => ModLister.GetActiveModWithIdentifier(id, true) != null;
-}
\ No newline at end of file
+}
diff --git a/Source/PerformanceFish/OnAssembliesLoaded.cs b/Source/PerformanceFish/OnAssembliesLoaded.cs
index 7690c00..48d0b40 100644
--- a/Source/PerformanceFish/OnAssembliesLoaded.cs
+++ b/Source/PerformanceFish/OnAssembliesLoaded.cs
@@ -50,7 +50,9 @@ public static class OnAssembliesLoaded
try
{
if (!patchClass.RequiresLoadedGameForPatching)
+ {
patchClass.Patches.PatchAll();
+ }
}
catch (Exception ex)
{
@@ -122,4 +124,4 @@ public static class OnAssembliesLoaded
action.Method.FullDescription()}:\n{ex}");
}
}
-}
\ No newline at end of file
+}
diff --git a/Source/PerformanceFish/PerformanceFish.sln b/Source/PerformanceFish/PerformanceFish.sln
index d417805..d68e95d 100644
--- a/Source/PerformanceFish/PerformanceFish.sln
+++ b/Source/PerformanceFish/PerformanceFish.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.1.31911.260
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1.5", "1.5.csproj", "{E8A3D63E-5850-4866-AB06-DBD44B11F67B}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1.6", "1.6.csproj", "{8B299603-8E75-4C35-AFC8-89748599E689}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1.4", "1.4.csproj", "{0257ABCD-7009-4C7D-9BD8-599FC47595DB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6948F7E6-7ED7-4716-87EF-CFF1F465F561}"
@@ -24,6 +26,10 @@ Global
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8B299603-8E75-4C35-AFC8-89748599E689}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8B299603-8E75-4C35-AFC8-89748599E689}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8B299603-8E75-4C35-AFC8-89748599E689}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8B299603-8E75-4C35-AFC8-89748599E689}.Release|Any CPU.Build.0 = Release|Any CPU
{0257ABCD-7009-4C7D-9BD8-599FC47595DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0257ABCD-7009-4C7D-9BD8-599FC47595DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0257ABCD-7009-4C7D-9BD8-599FC47595DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/Source/PerformanceFish/Planet/WorldObjectsOptimization.cs b/Source/PerformanceFish/Planet/WorldObjectsOptimization.cs
index 906f3d8..0e7c1e4 100644
--- a/Source/PerformanceFish/Planet/WorldObjectsOptimization.cs
+++ b/Source/PerformanceFish/Planet/WorldObjectsOptimization.cs
@@ -15,6 +15,9 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
{
public sealed class WorldObjectsHolderTickPatch : FishPrepatch
{
+ private static FishTable _skippableCompTypes = null!;
+ private static FishTable _skippableWorldObjectTypes = null!;
+
public override string? Description { get; }
= "The world objects holder is responsible for ticking every world object. This includes settlements, "
+ "caravans, outposts and any other object placed in the world, instead of maps. Normally it ticks "
@@ -35,7 +38,11 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
var worldObjects = WorldObjectsHolder.tmpWorldObjects;
for (var i = worldObjects.Count; i-- > 0;)
+#if V1_6
+ worldObjects[i].DoTick();
+#else
worldObjects[i].Tick();
+#endif
}
public static void UpdateCache(WorldObjectsHolder instance)
@@ -50,7 +57,7 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
var worldObject = instanceWorldObjects[i];
if (worldObject is not MapParent { HasMap: true }
- && SkippableWorldObjects.Contains(worldObject.GetType())
+ && CanSkipWorldObjectTick(worldObject)
&& (worldObject is not Settlement settlement || settlement.trader?.stock is null)
&& CanSkipCompTick(worldObject))
{
@@ -64,6 +71,15 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
CachedMapsVersion = Current.gameInt.maps._version;
}
+ private static bool CanSkipWorldObjectTick(WorldObject worldObject)
+ {
+#if V1_6
+ if (worldObject is IThingHolder)
+ return false;
+#endif
+ return IsSkippableWorldObjectType(worldObject.GetType());
+ }
+
private static bool CanSkipCompTick(WorldObject worldObject)
{
var comps = worldObject.comps;
@@ -74,7 +90,7 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
if (comp is EnterCooldownComp { Active: true })
return false;
- if (!SkippableComps.Contains(comp.GetType()))
+ if (!IsSkippableCompType(comp.GetType()))
return false;
comp.CompTick();
@@ -106,28 +122,69 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
public static void AddCompToWhiteList(Type compType)
{
_whitelistedTickingCompTypes = _whitelistedTickingCompTypes.Add(compType);
- SkippableComps = InitializeSkippableComps();
+ ResetSkippableCompTypes();
}
public static void AddWorldObjectToWhiteList(Type worldObjectType)
{
_whitelistedWorldObjectTypes = _whitelistedWorldObjectTypes.Add(worldObjectType);
- SkippableWorldObjects = InitializeSkippableWorldObjects();
+ ResetSkippableWorldObjectTypes();
}
- public static HashSet
- SkippableComps = InitializeSkippableComps(),
- SkippableWorldObjects = InitializeSkippableWorldObjects();
+ private static void ResetSkippableCompTypes()
+ {
+ _skippableCompTypes = new()
+ {
+ ValueInitializer = static type => ComputeIsSkippableCompType(type)
+ };
+ }
- private static HashSet InitializeSkippableComps()
- => MakeSubclassHashSet(typeof(WorldObjectComp), nameof(WorldObjectComp.CompTick),
- _whitelistedTickingCompTypes);
+ private static void ResetSkippableWorldObjectTypes()
+ {
+ _skippableWorldObjectTypes = new()
+ {
+ ValueInitializer = static type => ComputeIsSkippableWorldObjectType(type)
+ };
+ }
- private static HashSet InitializeSkippableWorldObjects()
- => MakeSubclassHashSet(typeof(WorldObject), nameof(WorldObject.Tick), _whitelistedWorldObjectTypes);
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsSkippableCompType(Type type) => _skippableCompTypes.GetOrAdd(type);
- private static HashSet MakeSubclassHashSet(Type type, string name, Type?[] allowedDeclaringTypes)
- => type.SubclassesWithNoMethodOverrideAndSelf(allowedDeclaringTypes, name).ToHashSet();
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsSkippableWorldObjectType(Type type) => _skippableWorldObjectTypes.GetOrAdd(type);
+
+ private static bool ComputeIsSkippableCompType(Type type)
+ {
+ if (AccessTools.Method(type, nameof(WorldObjectComp.CompTick))?.DeclaringType is not { } declaringType
+ || !_whitelistedTickingCompTypes.Contains(declaringType))
+ {
+ return false;
+ }
+
+#if V1_6
+ return AccessTools.Method(type, nameof(WorldObjectComp.CompTickInterval), [typeof(int)])?.DeclaringType
+ is { } intervalDeclaringType
+ && _whitelistedTickingCompTypes.Contains(intervalDeclaringType);
+#else
+ return true;
+#endif
+ }
+
+ private static bool ComputeIsSkippableWorldObjectType(Type type)
+ {
+ if (AccessTools.Method(type, "Tick")?.DeclaringType is not { } declaringType
+ || !_whitelistedWorldObjectTypes.Contains(declaringType))
+ {
+ return false;
+ }
+
+#if V1_6
+ return AccessTools.Method(type, "TickInterval", [typeof(int)])?.DeclaringType is { } intervalDeclaringType
+ && _whitelistedWorldObjectTypes.Contains(intervalDeclaringType);
+#else
+ return true;
+#endif
+ }
public static bool CacheDirty(WorldObjectsHolder instance)
=> CachedWorldObjectsVersion != instance.worldObjects._version
@@ -135,7 +192,12 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
public static void SetDirty() => CachedWorldObjectsVersion = CachedMapsVersion = -2;
- static WorldObjectsHolderTickPatch() => Cache.Utility.Cleared += SetDirty;
+ static WorldObjectsHolderTickPatch()
+ {
+ ResetSkippableCompTypes();
+ ResetSkippableWorldObjectTypes();
+ Cache.Utility.Cleared += SetDirty;
+ }
}
public sealed class ExpandingIconCaching : FishPrepatch
@@ -245,4 +307,4 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
}
}
}
-}
\ No newline at end of file
+}
diff --git a/Source/PerformanceFish/PrepatcherFields.cs b/Source/PerformanceFish/PrepatcherFields.cs
index b00ffd4..dd19b40 100644
--- a/Source/PerformanceFish/PrepatcherFields.cs
+++ b/Source/PerformanceFish/PrepatcherFields.cs
@@ -9,6 +9,7 @@ using PerformanceFish.Hauling;
using PerformanceFish.Planet;
using Prepatcher;
using RimWorld.Planet;
+using PFMapEvents = PerformanceFish.Events.MapEvents;
namespace PerformanceFish;
@@ -22,7 +23,7 @@ public static class PrepatcherFields
[PrepatcherField]
[ValueInitializer(nameof(CreateMapEvents))]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static extern MapEvents.Instanced Events(this Map map);
+ public static extern PFMapEvents.Instanced Events(this Map map);
[PrepatcherField]
[ValueInitializer(nameof(CreateThingEvents))]
@@ -175,7 +176,7 @@ public static class PrepatcherFields
public static FishTable CreateIndexMapByGroup() => new();
public static Cache.CellGrid CreateItemCountGrid(Map map) => new(map);
public static Cache.BitCellGrid CreateBitCellGrid(Map map) => new(map);
- public static MapEvents.Instanced CreateMapEvents() => new();
+ public static PFMapEvents.Instanced CreateMapEvents() => new();
public static ThingEvents.Instanced CreateThingEvents() => new();
public static StorageSettingsPatches.StorageSettingsCache CreateStorageSettingsCache() => new();
public static StorageDistrict[] GetDefaultDistrictArray() => StorageDistrict.GetDefaultArray();
@@ -290,4 +291,4 @@ public static class PrepatcherFields
}
// public static List CreateHediffCompList() => new();
-}
\ No newline at end of file
+}
diff --git a/Source/PerformanceFish/Prepatching/FishPrepatch.cs b/Source/PerformanceFish/Prepatching/FishPrepatch.cs
index be25dba..8c87d7a 100644
--- a/Source/PerformanceFish/Prepatching/FishPrepatch.cs
+++ b/Source/PerformanceFish/Prepatching/FishPrepatch.cs
@@ -4,6 +4,7 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
using System.Diagnostics;
+using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
@@ -192,14 +193,26 @@ public abstract class FishPrepatch : FishPrepatchBase
var stateParameter = postfixParameters.TryGetNamed("__state");
// Verify? It would make sense to match against prefix state
- // var skipOriginalFlag = ilProcessor.DeclareLocal(typeof(bool));
- var resultVariable = GetResultVariable(ilProcessor, ref patchVariables, resultParameter, targetMethod);
+ var hasReturnValue = targetMethod.ReturnType.FullName != module.TypeSystem.Void.FullName;
+ var resultVariable = hasReturnValue
+ ? patchVariables.Result ??= ilProcessor.DeclareLocal(targetMethod.ReturnType)
+ : GetResultVariable(ilProcessor, ref patchVariables, resultParameter, targetMethod);
var stateVariable = GetStateVariable(ilProcessor, ref patchVariables, stateParameter);
+ var retInstructions = instructions.Where(static instruction => instruction.OpCode == OpCodes.Ret).ToArray();
+ if (retInstructions.Length == 0)
+ return;
var postfixInstructions = new Collection