Compare commits

...

8 Commits

49 changed files with 1945 additions and 324 deletions
+3
View File
@@ -481,3 +481,6 @@ $RECYCLE.BIN/
# Vim temporary swap files
*.swp
# Cache folders used in disk caching
Cache/*
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -1
View File
@@ -5,6 +5,7 @@
<supportedVersions>
<li>1.4</li>
<li>1.5</li>
<li>1.6</li>
</supportedVersions>
<author>bradson</author>
<modIconPath IgnoreIfNoMatchingField="True">PerformanceFish/ModIcon</modIconPath>
@@ -56,4 +57,4 @@
<li>vanillaexpanded.storytellers.rick</li> <!-- sussy -->
</incompatibleWith>
<description>download more tps</description>
</ModMetaData>
</ModMetaData>
+3 -1
View File
@@ -3,6 +3,8 @@
<PropertyGroup>
<OutDir>..\..\1.5\Assemblies\</OutDir>
<Copyright>Copyright (c) 2024 Bradson</Copyright>
<FisheryRepoRoot Condition="Exists('$(MSBuildThisFileDirectory)..\..\..\Fishery\1.5\Assemblies\1Fishery.dll')">$(MSBuildThisFileDirectory)..\..\..\Fishery</FisheryRepoRoot>
<FisheryRepoRoot Condition="'$(FisheryRepoRoot)' == '' and Exists('$(MSBuildThisFileDirectory)..\..\..\Fishery-clean\1.5\Assemblies\1Fishery.dll')">$(MSBuildThisFileDirectory)..\..\..\Fishery-clean</FisheryRepoRoot>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DefineConstants>DEBUG;V1_5</DefineConstants>
@@ -14,7 +16,7 @@
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.5.*" />
<PackageReference Include="Lib.Harmony" Version="2.3.3" ExcludeAssets="runtime" />
<Reference Include="1Fishery">
<HintPath>..\..\..\Fishery\1.5\Assemblies\1Fishery.dll</HintPath>
<HintPath>$(FisheryRepoRoot)\1.5\Assemblies\1Fishery.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutDir>..\..\1.6\Assemblies\</OutDir>
<Copyright>Copyright (c) 2026 Bradson</Copyright>
<FisheryRepoRoot Condition="Exists('$(MSBuildThisFileDirectory)..\..\..\Fishery\1.6\Assemblies\1Fishery.dll')">$(MSBuildThisFileDirectory)..\..\..\Fishery</FisheryRepoRoot>
<FisheryRepoRoot Condition="'$(FisheryRepoRoot)' == '' and Exists('$(MSBuildThisFileDirectory)..\..\..\Fishery-clean\1.6\Assemblies\1Fishery.dll')">$(MSBuildThisFileDirectory)..\..\..\Fishery-clean</FisheryRepoRoot>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DefineConstants>DEBUG;V1_5;V1_6</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DefineConstants>V1_5;V1_6</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.6.*" />
<PackageReference Include="Lib.Harmony" Version="2.3.3" ExcludeAssets="runtime" />
<Reference Include="1Fishery">
<HintPath>$(FisheryRepoRoot)\1.6\Assemblies\1Fishery.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
</Project>
+39 -6
View File
@@ -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<long>();
_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;
}
}
}
[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}.");
}
}
}
+29 -7
View File
@@ -9,38 +9,60 @@ namespace PerformanceFish.Cache;
public sealed class CellGrid<T>
{
private readonly Map _map;
private T[] _innerArray;
private CellIndices _cellIndices;
public CellGrid(Map map)
{
_innerArray = null!;
_cellIndices = null!;
_map = map;
_innerArray = Array.Empty<T>();
_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];
}
}
}
}
+9 -4
View File
@@ -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<Thing> 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];
}
}
@@ -0,0 +1,353 @@
// Copyright (c) 2026 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;
namespace PerformanceFish;
public sealed class CompTickOptimization : ClassWithFishPrepatches
{
private static readonly MethodInfo _thingCompTickMethod
= AccessTools.DeclaredMethod(typeof(ThingComp), nameof(ThingComp.CompTick));
private static readonly MethodInfo _thingCompTickIntervalMethod
= AccessTools.DeclaredMethod(typeof(ThingComp), nameof(ThingComp.CompTickInterval), [typeof(int)]);
private static readonly MethodInfo _thingCompTickRareMethod
= AccessTools.DeclaredMethod(typeof(ThingComp), nameof(ThingComp.CompTickRare));
private static readonly MethodInfo _thingCompTickLongMethod
= AccessTools.DeclaredMethod(typeof(ThingComp), nameof(ThingComp.CompTickLong));
private static readonly MethodInfo _hediffCompPostTickMethod
= AccessTools.DeclaredMethod(typeof(HediffComp), nameof(HediffComp.CompPostTick), [typeof(float).MakeByRefType()]);
private static readonly MethodInfo _hediffCompPostTickIntervalMethod = AccessTools.DeclaredMethod(
typeof(HediffComp),
nameof(HediffComp.CompPostTickInterval),
[typeof(float).MakeByRefType(), typeof(int)]);
private static readonly bool _thingCompTickBaseIsTrivial = IsTrivialVoidMethod(_thingCompTickMethod);
private static readonly bool _thingCompTickIntervalBaseIsTrivial = IsTrivialVoidMethod(_thingCompTickIntervalMethod);
private static readonly bool _thingCompTickRareBaseIsTrivial = IsTrivialVoidMethod(_thingCompTickRareMethod);
private static readonly bool _thingCompTickLongBaseIsTrivial = IsTrivialVoidMethod(_thingCompTickLongMethod);
private static readonly bool _hediffCompPostTickBaseIsTrivial = IsTrivialVoidMethod(_hediffCompPostTickMethod);
private static readonly bool _hediffCompPostTickIntervalBaseIsTrivial
= IsTrivialVoidMethod(_hediffCompPostTickIntervalMethod);
private static readonly Lazy<bool> _canSkipInheritedBaseThingCompTick
= new(static () => _thingCompTickBaseIsTrivial && !HasHarmonyPatches(_thingCompTickMethod));
private static readonly Lazy<bool> _canSkipInheritedBaseThingCompTickInterval
= new(static () => _thingCompTickIntervalBaseIsTrivial && !HasHarmonyPatches(_thingCompTickIntervalMethod));
private static readonly Lazy<bool> _canSkipInheritedBaseThingCompTickRare
= new(static () => _thingCompTickRareBaseIsTrivial && !HasHarmonyPatches(_thingCompTickRareMethod));
private static readonly Lazy<bool> _canSkipInheritedBaseThingCompTickLong
= new(static () => _thingCompTickLongBaseIsTrivial && !HasHarmonyPatches(_thingCompTickLongMethod));
private static readonly Lazy<bool> _canSkipInheritedBaseHediffCompPostTick
= new(static () => _hediffCompPostTickBaseIsTrivial && !HasHarmonyPatches(_hediffCompPostTickMethod));
private static readonly Lazy<bool> _canSkipInheritedBaseHediffCompPostTickInterval = new(
static () => _hediffCompPostTickIntervalBaseIsTrivial && !HasHarmonyPatches(_hediffCompPostTickIntervalMethod));
private static readonly FishTable<Type, bool> _thingCompTickTypes = new()
{
ValueInitializer = static type => InheritsThingCompTick(type)
};
private static readonly FishTable<Type, bool> _thingCompTickIntervalTypes = new()
{
ValueInitializer = static type => InheritsThingCompTickInterval(type)
};
private static readonly FishTable<Type, bool> _thingCompTickRareTypes = new()
{
ValueInitializer = static type => InheritsThingCompTickRare(type)
};
private static readonly FishTable<Type, bool> _thingCompTickLongTypes = new()
{
ValueInitializer = static type => InheritsThingCompTickLong(type)
};
private static readonly FishTable<Type, bool> _hediffCompPostTickTypes = new()
{
ValueInitializer = static type => InheritsHediffCompPostTick(type)
};
private static readonly FishTable<Type, bool> _hediffCompPostTickIntervalTypes = new()
{
ValueInitializer = static type => InheritsHediffCompPostTickInterval(type)
};
public sealed class ThingTickPatch : FishPrepatch
{
public override bool DefaultState => false;
public override string? Description { get; }
= "Skips inherited empty ThingComp.CompTick calls when the base method is still unpatched and trivial.";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(ThingWithComps), "Tick");
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
public static void ReplacementBody(ThingWithComps instance)
{
var comps = instance.AllComps;
var count = comps.Count;
var canSkipInheritedBase = CanSkipInheritedBaseThingCompTick();
for (var i = 0; i < count; i++)
{
var comp = comps[i];
if (!canSkipInheritedBase || !_thingCompTickTypes.GetOrAdd(comp.GetType()))
comp.CompTick();
}
}
}
public sealed class ThingTickIntervalPatch : FishPrepatch
{
public override bool DefaultState => false;
public override string? Description { get; }
= "Skips inherited empty ThingComp.CompTickInterval calls when the base method is still unpatched and trivial.";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(ThingWithComps), "TickInterval", [typeof(int)]);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
public static void ReplacementBody(ThingWithComps instance, int delta)
{
var comps = instance.AllComps;
var count = comps.Count;
var canSkipInheritedBase = CanSkipInheritedBaseThingCompTickInterval();
for (var i = 0; i < count; i++)
{
var comp = comps[i];
if (!canSkipInheritedBase || !_thingCompTickIntervalTypes.GetOrAdd(comp.GetType()))
comp.CompTickInterval(delta);
}
}
}
public sealed class ThingTickRarePatch : FishPrepatch
{
public override bool DefaultState => false;
public override string? Description { get; }
= "Skips inherited empty ThingComp.CompTickRare calls when the base method is still unpatched and trivial.";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(ThingWithComps), nameof(ThingWithComps.TickRare));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
public static void ReplacementBody(ThingWithComps instance)
{
var comps = instance.AllComps;
var count = comps.Count;
var canSkipInheritedBase = CanSkipInheritedBaseThingCompTickRare();
for (var i = 0; i < count; i++)
{
var comp = comps[i];
if (!canSkipInheritedBase || !_thingCompTickRareTypes.GetOrAdd(comp.GetType()))
comp.CompTickRare();
}
}
}
public sealed class ThingTickLongPatch : FishPrepatch
{
public override bool DefaultState => false;
public override string? Description { get; }
= "Skips inherited empty ThingComp.CompTickLong calls when the base method is still unpatched and trivial.";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(ThingWithComps), nameof(ThingWithComps.TickLong));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
public static void ReplacementBody(ThingWithComps instance)
{
var comps = instance.AllComps;
var count = comps.Count;
var canSkipInheritedBase = CanSkipInheritedBaseThingCompTickLong();
for (var i = 0; i < count; i++)
{
var comp = comps[i];
if (!canSkipInheritedBase || !_thingCompTickLongTypes.GetOrAdd(comp.GetType()))
comp.CompTickLong();
}
}
}
public sealed class HediffPostTickPatch : FishPrepatch
{
public override bool DefaultState => false;
public override string? Description { get; }
= "Skips inherited empty HediffComp.CompPostTick calls when the base method is still unpatched and trivial.";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(HediffWithComps), nameof(HediffWithComps.PostTick));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
public static void ReplacementBody(HediffWithComps instance)
{
var comps = instance.comps;
if (comps == null)
return;
var count = comps.Count;
var severityAdjustment = 0f;
var canSkipInheritedBase = CanSkipInheritedBaseHediffCompPostTick();
for (var i = 0; i < count; i++)
{
var comp = comps[i];
if (!canSkipInheritedBase || !_hediffCompPostTickTypes.GetOrAdd(comp.GetType()))
comp.CompPostTick(ref severityAdjustment);
}
if (severityAdjustment != 0f)
instance.Severity += severityAdjustment;
}
}
public sealed class HediffPostTickIntervalPatch : FishPrepatch
{
public override bool DefaultState => false;
public override string? Description { get; }
= "Skips inherited empty HediffComp.CompPostTickInterval calls when the base method is still unpatched and trivial.";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(HediffWithComps), nameof(HediffWithComps.PostTickInterval), [typeof(int)]);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
public static void ReplacementBody(HediffWithComps instance, int delta)
{
var comps = instance.comps;
if (comps == null)
return;
var count = comps.Count;
var severityAdjustment = 0f;
var canSkipInheritedBase = CanSkipInheritedBaseHediffCompPostTickInterval();
for (var i = 0; i < count; i++)
{
var comp = comps[i];
if (!canSkipInheritedBase || !_hediffCompPostTickIntervalTypes.GetOrAdd(comp.GetType()))
comp.CompPostTickInterval(ref severityAdjustment, delta);
}
if (severityAdjustment != 0f)
instance.Severity += severityAdjustment;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CanSkipInheritedBaseThingCompTick()
=> _canSkipInheritedBaseThingCompTick.Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CanSkipInheritedBaseThingCompTickInterval()
=> _canSkipInheritedBaseThingCompTickInterval.Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CanSkipInheritedBaseThingCompTickRare()
=> _canSkipInheritedBaseThingCompTickRare.Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CanSkipInheritedBaseThingCompTickLong()
=> _canSkipInheritedBaseThingCompTickLong.Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CanSkipInheritedBaseHediffCompPostTick()
=> _canSkipInheritedBaseHediffCompPostTick.Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CanSkipInheritedBaseHediffCompPostTickInterval()
=> _canSkipInheritedBaseHediffCompPostTickInterval.Value;
private static bool InheritsThingCompTick(Type type)
=> AccessTools.Method(type, nameof(ThingComp.CompTick))?.DeclaringType == typeof(ThingComp);
private static bool InheritsThingCompTickInterval(Type type)
=> AccessTools.Method(type, nameof(ThingComp.CompTickInterval), [typeof(int)])?.DeclaringType == typeof(ThingComp);
private static bool InheritsThingCompTickRare(Type type)
=> AccessTools.Method(type, nameof(ThingComp.CompTickRare))?.DeclaringType == typeof(ThingComp);
private static bool InheritsThingCompTickLong(Type type)
=> AccessTools.Method(type, nameof(ThingComp.CompTickLong))?.DeclaringType == typeof(ThingComp);
private static bool InheritsHediffCompPostTick(Type type)
=> AccessTools.Method(type, nameof(HediffComp.CompPostTick), [typeof(float).MakeByRefType()])?.DeclaringType
== typeof(HediffComp);
private static bool InheritsHediffCompPostTickInterval(Type type)
=> AccessTools.Method(type, nameof(HediffComp.CompPostTickInterval),
[typeof(float).MakeByRefType(), typeof(int)])?.DeclaringType
== typeof(HediffComp);
private static bool IsTrivialVoidMethod(MethodInfo method)
{
if (method.ReturnType != typeof(void)
|| method.GetMethodBody() is not { ExceptionHandlingClauses.Count: 0, LocalVariables.Count: 0 } body)
{
return false;
}
var il = body.GetILAsByteArray();
if (il == null || il.Length == 0)
return false;
var sawRet = false;
for (var i = 0; i < il.Length; i++)
{
switch (il[i])
{
case 0x00:
continue;
case 0x2A when i == il.Length - 1 && !sawRet:
sawRet = true;
continue;
default:
return false;
}
}
return sawRet;
}
private static bool HasHarmonyPatches(MethodBase method)
{
var patches = Harmony.GetPatchInfo(method);
return patches is not null
&& (patches.Prefixes.Count != 0
|| patches.Postfixes.Count != 0
|| patches.Transpilers.Count != 0
|| patches.Finalizers.Count != 0);
}
}
@@ -56,8 +56,8 @@ public sealed class RecipeDefCaching : ClassWithFishPatches
{
private int _defDatabaseVersion = -2;
private int _defRecipeUsersVersion = -2;
private RecipeDef _def;
public List<ThingDef> RecipeUsers;
private RecipeDef? _def;
public List<ThingDef> RecipeUsers = [];
public void Update(RecipeDef instance, IEnumerable<ThingDef> result)
{
@@ -81,7 +81,7 @@ public sealed class RecipeDefCaching : ClassWithFishPatches
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
=> _defDatabaseVersion != DefDatabase<ThingDef>.defsList._version
|| _defRecipeUsersVersion != (_def.recipeUsers?._version ?? 0);
|| _defRecipeUsersVersion != (_def?.recipeUsers?._version ?? 0);
}
}
}
@@ -141,4 +141,4 @@ public sealed class RecipeDefCaching : ClassWithFishPatches
=> throw new NotImplementedException();
}
}*/
}
}
+15 -2
View File
@@ -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
}
}
}
+3 -2
View File
@@ -29,12 +29,13 @@
<ItemGroup>
<PackageReference Include="Zetrith.Prepatcher" Version="1.*" ExcludeAssets="runtime" />
<PackageReference Include="PolySharp" Version="1.*" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.*" ExcludeAssets="runtime" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.2" ExcludeAssets="runtime" />
<PackageReference Include="JetBrains.Annotations" Version="2023.*" Aliases="nuget" ExcludeAssets="runtime" />
<PackageReference Include="Krafs.Publicizer" Version="2.*" />
<Publicize Include="Assembly-CSharp;0Harmony" IncludeVirtualMembers="false" />
<Publicize Include="Assembly-CSharp:RimWorld.JobGiver_Haul.TryGiveJob" />
<Publicize Include="Assembly-CSharp:RimWorld.CompRottable.TickInterval" />
<!-- DoNotPublicize Include="0Harmony:Mono.Collections.Generic.Collection`1" / -->
<DoNotPublicize Include="0Harmony:System.Memory`1" />
<DoNotPublicize Include="0Harmony:System.ReadOnlyMemory`1" />
@@ -93,4 +94,4 @@
<PackagePath />
</None>
</ItemGroup>
</Project>
</Project>
@@ -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);
/// <summary>
/// For modders. Performance Fish doesn't automatically scribe added custom defs.
@@ -1136,4 +1137,4 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
}
// ReSharper restore UnusedMember.Local
}
}
}
+87 -39
View File
@@ -19,45 +19,17 @@ public sealed class GenLocalDateCaching : ClassWithFishPatches
+ "optimization, but yields accurate results instead of a placeholder value to avoid issues";
public override Delegate TargetMethodGroup { get; } = (Func<Thing, int>)GenLocalDate.DayTick;
public override int TranspilerMethodPriority => Priority.First;
public override int PrefixMethodPriority => Priority.First;
public static CodeInstructions Transpiler(CodeInstructions codes, MethodBase method, ILGenerator generator)
//=> Reflection.GetCodeInstructions(Replacement);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Thing thing, ref int __result)
{
var labelToStartOfMethod = generator.DefineLabel();
codes.First().labels.Add(labelToStartOfMethod);
if (thing.TryGetMap() is not { } map)
return true;
yield return FishTranspiler.Argument(method, "thing");
yield return FishTranspiler.Field(typeof(Thing), nameof(Thing.mapIndexOrState));
yield return FishTranspiler.Constant(0);
yield return FishTranspiler.IfLessThan(labelToStartOfMethod);
yield return FishTranspiler.Field(typeof(Current), nameof(Current.gameInt));
yield return FishTranspiler.Field(typeof(Game), nameof(Game.maps));
yield return FishTranspiler.Argument(method, "thing");
yield return FishTranspiler.Field(typeof(Thing), nameof(Thing.mapIndexOrState));
yield return FishTranspiler.Call(typeof(List<Map>), "get_Item");
yield return FishTranspiler.Call<Func<Map, int>>(GenLocalDate.DayTick);
yield return FishTranspiler.Return;
foreach (var code in codes)
yield return code;
__result = GenLocalDate.DayTick(map);
return false;
}
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//public static int Replacement(Thing thing)
// => thing.mapIndexOrState < 0 ? GenDate.DayTick(GenTicks.TicksAbs, GenLocalDate.LongitudeForDate(thing))
// : GenLocalDate.DayTick(Current.gameInt.maps[thing.mapIndexOrState]);
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
//public static bool Prefix(Thing thing, ref int __result)
//{
// if (thing.mapIndexOrState < 0)
// return true;
// __result = GenLocalDate.DayTick(Current.gameInt.maps[thing.mapIndexOrState]);
// return false;
//}
}
public sealed class DayTickByMap_Patch : FishPatch
@@ -79,7 +51,7 @@ public sealed class GenLocalDateCaching : ClassWithFishPatches
return __state = true;
}
if (SavedTick != TickHelper.TicksGame)
if (SavedDayTickTick != TickHelper.TicksGame)
return __state = true;
__result = DayTick;
@@ -92,12 +64,88 @@ public sealed class GenLocalDateCaching : ClassWithFishPatches
if (!__state || SavedMap != map)
return;
SavedTick = TickHelper.TicksGame;
SavedDayTickTick = TickHelper.TicksGame;
DayTick = __result;
}
}
public sealed class HourIntegerByMap_Patch : FishPatch
{
public override string Description { get; }
= "Caches results of GenLocalDate.HourInteger for the first map.";
public override Delegate TargetMethodGroup { get; } = (Func<Map, int>)GenLocalDate.HourInteger;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Map map, ref int __result, out bool __state)
{
if (map != SavedMap)
{
if (SavedMap is null && Find.Maps is { Count: > 0 } maps && maps[0] == map)
SavedMap = map;
return __state = true;
}
if (SavedHourIntegerTick != TickHelper.TicksGame)
return __state = true;
__result = HourInteger;
return __state = false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(Map map, int __result, bool __state)
{
if (!__state || SavedMap != map)
return;
SavedHourIntegerTick = TickHelper.TicksGame;
HourInteger = __result;
}
}
public sealed class SeasonByMap_Patch : FishPatch
{
public override string Description { get; }
= "Caches results of GenLocalDate.Season for the first map.";
public override Delegate TargetMethodGroup { get; } = (Func<Map, Season>)GenLocalDate.Season;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Map map, ref Season __result, out bool __state)
{
if (map != SavedMap)
{
if (SavedMap is null && Find.Maps is { Count: > 0 } maps && maps[0] == map)
SavedMap = map;
return __state = true;
}
if (SavedSeasonTick != TickHelper.TicksGame)
return __state = true;
__result = Season;
return __state = false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(Map map, Season __result, bool __state)
{
if (!__state || SavedMap != map)
return;
SavedSeasonTick = TickHelper.TicksGame;
Season = __result;
}
}
public static Map? SavedMap;
public static int SavedTick = -2;
public static int SavedDayTickTick = -2;
public static int SavedHourIntegerTick = -2;
public static int SavedSeasonTick = -2;
public static int DayTick = -2;
}
public static int HourInteger = -2;
public static Season Season = Season.Undefined;
}
+8 -16
View File
@@ -43,14 +43,15 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches
public static List<Type> ReplacementBody<TAttr>() 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<TAttr>()).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<Type, bool> 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<Type, bool> Predicate = static subClass => !subClass.IsAbstract;
}
}
}
@@ -14,6 +14,16 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
{
public sealed class GeneTrackerTickPatch : FishPrepatch
{
private static readonly FishTable<Type, bool> _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,64 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
genesToTick.Add(gene);
}
}
public static HashSet<Type> 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)
{
if (type == typeof(Gene))
return true;
var tickMethod = type.GetMethod(nameof(Gene.Tick), global::System.Reflection.BindingFlags.Instance
| global::System.Reflection.BindingFlags.Public
| global::System.Reflection.BindingFlags.NonPublic, null, Type.EmptyTypes, null);
var tickIntervalMethod = type.GetMethod(nameof(Gene.TickInterval),
global::System.Reflection.BindingFlags.Instance
| global::System.Reflection.BindingFlags.Public
| global::System.Reflection.BindingFlags.NonPublic, null, [typeof(int)], null);
return tickMethod?.DeclaringType == typeof(Gene)
&& tickIntervalMethod?.DeclaringType == typeof(Gene);
}
}
}
public sealed class GeneTrackerTickIntervalPatch : FishPrepatch
{
// i am iffy about this
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 "
+ "or basic stat modifiers. This patch improves the gene tracker to determine genes that need ticking in "
+ "advance, cache the list of them, and only tick those, skipping all the others.";
public override MethodBase TargetMethodBase { get; }
= AccessTools.Method(typeof(Pawn_GeneTracker), nameof(Pawn_GeneTracker.GeneTrackerTickInterval));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(GeneTrackerTickInterval);
public static void GeneTrackerTickInterval(Pawn_GeneTracker instance, int delta)
{
if (!ModLister.BiotechInstalled)
return;
var genesToTick = instance.GenesToTick();
if (GeneTrackerTickPatch.Dirty(instance, genesToTick))
GeneTrackerTickPatch.Update(instance, genesToTick);
for (var i = genesToTick.Count - 1; i >= 0; i--)
{
if (genesToTick[i].Active)
genesToTick[i].TickInterval(delta);
}
if (instance.pawn.IsSpawned()
&& instance.Xenotype != XenotypeDefOf.Baseliner
&& instance.pawn.IsHashIntervalTick(300, delta))
{
LessonAutoActivator.TeachOpportunity(ConceptDefOf.GenesAndXenotypes, OpportunityType.Important);
}
}
}
}
+1
View File
@@ -0,0 +1 @@
global using HashCode = FisheryLib.HashCode;
@@ -3,7 +3,7 @@
// 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/.
global using LwmCompCache = PerformanceFish.Cache.ByReference<Verse.Thing, PerformanceFish.Hauling.LwmCompCacheValue>;
global using LwmCompCache = PerformanceFish.Cache.ByReference<Verse.ThingDef, PerformanceFish.Hauling.LwmCompCacheValue>;
namespace PerformanceFish.Hauling;
@@ -11,9 +11,9 @@ public record struct LwmCompCacheValue
{
public CompProperties? CompProperties;
public static CompProperties? TryGetLwmProps(Thing slotGroupParent)
public static CompProperties? TryGetLwmProps(ThingDef thingDef)
=> ModCompatibility.Types.LWM.CompProperties != null
? slotGroupParent.def.comps?.Find(static props
? thingDef.comps?.Find(static props
=> props.GetType() == ModCompatibility.Types.LWM.CompProperties)
: null;
@@ -35,4 +35,4 @@ public record struct LwmCompCacheValue
=> ModCompatibility.Types.LWM.CompProperties is { } propsType
? AccessTools.FieldRefAccess<CompProperties, int>(AccessTools.Field(propsType, name))
: null;
}
}
@@ -4,7 +4,7 @@
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
global using RimfactoryExtensionCache
= PerformanceFish.Cache.ByReference<Verse.Thing, PerformanceFish.Hauling.RimfactoryExtensionCacheValue>;
= PerformanceFish.Cache.ByReference<Verse.ThingDef, PerformanceFish.Hauling.RimfactoryExtensionCacheValue>;
using RimfactoryTypes = PerformanceFish.ModCompatibility.Types.Rimfactory;
namespace PerformanceFish.Hauling;
@@ -13,17 +13,17 @@ public record struct RimfactoryExtensionCacheValue
{
public DefModExtension? ModExtension;
public static DefModExtension? TryGetExtension(Thing slotGroupParent)
public static DefModExtension? TryGetExtension(ThingDef thingDef)
=> RimfactoryTypes.ModExtension != null
? slotGroupParent.def.modExtensions?.Find(_extensionPredicate)
? thingDef.modExtensions?.Find(_extensionPredicate)
: null;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool HasExtension(Thing slotGroupParent)
=> RimfactoryTypes.ModExtension != null && TestExtension(slotGroupParent);
public static bool HasExtension(Thing thing)
=> RimfactoryTypes.ModExtension != null && TestExtension(thing.def);
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool TestExtension(Thing slotGroupParent) => slotGroupParent.TryGetRimfactoryExtension() != null;
private static bool TestExtension(ThingDef thingDef) => TryGetExtension(thingDef) != null;
static RimfactoryExtensionCacheValue() => EnsureInitialized();
@@ -44,4 +44,4 @@ public record struct RimfactoryExtensionCacheValue
=> RimfactoryTypes.ModExtension is { } extensionType
? AccessTools.FieldRefAccess<DefModExtension, int>(AccessTools.Field(extensionType, name))
: null;
}
}
@@ -24,11 +24,11 @@ public static class StorageExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static CompProperties? TryGetLwmCompProperties(this Thing thing)
=> LwmCompCache.GetOrAdd(thing).CompProperties;
=> LwmCompCache.GetOrAdd(thing.def).CompProperties;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DefModExtension? TryGetRimfactoryExtension(this Thing thing)
=> RimfactoryExtensionCache.GetOrAdd(thing).ModExtension;
=> RimfactoryExtensionCache.GetOrAdd(thing.def).ModExtension;
public static int GetTotalSlots(this SlotGroup slotGroup) => slotGroup.GetTotalSlots(slotGroup.CellsList.Count);
@@ -57,4 +57,4 @@ public static class StorageExtensions
LwmCompCacheValue.EnsureInitialized();
RimfactoryExtensionCacheValue.EnsureInitialized();
}
}
}
@@ -3,6 +3,7 @@
// 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.Linq;
using PerformanceFish.ModCompatibility;
namespace PerformanceFish.JobSystem;
@@ -41,13 +42,21 @@ public sealed class JobGiver_HaulOptimization : ClassWithFishPatches
}
}
public static List<Thing> SortedThingsPotentiallyNeedingHauling(List<Thing> things, Pawn pawn)
public static ICollection<Thing> SortedThingsPotentiallyNeedingHauling(ICollection<Thing> things, Pawn pawn)
{
((ThingPositionComparer)_comparer.Target).rootCell = pawn.Position;
things.Sort(_comparer);
return things;
if (things is List<Thing> list)
{
list.Sort(_comparer);
return list;
}
var sortedThings = things.ToList();
sortedThings.Sort(_comparer);
return sortedThings;
}
private static Comparison<Thing> _comparer = new ThingPositionComparer().Compare;
}
}
}
+70 -18
View File
@@ -6,6 +6,7 @@
using Mono.Cecil;
using Mono.Cecil.Cil;
using PerformanceFish.Prepatching;
using PerformanceFish.Utility;
namespace PerformanceFish.Listers;
@@ -40,7 +41,21 @@ public sealed class Buildings : ClassWithFishPrepatches
=> ilProcessor.ReplaceBodyWith(ReplacementBody<Building>);
public static IEnumerable<T> ReplacementBody<T>(ListerBuildings __instance)
=> (IEnumerable<T>)__instance.Cache().ColonistBuildingsByType.GetOrAdd(typeof(T));
=> typeof(Building).IsAssignableFrom(typeof(T))
? (IEnumerable<T>)__instance.Cache().ColonistBuildingsByType.GetOrAdd(typeof(T))
: FallbackLoop<T>(__instance);
[MethodImpl(MethodImplOptions.NoInlining)]
private static IEnumerable<T> FallbackLoop<T>(ListerBuildings __instance)
{
var allBuildingsColonist = __instance.allBuildingsColonist;
for (var i = 0; i < allBuildingsColonist.Count; i++)
{
if (allBuildingsColonist[i] is T val)
yield return val;
}
}
}
#endif
@@ -237,11 +252,17 @@ 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)
{
cache.ColonistBuildingsByDef.GetOrAdd(def).Add(b);
if (!cache.ColonistBuildingsByDef.GetOrAdd(def).TryAdd(b))
return;
if (b is Building_ResearchBench researchBench)
cache.ColonistResearchBenches.Add(researchBench);
@@ -250,18 +271,18 @@ public sealed class Buildings : ClassWithFishPrepatches
var buildingType = b.GetType();
do
while (buildingType != null && typeof(Building).IsAssignableFrom(buildingType))
{
if (buildingType == null)
cache.ColonistBuildingsByType.GetOrAdd(buildingType).Add(b);
if (buildingType == typeof(Building))
break;
cache.ColonistBuildingsByType.GetOrAdd(buildingType).Add(b);
buildingType = buildingType.BaseType;
}
while ((buildingType = buildingType.BaseType) != typeof(Building));
}
else
{
cache.NonColonistBuildingsByDef.GetOrAdd(def).Add(b);
cache.NonColonistBuildingsByDef.GetOrAdd(def).TryAdd(b);
}
}
}
@@ -290,6 +311,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)
@@ -302,14 +328,14 @@ public sealed class Buildings : ClassWithFishPrepatches
var buildingType = b.GetType();
do
while (buildingType != null && typeof(Building).IsAssignableFrom(buildingType))
{
if (buildingType == null)
cache.ColonistBuildingsByType.GetOrAdd(buildingType).Remove(b);
if (buildingType == typeof(Building))
break;
cache.ColonistBuildingsByType.GetOrAdd(buildingType).Remove(b);
buildingType = buildingType.BaseType;
}
while ((buildingType = buildingType.BaseType) != typeof(Building));
}
else
{
@@ -321,17 +347,43 @@ public sealed class Buildings : ClassWithFishPrepatches
public readonly record struct Cache()
{
public readonly FishTable<ThingDef, IndexedFishSet<Building>>
ColonistBuildingsByDef = [],
NonColonistBuildingsByDef = [];
ColonistBuildingsByDef = new() { ValueInitializer = static _ => InitializeBuildingsList<Building>() },
NonColonistBuildingsByDef = new() { ValueInitializer = static _ => InitializeBuildingsList<Building>() };
public readonly IndexedFishSet<Building_ResearchBench> ColonistResearchBenches = [];
public readonly IndexedFishSet<Building_ResearchBench> ColonistResearchBenches = InitializeBuildingsList<Building_ResearchBench>();
public readonly IndexedFishSet<Building> ColonistDeepScanners = [];
public readonly IndexedFishSet<Building> ColonistDeepScanners = InitializeBuildingsList<Building>();
public readonly FishTable<Type, IList> ColonistBuildingsByType = new()
{
ValueInitializer = static type
=> (IList)Activator.CreateInstance(typeof(IndexedFishSet<>).MakeGenericType(type))
=> (IList)_buildingsListInitializerDefinition.MakeGenericMethod(type).Invoke(null, null)
};
private static readonly MethodInfo _buildingsListInitializerDefinition
= methodof(InitializeBuildingsList<Building>).GetGenericMethodDefinition();
public static unsafe IndexedFishSet<T> InitializeBuildingsList<T>() where T : Building
#pragma warning disable CS8622
=> new(static () => new(0,
keyHashCodeByRefGetter: &GetKeyByRef,
keyHashCodeGetter: &ThingHelper.GetKey,
keyEqualityByRefComparer: &EqualsByKeyByRef,
keyEqualityComparer: &EqualsByKey));
#pragma warning restore CS8622
private static int GetKeyByRef<T>(ref T thing) where T : Building => thing.GetKey();
private static bool EqualsByKey<T>(T? left, T? right) where T : Building
=> ReferenceEquals(left, right)
|| left is not null
&& right is not null
&& left.GetKey() == right.GetKey();
private static bool EqualsByKeyByRef<T>(ref T? left, ref T? right) where T : Building
=> ReferenceEquals(left, right)
|| left is not null
&& right is not null
&& left.GetKey() == right.GetKey();
}
}
}
+6 -6
View File
@@ -183,22 +183,22 @@ public sealed class Haulables : ClassWithFishPrepatches
public readonly HashSet<Thing> ThingsQueuedToRemove = [];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void CheckUpdate(List<Thing> haulables)
public void CheckUpdate(ICollection<Thing> haulables)
{
if (IsDirty(haulables))
Update(haulables);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsDirty(List<Thing> haulables) => haulables.Count != ContainedThings.Count;
public bool IsDirty(ICollection<Thing> haulables) => haulables.Count != ContainedThings.Count;
[MethodImpl(MethodImplOptions.NoInlining)]
public void Update(List<Thing> haulables)
public void Update(ICollection<Thing> 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);
}
}
}
+26 -2
View File
@@ -12,6 +12,9 @@ namespace PerformanceFish.Listers;
public sealed class ThingsPrepatches : ClassWithFishPrepatches
{
private static readonly AccessTools.FieldRef<ListerThings, List<IHaulSource>> _haulSources
= AccessTools.FieldRefAccess<ListerThings, List<IHaulSource>>("haulSources");
public sealed class AddPatch : FishPrepatch
{
public override string? Description { get; }
@@ -31,6 +34,8 @@ public sealed class ThingsPrepatches : ClassWithFishPrepatches
lock (__instance.LockObject())
{
AddToDefList(__instance, t);
if (t is IHaulSource haulSource)
_haulSources(__instance).Add(haulSource);
var allGroups = ThingListGroupHelper.AllGroups;
foreach (var thingRequestGroup in allGroups)
@@ -74,6 +79,8 @@ public sealed class ThingsPrepatches : ClassWithFishPrepatches
lock (__instance.LockObject())
{
RemoveFromDefList(__instance, t);
if (t is IHaulSource haulSource)
_haulSources(__instance).Remove(haulSource);
var allGroups = ThingListGroupHelper.AllGroups;
for (var i = 0; i < allGroups.Length; i++)
@@ -268,6 +275,7 @@ public sealed class ThingsPrepatches : ClassWithFishPrepatches
__instance.IndexMapByDef().Clear();
__instance.IndexMapByGroup().Clear();
_haulSources(__instance).Clear();
ClearTypeLists(__instance);
}
@@ -568,9 +576,25 @@ public sealed class Things : ClassWithFishPatches
public static unsafe IndexedFishSet<T> InitializeThingsList<T>() 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<T>(ref T thing) where T : Thing => thing.GetKey();
private static bool EqualsByKey<T>(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<T>(ref T? left, ref T? right) where T : Thing
=> ReferenceEquals(left, right)
|| left is not null
&& right is not null
&& left.GetKey() == right.GetKey();
}
}
}
+6 -5
View File
@@ -59,15 +59,16 @@ public sealed class LogPatches : ClassWithFishPrepatches
public static void InsertEarlyMessages()
{
var unhandledMessages = Log.UnhandledMessages;
if (unhandledMessages is null)
Debug.LogError("Message queue is null");
Log.Ready = true;
Verse.Log.messageQueue.Enqueue(new(LogMessageType.Message, PERFORMANCE_FISH_WELCOME_MESSAGE,
PERFORMANCE_FISH_WELCOME_STACKTRACE));
while (unhandledMessages!.TryDequeue(out var message))
if (unhandledMessages is null)
return;
while (unhandledMessages.TryDequeue(out var message))
{
try
{
+14 -1
View File
@@ -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
}
}
}
+6 -3
View File
@@ -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<string>), 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;
}
}
@@ -0,0 +1,123 @@
// 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 System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using PerformanceFish.Prepatching;
namespace PerformanceFish;
// TODO: benchmark
public sealed class NeedsTrackerCaching : ClassWithFishPrepatches
{
public sealed class TryGetNeedPatch : FishPrepatch
{
public override string? Description { get; }
= "Caches generic need lookups behind the needs list version.";
public override MethodBase TargetMethodBase { get; } = GetTargetMethod(outParameter: false);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(TryGetNeed<Need>);
public static T? TryGetNeed<T>(Pawn_NeedsTracker instance) where T : Need
=> TryGetNeedInternal(instance, out T? need) ? need : null;
}
public sealed class TryGetNeedOutPatch : FishPrepatch
{
public override string? Description { get; }
= "Caches generic need lookups behind the needs list version.";
public override MethodBase TargetMethodBase { get; } = GetTargetMethod(outParameter: true);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(TryGetNeed<Need>);
public static bool TryGetNeed<T>(Pawn_NeedsTracker instance, out T? need) where T : Need
=> TryGetNeedInternal(instance, out need);
}
private static bool TryGetNeedInternal<T>(Pawn_NeedsTracker instance, out T? need) where T : Need
{
ref var cache
= ref Cache.ByReference<Pawn_NeedsTracker, RuntimeTypeHandle, CacheValue>.GetOrAddReference(instance,
typeof(T).TypeHandle);
var needs = instance.needs;
if (!cache.IsDirty(needs))
{
need = (T?)cache.Need;
return cache.Found;
}
return UpdateCache(instance, typeof(T), out need, ref cache);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool UpdateCache<T>(Pawn_NeedsTracker instance, Type needType, out T? need, ref CacheValue cache)
where T : Need
{
var needs = instance.needs;
for (var i = 0; i < needs.Count; i++)
{
var candidate = needs[i];
if (candidate.GetType() != needType)
continue;
need = (T)candidate;
cache.Update(needs, need, found: true);
return true;
}
need = null;
cache.Update(needs, null, found: false);
return false;
}
private static MethodInfo GetTargetMethod(bool outParameter)
{
foreach (var method in typeof(Pawn_NeedsTracker).GetMethods(AccessTools.all))
{
if (method.Name != nameof(Pawn_NeedsTracker.TryGetNeed)
|| !method.IsGenericMethodDefinition
|| method.GetParameters().Length != (outParameter ? 1 : 0))
{
continue;
}
return method;
}
return ThrowMissingMethodException(outParameter);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static MethodInfo ThrowMissingMethodException(bool outParameter)
=> throw new MissingMethodException(
$"Could not find Pawn_NeedsTracker.TryGetNeed generic overload with {(outParameter ? "one" : "zero")} parameter(s).");
public record struct CacheValue()
{
private List<Need>? _listReference;
private int _listVersion = -2;
public Need? Need;
public bool Found;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsDirty(List<Need> needs) => !ReferenceEquals(needs, _listReference) || needs._version != _listVersion;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(List<Need> needs, Need? need, bool found)
{
_listReference = needs;
_listVersion = needs._version;
Need = need;
Found = found;
}
}
}
+3 -1
View File
@@ -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}");
}
}
}
}
+5 -3
View File
@@ -193,8 +193,6 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasD
Patched = true;
foreach (var method in TargetMethodInfos)
{
DebugLog.Message($"Performance Fish is applying {GetType().Name} on {method.FullDescription()}");
try
{
if (IsBenchmarking)
@@ -232,6 +230,7 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasD
finalizer: TryMakeHarmonyMethod(FinalizerMethodInfo, FinalizerMethodPriority)));
}
}
}
catch (Exception e)
{
@@ -291,6 +290,9 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasD
private MethodInfo? TryGetMethod(string nameCaseInsensitive, Predicate<MethodInfo>? predicate = null)
=> GetType().TryGetMethod(nameCaseInsensitive, predicate);
private static string GetTargetMethodName(MethodBase method)
=> $"{method.DeclaringType?.FullName ?? "<no-type>"}.{method.Name}";
private static int TryGetPriority(MethodInfo? info)
=> info?.TryGetAttribute<HarmonyPriority>()?.info.priority ?? Priority.Normal;
@@ -378,4 +380,4 @@ internal static class Benchmarking
Method = method;
}
}
}
}
@@ -59,7 +59,9 @@ public class FishPatchHolder : IExposable, IEnumerable<FishPatch>
private void AddPatchesRecursively(Type type)
{
if (typeof(FishPatch).IsAssignableFrom(type) && !All.ContainsKey(type))
if (typeof(FishPatch).IsAssignableFrom(type)
&& !type.IsAbstract
&& !All.ContainsKey(type))
{
if (PerformanceFishMod.AllPatchClasses is { } allPatches)
RemoveDupes(allPatches, type);
@@ -92,4 +94,4 @@ public class FishPatchHolder : IExposable, IEnumerable<FishPatch>
IEnumerator IEnumerable.GetEnumerator() => All.Values.GetEnumerator();
private Type _type;
}
}
@@ -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
@@ -15,6 +15,10 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
{
public sealed class WorldObjectsHolderTickPatch : FishPrepatch
{
private static FishTable<Type, bool> _skippableCompTypes = null!;
private static FishTable<Type, bool> _skippableWorldObjectTypes = null!;
private static readonly List<IThingHolder> _tmpThingHolders = [];
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 +39,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 +58,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,9 +72,46 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
CachedMapsVersion = Current.gameInt.maps._version;
}
private static bool CanSkipWorldObjectTick(WorldObject worldObject)
{
return IsSkippableWorldObjectType(worldObject.GetType()) && !HasTickableContents(worldObject);
}
private static bool HasTickableContents(WorldObject worldObject)
{
if (worldObject is not IThingHolder holder)
return false;
lock (_tmpThingHolders)
{
_tmpThingHolders.Clear();
_tmpThingHolders.Add(holder);
holder.GetChildHolders(_tmpThingHolders);
for (var i = 0; i < _tmpThingHolders.Count; i++)
{
var thingHolder = _tmpThingHolders[i];
if (thingHolder is IThingHolderTickable tickable && !tickable.ShouldTickContents)
continue;
var directlyHeldThings = thingHolder.GetDirectlyHeldThings();
if (directlyHeldThings?.Owner is not null and not Map and not Caravan)
{
_tmpThingHolders.Clear();
return true;
}
}
_tmpThingHolders.Clear();
return false;
}
}
private static bool CanSkipCompTick(WorldObject worldObject)
{
var comps = worldObject.comps;
if (comps.Count == 0)
return true;
for (var i = comps.Count; i-- > 0;)
{
@@ -74,10 +119,8 @@ 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();
}
return true;
@@ -90,10 +133,7 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
private static Type?[]
_whitelistedTickingCompTypes =
[
typeof(WorldObjectComp),
typeof(FormCaravanComp),
typeof(TimedDetectionRaids),
typeof(EnterCooldownComp)
typeof(WorldObjectComp)
],
_whitelistedWorldObjectTypes =
[
@@ -106,28 +146,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<Type>
SkippableComps = InitializeSkippableComps(),
SkippableWorldObjects = InitializeSkippableWorldObjects();
private static void ResetSkippableCompTypes()
{
_skippableCompTypes = new()
{
ValueInitializer = static type => ComputeIsSkippableCompType(type)
};
}
private static HashSet<Type> InitializeSkippableComps()
=> MakeSubclassHashSet(typeof(WorldObjectComp), nameof(WorldObjectComp.CompTick),
_whitelistedTickingCompTypes);
private static void ResetSkippableWorldObjectTypes()
{
_skippableWorldObjectTypes = new()
{
ValueInitializer = static type => ComputeIsSkippableWorldObjectType(type)
};
}
private static HashSet<Type> InitializeSkippableWorldObjects()
=> MakeSubclassHashSet(typeof(WorldObject), nameof(WorldObject.Tick), _whitelistedWorldObjectTypes);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsSkippableCompType(Type type) => _skippableCompTypes.GetOrAdd(type);
private static HashSet<Type> 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 +216,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 +331,4 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
}
}
}
}
}
+4 -3
View File
@@ -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<GroupThingPair, int> CreateIndexMapByGroup() => new();
public static Cache.CellGrid<int> 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<HediffComp> CreateHediffCompList() => new();
}
}
@@ -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;
@@ -104,7 +105,7 @@ public abstract class FishPrepatch : FishPrepatchBase
Log.Error($"Exception while applying postfix for {GetType().FullName}\n{e}\n{new StackTrace(true)}");
}
}
targetMethodBody.SimplifyMacros();
targetMethodBody.OptimizeMacros();
}
@@ -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<object>();
if (resultVariable != null)
postfixInstructions.Add((OpCodes.Stloc, resultVariable));
if (hasReturnValue)
{
postfixInstructions.Add((OpCodes.Stloc, resultVariable!));
postfixInstructions.Add(OpCodes.Nop);
}
else
{
postfixInstructions.Add(OpCodes.Nop);
}
foreach (var parameter in postfixParameters)
{
@@ -217,36 +230,87 @@ public abstract class FishPrepatch : FishPrepatchBase
postfixInstructions.Add((OpCodes.Call, postfixMethodInfo));
}
if (postfixMethodInfo.ReturnType == typeof(void) && resultVariable != null)
postfixInstructions.Add((OpCodes.Ldloc, resultVariable));
if (postfixMethodInfo.ReturnType != typeof(void))
postfixInstructions.Add((OpCodes.Stloc, resultVariable!));
Span<int> retInstructions = stackalloc int[instructions.Count];
var retInstructionCount = 0;
for (var i = 0; i < instructions.Count; i++)
{
if (instructions[i].OpCode == OpCodes.Ret)
retInstructions[retInstructionCount++] = i;
}
if (hasReturnValue)
postfixInstructions.Add((OpCodes.Ldloc, resultVariable!));
var lastRetInstructionIndex = retInstructions[retInstructionCount - 1];
var lastRetInstruction = retInstructions[^1];
var lastRetInstructionIndex = instructions.IndexOf(lastRetInstruction);
ilProcessor.InsertRange(lastRetInstructionIndex, postfixInstructions);
var captureStart = instructions[lastRetInstructionIndex];
var postfixStart = instructions[lastRetInstructionIndex + (hasReturnValue ? 1 : 0)];
for (var i = 0; i < instructions.Count; i++)
RetargetExceptionHandlerEnds(targetMethod.Body, lastRetInstruction, captureStart);
var retInstructionSet = retInstructions.ToHashSet();
foreach (var instruction in instructions)
{
var instruction = instructions[i];
if (instruction.Operand is Instruction branchTarget && branchTarget.OpCode == OpCodes.Ret)
instruction.Operand = instructions[lastRetInstructionIndex];
if (instruction.Operand is Instruction branchTarget && retInstructionSet.Contains(branchTarget))
instruction.Operand = captureStart;
}
for (var i = 0; i < retInstructionCount - 1; i++)
for (var i = 0; i < retInstructions.Length - 1; i++)
{
var instruction = instructions[retInstructions[i]];
instruction.OpCode = OpCodes.Br;
instruction.Operand = instructions[lastRetInstructionIndex];
var instruction = retInstructions[i];
if (hasReturnValue)
ilProcessor.InsertBefore(instruction, Instruction.Create(OpCodes.Stloc, resultVariable!));
instruction.OpCode = IsInProtectedRegion(targetMethod.Body, instruction) ? OpCodes.Leave : OpCodes.Br;
instruction.Operand = postfixStart;
}
}
private static void RetargetExceptionHandlerEnds(Mono.Cecil.Cil.MethodBody methodBody, Instruction oldBoundary,
Instruction newBoundary)
{
foreach (var exceptionHandler in methodBody.ExceptionHandlers)
{
if (exceptionHandler.TryEnd == oldBoundary)
exceptionHandler.TryEnd = newBoundary;
if (exceptionHandler.HandlerEnd == oldBoundary)
exceptionHandler.HandlerEnd = newBoundary;
}
}
private static bool IsInProtectedRegion(Mono.Cecil.Cil.MethodBody methodBody, Instruction instruction)
{
var instructions = methodBody.Instructions;
var instructionIndex = instructions.IndexOf(instruction);
foreach (var exceptionHandler in methodBody.ExceptionHandlers)
{
if (IsWithinRange(instructions, instructionIndex, exceptionHandler.TryStart, exceptionHandler.TryEnd)
|| IsWithinRange(instructions, instructionIndex, exceptionHandler.HandlerStart,
exceptionHandler.HandlerEnd)
|| exceptionHandler.FilterStart != null
&& IsWithinRange(instructions, instructionIndex, exceptionHandler.FilterStart,
exceptionHandler.HandlerStart))
{
return true;
}
}
return false;
}
private static bool IsWithinRange(Mono.Collections.Generic.Collection<Instruction> instructions,
int instructionIndex, Instruction? rangeStart, Instruction? rangeEnd)
{
if (rangeStart == null)
return false;
var rangeStartIndex = instructions.IndexOf(rangeStart);
if (rangeStartIndex < 0 || instructionIndex < rangeStartIndex)
return false;
if (rangeEnd == null)
return true;
var rangeEndIndex = instructions.IndexOf(rangeEnd);
return rangeEndIndex < 0 || instructionIndex < rangeEndIndex;
}
private static VariableDefinition? GetStateVariable(ILProcessor ilProcessor, ref PatchVariables patchVariables,
ParameterInfo? stateParameter)
=> stateParameter != null
@@ -352,4 +416,4 @@ public abstract class FishPrepatch : FishPrepatchBase
{
public VariableDefinition? Result, State;
}
}
}
@@ -13,8 +13,9 @@ public static class FishPrepatchExtensions
{
var prepatches = ToSortedList(allPrepatchClasses, true);
foreach (var prepatch in prepatches)
for (var i = 0; i < prepatches.Count; i++)
{
var prepatch = prepatches[i];
if (!prepatch.Enabled)
continue;
@@ -40,8 +41,9 @@ public static class FishPrepatchExtensions
{
var prepatches = ToSortedList(allPrepatchClasses, false);
foreach (var prepatch in prepatches)
for (var i = 0; i < prepatches.Count; i++)
{
var prepatch = prepatches[i];
if (!prepatch.IsActive)
continue;
@@ -70,4 +72,5 @@ public static class FishPrepatchExtensions
prepatches.Sort(FishPrepatchBase.PriorityComparer);
return prepatches;
}
}
}
@@ -37,7 +37,9 @@ public class FishPrepatchHolder : IExposable, IEnumerable<FishPrepatchBase>
private void AddPatchesRecursively(Type type)
{
if (typeof(FishPrepatchBase).IsAssignableFrom(type) && !All.ContainsKey(type))
if (typeof(FishPrepatchBase).IsAssignableFrom(type)
&& !type.IsAbstract
&& !All.ContainsKey(type))
{
if (PerformanceFishMod.AllPrepatchClasses is { } allPatches)
RemoveDupes(allPatches, type);
@@ -69,4 +71,4 @@ public class FishPrepatchHolder : IExposable, IEnumerable<FishPrepatchBase>
IEnumerator IEnumerable.GetEnumerator() => All.Values.GetEnumerator();
private Type _type;
}
}
@@ -68,40 +68,79 @@ public sealed class DynamicDrawManagerPatches : ClassWithFishPrepatches
public static void CullAndInitializeThings(DynamicDrawManager instance)
{
var map = instance.map;
var mapSizeX = map.SizeX();
var cellIndices = map.cellIndices;
var mapSizeX = GetMapSizeX(cellIndices);
var fogGrid = map.fogGrid.fogGrid;
var snowGrid = map.snowGrid.depthGrid;
var checkShadows = MatBases.SunShadow.shader.isSupported;
var currentViewRect = Find.CameraDriver.CurrentViewRect.ClipInsideMap(map).ExpandedBy(1);
var currentViewRect = Find.CameraDriver.CurrentViewRect.ExpandedBy(1);
#if V1_6
if (WorldComponent_GravshipController.GravshipRenderInProgess)
currentViewRect = currentViewRect.Encapsulate(WorldComponent_GravshipController.GravshipRenderBounds);
#endif
currentViewRect = currentViewRect.ClipInsideMap(map);
var shadowViewRect = SectionLayer_SunShadows.GetSunShadowsViewRect(map, currentViewRect);
instance.drawThings.UnwrapArray(out var drawThings, out var drawThingsCount);
for (var i = 0; i < drawThingsCount; i++)
{
var drawThing = drawThings.UnsafeLoad(i);
var position = drawThing.Position;
var position = GetCullPosition(drawThing);
var drawThingDef = drawThing.def;
var cellIndex = position.CellToIndex(mapSizeX);
if ((!fogGrid[cellIndex] || drawThingDef.seeThroughFog)
&& (drawThingDef.hideAtSnowDepth >= 1f
|| drawThingDef.hideAtSnowDepth >= snowGrid[cellIndex]))
if (!CellIndicesContain(cellIndices, cellIndex)
|| (!drawThingDef.seeThroughFog
#if V1_6
&& fogGrid.IsSet(cellIndex))
#else
&& fogGrid[cellIndex])
#endif
#if V1_6
|| (drawThingDef.hideAtSnowOrSandDepth < 1f
&& snowGrid[cellIndex] > drawThingDef.hideAtSnowOrSandDepth))
#else
|| (drawThingDef.hideAtSnowDepth < 1f
&& snowGrid[cellIndex] > drawThingDef.hideAtSnowDepth))
#endif
{
if (currentViewRect.Contains(position)
|| currentViewRect.Overlaps(drawThing.OccupiedDrawRect()))
{
ThingsToDraw.Add(drawThing);
if (!DebugViewSettings.singleThreadedDrawing)
EnsureInitialized(drawThing);
}
else if (drawThing is Pawn pawn && checkShadows && shadowViewRect.Contains(drawThing.Position))
{
PawnShadowsToDraw.Add(pawn);
}
continue;
}
if (currentViewRect.Contains(position)
|| currentViewRect.Overlaps(drawThing.OccupiedDrawRect()))
{
ThingsToDraw.Add(drawThing);
if (!DebugViewSettings.singleThreadedDrawing)
EnsureInitialized(drawThing);
}
else if (drawThing is Pawn pawn && checkShadows && shadowViewRect.Contains(position))
{
PawnShadowsToDraw.Add(pawn);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static IntVec3 GetCullPosition(Thing drawThing)
=> drawThing is Pawn ? drawThing.DrawPos.ToIntVec3() : drawThing.Position;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CellIndicesContain(CellIndices cellIndices, int index)
#if V1_6
=> cellIndices.Contains(index);
#else
=> (uint)index < (uint)(cellIndices.mapSizeX * cellIndices.mapSizeZ);
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetMapSizeX(CellIndices cellIndices)
#if V1_6
=> cellIndices.SizeX;
#else
=> cellIndices.mapSizeX;
#endif
public static void EnsureInitialized(Thing drawThing)
{
try
@@ -228,4 +267,4 @@ public sealed class DynamicDrawManagerPatches : ClassWithFishPrepatches
}
}
}
#endif
#endif
@@ -0,0 +1,134 @@
// Copyright (c) 2026 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;
namespace PerformanceFish.Rendering;
public sealed class PawnRenderingOptimizations : ClassWithFishPrepatches
{
private const int DAMAGE_FLASH_TICKS_TOTAL = 16;
public sealed class IsHiddenFromPlayerPatch : FishPrepatch
{
// not 100% on this even benefitting, bionics, scars, etc likely worsen performance here because of the extra check
// probably better to just track if a hediff adds invisibility
public override string? Description { get; }
= "Fast-paths InvisibilityUtility.IsHiddenFromPlayer for the case where a pawn has no hediffs.";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(InvisibilityUtility),
nameof(InvisibilityUtility.IsHiddenFromPlayer), [typeof(Pawn)]);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ReplacementBody(Pawn pawn)
{
if (DebugSettings.showHiddenPawns)
return false;
if (pawn.Faction == Faction.OfPlayer)
return false;
var hediffs = pawn.health.hediffSet.hediffs;
if (hediffs.Count == 0)
return false;
for (var i = 0; i < hediffs.Count; i++)
{
var comp = hediffs[i].TryGetComp<HediffComp_Invisibility>();
if (comp != null && !comp.Props.visibleToPlayer && !comp.PsychologicallyVisible)
return true;
}
return false;
}
}
public sealed class PawnRenderTreeComputeMatrixPatch : FishPrepatch
{
public override string? Description { get; }
= "Avoids Matrix4x4 inverse work when undoing a pivot translation in PawnRenderTree.ComputeMatrix.";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(PawnRenderTree), "ComputeMatrix");
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReplacementBody(PawnRenderTree instance, ref Matrix4x4 matrix, in Vector3 offset,
in Vector3 pivot, in Quaternion rotation, in Vector3 scale, bool canRotate)
{
if (offset != Vector3.zero)
matrix *= Matrix4x4.Translate(offset);
if (pivot != Vector3.zero)
matrix *= Matrix4x4.Translate(pivot);
if (canRotate && rotation != Quaternion.identity)
matrix *= Matrix4x4.Rotate(rotation);
if (scale != Vector3.one)
matrix *= Matrix4x4.Scale(scale);
if (pivot != Vector3.zero)
matrix *= Matrix4x4.Translate(-pivot);
}
}
public sealed class DamageFlasherCurColorPatch : FishPrepatch
{
private static readonly AccessTools.FieldRef<DamageFlasher, int> _lastDamageTick
= AccessTools.FieldRefAccess<DamageFlasher, int>("lastDamageTick");
public override string? Description { get; }
= "Fast-paths DamageFlasher.CurColor when no damage flash is active.";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredPropertyGetter(typeof(DamageFlasher), nameof(DamageFlasher.CurColor));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Color ReplacementBody(DamageFlasher instance)
{
var ticksLeft = _lastDamageTick(instance) + DAMAGE_FLASH_TICKS_TOTAL - Find.TickManager.TicksGame;
return ticksLeft <= 0
? Color.white
: Color.Lerp(Color.white, DamagedMatPool.DamagedMatStartingColor,
(float)ticksLeft / DAMAGE_FLASH_TICKS_TOTAL);
}
}
public sealed class DamageFlasherGetDamagedMatPatch : FishPrepatch
{
private static readonly AccessTools.FieldRef<DamageFlasher, int> _lastDamageTick
= AccessTools.FieldRefAccess<DamageFlasher, int>("lastDamageTick");
public override string? Description { get; }
= "Fast-paths DamageFlasher.GetDamagedMat when no damage flash is active.";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(DamageFlasher), nameof(DamageFlasher.GetDamagedMat));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Material ReplacementBody(DamageFlasher instance, Material baseMat)
{
var ticksLeft = _lastDamageTick(instance) + DAMAGE_FLASH_TICKS_TOTAL - Find.TickManager.TicksGame;
return ticksLeft <= 0
? baseMat
: DamagedMatPool.GetDamageFlashMat(baseMat, (float)ticksLeft / DAMAGE_FLASH_TICKS_TOTAL);
}
}
}
@@ -0,0 +1,52 @@
// 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.Rendering;
public sealed class TextureAtlasPatches : ClassWithFishPatches
{
public sealed class StaticTextureAtlas_ApplyTextureCompression : FishPatch
{
public override bool DefaultState => false;
public override string? Description { get; }
= "Routes texture atlas compression through the GPU path when compute shaders are available, "
+ "regardless of the VRAM threshold check in UnityData.ComputeShadersSupported. Prevents native "
+ "crashes in debug Mono builds.";
public override MethodBase TargetMethodInfo
=> AccessTools.DeclaredMethod(typeof(StaticTextureAtlas), "ApplyTextureCompression");
public static bool Prefix(StaticTextureAtlas __instance, bool noGpuCompressionSupport)
{
if (!noGpuCompressionSupport || !SystemInfo.supportsComputeShaders)
return true;
if (__instance.ColorTexture != null)
{
var name = __instance.ColorTexture.name;
var compressed = StaticTextureAtlas.FastCompressDXT(__instance.ColorTexture, deleteOriginal: true);
compressed.name = name;
_colorTextureField.SetValue(__instance, compressed);
}
if (__instance.MaskTexture != null)
{
var name = __instance.MaskTexture.name;
var compressed = StaticTextureAtlas.FastCompressDXT(__instance.MaskTexture, deleteOriginal: true);
compressed.name = name;
_maskTextureField.SetValue(__instance, compressed);
}
return false;
}
private static readonly FieldInfo _colorTextureField
= AccessTools.DeclaredField(typeof(StaticTextureAtlas), "colorTexture");
private static readonly FieldInfo _maskTextureField
= AccessTools.DeclaredField(typeof(StaticTextureAtlas), "maskTexture");
}
}
@@ -93,9 +93,9 @@ public sealed class TextureLoadingPatches : ClassWithFishPrepatches
var data = file.ReadAllBytes();
texture2D = new(2, 2, TextureFormat.Alpha8, true);
texture2D.LoadImage(data);
FixMipMapsIfNeeded(texture2D, data, file);
FixMipMapsIfNeeded(ref texture2D, data, file);
}
if (!isDds && Prefs.TextureCompression)
texture2D.Compress(true);
@@ -120,11 +120,11 @@ public sealed class TextureLoadingPatches : ClassWithFishPrepatches
var texture2D = new Texture2D(2, 2, TextureFormat.Alpha8, true);
texture2D.LoadImage(data);
FixMipMapsIfNeeded(texture2D, data, file);
FixMipMapsIfNeeded(ref texture2D, data, file);
return texture2D;
}
public static void FixMipMapsIfNeeded(Texture2D texture2D, byte[] data, VirtualFile file)
public static void FixMipMapsIfNeeded(ref Texture2D texture2D, byte[] data, VirtualFile file)
{
if (((texture2D.width & 3) == 0) & ((texture2D.height & 3) == 0))
return;
+4 -5
View File
@@ -349,8 +349,7 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Room? RoomAt(IntVec3 c, Map map, RegionType allowedRegionTypes = RegionType.Set_All)
{
var cellIndices = map.cellIndices;
if (((uint)c.x >= (uint)cellIndices.mapSizeX) | ((uint)c.z >= (uint)cellIndices.mapSizeZ))
if (!c.InBounds(map))
return null;
var regionAndRoomUpdater = map.regionAndRoomUpdater;
@@ -360,8 +359,8 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
else if (regionAndRoomUpdater.AnythingToRebuild)
LogIncorrectResultWarning(c);
var region = map.regionGrid.regionGrid[(c.z * cellIndices.mapSizeX) + c.x];
return region is null || !region.valid || (region.type & allowedRegionTypes) == 0
var region = map.regionGrid.GetValidRegionAt_NoRebuild(c);
return region is null || (region.type & allowedRegionTypes) == 0
? null
: region.District?.Room;
}
@@ -400,4 +399,4 @@ public sealed class RoomPrepatches : ClassWithFishPrepatches
return tmpRegions;
}
}
}
}
@@ -382,6 +382,8 @@ public sealed class ReflectionCaching : ClassWithFishPatches
public sealed class GetFullName : FishPatch
{
public override bool DefaultState => true;
public override string? Description { get; }
= "Normally averages at over 1000ns per call. Caching makes this about 30 times faster, including "
+ "Harmony overhead. Type.Name is similarly slow too, but unfortunately can't be patched as it's an "
@@ -392,54 +394,83 @@ public sealed class ReflectionCaching : ClassWithFishPatches
= AccessTools.DeclaredPropertyGetter(typeof(RuntimeType), nameof(RuntimeType.FullName));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(RuntimeType __instance, ref string? __result, out bool __state)
public static bool Prefix(RuntimeType __instance, ref string? __result, out FullNameState __state)
{
ref var cache = ref TypeFullNameCache.GetOrAddReference(__instance.TypeHandle);
__state = default;
if (_fullNameRecursionDepth != 0)
return true;
if (!cache.Cached && !TryGetFromCentralCache(__instance, ref cache))
return __state = true;
__state.ReleaseGuard = true;
_fullNameRecursionDepth++;
var cache = TypeFullNameCache.GetOrAddReference(__instance.TypeHandle);
var fetchedFromCentralCache = false;
if (!cache.Cached)
{
fetchedFromCentralCache = TryGetFromCentralCache(__instance, out cache);
if (!fetchedFromCentralCache)
{
__state.ShouldStore = true;
return true;
}
}
if (fetchedFromCentralCache)
TypeFullNameCache.GetExistingReference(__instance.TypeHandle) = cache;
__result = cache.Name;
return __state = false;
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool TryGetFromCentralCache(RuntimeType instance,
ref TypeFullNameCacheValue cache)
out TypeFullNameCacheValue cache)
{
lock (_lock)
{
ref var centralCache
= ref TypeFullNameCache.GetDirectly.GetOrAddReference(instance.TypeHandle);
cache.Name = centralCache.Name;
cache = TypeFullNameCache.GetDirectly.GetOrAddReference(instance.TypeHandle);
if (!cache.Cached)
return false;
}
return cache.Cached;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(RuntimeType __instance, string? __result, bool __state)
public static void Postfix(RuntimeType __instance, string? __result, FullNameState __state)
{
if (!__state)
if (!__state.ShouldStore)
return;
UpdateCache(__instance, __result);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Exception? Finalizer(Exception? __exception, ref FullNameState __state)
{
if (__state.ReleaseGuard)
_fullNameRecursionDepth--;
return __exception;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateCache(RuntimeType __instance, string? __result)
{
ref var cache = ref TypeFullNameCache.GetExistingReference(__instance.TypeHandle);
cache.Name = __result;
var cache = new TypeFullNameCacheValue
{
Cached = true,
Name = __result
};
TypeFullNameCache.GetExistingReference(__instance.TypeHandle) = cache;
lock (_lock)
{
ref var centralCache
= ref TypeFullNameCache.GetDirectly.GetReference(__instance.TypeHandle);
centralCache.Name = __result;
}
TypeFullNameCache.GetDirectly.GetOrAddReference(__instance.TypeHandle) = cache;
}
[ThreadStatic]
private static int _fullNameRecursionDepth;
private static object _lock = new();
}
@@ -474,24 +505,16 @@ public sealed class ReflectionCaching : ClassWithFishPatches
public PropertyInfo? Info;
}
public record struct TypeFullNameCacheValue()
public record struct FullNameState
{
private object? _value = typeof(void);
public bool ReleaseGuard;
public bool ShouldStore;
}
public string? Name
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Unsafe.As<string?>(_value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => _value = value;
}
public bool Cached
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _value is not MemberInfo;
}
public record struct TypeFullNameCacheValue
{
public bool Cached;
public string? Name;
}
}
@@ -499,6 +522,8 @@ public sealed class ReflectionCaching : ClassWithFishPatches
{
public sealed class GetCustomAttributes : FishPatch
{
public override bool DefaultState => true;
public override string? Description { get; }
= "Caches attributes for reflection lookups. Decent load time improvement.";
@@ -510,58 +535,193 @@ public sealed class ReflectionCaching : ClassWithFishPatches
public static bool Prefix(ICustomAttributeProvider obj, Type attributeType, bool inherit,
out object[]? __result, out CustomAttributeState __state)
{
var key = new CustomAttributeCache(obj, (__state.AttributeType = attributeType).TypeHandle,
__state.Inherit = inherit);
var key = new CustomAttributeCache(obj, attributeType.TypeHandle, inherit);
__state = new() { Key = key };
ref var cache = ref CustomAttributeCache.GetOrAddReference(in key);
if (cache.Attributes is null)
TryGetFromCentralCache(ref key, ref cache);
var cacheLookup = TryGetCachedAttributes(ref key, out __result);
return __state.State = (__result = cache.Attributes) is null;
if (cacheLookup == CustomAttributeCacheLookup.Hit)
return false;
if (cacheLookup == CustomAttributeCacheLookup.InProgress)
{
__result = null;
return true;
}
MarkInProgress(ref key);
__state.ShouldStore = true;
__result = null;
return true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void TryGetFromCentralCache(ref CustomAttributeCache key,
ref CustomAttributeCacheValue cache)
private static CustomAttributeCacheLookup TryGetCachedAttributes(ref CustomAttributeCache key,
out object[]? result)
{
var localLookup = TryUseCache(CustomAttributeCache.Get, ref key, out result);
if (localLookup == CustomAttributeCacheLookup.Hit)
{
result = CopyCachedAttributes(result!);
return CustomAttributeCacheLookup.Hit;
}
if (localLookup == CustomAttributeCacheLookup.InProgress)
return localLookup;
lock (_lock)
cache.Attributes = CustomAttributeCache.GetDirectly.GetOrAdd(ref key).Attributes;
{
var centralLookup = TryUseCache(CustomAttributeCache.GetDirectly, ref key, out var centralResult);
if (centralLookup == CustomAttributeCacheLookup.Hit)
{
result = CopyCachedAttributes(centralResult!);
CustomAttributeCache.Get[key] = new()
{
Attributes = centralResult,
Status = CustomAttributeCacheStatus.Completed
};
return CustomAttributeCacheLookup.Hit;
}
if (centralLookup == CustomAttributeCacheLookup.InProgress)
return CustomAttributeCacheLookup.InProgress;
}
result = null;
return CustomAttributeCacheLookup.Miss;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(ICustomAttributeProvider obj, object[]? __result,
in CustomAttributeState __state)
ref CustomAttributeState __state)
{
if (!__state.State || __result is null)
if (!__state.ShouldStore || __result is null)
return;
UpdateCache(obj, __state, __result);
StoreCompleted(ref __state.Key, __result);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateCache(ICustomAttributeProvider obj, in CustomAttributeState __state,
object[]? __result)
public static Exception? Finalizer(Exception? __exception, ref CustomAttributeState __state)
{
var key = new CustomAttributeCache(obj, __state.AttributeType.TypeHandle, __state.Inherit);
CustomAttributeCache.GetExistingReference(in key).Attributes = __result;
if (__state.ShouldStore && __exception != null)
ClearInProgress(ref __state.Key);
return __exception;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static CustomAttributeCacheLookup TryUseCache(
Dictionary<CustomAttributeCache, CustomAttributeCacheValue> cache,
ref CustomAttributeCache key, out object[]? result)
{
if (!cache.TryGetValue(key, out var value))
{
result = null;
return CustomAttributeCacheLookup.Miss;
}
switch (value.Status)
{
case CustomAttributeCacheStatus.Completed:
result = value.Attributes;
return CustomAttributeCacheLookup.Hit;
case CustomAttributeCacheStatus.InProgress:
LogInProgress(ref key);
result = null;
return CustomAttributeCacheLookup.InProgress;
default:
result = null;
return CustomAttributeCacheLookup.Miss;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void MarkInProgress(ref CustomAttributeCache key)
{
var value = new CustomAttributeCacheValue
{
Status = CustomAttributeCacheStatus.InProgress
};
lock (_lock)
CustomAttributeCache.GetDirectly.GetReference(ref key).Attributes = __result;
{
CustomAttributeCache.Get[key] = value;
CustomAttributeCache.GetDirectly[key] = value;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void StoreCompleted(ref CustomAttributeCache key, object[] result)
{
var value = new CustomAttributeCacheValue
{
Attributes = CopyCachedAttributes(result),
Status = CustomAttributeCacheStatus.Completed
};
lock (_lock)
{
CustomAttributeCache.Get[key] = value;
CustomAttributeCache.GetDirectly[key] = value;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ClearInProgress(ref CustomAttributeCache key)
{
lock (_lock)
{
if (CustomAttributeCache.Get.TryGetValue(key, out var localValue)
&& localValue.Status == CustomAttributeCacheStatus.InProgress)
{
CustomAttributeCache.Get.Remove(key);
}
if (CustomAttributeCache.GetDirectly.TryGetValue(key, out var centralValue)
&& centralValue.Status == CustomAttributeCacheStatus.InProgress)
{
CustomAttributeCache.GetDirectly.Remove(key);
}
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void LogInProgress(ref CustomAttributeCache _)
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static object[] CopyCachedAttributes(object[] attributes)
=> attributes.Length != 0 ? (object[])attributes.Clone() : Array.Empty<object>();
private static object _lock = new();
}
public record struct CustomAttributeCacheValue
{
public object[]? Attributes;
public CustomAttributeCacheStatus Status;
}
public record struct CustomAttributeState
{
public bool State, Inherit;
public Type AttributeType;
public bool ShouldStore;
public CustomAttributeCache Key;
}
public enum CustomAttributeCacheStatus : byte
{
Uninitialized,
InProgress,
Completed
}
private enum CustomAttributeCacheLookup : byte
{
Miss,
Hit,
InProgress
}
}
@@ -577,8 +737,12 @@ public sealed class ReflectionCaching : ClassWithFishPatches
Guard.IsNotNull(array);
Guard.IsLessThanOrEqualTo(array.Length, byte.MaxValue);
Array = array;
var length = array.Length;
_hashCode = HashCode.Combine(length, length > 0 && Array[0] is { } item ? item.GetHashCode() : 0);
var hashCode = array.Length;
for (var i = 0; i < array.Length; i++)
hashCode = global::System.HashCode.Combine(hashCode, array[i]);
_hashCode = hashCode;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -587,13 +751,16 @@ public sealed class ReflectionCaching : ClassWithFishPatches
if (Array is null)
return other.Array is null;
if (_hashCode != other._hashCode)
return false;
if (other.Array is null || Array.Length != other.Array.Length)
return false;
var length = Array.Length;
for (var i = 0; i < length; i++)
{
if (!Array[i].Equals(other.Array![i]))
if (!EqualityComparer<T>.Default.Equals(Array[i], other.Array[i]))
return false;
}
@@ -608,6 +775,8 @@ public sealed class ReflectionCaching : ClassWithFishPatches
{
public sealed class CreateInstance_Type : FishPatch
{
public override bool DefaultState => true;
public override List<string> IncompatibleModIDs { get; } = [PackageIDs.MULTIPLAYER];
public override string? Description { get; }
@@ -619,21 +788,94 @@ public sealed class ReflectionCaching : ClassWithFishPatches
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type? type, ref object __result)
{
if (type is null)
if (type is not RuntimeType runtimeType)
return true;
ref var cache = ref ActivatorCache.GetOrAddReference(type.TypeHandle);
ref var cache = ref ActivatorCache.GetOrAddReference(runtimeType.TypeHandle);
if (ReferenceEquals(cache.Result, _unsupportedActivatorSentinel))
return true;
if (cache.Result is not null)
return TryInvoke(ref __result, cache.Result);
if (!CanUseFastPath(runtimeType))
return MarkUnsupported(runtimeType, ref cache);
if (cache.Dirty
&& !ActivatorCache.UpdateAsyncCache<ActivatorCacheValue, Type, Func<object>>(ref cache,
type.TypeHandle, type))
&& !ActivatorCache.UpdateAsyncCache<ActivatorCacheValue, RuntimeType, Func<object>>(ref cache,
runtimeType.TypeHandle, runtimeType))
{
return true;
}
__result = cache.Result!();
return false;
try
{
__result = cache.Result!();
return false;
}
catch (OverflowException)
{
throw;
}
catch (Exception ex)
{
throw new TargetInvocationException(ex);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool MarkUnsupported(RuntimeType type, ref ActivatorCacheValue cache)
{
cache.Result = _unsupportedActivatorSentinel;
lock (ActivatorCache.SyncLock)
ActivatorCache.GetDirectly.GetOrAddReference(type.TypeHandle).Result = _unsupportedActivatorSentinel;
return true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool TryInvoke(ref object __result, Func<object> activator)
{
try
{
__result = activator();
return false;
}
catch (OverflowException)
{
throw;
}
catch (Exception ex)
{
throw new TargetInvocationException(ex);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool CanUseFastPath(RuntimeType type)
{
if (type.HasElementType
|| type.ContainsGenericParameters
|| type.IsInterface
|| type.IsAbstract
|| type.IsCOMObject
|| type == typeof(void)
|| type == typeof(TypedReference)
|| type == typeof(ArgIterator)
|| type == typeof(RuntimeArgumentHandle))
{
return false;
}
return type.IsValueType
|| type.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null)
is not null;
}
private static readonly Func<object> _unsupportedActivatorSentinel
= static () => throw new InvalidOperationException("Unsupported activator sentinel");
}
// public sealed class CreateInstance_Type_Args : FishPatch
@@ -642,14 +884,34 @@ public sealed class ReflectionCaching : ClassWithFishPatches
// = (Func<Type, object[], object>)Activator.CreateInstance;
// }
public record struct ActivatorCacheValue : IAsyncCacheable<RuntimeTypeHandle, Type, Func<object>>
public record struct ActivatorCacheValue : IAsyncCacheable<RuntimeTypeHandle, RuntimeType, Func<object>>
{
public Func<object>? Result { get; set; }
public Task<Func<object>>? Task { get; set; }
public bool Dirty => Result == null;
public ValueTask<Func<object>?> MakeResultAsync(RuntimeTypeHandle key, Type type)
=> new(Reflection.CreateConstructorDelegate<Func<object>>(type));
public ValueTask<Func<object>?> MakeResultAsync(RuntimeTypeHandle key, RuntimeType type)
=> new(MakeActivatorDelegate(type));
[MethodImpl(MethodImplOptions.NoInlining)]
private static Func<object>? MakeActivatorDelegate(RuntimeType type)
{
if (type.IsValueType)
return Reflection.CreateConstructorDelegate<Func<object>>(type);
var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null,
Type.EmptyTypes, null);
if (constructor is null)
return null;
var dynamicMethod = new DynamicMethod($"Activator_{type.Name}", typeof(object), Type.EmptyTypes,
type, true);
var il = dynamicMethod.GetILGenerator();
il.Emit(FishTranspiler.New(constructor));
il.Emit(FishTranspiler.Return);
return (Func<object>)dynamicMethod.CreateDelegate(typeof(Func<object>));
}
}
// public record struct ActivatorWithArgumentsCacheValue : IAsyncCacheable<ActivatorWithArgumentsCache, Type,
@@ -739,6 +1001,8 @@ public sealed class ReflectionCaching : ClassWithFishPatches
{
public sealed class GetValue_Patch : FishPatch
{
public override bool DefaultState => true;
public override string Description { get; }
= "Optimizes the GetValue method by invoking it through specialized cached delegates";
@@ -751,12 +1015,17 @@ public sealed class ReflectionCaching : ClassWithFishPatches
// Log.Message(types.ToStringSafeEnumerable());
//}
public override MethodBase TargetMethodInfo { get; }
= AccessTools.DeclaredMethod(typeof(MonoField), nameof(MonoField.GetValue))!;
public override MethodBase? TargetMethodInfo { get; }
= AccessTools.DeclaredMethod(
AccessTools.TypeByName("System.Reflection.RuntimeFieldInfo")
?? AccessTools.TypeByName("System.Reflection.MonoField"),
nameof(FieldInfo.GetValue), [typeof(object)]);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(FieldInfo __instance, object? obj, ref object? __result)
{
ValidateGetValueCall(__instance, obj);
ref var cache = ref FieldGetters.GetOrAddReference(__instance.FieldHandle);
if (cache.Dirty
@@ -769,10 +1038,36 @@ public sealed class ReflectionCaching : ClassWithFishPatches
__result = cache.Result!(obj);
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ValidateGetValueCall(FieldInfo info, object? obj)
{
if (!info.IsStatic)
{
if (obj is null)
throw new TargetException("Non-static field requires a target");
var declaringType = info.DeclaringType!;
if (!declaringType.IsAssignableFrom(obj.GetType()))
{
throw new ArgumentException(
$"Field {info.Name} defined on type {declaringType} is not a field on the target object which is of type {obj.GetType()}.",
nameof(obj));
}
}
if (!info.IsLiteral && info.DeclaringType!.ContainsGenericParameters)
{
throw new InvalidOperationException(
"Late bound operations cannot be performed on fields with types for which Type.ContainsGenericParameters is true.");
}
}
}
public sealed class SetValue_Patch : FishPatch
{
public override bool DefaultState => false;
public override string Description { get; }
= "Optimizes the SetValue method by invoking it through specialized cached delegates";
@@ -794,14 +1089,20 @@ public sealed class ReflectionCaching : ClassWithFishPatches
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool TryUpdate(FieldInfo __instance, ref FieldInfoSetterCache cache)
=> cache.Task != _fieldSetterFailureTask && __instance.IsConst()
? ErrorForInvalidCallAttempt(__instance, ref cache)
: FieldSetters.UpdateAsyncCache<FieldInfoSetterCache, FieldInfo, FieldSetter>(ref cache,
{
if (__instance.IsConst())
return ErrorForInvalidCallAttempt(__instance, ref cache);
return FieldSetters.UpdateAsyncCache<FieldInfoSetterCache, FieldInfo, FieldSetter>(ref cache,
__instance.FieldHandle, __instance);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static bool ErrorForInvalidCallAttempt(FieldInfo info, ref FieldInfoSetterCache cache)
{
if (cache.Task == _fieldSetterFailureTask)
return false;
cache.Task = _fieldSetterFailureTask;
Log.Error($"FieldInfo.SetValue has been called on '{
info.FullDescription()}'. This is a const that gets copied at compile time. An attempt at "
@@ -811,7 +1112,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
}
}
private static Task<FieldSetter> _fieldSetterFailureTask = new(static () => null!);
private static Task<FieldSetter> _fieldSetterFailureTask = new(static () => null!); // sentinel only
public static FieldGetter? MakeGetterDelegate(FieldInfo info)
{
@@ -983,14 +1284,16 @@ public sealed class ReflectionCaching : ClassWithFishPatches
if (methodInfo is null && constructorInfo is null)
return null;
var parameters = methodBase.GetParameters();
if (!CanCacheInvoke(methodBase, methodInfo, constructorInfo, parameters))
return null;
var dm = new DynamicMethod($"MethodInvoker_{methodBase.Name}", typeof(object),
[typeof(object), typeof(object[])],
GetOwnerType(methodBase), true);
var il = dm.GetILGenerator();
var parameters = methodBase.GetParameters();
if (methodInfo != null && !methodBase.IsStatic)
EmitInstanceArgument(il, methodBase);
@@ -1025,6 +1328,49 @@ public sealed class ReflectionCaching : ClassWithFishPatches
}
}
private static bool CanCacheInvoke(MethodBase methodBase, MethodInfo? methodInfo,
ConstructorInfo? constructorInfo, ParameterInfo[] parameters)
{
if (methodBase.ContainsGenericParameters
|| (methodBase.CallingConvention & CallingConventions.VarArgs) != 0)
{
return false;
}
if (methodInfo != null && !IsSupportedInvokeType(methodInfo.ReturnType))
return false;
if (constructorInfo?.DeclaringType is { } declaringType
&& !IsSupportedInvokeType(declaringType))
{
return false;
}
for (var i = 0; i < parameters.Length; i++)
{
if (!IsSupportedInvokeParameter(parameters[i].ParameterType))
return false;
}
return true;
}
private static bool IsSupportedInvokeParameter(Type parameterType)
{
// object[] stores nullable value arguments as boxed T or null, not boxed Nullable<T>, so the
// current byref fast path cannot safely hand out a Nullable<T>&.
if (parameterType.IsByRef && parameterType.GetElementType()!.IsNullable())
return false;
if (parameterType.IsByRef)
parameterType = parameterType.GetElementType()!;
return IsSupportedInvokeType(parameterType);
}
private static bool IsSupportedInvokeType(Type type)
=> !type.IsPointer && !type.ContainsGenericParameters;
private static void EmitInvokeMethodCall(MethodBase methodBase, ILGenerator il, MethodInfo methodInfo)
{
il.Emit(FishTranspiler.Call(methodBase));
@@ -1119,7 +1465,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[SecuritySafeCritical]
public static ref TTo CastRefSafely<TTo>(ref object? from)
public static ref TTo CastRefSafely<TTo>(ref object? from) where TTo : class?
=> ref from is TTo or null
? ref Unsafe.As<object?, TTo>(ref from)
: ref ThrowInvalidCastException<TTo>();
@@ -1131,7 +1477,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
? ref Unsafe.Unbox<TTo>(from)
: ref Unsafe.Unbox<TTo>(from
= from is null
? default
? default(TTo)
: FisheryLib.Convert.Type<object, TTo>(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -1181,4 +1527,4 @@ public sealed class ReflectionCaching : ClassWithFishPatches
// populated with dynamic methods by MethodBasePatches.MakeInvokeDelegate, MakeGetterDelegate and
// MakeSetterDelegate
}
}
}
@@ -12,7 +12,7 @@ public static class CellIndexExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CellToIndex(this in IntVec3 cell, CellIndices cellIndices)
=> cell.CellToIndex(cellIndices.mapSizeX);
=> cell.CellToIndex(GetMapSizeX(cellIndices));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CellToIndex(this in IntVec3 cell, int mapSizeX) => (cell.z * mapSizeX) + cell.x;
@@ -22,7 +22,7 @@ public static class CellIndexExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CellToIndex(this IntVec2 cell, CellIndices cellIndices)
=> cell.CellToIndex(cellIndices.mapSizeX);
=> cell.CellToIndex(GetMapSizeX(cellIndices));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CellToIndex(this IntVec2 cell, int mapSizeX) => (cell.z * mapSizeX) + cell.x;
@@ -31,7 +31,7 @@ public static class CellIndexExtensions
public static int CellToIndex(int x, int z, Map map) => CellToIndex(x, z, map.cellIndices);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CellToIndex(int x, int z, CellIndices cellIndices) => CellToIndex(x, z, cellIndices.mapSizeX);
public static int CellToIndex(int x, int z, CellIndices cellIndices) => CellToIndex(x, z, GetMapSizeX(cellIndices));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CellToIndex(int x, int z, int mapSizeX) => (z * mapSizeX) + x;
@@ -41,11 +41,24 @@ public static class CellIndexExtensions
=> thingGrid.thingGrid[cellIndex.Value];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsFogged(this FogGrid fogGrid, CellIndex cellIndex) => fogGrid.fogGrid[cellIndex.Value];
public static bool IsFogged(this FogGrid fogGrid, CellIndex cellIndex)
#if V1_6
=> fogGrid.fogGrid.IsSet(cellIndex.Value);
#else
=> fogGrid.fogGrid[cellIndex.Value];
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float GetDepth(this SnowGrid snowGrid, CellIndex cellIndex) => snowGrid.depthGrid[cellIndex.Value];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float GetDepth(this SnowGrid snowGrid, int cellIndex) => snowGrid.depthGrid[cellIndex];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetMapSizeX(CellIndices cellIndices)
#if V1_6
=> cellIndices.SizeX;
#else
=> cellIndices.mapSizeX;
#endif
}
@@ -97,11 +97,14 @@ public static class MiscExtensions
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int SizeX(this Map map) => map.cellIndices.mapSizeX;
public static int SizeX(this Map map) => map.Size.x;
public static Map? TryGetMapHeld(this Thing thing)
{
var maps = Current.Game.Maps;
var maps = Find.Maps;
if (maps == null)
return thing.ParentHolder is { } rootHolder ? ThingOwnerUtility.GetRootMap(rootHolder) : null;
var mapIndex = (uint)thing.mapIndexOrState;
return mapIndex < (uint)maps.Count
@@ -114,7 +117,10 @@ public static class MiscExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Map? TryGetMap(this Thing thing)
{
var maps = Current.Game.Maps;
var maps = Find.Maps;
if (maps == null)
return null;
var mapIndex = (uint)thing.mapIndexOrState;
return mapIndex < (uint)maps.Count ? maps[(int)mapIndex] : null;
@@ -127,17 +133,36 @@ public static class MiscExtensions
public static Map GetMap(this Region region) => Current.Game.Maps[region.mapIndex];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsSpawned(this Thing thing) => (uint)thing.mapIndexOrState < (uint)Current.Game.Maps.Count;
public static bool IsSpawned(this Thing thing)
{
var maps = Find.Maps;
if (thing.mapIndexOrState < 0 || maps == null)
return false;
if (thing.mapIndexOrState < maps.Count)
return true;
Verse.Log.ErrorOnce($"Thing {thing.ThingID} is associated with invalid map index {thing.mapIndexOrState}", 64664487);
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetLoadID(this Bill bill) => bill.loadID;
public static void InvokeWhenCellIndicesReady(this Map map, Action<Map> action)
{
#if V1_6
if (map.cellIndices.NumGridCells > 0)
#else
if (map.cellIndices != null)
#endif
{
action(map);
}
else
{
map.Events().ComponentsConstructed += action;
}
}
public static void AddLetterSilently(this LetterStack letterStack, Letter letter, Color color, bool playSound = false,
@@ -164,4 +189,4 @@ public static class MiscExtensions
letterStack.letters.Add(letter);
letter.Received();
}
}
}
+57 -1
View File
@@ -26,6 +26,9 @@ public sealed class UtilityPrepatches : ClassWithFishPrepatches
public override MethodBase TargetMethodBase { get; } = methodof(LoadedModManager.CreateModClasses);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
public static void Prefix()
{
try
@@ -37,6 +40,59 @@ public sealed class UtilityPrepatches : ClassWithFishPrepatches
Log.Error($"Exception caught within OnAssembliesLoaded:\n{e}\n{new StackTrace(true)}");
}
}
public static void ReplacementBody()
{
var runningModClasses = RunningModClasses;
var assemblyToModContentPack = BuildAssemblyToModContentPackMap();
foreach (var type in typeof(Mod).InstantiableDescendantsAndSelf())
{
if (type is null)
continue;
DeepProfiler.Start("Loading " + type?.ToString() + " mod class");
try
{
if (!runningModClasses.ContainsKey(type))
{
assemblyToModContentPack.TryGetValue(type.Assembly, out var modContentPack);
runningModClasses[type] = (Mod)Activator.CreateInstance(type, modContentPack);
}
}
catch (Exception ex)
{
Log.Error("Error while instantiating a mod of type " + type?.ToString() + ": " + ex);
}
finally
{
DeepProfiler.End();
}
}
}
private static Dictionary<Assembly, ModContentPack> BuildAssemblyToModContentPackMap()
{
var runningMods = LoadedModManager.RunningModsListForReading;
var assemblyToModContentPack = new Dictionary<Assembly, ModContentPack>();
for (var i = 0; i < runningMods.Count; i++)
{
var modContentPack = runningMods[i];
var loadedAssemblies = modContentPack.assemblies.loadedAssemblies;
for (var j = 0; j < loadedAssemblies.Count; j++)
assemblyToModContentPack.TryAdd(loadedAssemblies[j], modContentPack);
}
return assemblyToModContentPack;
}
private static Dictionary<Type, Mod> RunningModClasses
=> (Dictionary<Type, Mod>)(_runningModClassesField ??=
AccessTools.Field(typeof(LoadedModManager), "runningModClasses")).GetValue(null)!;
private static FieldInfo? _runningModClassesField;
}
public sealed class NoSteamLogWarning : FishPrepatch
@@ -93,4 +149,4 @@ public sealed class UtilityPrepatches : ClassWithFishPrepatches
instruction.Operand = null;
}
}
}
}