Compare commits
15 Commits
cd311df5ae
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
063d5641d0
|
|||
|
b6f255ebbb
|
|||
|
e766ac833c
|
|||
|
e1a79fc0b0
|
|||
|
ff3119a810
|
|||
|
9d31b36805
|
|||
|
227dcd173f
|
|||
| 3199c1a85e | |||
| 7d96ecdb1d | |||
| 1acf3ce319 | |||
| aba7b38b37 | |||
| c75f383020 | |||
| 35187cf2d2 | |||
| 9821c68999 | |||
| c020968638 |
@@ -481,3 +481,6 @@ $RECYCLE.BIN/
|
|||||||
|
|
||||||
# Vim temporary swap files
|
# Vim temporary swap files
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
|
# Cache folders used in disk caching
|
||||||
|
Cache/*
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4
-3
@@ -5,10 +5,11 @@
|
|||||||
<supportedVersions>
|
<supportedVersions>
|
||||||
<li>1.4</li>
|
<li>1.4</li>
|
||||||
<li>1.5</li>
|
<li>1.5</li>
|
||||||
|
<li>1.6</li>
|
||||||
</supportedVersions>
|
</supportedVersions>
|
||||||
<author>bradson</author>
|
<author>bradson, localavaster, Lionel Colaso, Riggs0</author>
|
||||||
<modIconPath IgnoreIfNoMatchingField="True">PerformanceFish/ModIcon</modIconPath>
|
<modIconPath IgnoreIfNoMatchingField="True">PerformanceFish/ModIcon</modIconPath>
|
||||||
<url>https://github.com/bbradson/performance-fish</url>
|
<url>hhttps://git.hudsonriggs.systems/HRiggs/Performance-Fish</url>
|
||||||
<loadBefore>
|
<loadBefore>
|
||||||
<li>Dubwise.DubsPerformanceAnalyzer</li>
|
<li>Dubwise.DubsPerformanceAnalyzer</li>
|
||||||
<li>Dubwise.DubsPerformanceAnalyzerButchered</li>
|
<li>Dubwise.DubsPerformanceAnalyzerButchered</li>
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
<packageId>bs.fishery</packageId>
|
<packageId>bs.fishery</packageId>
|
||||||
<displayName>Fishery</displayName>
|
<displayName>Fishery</displayName>
|
||||||
<steamWorkshopUrl>n/a</steamWorkshopUrl>
|
<steamWorkshopUrl>n/a</steamWorkshopUrl>
|
||||||
<downloadUrl>https://github.com/bbradson/Fishery</downloadUrl>
|
<downloadUrl>https://git.hudsonriggs.systems/HRiggs/Fishery</downloadUrl>
|
||||||
</li>
|
</li>
|
||||||
</modDependencies>
|
</modDependencies>
|
||||||
<incompatibleWith>
|
<incompatibleWith>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
3811986740
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
NOTE: SWAP TO THE rimworld-1.6 BRANCH BEFORE DOWNLOADING OTHERWISE IT WILL NOT WORK FOR 1.6!
|
|
||||||
# Performance Fish
|
# Performance Fish
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutDir>..\..\1.5\Assemblies\</OutDir>
|
<OutDir>..\..\1.5\Assemblies\</OutDir>
|
||||||
<Copyright>Copyright (c) 2024 Bradson</Copyright>
|
<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>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
<DefineConstants>DEBUG;V1_5</DefineConstants>
|
<DefineConstants>DEBUG;V1_5</DefineConstants>
|
||||||
@@ -14,7 +16,7 @@
|
|||||||
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.5.*" />
|
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.5.*" />
|
||||||
<PackageReference Include="Lib.Harmony" Version="2.3.3" ExcludeAssets="runtime" />
|
<PackageReference Include="Lib.Harmony" Version="2.3.3" ExcludeAssets="runtime" />
|
||||||
<Reference Include="1Fishery">
|
<Reference Include="1Fishery">
|
||||||
<HintPath>..\..\..\Fishery\1.5\Assemblies\1Fishery.dll</HintPath>
|
<HintPath>$(FisheryRepoRoot)\1.5\Assemblies\1Fishery.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutDir>..\..\1.6\Assemblies\</OutDir>
|
||||||
|
<Copyright>Copyright (c) 2026 Bradson</Copyright>
|
||||||
|
<PolySharpUseEmbeddedAttributeForGeneratedTypes>false</PolySharpUseEmbeddedAttributeForGeneratedTypes>
|
||||||
|
<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="Microsoft.NETFramework.ReferenceAssemblies.net48" Version="1.0.3" PrivateAssets="all" />
|
||||||
|
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.6.4871" />
|
||||||
|
<PackageReference Include="Lib.Harmony" Version="2.3.3" ExcludeAssets="runtime" />
|
||||||
|
<PackageReference Update="Zetrith.Prepatcher" Version="1.2.0" />
|
||||||
|
<PackageReference Update="PolySharp" Version="1.16.0" />
|
||||||
|
<PackageReference Update="Krafs.Publicizer" Version="2.3.2" />
|
||||||
|
<PackageReference Update="JetBrains.Annotations" Version="2023.3.0" />
|
||||||
|
<Reference Include="1Fishery">
|
||||||
|
<HintPath>$(FisheryRepoRoot)\1.6\Assemblies\1Fishery.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright (c) 2026
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
#if V1_6
|
||||||
|
using Mono.Cecil;
|
||||||
|
using Mono.Cecil.Cil;
|
||||||
|
using PerformanceFish.Prepatching;
|
||||||
|
|
||||||
|
namespace PerformanceFish;
|
||||||
|
|
||||||
|
public sealed class AbilityTrackerTickOptimization : ClassWithFishPrepatches
|
||||||
|
{
|
||||||
|
public sealed class AbilitiesTickPatch : FishPrepatch
|
||||||
|
{
|
||||||
|
public override string? Description { get; }
|
||||||
|
= "Reads the pawn ability list once per ability tick instead of twice, while still observing list changes between abilities.";
|
||||||
|
|
||||||
|
public override MethodBase TargetMethodBase { get; }
|
||||||
|
= AccessTools.DeclaredMethod(typeof(Pawn_AbilityTracker), nameof(Pawn_AbilityTracker.AbilitiesTick));
|
||||||
|
|
||||||
|
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||||
|
=> ilProcessor.ReplaceBodyWith(AbilitiesTick);
|
||||||
|
|
||||||
|
public static void AbilitiesTick(Pawn_AbilityTracker instance)
|
||||||
|
{
|
||||||
|
for (var i = 0;; i++)
|
||||||
|
{
|
||||||
|
var abilities = instance.AllAbilitiesForReading;
|
||||||
|
if (i >= abilities.Count)
|
||||||
|
return;
|
||||||
|
|
||||||
|
abilities[i].AbilityTick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -7,13 +7,15 @@ namespace PerformanceFish.Cache;
|
|||||||
|
|
||||||
public sealed class BitCellGrid
|
public sealed class BitCellGrid
|
||||||
{
|
{
|
||||||
|
private readonly Map _map;
|
||||||
private long[] _innerArray;
|
private long[] _innerArray;
|
||||||
private CellIndices _cellIndices;
|
private CellIndices _cellIndices;
|
||||||
|
|
||||||
public BitCellGrid(Map map)
|
public BitCellGrid(Map map)
|
||||||
{
|
{
|
||||||
_innerArray = null!;
|
_map = map;
|
||||||
_cellIndices = null!;
|
_innerArray = Array.Empty<long>();
|
||||||
|
_cellIndices = default;
|
||||||
map.InvokeWhenCellIndicesReady(Initialize);
|
map.InvokeWhenCellIndicesReady(Initialize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,12 +25,26 @@ public sealed class BitCellGrid
|
|||||||
_innerArray = new long[(Math.Max(0, _cellIndices.NumGridCells - 1) >> 6) + 1];
|
_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]
|
public bool this[int index]
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get => (_innerArray[index >> 6] & (1L << (index & 63))) != 0L;
|
get
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
ValidateIndex(index);
|
||||||
|
return (_innerArray[index >> 6] & (1L << (index & 63))) != 0L;
|
||||||
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
ValidateIndex(index);
|
||||||
ref var bucket = ref _innerArray[index >> 6];
|
ref var bucket = ref _innerArray[index >> 6];
|
||||||
bucket ^= (-(long)value.AsInt() ^ bucket) & (1L << (index & 63));
|
bucket ^= (-(long)value.AsInt() ^ bucket) & (1L << (index & 63));
|
||||||
}
|
}
|
||||||
@@ -37,7 +53,24 @@ public sealed class BitCellGrid
|
|||||||
public bool this[in IntVec3 c]
|
public bool this[in IntVec3 c]
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get => this[c.CellToIndex(_cellIndices)];
|
get
|
||||||
set => this[c.CellToIndex(_cellIndices)] = value;
|
{
|
||||||
|
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}.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,38 +9,60 @@ namespace PerformanceFish.Cache;
|
|||||||
|
|
||||||
public sealed class CellGrid<T>
|
public sealed class CellGrid<T>
|
||||||
{
|
{
|
||||||
|
private readonly Map _map;
|
||||||
private T[] _innerArray;
|
private T[] _innerArray;
|
||||||
private CellIndices _cellIndices;
|
private CellIndices _cellIndices;
|
||||||
|
|
||||||
public CellGrid(Map map)
|
public CellGrid(Map map)
|
||||||
{
|
{
|
||||||
_innerArray = null!;
|
_map = map;
|
||||||
_cellIndices = null!;
|
_innerArray = Array.Empty<T>();
|
||||||
|
_cellIndices = default;
|
||||||
map.InvokeWhenCellIndicesReady(Initialize);
|
map.InvokeWhenCellIndicesReady(Initialize);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MemberNotNull(nameof(_innerArray), nameof(_cellIndices))]
|
[MemberNotNull(nameof(_innerArray))]
|
||||||
private void Initialize(Map map)
|
private void Initialize(Map map)
|
||||||
{
|
{
|
||||||
_cellIndices = map.cellIndices;
|
_cellIndices = map.cellIndices;
|
||||||
_innerArray = new T[_cellIndices.NumGridCells];
|
_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]
|
public ref T this[int index]
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get => ref _innerArray[index];
|
get
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
return ref _innerArray[index];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref T this[in IntVec3 c]
|
public ref T this[in IntVec3 c]
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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]
|
public ref T this[CellIndex cellIndex]
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get => ref _innerArray[cellIndex.Value];
|
get
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
return ref _innerArray[cellIndex.Value];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,27 +59,33 @@ public static class Utility
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
_gcInProgress = true;
|
_gcInProgress = true;
|
||||||
|
try
|
||||||
if (_cachesToProcessForGC is null || _currentGCProgress >= _cachesToProcessForGC.Length)
|
|
||||||
{
|
{
|
||||||
_cachesToProcessForGC = All.ToArray();
|
if (_cachesToProcessForGC is null || _currentGCProgress >= _cachesToProcessForGC.Length)
|
||||||
_gcRate = GenDate.TicksPerTwelfth / _cachesToProcessForGC.CountNonEmpty();
|
{
|
||||||
_currentGCProgress = 0;
|
_cachesToProcessForGC = All.ToArray();
|
||||||
_destroyedThingCounter = 0;
|
var nonEmptyCount = _cachesToProcessForGC.CountNonEmpty();
|
||||||
}
|
_gcRate = nonEmptyCount > 0 ? GenDate.TicksPerTwelfth / nonEmptyCount : 1000;
|
||||||
|
_currentGCProgress = 0;
|
||||||
|
_destroyedThingCounter = 0;
|
||||||
|
if (nonEmptyCount == 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
while (_cachesToProcessForGC[_currentGCProgress].Count == 0
|
while (_currentGCProgress < _cachesToProcessForGC.Length
|
||||||
&& _cachesToProcessForGC.Length - _currentGCProgress > 1)
|
&& _cachesToProcessForGC[_currentGCProgress].Count == 0)
|
||||||
|
{
|
||||||
|
_currentGCProgress++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_currentGCProgress < _cachesToProcessForGC.Length)
|
||||||
|
_cachesToProcessForGC[_currentGCProgress++].Clear();
|
||||||
|
}
|
||||||
|
finally
|
||||||
{
|
{
|
||||||
_currentGCProgress++;
|
_lastGCTick = TickHelper.TicksGame;
|
||||||
|
_gcInProgress = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_currentGCProgress < _cachesToProcessForGC.Length)
|
|
||||||
_cachesToProcessForGC[_currentGCProgress++].Clear();
|
|
||||||
|
|
||||||
_lastGCTick = TickHelper.TicksGame;
|
|
||||||
|
|
||||||
_gcInProgress = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,18 +10,23 @@ public record struct CellIndex(int Value)
|
|||||||
public int Value = Value;
|
public int Value = Value;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public CellIndex(IntVec3 cell, int mapSizeX) : this(cell.CellToIndex(mapSizeX)) {}
|
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 IntVec3 ToCell(int mapSizeX) => CellIndicesUtility.IndexToCell(Value, mapSizeX);
|
||||||
|
|
||||||
public List<Thing> GetThingList(Map map) => map.thingGrid.thingGrid[Value];
|
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 float GetSnowDepth(Map map) => map.snowGrid.depthGrid[Value];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,460 @@
|
|||||||
|
// 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
|
||||||
|
{
|
||||||
|
#if V1_6
|
||||||
|
public struct ThingTickCache
|
||||||
|
{
|
||||||
|
public List<ThingComp>? Source;
|
||||||
|
public int Version;
|
||||||
|
public bool AllTrivial;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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 struct BasePatchState
|
||||||
|
{
|
||||||
|
public int CheckedTick;
|
||||||
|
public bool HasChecked;
|
||||||
|
public bool CanSkip;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BasePatchState _thingCompTickState;
|
||||||
|
private static BasePatchState _thingCompTickIntervalState;
|
||||||
|
private static BasePatchState _thingCompTickRareState;
|
||||||
|
private static BasePatchState _thingCompTickLongState;
|
||||||
|
private static BasePatchState _hediffCompPostTickState;
|
||||||
|
private static BasePatchState _hediffCompPostTickIntervalState;
|
||||||
|
|
||||||
|
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 =>
|
||||||
|
#if V1_6
|
||||||
|
true;
|
||||||
|
#else
|
||||||
|
false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public override string? Description { get; }
|
||||||
|
#if V1_6
|
||||||
|
= "Skips the component tick loop when every component inherits the unpatched, empty base CompTick method.";
|
||||||
|
#else
|
||||||
|
= "Skips inherited empty ThingComp.CompTick calls when the base method is still unpatched and trivial.";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
#if V1_6
|
||||||
|
var comps = instance.comps;
|
||||||
|
if (comps is null)
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
var comps = instance.AllComps;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
var count = comps.Count;
|
||||||
|
#if V1_6
|
||||||
|
// Small lists cost less to dispatch normally than to validate a cache.
|
||||||
|
if (count >= 4 && CanSkipInheritedBaseThingCompTick())
|
||||||
|
{
|
||||||
|
ref var cache = ref instance.ThingTickCache();
|
||||||
|
if (!ReferenceEquals(cache.Source, comps) || cache.Version != comps._version)
|
||||||
|
UpdateThingTickCache(comps, ref cache);
|
||||||
|
|
||||||
|
if (cache.AllTrivial)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
var canSkipInheritedBase = CanSkipInheritedBaseThingCompTick();
|
||||||
|
#endif
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
#if V1_6
|
||||||
|
instance.comps![i].CompTick();
|
||||||
|
#else
|
||||||
|
var comp = comps[i];
|
||||||
|
if (!canSkipInheritedBase || !_thingCompTickTypes.GetOrAdd(comp.GetType()))
|
||||||
|
comp.CompTick();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if V1_6
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static void UpdateThingTickCache(List<ThingComp> comps, ref ThingTickCache cache)
|
||||||
|
{
|
||||||
|
var allTrivial = true;
|
||||||
|
for (var i = 0; i < comps.Count; i++)
|
||||||
|
{
|
||||||
|
var comp = comps[i];
|
||||||
|
if (comp is null || !_thingCompTickTypes.GetOrAdd(comp.GetType()))
|
||||||
|
{
|
||||||
|
allTrivial = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Source = comps;
|
||||||
|
cache.Version = comps._version;
|
||||||
|
cache.AllTrivial = allTrivial;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
#if V1_6
|
||||||
|
var comps = instance.comps;
|
||||||
|
if (comps is null)
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
var comps = instance.AllComps;
|
||||||
|
#endif
|
||||||
|
var count = comps.Count;
|
||||||
|
var canSkipInheritedBase = CanSkipInheritedBaseThingCompTickInterval();
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
#if V1_6
|
||||||
|
var comp = instance.comps![i];
|
||||||
|
#else
|
||||||
|
var comp = comps[i];
|
||||||
|
#endif
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
#if V1_6
|
||||||
|
var comps = instance.comps;
|
||||||
|
if (comps is null)
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
var comps = instance.AllComps;
|
||||||
|
#endif
|
||||||
|
var count = comps.Count;
|
||||||
|
var canSkipInheritedBase = CanSkipInheritedBaseThingCompTickRare();
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
#if V1_6
|
||||||
|
var comp = instance.comps![i];
|
||||||
|
#else
|
||||||
|
var comp = comps[i];
|
||||||
|
#endif
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
#if V1_6
|
||||||
|
var comps = instance.comps;
|
||||||
|
if (comps is null)
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
var comps = instance.AllComps;
|
||||||
|
#endif
|
||||||
|
var count = comps.Count;
|
||||||
|
var canSkipInheritedBase = CanSkipInheritedBaseThingCompTickLong();
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
#if V1_6
|
||||||
|
var comp = instance.comps![i];
|
||||||
|
#else
|
||||||
|
var comp = comps[i];
|
||||||
|
#endif
|
||||||
|
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 severityAdjustment = 0f;
|
||||||
|
var canSkipInheritedBase = CanSkipInheritedBaseHediffCompPostTick();
|
||||||
|
|
||||||
|
for (var i = 0; i < instance.comps!.Count; i++)
|
||||||
|
{
|
||||||
|
var comp = instance.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 severityAdjustment = 0f;
|
||||||
|
var canSkipInheritedBase = CanSkipInheritedBaseHediffCompPostTickInterval();
|
||||||
|
|
||||||
|
for (var i = 0; i < instance.comps!.Count; i++)
|
||||||
|
{
|
||||||
|
var comp = instance.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()
|
||||||
|
=> CanSkipBaseMethod(_thingCompTickMethod, _thingCompTickBaseIsTrivial, ref _thingCompTickState);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool CanSkipInheritedBaseThingCompTickInterval()
|
||||||
|
=> CanSkipBaseMethod(_thingCompTickIntervalMethod, _thingCompTickIntervalBaseIsTrivial,
|
||||||
|
ref _thingCompTickIntervalState);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool CanSkipInheritedBaseThingCompTickRare()
|
||||||
|
=> CanSkipBaseMethod(_thingCompTickRareMethod, _thingCompTickRareBaseIsTrivial, ref _thingCompTickRareState);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool CanSkipInheritedBaseThingCompTickLong()
|
||||||
|
=> CanSkipBaseMethod(_thingCompTickLongMethod, _thingCompTickLongBaseIsTrivial, ref _thingCompTickLongState);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool CanSkipInheritedBaseHediffCompPostTick()
|
||||||
|
=> CanSkipBaseMethod(_hediffCompPostTickMethod, _hediffCompPostTickBaseIsTrivial,
|
||||||
|
ref _hediffCompPostTickState);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool CanSkipInheritedBaseHediffCompPostTickInterval()
|
||||||
|
=> CanSkipBaseMethod(_hediffCompPostTickIntervalMethod, _hediffCompPostTickIntervalBaseIsTrivial,
|
||||||
|
ref _hediffCompPostTickIntervalState);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool CanSkipBaseMethod(MethodBase method, bool isTrivial, ref BasePatchState state)
|
||||||
|
{
|
||||||
|
if (!isTrivial)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var tick = Find.TickManager?.TicksGame ?? int.MinValue;
|
||||||
|
if (!state.HasChecked || state.CheckedTick != tick)
|
||||||
|
{
|
||||||
|
state.CanSkip = !HasHarmonyPatches(method);
|
||||||
|
state.CheckedTick = tick;
|
||||||
|
state.HasChecked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.CanSkip;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 _defDatabaseVersion = -2;
|
||||||
private int _defRecipeUsersVersion = -2;
|
private int _defRecipeUsersVersion = -2;
|
||||||
private RecipeDef _def;
|
private RecipeDef? _def;
|
||||||
public List<ThingDef> RecipeUsers;
|
public List<ThingDef> RecipeUsers = [];
|
||||||
|
|
||||||
public void Update(RecipeDef instance, IEnumerable<ThingDef> result)
|
public void Update(RecipeDef instance, IEnumerable<ThingDef> result)
|
||||||
{
|
{
|
||||||
@@ -81,7 +81,7 @@ public sealed class RecipeDefCaching : ClassWithFishPatches
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get
|
get
|
||||||
=> _defDatabaseVersion != DefDatabase<ThingDef>.defsList._version
|
=> _defDatabaseVersion != DefDatabase<ThingDef>.defsList._version
|
||||||
|| _defRecipeUsersVersion != (_def.recipeUsers?._version ?? 0);
|
|| _defRecipeUsersVersion != (_def?.recipeUsers?._version ?? 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,25 @@ public sealed class ThingDefPatches : ClassWithFishPrepatches
|
|||||||
public sealed class GetHashCodePatch : FishClassPrepatch
|
public sealed class GetHashCodePatch : FishClassPrepatch
|
||||||
{
|
{
|
||||||
public override string? Description { get; }
|
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. "
|
= "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.";
|
+ "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)
|
public override void FreePatch(TypeDefinition typeDefinition)
|
||||||
{
|
{
|
||||||
|
#if V1_6
|
||||||
|
return;
|
||||||
|
#else
|
||||||
var module = typeDefinition.Module;
|
var module = typeDefinition.Module;
|
||||||
|
|
||||||
var fishHashField = new FieldDefinition(FISH_HASH_FIELD_NAME, FieldAttributes.Private,
|
var fishHashField = new FieldDefinition(FISH_HASH_FIELD_NAME, FieldAttributes.Private,
|
||||||
@@ -74,6 +86,7 @@ public sealed class ThingDefPatches : ClassWithFishPrepatches
|
|||||||
getHashCodeBody.MaxStackSize = 2;
|
getHashCodeBody.MaxStackSize = 2;
|
||||||
getHashCodeBody.Method.ImplAttributes |= MethodImplAttributes.AggressiveInlining;
|
getHashCodeBody.Method.ImplAttributes |= MethodImplAttributes.AggressiveInlining;
|
||||||
getHashCodeBody.OptimizeMacros();
|
getHashCodeBody.OptimizeMacros();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -29,12 +29,13 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Zetrith.Prepatcher" Version="1.*" ExcludeAssets="runtime" />
|
<PackageReference Include="Zetrith.Prepatcher" Version="1.*" ExcludeAssets="runtime" />
|
||||||
<PackageReference Include="PolySharp" Version="1.*" />
|
<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="JetBrains.Annotations" Version="2023.*" Aliases="nuget" ExcludeAssets="runtime" />
|
||||||
<PackageReference Include="Krafs.Publicizer" Version="2.*" />
|
<PackageReference Include="Krafs.Publicizer" Version="2.*" />
|
||||||
|
|
||||||
<Publicize Include="Assembly-CSharp;0Harmony" IncludeVirtualMembers="false" />
|
<Publicize Include="Assembly-CSharp;0Harmony" IncludeVirtualMembers="false" />
|
||||||
<Publicize Include="Assembly-CSharp:RimWorld.JobGiver_Haul.TryGiveJob" />
|
<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:Mono.Collections.Generic.Collection`1" / -->
|
||||||
<DoNotPublicize Include="0Harmony:System.Memory`1" />
|
<DoNotPublicize Include="0Harmony:System.Memory`1" />
|
||||||
<DoNotPublicize Include="0Harmony:System.ReadOnlyMemory`1" />
|
<DoNotPublicize Include="0Harmony:System.ReadOnlyMemory`1" />
|
||||||
|
|||||||
@@ -41,7 +41,8 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
|
|||||||
= "Required by the gas grid optimization";
|
= "Required by the gas grid optimization";
|
||||||
|
|
||||||
public override MethodBase TargetMethodBase { get; }
|
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)
|
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||||
@@ -71,13 +72,89 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if V1_6
|
||||||
|
public abstract class PackedGasPatch : FishPrepatch
|
||||||
|
{
|
||||||
|
public override bool ShowSettings => false;
|
||||||
|
public override bool Enabled
|
||||||
|
{
|
||||||
|
get => base.Enabled && Get<TickPatch>().Enabled;
|
||||||
|
set => base.Enabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static uint PackedDensityAt(GasGrid grid, int index)
|
||||||
|
{
|
||||||
|
var grids = grid.ParallelGasGrids();
|
||||||
|
return ((uint)grids[3].DensityAt(index) << 24)
|
||||||
|
| ((uint)grids[2].DensityAt(index) << 16)
|
||||||
|
| ((uint)grids[1].DensityAt(index) << 8)
|
||||||
|
| grids[0].DensityAt(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class GetDirectIndexPatch : PackedGasPatch
|
||||||
|
{
|
||||||
|
public override MethodBase TargetMethodBase { get; }
|
||||||
|
= AccessTools.DeclaredMethod(typeof(GasGrid), nameof(GasGrid.GetDirect), [typeof(int)]);
|
||||||
|
|
||||||
|
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||||
|
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||||
|
|
||||||
|
public static uint ReplacementBody(GasGrid __instance, int index)
|
||||||
|
=> PackedDensityAt(__instance, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class GetDirectCellPatch : PackedGasPatch
|
||||||
|
{
|
||||||
|
public override MethodBase TargetMethodBase { get; }
|
||||||
|
= AccessTools.DeclaredMethod(typeof(GasGrid), nameof(GasGrid.GetDirect), [typeof(IntVec3)]);
|
||||||
|
|
||||||
|
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||||
|
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||||
|
|
||||||
|
public static uint ReplacementBody(GasGrid __instance, IntVec3 c)
|
||||||
|
=> PackedDensityAt(__instance, c.CellToIndex(__instance.map));
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SetPackedDirectPatch : PackedGasPatch
|
||||||
|
{
|
||||||
|
public override MethodBase TargetMethodBase { get; }
|
||||||
|
= AccessTools.DeclaredMethod(typeof(GasGrid), nameof(GasGrid.SetDirect),
|
||||||
|
[typeof(int), typeof(uint)]);
|
||||||
|
|
||||||
|
public static void Postfix(GasGrid __instance, int index)
|
||||||
|
{
|
||||||
|
var density = __instance.gasDensity[index];
|
||||||
|
SetDirectPatch.ReplacementBody(__instance, index, (byte)density, (byte)(density >> 8),
|
||||||
|
(byte)(density >> 16), (byte)(density >> 24));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ClearCellUnsafePatch : PackedGasPatch
|
||||||
|
{
|
||||||
|
public override MethodBase TargetMethodBase { get; }
|
||||||
|
= AccessTools.DeclaredMethod(typeof(GasGrid), nameof(GasGrid.ClearCellUnsafe));
|
||||||
|
|
||||||
|
public static void Postfix(GasGrid __instance, IntVec3 cell)
|
||||||
|
{
|
||||||
|
var index = cell.CellToIndex(__instance.map);
|
||||||
|
var grids = __instance.ParallelGasGrids();
|
||||||
|
for (var i = 0; i < grids.Length; i++)
|
||||||
|
grids[i].SetDirect(index, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
public sealed class TickPatch : FishPrepatch
|
public sealed class TickPatch : FishPrepatch
|
||||||
{
|
{
|
||||||
public override List<Type> LinkedPatches { get; } =
|
public override List<Type> LinkedPatches { get; } =
|
||||||
[
|
[
|
||||||
typeof(SetDirectPatch),
|
typeof(SetDirectPatch),
|
||||||
#if V1_4
|
typeof(CalculateGasEffectsPatch),
|
||||||
typeof(ColorAtPatch),
|
typeof(ColorAtPatch),
|
||||||
|
#if V1_6
|
||||||
|
typeof(GetDirectIndexPatch), typeof(GetDirectCellPatch),
|
||||||
|
typeof(SetPackedDirectPatch), typeof(ClearCellUnsafePatch),
|
||||||
#endif
|
#endif
|
||||||
typeof(AddGasPatch), typeof(AnyGasAtPatch),
|
typeof(AddGasPatch), typeof(AnyGasAtPatch),
|
||||||
typeof(DensityAtPatch), typeof(Debug_ClearAllPatch), typeof(Debug_FillAllPatch),
|
typeof(DensityAtPatch), typeof(Debug_ClearAllPatch), typeof(Debug_FillAllPatch),
|
||||||
@@ -173,6 +250,35 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class CalculateGasEffectsPatch : FishPrepatch
|
||||||
|
{
|
||||||
|
public override bool ShowSettings => false;
|
||||||
|
public override bool Enabled
|
||||||
|
{
|
||||||
|
get => base.Enabled && Get<TickPatch>().Enabled;
|
||||||
|
set => base.Enabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MethodBase TargetMethodBase { get; }
|
||||||
|
= AccessTools.PropertyGetter(typeof(GasGrid), nameof(GasGrid.CalculateGasEffects));
|
||||||
|
|
||||||
|
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||||
|
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||||
|
|
||||||
|
public static bool ReplacementBody(GasGrid __instance)
|
||||||
|
{
|
||||||
|
// Vanilla uses this to decide whether to equalize gas through buildings.
|
||||||
|
var gasGrids = __instance.ParallelGasGrids();
|
||||||
|
for (var i = 0; i < gasGrids.Length; i++)
|
||||||
|
{
|
||||||
|
if (gasGrids[i].CalculateGasEffects)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if !V1_4
|
#if !V1_4
|
||||||
public class ParallelJob : IJobParallelFor
|
public class ParallelJob : IJobParallelFor
|
||||||
{
|
{
|
||||||
@@ -185,6 +291,11 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
|
|||||||
public sealed class ColorAtPatch : FishPrepatch
|
public sealed class ColorAtPatch : FishPrepatch
|
||||||
{
|
{
|
||||||
public override bool ShowSettings => false;
|
public override bool ShowSettings => false;
|
||||||
|
public override bool Enabled
|
||||||
|
{
|
||||||
|
get => base.Enabled && Get<TickPatch>().Enabled;
|
||||||
|
set => base.Enabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
public override MethodBase TargetMethodBase { get; }
|
public override MethodBase TargetMethodBase { get; }
|
||||||
#if V1_4
|
#if V1_4
|
||||||
@@ -415,7 +526,7 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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
|
public sealed class ExposeDataPatch : FishPrepatch
|
||||||
@@ -1075,7 +1186,7 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
|
|||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private IntVec3 IndexToCell(int index)
|
private IntVec3 IndexToCell(int index)
|
||||||
=> CellIndicesUtility.IndexToCell(index, Map.cellIndices.mapSizeX);
|
=> CellIndicesUtility.IndexToCell(index, Map.Size.x);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// For modders. Performance Fish doesn't automatically scribe added custom defs.
|
/// For modders. Performance Fish doesn't automatically scribe added custom defs.
|
||||||
|
|||||||
@@ -19,45 +19,17 @@ public sealed class GenLocalDateCaching : ClassWithFishPatches
|
|||||||
+ "optimization, but yields accurate results instead of a placeholder value to avoid issues";
|
+ "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 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)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
//=> Reflection.GetCodeInstructions(Replacement);
|
public static bool Prefix(Thing thing, ref int __result)
|
||||||
{
|
{
|
||||||
var labelToStartOfMethod = generator.DefineLabel();
|
if (thing.TryGetMap() is not { } map)
|
||||||
codes.First().labels.Add(labelToStartOfMethod);
|
return true;
|
||||||
|
|
||||||
yield return FishTranspiler.Argument(method, "thing");
|
__result = GenLocalDate.DayTick(map);
|
||||||
yield return FishTranspiler.Field(typeof(Thing), nameof(Thing.mapIndexOrState));
|
return false;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//[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
|
public sealed class DayTickByMap_Patch : FishPatch
|
||||||
@@ -79,7 +51,7 @@ public sealed class GenLocalDateCaching : ClassWithFishPatches
|
|||||||
return __state = true;
|
return __state = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SavedTick != TickHelper.TicksGame)
|
if (SavedDayTickTick != TickHelper.TicksGame)
|
||||||
return __state = true;
|
return __state = true;
|
||||||
|
|
||||||
__result = DayTick;
|
__result = DayTick;
|
||||||
@@ -92,12 +64,88 @@ public sealed class GenLocalDateCaching : ClassWithFishPatches
|
|||||||
if (!__state || SavedMap != map)
|
if (!__state || SavedMap != map)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SavedTick = TickHelper.TicksGame;
|
SavedDayTickTick = TickHelper.TicksGame;
|
||||||
DayTick = __result;
|
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 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 DayTick = -2;
|
||||||
|
public static int HourInteger = -2;
|
||||||
|
public static Season Season = Season.Undefined;
|
||||||
}
|
}
|
||||||
@@ -43,14 +43,15 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches
|
|||||||
|
|
||||||
public static List<Type> ReplacementBody<TAttr>() where TAttr : Attribute
|
public static List<Type> ReplacementBody<TAttr>() where TAttr : Attribute
|
||||||
{
|
{
|
||||||
|
var attributeType = typeof(TAttr);
|
||||||
lock (Lock)
|
lock (Lock)
|
||||||
{
|
{
|
||||||
var typesWithAttribute = GenTypes.cachedTypesWithAttribute.TryGetValue(typeof(TAttr));
|
var typesWithAttribute = GenTypes.cachedTypesWithAttribute.TryGetValue(attributeType);
|
||||||
|
|
||||||
if (typesWithAttribute is null)
|
if (typesWithAttribute is null)
|
||||||
{
|
{
|
||||||
GenTypes.cachedTypesWithAttribute.Add(typeof(TAttr),
|
GenTypes.cachedTypesWithAttribute.Add(attributeType,
|
||||||
typesWithAttribute = GenTypes.AllTypes.AsParallel()
|
typesWithAttribute = GenTypes.AllTypes
|
||||||
.Where(Predicate<TAttr>()).ToList());
|
.Where(Predicate<TAttr>()).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,23 +83,14 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches
|
|||||||
|
|
||||||
if (subclasses is null)
|
if (subclasses is null)
|
||||||
{
|
{
|
||||||
((PredicateClass)Predicate.Target).BaseType = baseType;
|
|
||||||
GenTypes.cachedSubclasses.Add(baseType,
|
GenTypes.cachedSubclasses.Add(baseType,
|
||||||
subclasses = GenTypes.AllTypes.AsParallel()
|
subclasses = GenTypes.AllTypes
|
||||||
.Where(Predicate).ToList());
|
.Where(type => type.IsSubclassOf(baseType)).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
return subclasses;
|
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
|
public sealed class AllSubclassesNonAbstract : FishPrepatch
|
||||||
@@ -123,7 +115,7 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches
|
|||||||
if (subclassesNonAbstract is null)
|
if (subclassesNonAbstract is null)
|
||||||
{
|
{
|
||||||
GenTypes.cachedSubclassesNonAbstract.Add(baseType,
|
GenTypes.cachedSubclassesNonAbstract.Add(baseType,
|
||||||
subclassesNonAbstract = baseType.AllSubclasses().AsParallel()
|
subclassesNonAbstract = baseType.AllSubclasses()
|
||||||
.Where(Predicate).ToList());
|
.Where(Predicate).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,16 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
|
|||||||
{
|
{
|
||||||
public sealed class GeneTrackerTickPatch : FishPrepatch
|
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; }
|
public override string? Description { get; }
|
||||||
= "Every pawn has a gene tracker, which is responsible for ticking each of their genes. Normally it ticks "
|
= "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 "
|
+ "all of them equally, including those don't change or affect anything through ticking, like skin colors "
|
||||||
@@ -51,7 +61,15 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
|
|||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool Dirty(Pawn_GeneTracker instance, List<Gene> genesToTick)
|
public static bool Dirty(Pawn_GeneTracker instance, List<Gene> genesToTick)
|
||||||
|
#if V1_6
|
||||||
|
{
|
||||||
|
ref var versions = ref instance.GeneListVersions();
|
||||||
|
return !versions.Initialized || versions.Xenogenes != instance.xenogenes._version
|
||||||
|
|| versions.Endogenes != instance.endogenes._version;
|
||||||
|
}
|
||||||
|
#else
|
||||||
=> GetListVersion(instance) != genesToTick._version;
|
=> GetListVersion(instance) != genesToTick._version;
|
||||||
|
#endif
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public static void Update(Pawn_GeneTracker instance, List<Gene> genesToTick)
|
public static void Update(Pawn_GeneTracker instance, List<Gene> genesToTick)
|
||||||
@@ -61,12 +79,21 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
|
|||||||
AddTickableGenes(genesToTick, instance.xenogenes);
|
AddTickableGenes(genesToTick, instance.xenogenes);
|
||||||
AddTickableGenes(genesToTick, instance.endogenes);
|
AddTickableGenes(genesToTick, instance.endogenes);
|
||||||
|
|
||||||
|
#if V1_6
|
||||||
|
ref var versions = ref instance.GeneListVersions();
|
||||||
|
versions.Xenogenes = instance.xenogenes._version;
|
||||||
|
versions.Endogenes = instance.endogenes._version;
|
||||||
|
versions.Initialized = true;
|
||||||
|
#else
|
||||||
genesToTick._version = GetListVersion(instance);
|
genesToTick._version = GetListVersion(instance);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !V1_6
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int GetListVersion(Pawn_GeneTracker instance)
|
private static int GetListVersion(Pawn_GeneTracker instance)
|
||||||
=> (instance.xenogenes._version << 16) | (instance.endogenes._version & 0b1111_1111_1111_1111);
|
=> (instance.xenogenes._version << 16) | (instance.endogenes._version & 0b1111_1111_1111_1111);
|
||||||
|
#endif
|
||||||
|
|
||||||
private static void AddTickableGenes(List<Gene> genesToTick, List<Gene> genes)
|
private static void AddTickableGenes(List<Gene> genesToTick, List<Gene> genes)
|
||||||
{
|
{
|
||||||
@@ -74,7 +101,7 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
|
|||||||
for (var i = 0; i < count; i++)
|
for (var i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
var gene = genes[i];
|
var gene = genes[i];
|
||||||
if (SkippableTypes.Contains(gene.GetType())
|
if (IsSkippableType(gene.GetType())
|
||||||
&& (gene.def.mentalBreakMtbDays <= 0f || gene.def.mentalBreakDef == null))
|
&& (gene.def.mentalBreakMtbDays <= 0f || gene.def.mentalBreakDef == null))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -84,7 +111,72 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HashSet<Type> SkippableTypes
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
= typeof(Gene).SubclassesWithNoMethodOverrideAndSelf(nameof(Gene.Tick)).ToHashSet();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if V1_6
|
||||||
|
public struct GeneListVersionState
|
||||||
|
{
|
||||||
|
public int Xenogenes;
|
||||||
|
public int Endogenes;
|
||||||
|
public bool Initialized;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,11 +53,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
|
|
||||||
public record struct CacheValue<T>() where T : ThingComp
|
public record struct CacheValue<T>() where T : ThingComp
|
||||||
{
|
{
|
||||||
|
private List<ThingComp>? _listReference;
|
||||||
private int _listVersion = -2;
|
private int _listVersion = -2;
|
||||||
public T? Comp;
|
public T? Comp;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool IsDirty(List<ThingComp> comps) => comps._version != _listVersion;
|
public bool IsDirty(List<ThingComp> comps)
|
||||||
|
=> !ReferenceEquals(comps, _listReference) || comps._version != _listVersion;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public void Update(ThingWithComps thing, T? comp)
|
public void Update(ThingWithComps thing, T? comp)
|
||||||
@@ -65,6 +67,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
if (thing.thingIDNumber < 0)
|
if (thing.thingIDNumber < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_listReference = thing.comps;
|
||||||
_listVersion = thing.comps._version;
|
_listVersion = thing.comps._version;
|
||||||
Comp = comp;
|
Comp = comp;
|
||||||
}
|
}
|
||||||
@@ -113,11 +116,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
|
|
||||||
public record struct CacheValue<T>() where T : CompProperties
|
public record struct CacheValue<T>() where T : CompProperties
|
||||||
{
|
{
|
||||||
|
private List<CompProperties>? _listReference;
|
||||||
private int _listVersion = -2;
|
private int _listVersion = -2;
|
||||||
public T? Comp;
|
public T? Comp;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool IsDirty(List<CompProperties> comps) => comps._version != _listVersion;
|
public bool IsDirty(List<CompProperties> comps)
|
||||||
|
=> !ReferenceEquals(comps, _listReference) || comps._version != _listVersion;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public void Update(ThingDef thingDef, T? comp)
|
public void Update(ThingDef thingDef, T? comp)
|
||||||
@@ -125,6 +130,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
if (thingDef.shortHash == default)
|
if (thingDef.shortHash == default)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_listReference = thingDef.comps;
|
||||||
_listVersion = thingDef.comps._version;
|
_listVersion = thingDef.comps._version;
|
||||||
Comp = comp;
|
Comp = comp;
|
||||||
}
|
}
|
||||||
@@ -174,11 +180,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
|
|
||||||
public record struct CacheValue()
|
public record struct CacheValue()
|
||||||
{
|
{
|
||||||
|
private List<CompProperties>? _listReference;
|
||||||
private int _listVersion = -2;
|
private int _listVersion = -2;
|
||||||
public bool HasComp;
|
public bool HasComp;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool IsDirty(List<CompProperties> comps) => comps._version != _listVersion;
|
public bool IsDirty(List<CompProperties> comps)
|
||||||
|
=> !ReferenceEquals(comps, _listReference) || comps._version != _listVersion;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public void Update(ThingDef thingDef, bool hasComp)
|
public void Update(ThingDef thingDef, bool hasComp)
|
||||||
@@ -186,6 +194,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
if (thingDef.shortHash == default)
|
if (thingDef.shortHash == default)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_listReference = thingDef.comps;
|
||||||
_listVersion = thingDef.comps._version;
|
_listVersion = thingDef.comps._version;
|
||||||
HasComp = hasComp;
|
HasComp = hasComp;
|
||||||
}
|
}
|
||||||
@@ -236,11 +245,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
|
|
||||||
public record struct CacheValue<T>() where T : ThingComp
|
public record struct CacheValue<T>() where T : ThingComp
|
||||||
{
|
{
|
||||||
|
private List<CompProperties>? _listReference;
|
||||||
private int _listVersion = -2;
|
private int _listVersion = -2;
|
||||||
public bool HasComp;
|
public bool HasComp;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool IsDirty(List<CompProperties> comps) => comps._version != _listVersion;
|
public bool IsDirty(List<CompProperties> comps)
|
||||||
|
=> !ReferenceEquals(comps, _listReference) || comps._version != _listVersion;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public void Update(ThingDef thingDef, bool hasComp)
|
public void Update(ThingDef thingDef, bool hasComp)
|
||||||
@@ -248,6 +259,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
if (thingDef.shortHash == default)
|
if (thingDef.shortHash == default)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_listReference = thingDef.comps;
|
||||||
_listVersion = thingDef.comps._version;
|
_listVersion = thingDef.comps._version;
|
||||||
HasComp = hasComp;
|
HasComp = hasComp;
|
||||||
}
|
}
|
||||||
@@ -296,11 +308,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
|
|
||||||
public record struct CacheValue<T>() where T : HediffComp
|
public record struct CacheValue<T>() where T : HediffComp
|
||||||
{
|
{
|
||||||
|
private List<HediffComp>? _listReference;
|
||||||
private int _listVersion = -2;
|
private int _listVersion = -2;
|
||||||
public T? Comp;
|
public T? Comp;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool IsDirty(List<HediffComp> comps) => comps._version != _listVersion;
|
public bool IsDirty(List<HediffComp> comps)
|
||||||
|
=> !ReferenceEquals(comps, _listReference) || comps._version != _listVersion;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public void Update(HediffWithComps hediff, T? comp)
|
public void Update(HediffWithComps hediff, T? comp)
|
||||||
@@ -308,6 +322,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
if (hediff.loadID < 0)
|
if (hediff.loadID < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_listReference = hediff.comps;
|
||||||
_listVersion = hediff.comps._version;
|
_listVersion = hediff.comps._version;
|
||||||
Comp = comp;
|
Comp = comp;
|
||||||
}
|
}
|
||||||
@@ -355,11 +370,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
|
|
||||||
public record struct CacheValue<T>() where T : HediffCompProperties
|
public record struct CacheValue<T>() where T : HediffCompProperties
|
||||||
{
|
{
|
||||||
|
private List<HediffCompProperties>? _listReference;
|
||||||
private int _listVersion = -2;
|
private int _listVersion = -2;
|
||||||
public T? Comp;
|
public T? Comp;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool IsDirty(List<HediffCompProperties> comps) => comps._version != _listVersion;
|
public bool IsDirty(List<HediffCompProperties> comps)
|
||||||
|
=> !ReferenceEquals(comps, _listReference) || comps._version != _listVersion;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public void Update(HediffDef hediffDef, T? comp)
|
public void Update(HediffDef hediffDef, T? comp)
|
||||||
@@ -367,6 +384,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
if (hediffDef.shortHash == 0)
|
if (hediffDef.shortHash == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_listReference = hediffDef.comps;
|
||||||
_listVersion = hediffDef.comps._version;
|
_listVersion = hediffDef.comps._version;
|
||||||
Comp = comp;
|
Comp = comp;
|
||||||
}
|
}
|
||||||
@@ -414,11 +432,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
|
|
||||||
public record struct CacheValue<T>() where T : AbilityComp
|
public record struct CacheValue<T>() where T : AbilityComp
|
||||||
{
|
{
|
||||||
|
private List<AbilityComp>? _listReference;
|
||||||
private int _listVersion = -2;
|
private int _listVersion = -2;
|
||||||
public T? Comp;
|
public T? Comp;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool IsDirty(List<AbilityComp> comps) => comps._version != _listVersion;
|
public bool IsDirty(List<AbilityComp> comps)
|
||||||
|
=> !ReferenceEquals(comps, _listReference) || comps._version != _listVersion;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public void Update(Ability ability, T? comp)
|
public void Update(Ability ability, T? comp)
|
||||||
@@ -426,6 +446,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
if (ability.Id < 0)
|
if (ability.Id < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_listReference = ability.comps;
|
||||||
_listVersion = ability.comps._version;
|
_listVersion = ability.comps._version;
|
||||||
Comp = comp;
|
Comp = comp;
|
||||||
}
|
}
|
||||||
@@ -468,11 +489,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
|
|
||||||
public record struct CacheValue<T>() where T : WorldObjectComp
|
public record struct CacheValue<T>() where T : WorldObjectComp
|
||||||
{
|
{
|
||||||
|
private List<WorldObjectComp>? _listReference;
|
||||||
private int _listVersion = -2;
|
private int _listVersion = -2;
|
||||||
public T? Comp;
|
public T? Comp;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool IsDirty(List<WorldObjectComp> comps) => comps._version != _listVersion;
|
public bool IsDirty(List<WorldObjectComp> comps)
|
||||||
|
=> !ReferenceEquals(comps, _listReference) || comps._version != _listVersion;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public void Update(WorldObject worldObject, T? comp)
|
public void Update(WorldObject worldObject, T? comp)
|
||||||
@@ -480,6 +503,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
if (worldObject.ID < 0)
|
if (worldObject.ID < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_listReference = worldObject.comps;
|
||||||
_listVersion = worldObject.comps._version;
|
_listVersion = worldObject.comps._version;
|
||||||
Comp = comp;
|
Comp = comp;
|
||||||
}
|
}
|
||||||
@@ -522,11 +546,13 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
|
|
||||||
public record struct CacheValue<T>() where T : MapComponent
|
public record struct CacheValue<T>() where T : MapComponent
|
||||||
{
|
{
|
||||||
|
private List<MapComponent>? _listReference;
|
||||||
private int _listVersion = -2;
|
private int _listVersion = -2;
|
||||||
public T? Comp;
|
public T? Comp;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public bool IsDirty(List<MapComponent> comps) => comps._version != _listVersion;
|
public bool IsDirty(List<MapComponent> comps)
|
||||||
|
=> !ReferenceEquals(comps, _listReference) || comps._version != _listVersion;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public void Update(Map map, T? comp)
|
public void Update(Map map, T? comp)
|
||||||
@@ -534,6 +560,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
|
|||||||
if (map.uniqueID < 0)
|
if (map.uniqueID < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_listReference = map.components;
|
||||||
_listVersion = map.components._version;
|
_listVersion = map.components._version;
|
||||||
Comp = comp;
|
Comp = comp;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
// 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/.
|
// 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;
|
namespace PerformanceFish.Hauling;
|
||||||
|
|
||||||
@@ -11,9 +11,9 @@ public record struct LwmCompCacheValue
|
|||||||
{
|
{
|
||||||
public CompProperties? CompProperties;
|
public CompProperties? CompProperties;
|
||||||
|
|
||||||
public static CompProperties? TryGetLwmProps(Thing slotGroupParent)
|
public static CompProperties? TryGetLwmProps(ThingDef thingDef)
|
||||||
=> ModCompatibility.Types.LWM.CompProperties != null
|
=> ModCompatibility.Types.LWM.CompProperties != null
|
||||||
? slotGroupParent.def.comps?.Find(static props
|
? thingDef.comps?.Find(static props
|
||||||
=> props.GetType() == ModCompatibility.Types.LWM.CompProperties)
|
=> props.GetType() == ModCompatibility.Types.LWM.CompProperties)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
global using RimfactoryExtensionCache
|
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;
|
using RimfactoryTypes = PerformanceFish.ModCompatibility.Types.Rimfactory;
|
||||||
|
|
||||||
namespace PerformanceFish.Hauling;
|
namespace PerformanceFish.Hauling;
|
||||||
@@ -13,17 +13,17 @@ public record struct RimfactoryExtensionCacheValue
|
|||||||
{
|
{
|
||||||
public DefModExtension? ModExtension;
|
public DefModExtension? ModExtension;
|
||||||
|
|
||||||
public static DefModExtension? TryGetExtension(Thing slotGroupParent)
|
public static DefModExtension? TryGetExtension(ThingDef thingDef)
|
||||||
=> RimfactoryTypes.ModExtension != null
|
=> RimfactoryTypes.ModExtension != null
|
||||||
? slotGroupParent.def.modExtensions?.Find(_extensionPredicate)
|
? thingDef.modExtensions?.Find(_extensionPredicate)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool HasExtension(Thing slotGroupParent)
|
public static bool HasExtension(Thing thing)
|
||||||
=> RimfactoryTypes.ModExtension != null && TestExtension(slotGroupParent);
|
=> RimfactoryTypes.ModExtension != null && TestExtension(thing.def);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private static bool TestExtension(Thing slotGroupParent) => slotGroupParent.TryGetRimfactoryExtension() != null;
|
private static bool TestExtension(ThingDef thingDef) => TryGetExtension(thingDef) != null;
|
||||||
|
|
||||||
static RimfactoryExtensionCacheValue() => EnsureInitialized();
|
static RimfactoryExtensionCacheValue() => EnsureInitialized();
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ public static class StorageExtensions
|
|||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static CompProperties? TryGetLwmCompProperties(this Thing thing)
|
public static CompProperties? TryGetLwmCompProperties(this Thing thing)
|
||||||
=> LwmCompCache.GetOrAdd(thing).CompProperties;
|
=> LwmCompCache.GetOrAdd(thing.def).CompProperties;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static DefModExtension? TryGetRimfactoryExtension(this Thing thing)
|
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);
|
public static int GetTotalSlots(this SlotGroup slotGroup) => slotGroup.GetTotalSlots(slotGroup.CellsList.Count);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// 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/.
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using PerformanceFish.ModCompatibility;
|
using PerformanceFish.ModCompatibility;
|
||||||
|
|
||||||
namespace PerformanceFish.JobSystem;
|
namespace PerformanceFish.JobSystem;
|
||||||
@@ -41,11 +42,19 @@ 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;
|
((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;
|
private static Comparison<Thing> _comparer = new ThingPositionComparer().Compare;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
using PerformanceFish.Prepatching;
|
using PerformanceFish.Prepatching;
|
||||||
|
using PerformanceFish.Utility;
|
||||||
|
|
||||||
namespace PerformanceFish.Listers;
|
namespace PerformanceFish.Listers;
|
||||||
|
|
||||||
@@ -40,7 +41,21 @@ public sealed class Buildings : ClassWithFishPrepatches
|
|||||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody<Building>);
|
=> ilProcessor.ReplaceBodyWith(ReplacementBody<Building>);
|
||||||
|
|
||||||
public static IEnumerable<T> ReplacementBody<T>(ListerBuildings __instance)
|
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
|
#endif
|
||||||
|
|
||||||
@@ -237,11 +252,17 @@ public sealed class Buildings : ClassWithFishPrepatches
|
|||||||
if (def.building is { isNaturalRock: true })
|
if (def.building is { isNaturalRock: true })
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
UpdateCacheOnAdd(__instance, b, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateCacheOnAdd(ListerBuildings __instance, Building b, ThingDef def)
|
||||||
|
{
|
||||||
var cache = __instance.Cache();
|
var cache = __instance.Cache();
|
||||||
|
|
||||||
if (b.Faction == Faction.OfPlayer)
|
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)
|
if (b is Building_ResearchBench researchBench)
|
||||||
cache.ColonistResearchBenches.Add(researchBench);
|
cache.ColonistResearchBenches.Add(researchBench);
|
||||||
|
|
||||||
@@ -250,18 +271,18 @@ public sealed class Buildings : ClassWithFishPrepatches
|
|||||||
|
|
||||||
var buildingType = b.GetType();
|
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;
|
break;
|
||||||
|
|
||||||
cache.ColonistBuildingsByType.GetOrAdd(buildingType).Add(b);
|
buildingType = buildingType.BaseType;
|
||||||
}
|
}
|
||||||
while ((buildingType = buildingType.BaseType) != typeof(Building));
|
|
||||||
}
|
}
|
||||||
else
|
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 })
|
if (def.building is { isNaturalRock: true })
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
UpdateCacheOnRemove(__instance, b, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateCacheOnRemove(ListerBuildings __instance, Building b, ThingDef def)
|
||||||
|
{
|
||||||
var cache = __instance.Cache();
|
var cache = __instance.Cache();
|
||||||
|
|
||||||
if (b.Faction == Faction.OfPlayer)
|
if (b.Faction == Faction.OfPlayer)
|
||||||
@@ -302,14 +328,14 @@ public sealed class Buildings : ClassWithFishPrepatches
|
|||||||
|
|
||||||
var buildingType = b.GetType();
|
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;
|
break;
|
||||||
|
|
||||||
cache.ColonistBuildingsByType.GetOrAdd(buildingType).Remove(b);
|
buildingType = buildingType.BaseType;
|
||||||
}
|
}
|
||||||
while ((buildingType = buildingType.BaseType) != typeof(Building));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -321,17 +347,43 @@ public sealed class Buildings : ClassWithFishPrepatches
|
|||||||
public readonly record struct Cache()
|
public readonly record struct Cache()
|
||||||
{
|
{
|
||||||
public readonly FishTable<ThingDef, IndexedFishSet<Building>>
|
public readonly FishTable<ThingDef, IndexedFishSet<Building>>
|
||||||
ColonistBuildingsByDef = [],
|
ColonistBuildingsByDef = new() { ValueInitializer = static _ => InitializeBuildingsList<Building>() },
|
||||||
NonColonistBuildingsByDef = [];
|
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()
|
public readonly FishTable<Type, IList> ColonistBuildingsByType = new()
|
||||||
{
|
{
|
||||||
ValueInitializer = static type
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,22 +183,22 @@ public sealed class Haulables : ClassWithFishPrepatches
|
|||||||
public readonly HashSet<Thing> ThingsQueuedToRemove = [];
|
public readonly HashSet<Thing> ThingsQueuedToRemove = [];
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void CheckUpdate(List<Thing> haulables)
|
public void CheckUpdate(ICollection<Thing> haulables)
|
||||||
{
|
{
|
||||||
if (IsDirty(haulables))
|
if (IsDirty(haulables))
|
||||||
Update(haulables);
|
Update(haulables);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
public void Update(List<Thing> haulables)
|
public void Update(ICollection<Thing> haulables)
|
||||||
{
|
{
|
||||||
ContainedThings.Clear();
|
ContainedThings.Clear();
|
||||||
|
|
||||||
for (var i = haulables.Count; i-- > 0;)
|
foreach (var haulable in haulables)
|
||||||
ContainedThings.Add(haulables[i].GetKey());
|
ContainedThings.Add(haulable.GetKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
// 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 System.Collections;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PerformanceFish.Listers;
|
||||||
|
|
||||||
|
// ListerThings contains object instances. An index map keeps removals constant-time without
|
||||||
|
// using FishTable's collision-chain relocation, which can fail during removal.
|
||||||
|
internal sealed class IndexedReferenceList<T> : IList<T>, IList, IReadOnlyList<T> where T : Thing
|
||||||
|
{
|
||||||
|
private readonly List<T> _items = [];
|
||||||
|
private readonly Dictionary<T, int> _indices = new(ReferenceComparer.Instance);
|
||||||
|
|
||||||
|
public T this[int index]
|
||||||
|
{
|
||||||
|
get => _items[index];
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
throw new ArgumentNullException(nameof(value));
|
||||||
|
|
||||||
|
var previous = _items[index];
|
||||||
|
if (ReferenceEquals(previous, value))
|
||||||
|
return;
|
||||||
|
if (_indices.ContainsKey(value))
|
||||||
|
throw new InvalidOperationException("Thing is already in the type cache.");
|
||||||
|
|
||||||
|
_indices.Remove(previous);
|
||||||
|
_items[index] = value;
|
||||||
|
_indices.Add(value, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object? IList.this[int index]
|
||||||
|
{
|
||||||
|
get => this[index];
|
||||||
|
set => this[index] = RequireThing(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => _items.Count;
|
||||||
|
public bool IsReadOnly => false;
|
||||||
|
public bool IsFixedSize => false;
|
||||||
|
public bool IsSynchronized => false;
|
||||||
|
public object SyncRoot => this;
|
||||||
|
|
||||||
|
public void Add(T item)
|
||||||
|
{
|
||||||
|
if (item is null)
|
||||||
|
throw new ArgumentNullException(nameof(item));
|
||||||
|
if (_indices.ContainsKey(item))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_indices.Add(item, _items.Count);
|
||||||
|
_items.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
int IList.Add(object? value)
|
||||||
|
{
|
||||||
|
var item = RequireThing(value);
|
||||||
|
if (_indices.TryGetValue(item, out var index))
|
||||||
|
return index;
|
||||||
|
Add(item);
|
||||||
|
return _items.Count - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(T item)
|
||||||
|
{
|
||||||
|
if (item is null || !_indices.TryGetValue(item, out var index))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
RemoveAt(index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IList.Remove(object? value)
|
||||||
|
{
|
||||||
|
if (value is T item)
|
||||||
|
Remove(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
var item = _items[index];
|
||||||
|
var lastIndex = _items.Count - 1;
|
||||||
|
var lastItem = _items[lastIndex];
|
||||||
|
_indices.Remove(item);
|
||||||
|
if (index != lastIndex)
|
||||||
|
{
|
||||||
|
_items[index] = lastItem;
|
||||||
|
_indices[lastItem] = index;
|
||||||
|
}
|
||||||
|
_items.RemoveAt(lastIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Insert(int index, T item)
|
||||||
|
{
|
||||||
|
if (item is null)
|
||||||
|
throw new ArgumentNullException(nameof(item));
|
||||||
|
if ((uint)index > (uint)_items.Count)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(index));
|
||||||
|
if (_indices.ContainsKey(item))
|
||||||
|
return;
|
||||||
|
if (index == _items.Count)
|
||||||
|
{
|
||||||
|
Add(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var displaced = _items[index];
|
||||||
|
_items.Add(displaced);
|
||||||
|
_items[index] = item;
|
||||||
|
_indices[displaced] = _items.Count - 1;
|
||||||
|
_indices.Add(item, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IList.Insert(int index, object? value) => Insert(index, RequireThing(value));
|
||||||
|
|
||||||
|
public bool Contains(T item) => item is not null && _indices.ContainsKey(item);
|
||||||
|
bool IList.Contains(object? value) => value is T item && Contains(item);
|
||||||
|
|
||||||
|
public int IndexOf(T item) => item is not null && _indices.TryGetValue(item, out var index) ? index : -1;
|
||||||
|
int IList.IndexOf(object? value) => value is T item ? IndexOf(item) : -1;
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_indices.Clear();
|
||||||
|
_items.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(T[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex);
|
||||||
|
void ICollection.CopyTo(Array array, int index) => ((ICollection)_items).CopyTo(array, index);
|
||||||
|
|
||||||
|
public List<T>.Enumerator GetEnumerator() => _items.GetEnumerator();
|
||||||
|
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
private static T RequireThing(object? value)
|
||||||
|
=> value is T item ? item : throw new ArgumentException("Expected a thing of the cached type.", nameof(value));
|
||||||
|
|
||||||
|
private sealed class ReferenceComparer : IEqualityComparer<T>
|
||||||
|
{
|
||||||
|
public static readonly ReferenceComparer Instance = new();
|
||||||
|
public bool Equals(T? x, T? y) => ReferenceEquals(x, y);
|
||||||
|
public int GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,9 @@ namespace PerformanceFish.Listers;
|
|||||||
|
|
||||||
public sealed class ThingsPrepatches : ClassWithFishPrepatches
|
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 sealed class AddPatch : FishPrepatch
|
||||||
{
|
{
|
||||||
public override string? Description { get; }
|
public override string? Description { get; }
|
||||||
@@ -30,7 +33,15 @@ public sealed class ThingsPrepatches : ClassWithFishPrepatches
|
|||||||
|
|
||||||
lock (__instance.LockObject())
|
lock (__instance.LockObject())
|
||||||
{
|
{
|
||||||
|
// Re-registering the same thing must not duplicate the def and group lists.
|
||||||
|
if (__instance.IndexMapByDef().ContainsKey(t.GetKey())
|
||||||
|
&& __instance.listsByDef.TryGetValue(t.def, out var existing)
|
||||||
|
&& existing.Contains(t))
|
||||||
|
return;
|
||||||
|
|
||||||
AddToDefList(__instance, t);
|
AddToDefList(__instance, t);
|
||||||
|
if (t is IHaulSource haulSource)
|
||||||
|
_haulSources(__instance).Add(haulSource);
|
||||||
|
|
||||||
var allGroups = ThingListGroupHelper.AllGroups;
|
var allGroups = ThingListGroupHelper.AllGroups;
|
||||||
foreach (var thingRequestGroup in allGroups)
|
foreach (var thingRequestGroup in allGroups)
|
||||||
@@ -74,6 +85,8 @@ public sealed class ThingsPrepatches : ClassWithFishPrepatches
|
|||||||
lock (__instance.LockObject())
|
lock (__instance.LockObject())
|
||||||
{
|
{
|
||||||
RemoveFromDefList(__instance, t);
|
RemoveFromDefList(__instance, t);
|
||||||
|
if (t is IHaulSource haulSource)
|
||||||
|
_haulSources(__instance).Remove(haulSource);
|
||||||
|
|
||||||
var allGroups = ThingListGroupHelper.AllGroups;
|
var allGroups = ThingListGroupHelper.AllGroups;
|
||||||
for (var i = 0; i < allGroups.Length; i++)
|
for (var i = 0; i < allGroups.Length; i++)
|
||||||
@@ -268,6 +281,7 @@ public sealed class ThingsPrepatches : ClassWithFishPrepatches
|
|||||||
|
|
||||||
__instance.IndexMapByDef().Clear();
|
__instance.IndexMapByDef().Clear();
|
||||||
__instance.IndexMapByGroup().Clear();
|
__instance.IndexMapByGroup().Clear();
|
||||||
|
_haulSources(__instance).Clear();
|
||||||
ClearTypeLists(__instance);
|
ClearTypeLists(__instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,13 +578,33 @@ public sealed class Things : ClassWithFishPatches
|
|||||||
};
|
};
|
||||||
|
|
||||||
private static readonly MethodInfo _thingsListInitializerDefinition
|
private static readonly MethodInfo _thingsListInitializerDefinition
|
||||||
= methodof(InitializeThingsList<Thing>).GetGenericMethodDefinition();
|
= methodof(InitializeReferenceThingsList<Thing>).GetGenericMethodDefinition();
|
||||||
|
|
||||||
|
private static IndexedReferenceList<T> InitializeReferenceThingsList<T>() where T : Thing => new();
|
||||||
|
|
||||||
|
// Keep the public factory for mods that call it directly; the ListerThings type cache uses the
|
||||||
|
// reference-indexed implementation above.
|
||||||
public static unsafe IndexedFishSet<T> InitializeThingsList<T>() where T : Thing
|
public static unsafe IndexedFishSet<T> InitializeThingsList<T>() where T : Thing
|
||||||
#pragma warning disable CS8622
|
#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
|
#pragma warning restore CS8622
|
||||||
|
|
||||||
private static int GetKeyByRef<T>(ref T thing) where T : Thing => thing.GetKey();
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,15 +59,16 @@ public sealed class LogPatches : ClassWithFishPrepatches
|
|||||||
public static void InsertEarlyMessages()
|
public static void InsertEarlyMessages()
|
||||||
{
|
{
|
||||||
var unhandledMessages = Log.UnhandledMessages;
|
var unhandledMessages = Log.UnhandledMessages;
|
||||||
if (unhandledMessages is null)
|
|
||||||
Debug.LogError("Message queue is null");
|
|
||||||
|
|
||||||
Log.Ready = true;
|
Log.Ready = true;
|
||||||
|
|
||||||
Verse.Log.messageQueue.Enqueue(new(LogMessageType.Message, PERFORMANCE_FISH_WELCOME_MESSAGE,
|
Verse.Log.messageQueue.Enqueue(new(LogMessageType.Message, PERFORMANCE_FISH_WELCOME_MESSAGE,
|
||||||
PERFORMANCE_FISH_WELCOME_STACKTRACE));
|
PERFORMANCE_FISH_WELCOME_STACKTRACE));
|
||||||
|
|
||||||
while (unhandledMessages!.TryDequeue(out var message))
|
if (unhandledMessages is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (unhandledMessages.TryDequeue(out var message))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -114,17 +114,30 @@ public sealed class MiscOptimizations : ClassWithFishPatches
|
|||||||
= "Throttles rotting on misconfigured defs to only recalculate every 256 ticks, instead of constantly.";
|
= "Throttles rotting on misconfigured defs to only recalculate every 256 ticks, instead of constantly.";
|
||||||
|
|
||||||
public override MethodBase TargetMethodInfo { get; }
|
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));
|
= AccessTools.Method(typeof(RimWorld.CompRottable), nameof(RimWorld.CompRottable.CompTick));
|
||||||
|
#endif
|
||||||
|
|
||||||
public static CodeInstructions Transpiler(CodeInstructions codes, ILGenerator generator)
|
public static CodeInstructions Transpiler(CodeInstructions codes, ILGenerator generator)
|
||||||
=> Reflection.GetCodeInstructions(Replacement, generator);
|
=> Reflection.GetCodeInstructions(Replacement, generator);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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)
|
public static void Replacement(RimWorld.CompRottable instance)
|
||||||
{
|
{
|
||||||
if ((TickHelper.TicksGame & 255) == (instance.parent.thingIDNumber & 255))
|
if ((TickHelper.TicksGame & 255) == (instance.parent.thingIDNumber & 255))
|
||||||
instance.Tick(256);
|
instance.Tick(256);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class RitualObligationTrigger_Date : FirstPriorityFishPatch
|
public sealed class RitualObligationTrigger_Date : FirstPriorityFishPatch
|
||||||
|
|||||||
@@ -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 "
|
= "Fixes a MayRequire bug causing it to normally not recognize steam versions of mods if another local "
|
||||||
+ "copy exists.";
|
+ "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)
|
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||||
=> IsActiveFix(ilProcessor, 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 "
|
= "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.";
|
+ "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)
|
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||||
=> IsActiveFix(ilProcessor, module);
|
=> IsActiveFix(ilProcessor, module);
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
// 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 bool DefaultState => true;
|
||||||
|
|
||||||
|
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 bool DefaultState => true;
|
||||||
|
|
||||||
|
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, out need, ref cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static bool UpdateCache<T>(Pawn_NeedsTracker instance, 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() != typeof(T))
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ public static class OnAssembliesLoaded
|
|||||||
PerformanceFishMod.AllPatchClasses = PerformanceFishMod.InitializeAllPatchClasses<IHasFishPatch>();
|
PerformanceFishMod.AllPatchClasses = PerformanceFishMod.InitializeAllPatchClasses<IHasFishPatch>();
|
||||||
PerformanceFishMod.AllPrepatchClasses ??= PerformanceFishMod.InitializeAllPatchClasses<ClassWithFishPrepatches>();
|
PerformanceFishMod.AllPrepatchClasses ??= PerformanceFishMod.InitializeAllPatchClasses<ClassWithFishPrepatches>();
|
||||||
|
|
||||||
TryInitialize(AnalyzerFixes.Patch, true);
|
TryInitialize(AnalyzerFixes.Patch);
|
||||||
|
|
||||||
_ = FishSettings.Instance;
|
_ = FishSettings.Instance;
|
||||||
|
|
||||||
@@ -50,7 +50,9 @@ public static class OnAssembliesLoaded
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!patchClass.RequiresLoadedGameForPatching)
|
if (!patchClass.RequiresLoadedGameForPatching)
|
||||||
|
{
|
||||||
patchClass.Patches.PatchAll();
|
patchClass.Patches.PatchAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -107,14 +109,11 @@ public static class OnAssembliesLoaded
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async void TryInitialize(Action action, bool async = false)
|
private static void TryInitialize(Action action)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (async)
|
action();
|
||||||
await Task.Run(action).ConfigureAwait(false);
|
|
||||||
else
|
|
||||||
action();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasD
|
|||||||
public List<MethodInfo> BenchmarkHarmonyMethodInfos { get; } = [];
|
public List<MethodInfo> BenchmarkHarmonyMethodInfos { get; } = [];
|
||||||
private bool Patched { get; set; }
|
private bool Patched { get; set; }
|
||||||
private bool ShouldBePatched { get; set; }
|
private bool ShouldBePatched { get; set; }
|
||||||
|
private readonly HashSet<MethodBase> _patchedTargets = [];
|
||||||
|
|
||||||
public virtual string? Name => null;
|
public virtual string? Name => null;
|
||||||
|
|
||||||
@@ -190,17 +191,16 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasD
|
|||||||
|
|
||||||
protected virtual void ApplyPatch()
|
protected virtual void ApplyPatch()
|
||||||
{
|
{
|
||||||
Patched = true;
|
var targets = TargetMethodInfos.ToArray();
|
||||||
foreach (var method in TargetMethodInfos)
|
foreach (var method in targets)
|
||||||
{
|
{
|
||||||
DebugLog.Message($"Performance Fish is applying {GetType().Name} on {method.FullDescription()}");
|
if (_patchedTargets.Contains(method))
|
||||||
|
continue;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (IsBenchmarking)
|
if (IsBenchmarking)
|
||||||
{
|
{
|
||||||
Patched = false;
|
|
||||||
|
|
||||||
if (AccessTools.EnumeratorMoveNext(method) is { } moveNext)
|
if (AccessTools.EnumeratorMoveNext(method) is { } moveNext)
|
||||||
{
|
{
|
||||||
BenchmarkHarmonyMethodInfos.Add(PerformanceFishMod.Harmony!.Patch(moveNext,
|
BenchmarkHarmonyMethodInfos.Add(PerformanceFishMod.Harmony!.Patch(moveNext,
|
||||||
@@ -231,7 +231,9 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasD
|
|||||||
transpiler: TryMakeHarmonyMethod(TranspilerMethodInfo, TranspilerMethodPriority),
|
transpiler: TryMakeHarmonyMethod(TranspilerMethodInfo, TranspilerMethodPriority),
|
||||||
finalizer: TryMakeHarmonyMethod(FinalizerMethodInfo, FinalizerMethodPriority)));
|
finalizer: TryMakeHarmonyMethod(FinalizerMethodInfo, FinalizerMethodPriority)));
|
||||||
}
|
}
|
||||||
|
_patchedTargets.Add(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -241,6 +243,7 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasD
|
|||||||
FinalizerMethodInfo.FullDescription()}:\n{e}");
|
FinalizerMethodInfo.FullDescription()}:\n{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Patched = !IsBenchmarking && targets.All(_patchedTargets.Contains);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HarmonyMethod? TryMakeHarmonyMethod(MethodInfo? method, int priority = -1, string[]? before = null,
|
private static HarmonyMethod? TryMakeHarmonyMethod(MethodInfo? method, int priority = -1, string[]? before = null,
|
||||||
@@ -250,7 +253,7 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasD
|
|||||||
public virtual void TryUnpatch()
|
public virtual void TryUnpatch()
|
||||||
{
|
{
|
||||||
ShouldBePatched = false;
|
ShouldBePatched = false;
|
||||||
if ( /*!Enabled &&*/ Patched)
|
if (Patched || _patchedTargets.Count != 0)
|
||||||
ApplyUnpatching();
|
ApplyUnpatching();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,6 +273,7 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasD
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_patchedTargets.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void TryUnpatchMethod(MethodBase targetMethod, MethodInfo? patchMethod)
|
private static void TryUnpatchMethod(MethodBase targetMethod, MethodInfo? patchMethod)
|
||||||
@@ -291,6 +295,9 @@ public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasD
|
|||||||
private MethodInfo? TryGetMethod(string nameCaseInsensitive, Predicate<MethodInfo>? predicate = null)
|
private MethodInfo? TryGetMethod(string nameCaseInsensitive, Predicate<MethodInfo>? predicate = null)
|
||||||
=> GetType().TryGetMethod(nameCaseInsensitive, predicate);
|
=> GetType().TryGetMethod(nameCaseInsensitive, predicate);
|
||||||
|
|
||||||
|
private static string GetTargetMethodName(MethodBase method)
|
||||||
|
=> $"{method.DeclaringType?.FullName ?? "<no-type>"}.{method.Name}";
|
||||||
|
|
||||||
private static int TryGetPriority(MethodInfo? info)
|
private static int TryGetPriority(MethodInfo? info)
|
||||||
=> info?.TryGetAttribute<HarmonyPriority>()?.info.priority ?? Priority.Normal;
|
=> info?.TryGetAttribute<HarmonyPriority>()?.info.priority ?? Priority.Normal;
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,9 @@ public class FishPatchHolder : IExposable, IEnumerable<FishPatch>
|
|||||||
|
|
||||||
private void AddPatchesRecursively(Type type)
|
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)
|
if (PerformanceFishMod.AllPatchClasses is { } allPatches)
|
||||||
RemoveDupes(allPatches, type);
|
RemoveDupes(allPatches, type);
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.1.31911.260
|
|||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1.5", "1.5.csproj", "{E8A3D63E-5850-4866-AB06-DBD44B11F67B}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1.5", "1.5.csproj", "{E8A3D63E-5850-4866-AB06-DBD44B11F67B}"
|
||||||
EndProject
|
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}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1.4", "1.4.csproj", "{0257ABCD-7009-4C7D-9BD8-599FC47595DB}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6948F7E6-7ED7-4716-87EF-CFF1F465F561}"
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{0257ABCD-7009-4C7D-9BD8-599FC47595DB}.Debug|Any CPU.Build.0 = 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
|
{0257ABCD-7009-4C7D-9BD8-599FC47595DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// 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/.
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
using PerformanceFish.Prepatching;
|
using PerformanceFish.Prepatching;
|
||||||
@@ -15,12 +14,14 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
|
|||||||
{
|
{
|
||||||
public sealed class WorldObjectsHolderTickPatch : FishPrepatch
|
public sealed class WorldObjectsHolderTickPatch : FishPrepatch
|
||||||
{
|
{
|
||||||
|
public override bool DefaultState => true;
|
||||||
|
|
||||||
|
private static List<WorldObject>? _cachedWorldObjectsReference;
|
||||||
|
private static List<WorldObject>? _cachedSnapshotReference;
|
||||||
|
private static int _cachedSnapshotVersion = -2;
|
||||||
|
|
||||||
public override string? Description { get; }
|
public override string? Description { get; }
|
||||||
= "The world objects holder is responsible for ticking every world object. This includes settlements, "
|
= "Reuses the world-object tick snapshot until the source list changes. Every object still receives DoTick.";
|
||||||
+ "caravans, outposts and any other object placed in the world, instead of maps. Normally it ticks "
|
|
||||||
+ "everything equally, including many static objects that cannot possibly affect anything from a tick. "
|
|
||||||
+ "This patch improves the world objects holder to determine objects that need ticking in advance, cache "
|
|
||||||
+ "the list of them, and only tick those, skipping all the others.";
|
|
||||||
|
|
||||||
public override MethodBase TargetMethodBase { get; }
|
public override MethodBase TargetMethodBase { get; }
|
||||||
= AccessTools.Method(typeof(WorldObjectsHolder), nameof(WorldObjectsHolder.WorldObjectsHolderTick));
|
= AccessTools.Method(typeof(WorldObjectsHolder), nameof(WorldObjectsHolder.WorldObjectsHolderTick));
|
||||||
@@ -34,108 +35,52 @@ public sealed class WorldObjectsOptimization : ClassWithFishPrepatches
|
|||||||
UpdateCache(instance);
|
UpdateCache(instance);
|
||||||
|
|
||||||
var worldObjects = WorldObjectsHolder.tmpWorldObjects;
|
var worldObjects = WorldObjectsHolder.tmpWorldObjects;
|
||||||
for (var i = worldObjects.Count; i-- > 0;)
|
for (var i = 0; i < worldObjects.Count; i++)
|
||||||
|
#if V1_6
|
||||||
|
worldObjects[i].DoTick();
|
||||||
|
#else
|
||||||
worldObjects[i].Tick();
|
worldObjects[i].Tick();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UpdateCache(WorldObjectsHolder instance)
|
public static void UpdateCache(WorldObjectsHolder instance)
|
||||||
{
|
{
|
||||||
var staticWorldObjects = WorldObjectsHolder.tmpWorldObjects;
|
|
||||||
staticWorldObjects.Clear();
|
|
||||||
|
|
||||||
var instanceWorldObjects = instance.worldObjects;
|
var instanceWorldObjects = instance.worldObjects;
|
||||||
|
var snapshot = WorldObjectsHolder.tmpWorldObjects;
|
||||||
for (var i = instanceWorldObjects.Count; i-- > 0;)
|
snapshot.Clear();
|
||||||
{
|
snapshot.AddRange(instanceWorldObjects);
|
||||||
var worldObject = instanceWorldObjects[i];
|
|
||||||
|
|
||||||
if (worldObject is not MapParent { HasMap: true }
|
|
||||||
&& SkippableWorldObjects.Contains(worldObject.GetType())
|
|
||||||
&& (worldObject is not Settlement settlement || settlement.trader?.stock is null)
|
|
||||||
&& CanSkipCompTick(worldObject))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
staticWorldObjects.Add(worldObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
CachedWorldObjectsVersion = instanceWorldObjects._version;
|
CachedWorldObjectsVersion = instanceWorldObjects._version;
|
||||||
CachedMapsVersion = Current.gameInt.maps._version;
|
_cachedWorldObjectsReference = instanceWorldObjects;
|
||||||
}
|
_cachedSnapshotReference = snapshot;
|
||||||
|
_cachedSnapshotVersion = snapshot._version;
|
||||||
private static bool CanSkipCompTick(WorldObject worldObject)
|
|
||||||
{
|
|
||||||
var comps = worldObject.comps;
|
|
||||||
|
|
||||||
for (var i = comps.Count; i-- > 0;)
|
|
||||||
{
|
|
||||||
var comp = comps[i];
|
|
||||||
if (comp is EnterCooldownComp { Active: true })
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!SkippableComps.Contains(comp.GetType()))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
comp.CompTick();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int
|
public static int
|
||||||
CachedWorldObjectsVersion = -2,
|
CachedWorldObjectsVersion = -2,
|
||||||
CachedMapsVersion = -2;
|
CachedMapsVersion = -2;
|
||||||
|
|
||||||
private static Type?[]
|
// Retained for mods that called the old whitelist API. The snapshot no longer skips any ticks.
|
||||||
_whitelistedTickingCompTypes =
|
public static void AddCompToWhiteList(Type compType) => SetDirty();
|
||||||
[
|
public static void AddWorldObjectToWhiteList(Type worldObjectType) => SetDirty();
|
||||||
typeof(WorldObjectComp),
|
|
||||||
typeof(FormCaravanComp),
|
|
||||||
typeof(TimedDetectionRaids),
|
|
||||||
typeof(EnterCooldownComp)
|
|
||||||
],
|
|
||||||
_whitelistedWorldObjectTypes =
|
|
||||||
[
|
|
||||||
typeof(WorldObject),
|
|
||||||
typeof(MapParent),
|
|
||||||
typeof(Settlement),
|
|
||||||
ModCompatibility.Types.RealRuins.POIWorldObject
|
|
||||||
];
|
|
||||||
|
|
||||||
public static void AddCompToWhiteList(Type compType)
|
|
||||||
{
|
|
||||||
_whitelistedTickingCompTypes = _whitelistedTickingCompTypes.Add(compType);
|
|
||||||
SkippableComps = InitializeSkippableComps();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void AddWorldObjectToWhiteList(Type worldObjectType)
|
|
||||||
{
|
|
||||||
_whitelistedWorldObjectTypes = _whitelistedWorldObjectTypes.Add(worldObjectType);
|
|
||||||
SkippableWorldObjects = InitializeSkippableWorldObjects();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static HashSet<Type>
|
|
||||||
SkippableComps = InitializeSkippableComps(),
|
|
||||||
SkippableWorldObjects = InitializeSkippableWorldObjects();
|
|
||||||
|
|
||||||
private static HashSet<Type> InitializeSkippableComps()
|
|
||||||
=> MakeSubclassHashSet(typeof(WorldObjectComp), nameof(WorldObjectComp.CompTick),
|
|
||||||
_whitelistedTickingCompTypes);
|
|
||||||
|
|
||||||
private static HashSet<Type> InitializeSkippableWorldObjects()
|
|
||||||
=> MakeSubclassHashSet(typeof(WorldObject), nameof(WorldObject.Tick), _whitelistedWorldObjectTypes);
|
|
||||||
|
|
||||||
private static HashSet<Type> MakeSubclassHashSet(Type type, string name, Type?[] allowedDeclaringTypes)
|
|
||||||
=> type.SubclassesWithNoMethodOverrideAndSelf(allowedDeclaringTypes, name).ToHashSet();
|
|
||||||
|
|
||||||
public static bool CacheDirty(WorldObjectsHolder instance)
|
public static bool CacheDirty(WorldObjectsHolder instance)
|
||||||
=> CachedWorldObjectsVersion != instance.worldObjects._version
|
{
|
||||||
|| CachedMapsVersion != Current.gameInt.maps._version;
|
var source = instance.worldObjects;
|
||||||
|
var snapshot = WorldObjectsHolder.tmpWorldObjects;
|
||||||
|
return !ReferenceEquals(_cachedWorldObjectsReference, source)
|
||||||
|
|| CachedWorldObjectsVersion != source._version
|
||||||
|
|| !ReferenceEquals(_cachedSnapshotReference, snapshot)
|
||||||
|
|| _cachedSnapshotVersion != snapshot._version
|
||||||
|
|| snapshot.Count != source.Count;
|
||||||
|
}
|
||||||
|
|
||||||
public static void SetDirty() => CachedWorldObjectsVersion = CachedMapsVersion = -2;
|
public static void SetDirty() => CachedWorldObjectsVersion = CachedMapsVersion = -2;
|
||||||
|
|
||||||
static WorldObjectsHolderTickPatch() => Cache.Utility.Cleared += SetDirty;
|
static WorldObjectsHolderTickPatch()
|
||||||
|
{
|
||||||
|
Cache.Utility.Cleared += SetDirty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class ExpandingIconCaching : FishPrepatch
|
public sealed class ExpandingIconCaching : FishPrepatch
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using PerformanceFish.Hauling;
|
|||||||
using PerformanceFish.Planet;
|
using PerformanceFish.Planet;
|
||||||
using Prepatcher;
|
using Prepatcher;
|
||||||
using RimWorld.Planet;
|
using RimWorld.Planet;
|
||||||
|
using PFMapEvents = PerformanceFish.Events.MapEvents;
|
||||||
|
|
||||||
namespace PerformanceFish;
|
namespace PerformanceFish;
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ public static class PrepatcherFields
|
|||||||
[PrepatcherField]
|
[PrepatcherField]
|
||||||
[ValueInitializer(nameof(CreateMapEvents))]
|
[ValueInitializer(nameof(CreateMapEvents))]
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static extern MapEvents.Instanced Events(this Map map);
|
public static extern PFMapEvents.Instanced Events(this Map map);
|
||||||
|
|
||||||
[PrepatcherField]
|
[PrepatcherField]
|
||||||
[ValueInitializer(nameof(CreateThingEvents))]
|
[ValueInitializer(nameof(CreateThingEvents))]
|
||||||
@@ -34,6 +35,16 @@ public static class PrepatcherFields
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static extern List<Gene> GenesToTick(this Pawn_GeneTracker geneTracker);
|
public static extern List<Gene> GenesToTick(this Pawn_GeneTracker geneTracker);
|
||||||
|
|
||||||
|
#if V1_6
|
||||||
|
[PrepatcherField]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static extern ref GeneTrackerOptimization.GeneListVersionState GeneListVersions(this Pawn_GeneTracker geneTracker);
|
||||||
|
|
||||||
|
[PrepatcherField]
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static extern ref CompTickOptimization.ThingTickCache ThingTickCache(this ThingWithComps thing);
|
||||||
|
#endif
|
||||||
|
|
||||||
[PrepatcherField]
|
[PrepatcherField]
|
||||||
[ValueInitializer(nameof(CreateWorldPawnsCache))]
|
[ValueInitializer(nameof(CreateWorldPawnsCache))]
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -175,7 +186,7 @@ public static class PrepatcherFields
|
|||||||
public static FishTable<GroupThingPair, int> CreateIndexMapByGroup() => new();
|
public static FishTable<GroupThingPair, int> CreateIndexMapByGroup() => new();
|
||||||
public static Cache.CellGrid<int> CreateItemCountGrid(Map map) => new(map);
|
public static Cache.CellGrid<int> CreateItemCountGrid(Map map) => new(map);
|
||||||
public static Cache.BitCellGrid CreateBitCellGrid(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 ThingEvents.Instanced CreateThingEvents() => new();
|
||||||
public static StorageSettingsPatches.StorageSettingsCache CreateStorageSettingsCache() => new();
|
public static StorageSettingsPatches.StorageSettingsCache CreateStorageSettingsCache() => new();
|
||||||
public static StorageDistrict[] GetDefaultDistrictArray() => StorageDistrict.GetDefaultArray();
|
public static StorageDistrict[] GetDefaultDistrictArray() => StorageDistrict.GetDefaultArray();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
using Mono.Cecil.Rocks;
|
using Mono.Cecil.Rocks;
|
||||||
@@ -192,14 +193,26 @@ public abstract class FishPrepatch : FishPrepatchBase
|
|||||||
var stateParameter = postfixParameters.TryGetNamed("__state");
|
var stateParameter = postfixParameters.TryGetNamed("__state");
|
||||||
// Verify? It would make sense to match against prefix state
|
// Verify? It would make sense to match against prefix state
|
||||||
|
|
||||||
// var skipOriginalFlag = ilProcessor.DeclareLocal(typeof(bool));
|
var hasReturnValue = targetMethod.ReturnType.FullName != module.TypeSystem.Void.FullName;
|
||||||
var resultVariable = GetResultVariable(ilProcessor, ref patchVariables, resultParameter, targetMethod);
|
var resultVariable = hasReturnValue
|
||||||
|
? patchVariables.Result ??= ilProcessor.DeclareLocal(targetMethod.ReturnType)
|
||||||
|
: GetResultVariable(ilProcessor, ref patchVariables, resultParameter, targetMethod);
|
||||||
var stateVariable = GetStateVariable(ilProcessor, ref patchVariables, stateParameter);
|
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>();
|
var postfixInstructions = new Collection<object>();
|
||||||
|
|
||||||
if (resultVariable != null)
|
if (hasReturnValue)
|
||||||
postfixInstructions.Add((OpCodes.Stloc, resultVariable));
|
{
|
||||||
|
postfixInstructions.Add((OpCodes.Stloc, resultVariable!));
|
||||||
|
postfixInstructions.Add(OpCodes.Nop);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
postfixInstructions.Add(OpCodes.Nop);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var parameter in postfixParameters)
|
foreach (var parameter in postfixParameters)
|
||||||
{
|
{
|
||||||
@@ -217,36 +230,87 @@ public abstract class FishPrepatch : FishPrepatchBase
|
|||||||
postfixInstructions.Add((OpCodes.Call, postfixMethodInfo));
|
postfixInstructions.Add((OpCodes.Call, postfixMethodInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (postfixMethodInfo.ReturnType == typeof(void) && resultVariable != null)
|
if (postfixMethodInfo.ReturnType != typeof(void))
|
||||||
postfixInstructions.Add((OpCodes.Ldloc, resultVariable));
|
postfixInstructions.Add((OpCodes.Stloc, resultVariable!));
|
||||||
|
|
||||||
Span<int> retInstructions = stackalloc int[instructions.Count];
|
if (hasReturnValue)
|
||||||
var retInstructionCount = 0;
|
postfixInstructions.Add((OpCodes.Ldloc, resultVariable!));
|
||||||
|
|
||||||
for (var i = 0; i < instructions.Count; i++)
|
var lastRetInstruction = retInstructions[^1];
|
||||||
{
|
var lastRetInstructionIndex = instructions.IndexOf(lastRetInstruction);
|
||||||
if (instructions[i].OpCode == OpCodes.Ret)
|
|
||||||
retInstructions[retInstructionCount++] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastRetInstructionIndex = retInstructions[retInstructionCount - 1];
|
|
||||||
ilProcessor.InsertRange(lastRetInstructionIndex, postfixInstructions);
|
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 && retInstructionSet.Contains(branchTarget))
|
||||||
if (instruction.Operand is Instruction branchTarget && branchTarget.OpCode == OpCodes.Ret)
|
instruction.Operand = captureStart;
|
||||||
instruction.Operand = instructions[lastRetInstructionIndex];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < retInstructionCount - 1; i++)
|
for (var i = 0; i < retInstructions.Length - 1; i++)
|
||||||
{
|
{
|
||||||
var instruction = instructions[retInstructions[i]];
|
var instruction = retInstructions[i];
|
||||||
instruction.OpCode = OpCodes.Br;
|
if (hasReturnValue)
|
||||||
instruction.Operand = instructions[lastRetInstructionIndex];
|
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,
|
private static VariableDefinition? GetStateVariable(ILProcessor ilProcessor, ref PatchVariables patchVariables,
|
||||||
ParameterInfo? stateParameter)
|
ParameterInfo? stateParameter)
|
||||||
=> stateParameter != null
|
=> stateParameter != null
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ public static class FishPrepatchExtensions
|
|||||||
{
|
{
|
||||||
var prepatches = ToSortedList(allPrepatchClasses, true);
|
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)
|
if (!prepatch.Enabled)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -40,8 +41,9 @@ public static class FishPrepatchExtensions
|
|||||||
{
|
{
|
||||||
var prepatches = ToSortedList(allPrepatchClasses, false);
|
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)
|
if (!prepatch.IsActive)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -70,4 +72,5 @@ public static class FishPrepatchExtensions
|
|||||||
prepatches.Sort(FishPrepatchBase.PriorityComparer);
|
prepatches.Sort(FishPrepatchBase.PriorityComparer);
|
||||||
return prepatches;
|
return prepatches;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,9 @@ public class FishPrepatchHolder : IExposable, IEnumerable<FishPrepatchBase>
|
|||||||
|
|
||||||
private void AddPatchesRecursively(Type type)
|
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)
|
if (PerformanceFishMod.AllPrepatchClasses is { } allPatches)
|
||||||
RemoveDupes(allPatches, type);
|
RemoveDupes(allPatches, type);
|
||||||
|
|||||||
@@ -68,40 +68,79 @@ public sealed class DynamicDrawManagerPatches : ClassWithFishPrepatches
|
|||||||
public static void CullAndInitializeThings(DynamicDrawManager instance)
|
public static void CullAndInitializeThings(DynamicDrawManager instance)
|
||||||
{
|
{
|
||||||
var map = instance.map;
|
var map = instance.map;
|
||||||
var mapSizeX = map.SizeX();
|
var cellIndices = map.cellIndices;
|
||||||
|
var mapSizeX = GetMapSizeX(cellIndices);
|
||||||
var fogGrid = map.fogGrid.fogGrid;
|
var fogGrid = map.fogGrid.fogGrid;
|
||||||
var snowGrid = map.snowGrid.depthGrid;
|
var snowGrid = map.snowGrid.depthGrid;
|
||||||
var checkShadows = MatBases.SunShadow.shader.isSupported;
|
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);
|
var shadowViewRect = SectionLayer_SunShadows.GetSunShadowsViewRect(map, currentViewRect);
|
||||||
instance.drawThings.UnwrapArray(out var drawThings, out var drawThingsCount);
|
instance.drawThings.UnwrapArray(out var drawThings, out var drawThingsCount);
|
||||||
|
|
||||||
for (var i = 0; i < drawThingsCount; i++)
|
for (var i = 0; i < drawThingsCount; i++)
|
||||||
{
|
{
|
||||||
var drawThing = drawThings.UnsafeLoad(i);
|
var drawThing = drawThings.UnsafeLoad(i);
|
||||||
var position = drawThing.Position;
|
var position = GetCullPosition(drawThing);
|
||||||
var drawThingDef = drawThing.def;
|
var drawThingDef = drawThing.def;
|
||||||
var cellIndex = position.CellToIndex(mapSizeX);
|
var cellIndex = position.CellToIndex(mapSizeX);
|
||||||
if ((!fogGrid[cellIndex] || drawThingDef.seeThroughFog)
|
if (!CellIndicesContain(cellIndices, cellIndex)
|
||||||
&& (drawThingDef.hideAtSnowDepth >= 1f
|
|| (!drawThingDef.seeThroughFog
|
||||||
|| drawThingDef.hideAtSnowDepth >= snowGrid[cellIndex]))
|
#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)
|
continue;
|
||||||
|| currentViewRect.Overlaps(drawThing.OccupiedDrawRect()))
|
}
|
||||||
{
|
|
||||||
ThingsToDraw.Add(drawThing);
|
|
||||||
|
|
||||||
if (!DebugViewSettings.singleThreadedDrawing)
|
if (currentViewRect.Contains(position)
|
||||||
EnsureInitialized(drawThing);
|
|| currentViewRect.Overlaps(drawThing.OccupiedDrawRect()))
|
||||||
}
|
{
|
||||||
else if (drawThing is Pawn pawn && checkShadows && shadowViewRect.Contains(drawThing.Position))
|
ThingsToDraw.Add(drawThing);
|
||||||
{
|
|
||||||
PawnShadowsToDraw.Add(pawn);
|
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)
|
public static void EnsureInitialized(Thing drawThing)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// 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
|
||||||
|
{
|
||||||
|
public override bool DefaultState => true;
|
||||||
|
|
||||||
|
// 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 bool DefaultState => true;
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
public override bool DefaultState => true;
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
public override bool DefaultState => true;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -93,7 +93,7 @@ public sealed class TextureLoadingPatches : ClassWithFishPrepatches
|
|||||||
var data = file.ReadAllBytes();
|
var data = file.ReadAllBytes();
|
||||||
texture2D = new(2, 2, TextureFormat.Alpha8, true);
|
texture2D = new(2, 2, TextureFormat.Alpha8, true);
|
||||||
texture2D.LoadImage(data);
|
texture2D.LoadImage(data);
|
||||||
FixMipMapsIfNeeded(texture2D, data, file);
|
FixMipMapsIfNeeded(ref texture2D, data, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isDds && Prefs.TextureCompression)
|
if (!isDds && Prefs.TextureCompression)
|
||||||
@@ -120,11 +120,11 @@ public sealed class TextureLoadingPatches : ClassWithFishPrepatches
|
|||||||
|
|
||||||
var texture2D = new Texture2D(2, 2, TextureFormat.Alpha8, true);
|
var texture2D = new Texture2D(2, 2, TextureFormat.Alpha8, true);
|
||||||
texture2D.LoadImage(data);
|
texture2D.LoadImage(data);
|
||||||
FixMipMapsIfNeeded(texture2D, data, file);
|
FixMipMapsIfNeeded(ref texture2D, data, file);
|
||||||
return texture2D;
|
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))
|
if (((texture2D.width & 3) == 0) & ((texture2D.height & 3) == 0))
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -349,8 +349,7 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Room? RoomAt(IntVec3 c, Map map, RegionType allowedRegionTypes = RegionType.Set_All)
|
public static Room? RoomAt(IntVec3 c, Map map, RegionType allowedRegionTypes = RegionType.Set_All)
|
||||||
{
|
{
|
||||||
var cellIndices = map.cellIndices;
|
if (!c.InBounds(map))
|
||||||
if (((uint)c.x >= (uint)cellIndices.mapSizeX) | ((uint)c.z >= (uint)cellIndices.mapSizeZ))
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var regionAndRoomUpdater = map.regionAndRoomUpdater;
|
var regionAndRoomUpdater = map.regionAndRoomUpdater;
|
||||||
@@ -360,8 +359,8 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
|
|||||||
else if (regionAndRoomUpdater.AnythingToRebuild)
|
else if (regionAndRoomUpdater.AnythingToRebuild)
|
||||||
LogIncorrectResultWarning(c);
|
LogIncorrectResultWarning(c);
|
||||||
|
|
||||||
var region = map.regionGrid.regionGrid[(c.z * cellIndices.mapSizeX) + c.x];
|
var region = map.regionGrid.GetValidRegionAt_NoRebuild(c);
|
||||||
return region is null || !region.valid || (region.type & allowedRegionTypes) == 0
|
return region is null || (region.type & allowedRegionTypes) == 0
|
||||||
? null
|
? null
|
||||||
: region.District?.Room;
|
: region.District?.Room;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -382,6 +382,8 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
|
|
||||||
public sealed class GetFullName : FishPatch
|
public sealed class GetFullName : FishPatch
|
||||||
{
|
{
|
||||||
|
public override bool DefaultState => true;
|
||||||
|
|
||||||
public override string? Description { get; }
|
public override string? Description { get; }
|
||||||
= "Normally averages at over 1000ns per call. Caching makes this about 30 times faster, including "
|
= "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 "
|
+ "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));
|
= AccessTools.DeclaredPropertyGetter(typeof(RuntimeType), nameof(RuntimeType.FullName));
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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))
|
__state.ReleaseGuard = true;
|
||||||
return __state = 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;
|
__result = cache.Name;
|
||||||
return __state = false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private static bool TryGetFromCentralCache(RuntimeType instance,
|
private static bool TryGetFromCentralCache(RuntimeType instance,
|
||||||
ref TypeFullNameCacheValue cache)
|
out TypeFullNameCacheValue cache)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
ref var centralCache
|
cache = TypeFullNameCache.GetDirectly.GetOrAddReference(instance.TypeHandle);
|
||||||
= ref TypeFullNameCache.GetDirectly.GetOrAddReference(instance.TypeHandle);
|
if (!cache.Cached)
|
||||||
cache.Name = centralCache.Name;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cache.Cached;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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;
|
return;
|
||||||
|
|
||||||
UpdateCache(__instance, __result);
|
UpdateCache(__instance, __result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Exception? Finalizer(Exception? __exception, ref FullNameState __state)
|
||||||
|
{
|
||||||
|
if (__state.ReleaseGuard)
|
||||||
|
_fullNameRecursionDepth--;
|
||||||
|
|
||||||
|
return __exception;
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private static void UpdateCache(RuntimeType __instance, string? __result)
|
private static void UpdateCache(RuntimeType __instance, string? __result)
|
||||||
{
|
{
|
||||||
ref var cache = ref TypeFullNameCache.GetExistingReference(__instance.TypeHandle);
|
var cache = new TypeFullNameCacheValue
|
||||||
cache.Name = __result;
|
{
|
||||||
|
Cached = true,
|
||||||
|
Name = __result
|
||||||
|
};
|
||||||
|
TypeFullNameCache.GetExistingReference(__instance.TypeHandle) = cache;
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
TypeFullNameCache.GetDirectly.GetOrAddReference(__instance.TypeHandle) = cache;
|
||||||
ref var centralCache
|
|
||||||
= ref TypeFullNameCache.GetDirectly.GetReference(__instance.TypeHandle);
|
|
||||||
centralCache.Name = __result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static int _fullNameRecursionDepth;
|
||||||
|
|
||||||
private static object _lock = new();
|
private static object _lock = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,24 +505,16 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
public PropertyInfo? Info;
|
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
|
public record struct TypeFullNameCacheValue
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
public bool Cached;
|
||||||
get => Unsafe.As<string?>(_value);
|
public string? Name;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
set => _value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Cached
|
|
||||||
{
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
get => _value is not MemberInfo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,8 +522,13 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
{
|
{
|
||||||
public sealed class GetCustomAttributes : FishPatch
|
public sealed class GetCustomAttributes : FishPatch
|
||||||
{
|
{
|
||||||
|
private static readonly Type RuntimeTypeType = typeof(object).GetType();
|
||||||
|
|
||||||
|
public override bool DefaultState => true;
|
||||||
|
|
||||||
public override string? Description { get; }
|
public override string? Description { get; }
|
||||||
= "Caches attributes for reflection lookups. Decent load time improvement.";
|
= "Caches empty attribute lookups for fixed metadata. Nonempty results are recreated so attribute "
|
||||||
|
+ "constructors and mutable attribute instances retain normal reflection behavior.";
|
||||||
|
|
||||||
public override Delegate? TargetMethodGroup { get; }
|
public override Delegate? TargetMethodGroup { get; }
|
||||||
= (Func<ICustomAttributeProvider, Type, bool, object[]>)global::System.MonoCustomAttrs
|
= (Func<ICustomAttributeProvider, Type, bool, object[]>)global::System.MonoCustomAttrs
|
||||||
@@ -510,58 +538,208 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
public static bool Prefix(ICustomAttributeProvider obj, Type attributeType, bool inherit,
|
public static bool Prefix(ICustomAttributeProvider obj, Type attributeType, bool inherit,
|
||||||
out object[]? __result, out CustomAttributeState __state)
|
out object[]? __result, out CustomAttributeState __state)
|
||||||
{
|
{
|
||||||
var key = new CustomAttributeCache(obj, (__state.AttributeType = attributeType).TypeHandle,
|
if (obj is null || attributeType is null || !HasFixedAttributeMetadata(obj)
|
||||||
__state.Inherit = inherit);
|
|| attributeType.GetType() != RuntimeTypeType || attributeType.Assembly.IsDynamic)
|
||||||
|
{
|
||||||
|
__state = default;
|
||||||
|
__result = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
ref var cache = ref CustomAttributeCache.GetOrAddReference(in key);
|
var key = new CustomAttributeCache(obj, attributeType.TypeHandle, inherit);
|
||||||
if (cache.Attributes is null)
|
__state = new() { Key = key };
|
||||||
TryGetFromCentralCache(ref key, ref cache);
|
|
||||||
|
|
||||||
return __state.State = (__result = cache.Attributes) is null;
|
var cacheLookup = TryGetCachedAttributes(ref key, out __result);
|
||||||
|
|
||||||
|
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)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private static void TryGetFromCentralCache(ref CustomAttributeCache key,
|
private static CustomAttributeCacheLookup TryGetCachedAttributes(ref CustomAttributeCache key,
|
||||||
ref CustomAttributeCacheValue cache)
|
out object[]? result)
|
||||||
{
|
{
|
||||||
|
var localLookup = TryUseCache(CustomAttributeCache.Get, ref key, out result);
|
||||||
|
if (localLookup == CustomAttributeCacheLookup.Hit)
|
||||||
|
return CustomAttributeCacheLookup.Hit;
|
||||||
|
|
||||||
|
if (localLookup == CustomAttributeCacheLookup.InProgress)
|
||||||
|
return localLookup;
|
||||||
|
|
||||||
lock (_lock)
|
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 = 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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static void Postfix(ICustomAttributeProvider obj, object[]? __result,
|
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;
|
return;
|
||||||
|
|
||||||
UpdateCache(obj, __state, __result);
|
if (__result.Length == 0)
|
||||||
|
StoreCompleted(ref __state.Key, __result);
|
||||||
|
else
|
||||||
|
ClearInProgress(ref __state.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private static void UpdateCache(ICustomAttributeProvider obj, in CustomAttributeState __state,
|
public static Exception? Finalizer(Exception? __exception, ref CustomAttributeState __state)
|
||||||
object[]? __result)
|
|
||||||
{
|
{
|
||||||
var key = new CustomAttributeCache(obj, __state.AttributeType.TypeHandle, __state.Inherit);
|
if (__state.ShouldStore && __exception != null)
|
||||||
|
ClearInProgress(ref __state.Key);
|
||||||
|
|
||||||
CustomAttributeCache.GetExistingReference(in key).Attributes = __result;
|
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)
|
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 = 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 _)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasFixedAttributeMetadata(ICustomAttributeProvider provider)
|
||||||
|
=> provider.GetType().Assembly == typeof(object).Assembly && (provider switch
|
||||||
|
{
|
||||||
|
MemberInfo member => !member.Module.Assembly.IsDynamic,
|
||||||
|
ParameterInfo parameter => parameter.Member is MemberInfo member
|
||||||
|
&& !member.Module.Assembly.IsDynamic,
|
||||||
|
Assembly assembly => !assembly.IsDynamic,
|
||||||
|
Module module => !module.Assembly.IsDynamic,
|
||||||
|
_ => false
|
||||||
|
});
|
||||||
|
|
||||||
private static object _lock = new();
|
private static object _lock = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public record struct CustomAttributeCacheValue
|
public record struct CustomAttributeCacheValue
|
||||||
{
|
{
|
||||||
public object[]? Attributes;
|
public object[]? Attributes;
|
||||||
|
public CustomAttributeCacheStatus Status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public record struct CustomAttributeState
|
public record struct CustomAttributeState
|
||||||
{
|
{
|
||||||
public bool State, Inherit;
|
public bool ShouldStore;
|
||||||
public Type AttributeType;
|
public CustomAttributeCache Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CustomAttributeCacheStatus : byte
|
||||||
|
{
|
||||||
|
Uninitialized,
|
||||||
|
InProgress,
|
||||||
|
Completed
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum CustomAttributeCacheLookup : byte
|
||||||
|
{
|
||||||
|
Miss,
|
||||||
|
Hit,
|
||||||
|
InProgress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -577,8 +755,12 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
Guard.IsNotNull(array);
|
Guard.IsNotNull(array);
|
||||||
Guard.IsLessThanOrEqualTo(array.Length, byte.MaxValue);
|
Guard.IsLessThanOrEqualTo(array.Length, byte.MaxValue);
|
||||||
Array = array;
|
Array = array;
|
||||||
var length = array.Length;
|
var hashCode = array.Length;
|
||||||
_hashCode = HashCode.Combine(length, length > 0 && Array[0] is { } item ? item.GetHashCode() : 0);
|
|
||||||
|
for (var i = 0; i < array.Length; i++)
|
||||||
|
hashCode = global::System.HashCode.Combine(hashCode, array[i]);
|
||||||
|
|
||||||
|
_hashCode = hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -587,13 +769,16 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
if (Array is null)
|
if (Array is null)
|
||||||
return other.Array is null;
|
return other.Array is null;
|
||||||
|
|
||||||
|
if (_hashCode != other._hashCode)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (other.Array is null || Array.Length != other.Array.Length)
|
if (other.Array is null || Array.Length != other.Array.Length)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var length = Array.Length;
|
var length = Array.Length;
|
||||||
for (var i = 0; i < length; i++)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,6 +793,8 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
{
|
{
|
||||||
public sealed class CreateInstance_Type : FishPatch
|
public sealed class CreateInstance_Type : FishPatch
|
||||||
{
|
{
|
||||||
|
public override bool DefaultState => true;
|
||||||
|
|
||||||
public override List<string> IncompatibleModIDs { get; } = [PackageIDs.MULTIPLAYER];
|
public override List<string> IncompatibleModIDs { get; } = [PackageIDs.MULTIPLAYER];
|
||||||
|
|
||||||
public override string? Description { get; }
|
public override string? Description { get; }
|
||||||
@@ -619,21 +806,94 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool Prefix(Type? type, ref object __result)
|
public static bool Prefix(Type? type, ref object __result)
|
||||||
{
|
{
|
||||||
if (type is null)
|
if (type is not RuntimeType runtimeType)
|
||||||
return true;
|
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
|
if (cache.Dirty
|
||||||
&& !ActivatorCache.UpdateAsyncCache<ActivatorCacheValue, Type, Func<object>>(ref cache,
|
&& !ActivatorCache.UpdateAsyncCache<ActivatorCacheValue, RuntimeType, Func<object>>(ref cache,
|
||||||
type.TypeHandle, type))
|
runtimeType.TypeHandle, runtimeType))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
__result = cache.Result!();
|
try
|
||||||
return false;
|
{
|
||||||
|
__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
|
// public sealed class CreateInstance_Type_Args : FishPatch
|
||||||
@@ -642,14 +902,34 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
// = (Func<Type, object[], object>)Activator.CreateInstance;
|
// = (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 Func<object>? Result { get; set; }
|
||||||
public Task<Func<object>>? Task { get; set; }
|
public Task<Func<object>>? Task { get; set; }
|
||||||
public bool Dirty => Result == null;
|
public bool Dirty => Result == null;
|
||||||
|
|
||||||
public ValueTask<Func<object>?> MakeResultAsync(RuntimeTypeHandle key, Type type)
|
public ValueTask<Func<object>?> MakeResultAsync(RuntimeTypeHandle key, RuntimeType type)
|
||||||
=> new(Reflection.CreateConstructorDelegate<Func<object>>(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,
|
// public record struct ActivatorWithArgumentsCacheValue : IAsyncCacheable<ActivatorWithArgumentsCache, Type,
|
||||||
@@ -737,10 +1017,16 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
|
|
||||||
public sealed class FieldInfoPatches
|
public sealed class FieldInfoPatches
|
||||||
{
|
{
|
||||||
|
private static readonly Type? RuntimeFieldInfoType
|
||||||
|
= AccessTools.TypeByName("System.Reflection.RuntimeFieldInfo")
|
||||||
|
?? AccessTools.TypeByName("System.Reflection.MonoField");
|
||||||
|
|
||||||
public sealed class GetValue_Patch : FishPatch
|
public sealed class GetValue_Patch : FishPatch
|
||||||
{
|
{
|
||||||
|
public override bool DefaultState => true;
|
||||||
|
|
||||||
public override string Description { get; }
|
public override string Description { get; }
|
||||||
= "Optimizes the GetValue method by invoking it through specialized cached delegates";
|
= "Uses cached getters for ordinary fields. Generic, dynamic and invalid calls use Mono reflection.";
|
||||||
|
|
||||||
//public GetValue_Patch()
|
//public GetValue_Patch()
|
||||||
//{
|
//{
|
||||||
@@ -751,13 +1037,21 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
// Log.Message(types.ToStringSafeEnumerable());
|
// Log.Message(types.ToStringSafeEnumerable());
|
||||||
//}
|
//}
|
||||||
|
|
||||||
public override MethodBase TargetMethodInfo { get; }
|
public override MethodBase? TargetMethodInfo { get; }
|
||||||
= AccessTools.DeclaredMethod(typeof(MonoField), nameof(MonoField.GetValue))!;
|
= AccessTools.DeclaredMethod(
|
||||||
|
AccessTools.TypeByName("System.Reflection.RuntimeFieldInfo")
|
||||||
|
?? AccessTools.TypeByName("System.Reflection.MonoField"),
|
||||||
|
nameof(FieldInfo.GetValue), [typeof(object)]);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool Prefix(FieldInfo __instance, object? obj, ref object? __result)
|
public static bool Prefix(FieldInfo __instance, object? obj, ref object? __result)
|
||||||
{
|
{
|
||||||
|
if (__instance.GetType() != RuntimeFieldInfoType)
|
||||||
|
return true;
|
||||||
|
|
||||||
ref var cache = ref FieldGetters.GetOrAddReference(__instance.FieldHandle);
|
ref var cache = ref FieldGetters.GetOrAddReference(__instance.FieldHandle);
|
||||||
|
if (!CanUseFieldFastPath(__instance, obj, ref cache.FastPath))
|
||||||
|
return true;
|
||||||
|
|
||||||
if (cache.Dirty
|
if (cache.Dirty
|
||||||
&& !FieldGetters.UpdateAsyncCache<FieldInfoGetterCache, FieldInfo, FieldGetter>(ref cache,
|
&& !FieldGetters.UpdateAsyncCache<FieldInfoGetterCache, FieldInfo, FieldGetter>(ref cache,
|
||||||
@@ -773,8 +1067,10 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
|
|
||||||
public sealed class SetValue_Patch : FishPatch
|
public sealed class SetValue_Patch : FishPatch
|
||||||
{
|
{
|
||||||
|
public override bool DefaultState => true;
|
||||||
|
|
||||||
public override string Description { get; }
|
public override string Description { get; }
|
||||||
= "Optimizes the SetValue method by invoking it through specialized cached delegates";
|
= "Uses cached setters only for ordinary fields and already-compatible values. Other calls use Mono reflection.";
|
||||||
|
|
||||||
public override MethodBase? TargetMethodInfo { get; }
|
public override MethodBase? TargetMethodInfo { get; }
|
||||||
= AccessTools.DeclaredMethod(typeof(FieldInfo), nameof(FieldInfo.SetValue),
|
= AccessTools.DeclaredMethod(typeof(FieldInfo), nameof(FieldInfo.SetValue),
|
||||||
@@ -783,7 +1079,13 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool Prefix(FieldInfo __instance, object? obj, object? value)
|
public static bool Prefix(FieldInfo __instance, object? obj, object? value)
|
||||||
{
|
{
|
||||||
|
if (__instance.GetType() != RuntimeFieldInfoType)
|
||||||
|
return true;
|
||||||
|
|
||||||
ref var cache = ref FieldSetters.GetOrAddReference(__instance.FieldHandle);
|
ref var cache = ref FieldSetters.GetOrAddReference(__instance.FieldHandle);
|
||||||
|
if (!CanUseFieldFastPath(__instance, obj, ref cache.FastPath, forSetter: true)
|
||||||
|
|| !CanSetFieldExactly(ref cache.FastPath, value))
|
||||||
|
return true;
|
||||||
|
|
||||||
if (cache.Dirty && !TryUpdate(__instance, ref cache))
|
if (cache.Dirty && !TryUpdate(__instance, ref cache))
|
||||||
return true;
|
return true;
|
||||||
@@ -794,24 +1096,68 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
private static bool TryUpdate(FieldInfo __instance, ref FieldInfoSetterCache cache)
|
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,
|
|
||||||
__instance.FieldHandle, __instance);
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
private static bool ErrorForInvalidCallAttempt(FieldInfo info, ref FieldInfoSetterCache cache)
|
|
||||||
{
|
{
|
||||||
cache.Task = _fieldSetterFailureTask;
|
return FieldSetters.UpdateAsyncCache<FieldInfoSetterCache, FieldInfo, FieldSetter>(ref cache,
|
||||||
Log.Error($"FieldInfo.SetValue has been called on '{
|
__instance.FieldHandle, __instance);
|
||||||
info.FullDescription()}'. This is a const that gets copied at compile time. An attempt at "
|
|
||||||
+ $"setting a value will always fail.\n{new StackTrace(2, true)}");
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task<FieldSetter> _fieldSetterFailureTask = new(static () => null!);
|
public struct FieldFastPath
|
||||||
|
{
|
||||||
|
public bool Checked;
|
||||||
|
public bool Unsupported;
|
||||||
|
public bool IsStatic;
|
||||||
|
public bool IsValueType;
|
||||||
|
public Type? DeclaringType;
|
||||||
|
public Type? FieldType;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool CanUseFieldFastPath(FieldInfo info, object? obj, ref FieldFastPath state,
|
||||||
|
bool forSetter = false)
|
||||||
|
{
|
||||||
|
if (!state.Checked)
|
||||||
|
InitializeFieldFastPath(info, ref state, forSetter);
|
||||||
|
|
||||||
|
if (state.Unsupported)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (state.IsStatic)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return obj is not null && obj is not MarshalByRefObject
|
||||||
|
&& (obj.GetType() == state.DeclaringType || state.DeclaringType!.IsInstanceOfType(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool CanSetFieldExactly(ref FieldFastPath state, object? value)
|
||||||
|
{
|
||||||
|
if (value is null)
|
||||||
|
return !state.IsValueType;
|
||||||
|
|
||||||
|
var fieldType = state.FieldType!;
|
||||||
|
var valueType = value.GetType();
|
||||||
|
return valueType == fieldType || !state.IsValueType && fieldType.IsAssignableFrom(valueType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private static void InitializeFieldFastPath(FieldInfo info, ref FieldFastPath state, bool forSetter)
|
||||||
|
{
|
||||||
|
var declaringType = info.DeclaringType;
|
||||||
|
var fieldType = info.FieldType;
|
||||||
|
state.DeclaringType = declaringType;
|
||||||
|
state.FieldType = fieldType;
|
||||||
|
state.IsStatic = info.IsStatic;
|
||||||
|
state.IsValueType = fieldType.IsValueType;
|
||||||
|
// Constructed generic fields can share a RuntimeFieldHandle but need different delegates.
|
||||||
|
state.Unsupported = declaringType is null || declaringType.IsGenericType
|
||||||
|
|| declaringType.ContainsGenericParameters
|
||||||
|
|| typeof(MarshalByRefObject).IsAssignableFrom(declaringType)
|
||||||
|
|| info.Module.Assembly.IsDynamic || info.GetRequiredCustomModifiers().Length != 0
|
||||||
|
|| info.IsLiteral || forSetter && info.IsInitOnly || fieldType.IsPointer
|
||||||
|
|| fieldType.IsByRef || fieldType.ContainsGenericParameters;
|
||||||
|
state.Checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
public static FieldGetter? MakeGetterDelegate(FieldInfo info)
|
public static FieldGetter? MakeGetterDelegate(FieldInfo info)
|
||||||
{
|
{
|
||||||
@@ -896,6 +1242,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
{
|
{
|
||||||
public FieldGetter? Result { get; set; }
|
public FieldGetter? Result { get; set; }
|
||||||
public Task<FieldGetter>? Task { get; set; }
|
public Task<FieldGetter>? Task { get; set; }
|
||||||
|
public FieldFastPath FastPath;
|
||||||
|
|
||||||
public bool Dirty => Result == null;
|
public bool Dirty => Result == null;
|
||||||
|
|
||||||
@@ -907,6 +1254,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
{
|
{
|
||||||
public FieldSetter? Result { get; set; }
|
public FieldSetter? Result { get; set; }
|
||||||
public Task<FieldSetter>? Task { get; set; }
|
public Task<FieldSetter>? Task { get; set; }
|
||||||
|
public FieldFastPath FastPath;
|
||||||
|
|
||||||
public bool Dirty => Result == null;
|
public bool Dirty => Result == null;
|
||||||
|
|
||||||
@@ -922,13 +1270,16 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
|
|
||||||
public sealed class MethodBasePatches
|
public sealed class MethodBasePatches
|
||||||
{
|
{
|
||||||
|
private static readonly Type? RuntimeMethodInfoType
|
||||||
|
= AccessTools.TypeByName("System.Reflection.RuntimeMethodInfo")
|
||||||
|
?? AccessTools.TypeByName("System.Reflection.MonoMethod");
|
||||||
|
|
||||||
public sealed class Invoke_Patch : FishPatch
|
public sealed class Invoke_Patch : FishPatch
|
||||||
{
|
{
|
||||||
public override bool DefaultState => false; // TODO: fix
|
public override bool DefaultState => true;
|
||||||
|
|
||||||
public override string Description { get; }
|
public override string Description { get; }
|
||||||
= "Optimizes the Invoke method by compiling specialized cached delegates. Disabled by default due to "
|
= "Uses cached delegates for static, parameterless methods with fixed metadata. Other calls use Mono reflection.";
|
||||||
+ "issues with various mods, like RIMMSqol and Performance Optimizer";
|
|
||||||
|
|
||||||
public override MethodBase? TargetMethodInfo { get; }
|
public override MethodBase? TargetMethodInfo { get; }
|
||||||
= AccessTools.DeclaredMethod(typeof(MethodBase), nameof(MethodBase.Invoke),
|
= AccessTools.DeclaredMethod(typeof(MethodBase), nameof(MethodBase.Invoke),
|
||||||
@@ -937,22 +1288,21 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static bool Prefix(MethodBase __instance, object? obj, object[]? parameters, ref object? __result)
|
public static bool Prefix(MethodBase __instance, object? obj, object[]? parameters, ref object? __result)
|
||||||
{
|
{
|
||||||
ref var cache = ref MethodInvokers.GetOrAddReference(__instance.MethodHandle);
|
if (__instance is not MethodInfo methodInfo
|
||||||
|
|| methodInfo.GetType() != RuntimeMethodInfoType || parameters is { Length: > 0 })
|
||||||
|
return true;
|
||||||
|
|
||||||
// if (cache.Dirty) // TODO: remove
|
ref var cache = ref MethodInvokers.GetOrAddReference(__instance.MethodHandle);
|
||||||
// {
|
if (cache.Unsupported)
|
||||||
// if (OnStartup.State.Initialized)
|
return true;
|
||||||
// {
|
|
||||||
// Log.Message($"Called Invoke for {__instance.FullDescription()} with {obj.ToStringSafe()}, {
|
if (!cache.EligibilityChecked)
|
||||||
// parameters?.Length.ToStringSafe() ?? "null"} parameters");
|
{
|
||||||
// }
|
cache.EligibilityChecked = true;
|
||||||
//
|
cache.Unsupported = !CanCacheInvoke(methodInfo);
|
||||||
// if (!MethodInvokers.UpdateAsyncCache<MethodInvokerCache, MethodBase, MethodInvoker>(ref cache,
|
if (cache.Unsupported)
|
||||||
// __instance.MethodHandle, __instance))
|
return true;
|
||||||
// {
|
}
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (cache.Dirty
|
if (cache.Dirty
|
||||||
&& !MethodInvokers.UpdateAsyncCache<MethodInvokerCache, MethodBase, MethodInvoker>(ref cache,
|
&& !MethodInvokers.UpdateAsyncCache<MethodInvokerCache, MethodBase, MethodInvoker>(ref cache,
|
||||||
@@ -961,56 +1311,31 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
__result = cache.Result!(obj, parameters);
|
try
|
||||||
|
{
|
||||||
|
__result = cache.Result!(obj, parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new TargetInvocationException(ex);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Similar to HarmonyLib.MethodInvoker, but correctly handles a couple of edge cases Harmony would throw on.
|
// Restrict the delegate to a method whose reflection behavior can be reproduced cheaply.
|
||||||
[SuppressMessage("Globalization", "CA1310")]
|
|
||||||
public static MethodInvoker? MakeInvokeDelegate(MethodBase methodBase)
|
public static MethodInvoker? MakeInvokeDelegate(MethodBase methodBase)
|
||||||
{
|
{
|
||||||
if (methodBase.DeclaringType is null && methodBase.Name.StartsWith("PadMethod"))
|
if (methodBase is not MethodInfo methodInfo || !CanCacheInvoke(methodInfo))
|
||||||
return null; // padding methods generated by harmony for linux
|
return null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var methodInfo = methodBase as MethodInfo;
|
|
||||||
if (methodInfo is { ReturnType.IsByRef: true })
|
|
||||||
return null; // how to box a ref return?
|
|
||||||
|
|
||||||
var constructorInfo = methodBase as ConstructorInfo;
|
|
||||||
|
|
||||||
if (methodInfo is null && constructorInfo is null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var dm = new DynamicMethod($"MethodInvoker_{methodBase.Name}", typeof(object),
|
var dm = new DynamicMethod($"MethodInvoker_{methodBase.Name}", typeof(object),
|
||||||
[typeof(object), typeof(object[])],
|
[typeof(object), typeof(object[])],
|
||||||
GetOwnerType(methodBase), true);
|
GetOwnerType(methodBase), true);
|
||||||
var il = dm.GetILGenerator();
|
var il = dm.GetILGenerator();
|
||||||
|
EmitInvokeMethodCall(methodInfo, il);
|
||||||
var parameters = methodBase.GetParameters();
|
|
||||||
|
|
||||||
if (methodInfo != null && !methodBase.IsStatic)
|
|
||||||
EmitInstanceArgument(il, methodBase);
|
|
||||||
|
|
||||||
for (var i = 0; i < parameters.Length; i++)
|
|
||||||
{
|
|
||||||
il.Emit(FishTranspiler.Argument(1));
|
|
||||||
il.Emit(FishTranspiler.Constant(i));
|
|
||||||
|
|
||||||
var parameterType = parameters[i].ParameterType;
|
|
||||||
|
|
||||||
if (parameterType.IsByRef)
|
|
||||||
EmitInvokeByRefParameterLoad(il, parameterType);
|
|
||||||
else
|
|
||||||
EmitInvokeParameterLoad(il, parameterType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (methodInfo != null)
|
|
||||||
EmitInvokeMethodCall(methodBase, il, methodInfo);
|
|
||||||
else if (constructorInfo != null)
|
|
||||||
il.Emit(FishTranspiler.New(constructorInfo));
|
|
||||||
|
|
||||||
il.Emit(FishTranspiler.Return);
|
il.Emit(FishTranspiler.Return);
|
||||||
|
|
||||||
@@ -1025,66 +1350,45 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmitInvokeMethodCall(MethodBase methodBase, ILGenerator il, MethodInfo methodInfo)
|
private static bool CanCacheInvoke(MethodInfo methodInfo)
|
||||||
{
|
{
|
||||||
il.Emit(FishTranspiler.Call(methodBase));
|
try
|
||||||
|
{
|
||||||
if (!methodInfo.ReturnType.IsValueType)
|
var declaringType = methodInfo.DeclaringType;
|
||||||
return;
|
var returnType = methodInfo.ReturnType;
|
||||||
|
// Constructed generic methods and types need more than a RuntimeMethodHandle as a key.
|
||||||
il.Emit(methodInfo.ReturnType == typeof(void)
|
return methodInfo.IsStatic && !methodInfo.IsGenericMethod
|
||||||
? FishTranspiler.Null
|
&& declaringType is not null && !declaringType.IsGenericType
|
||||||
: FishTranspiler.Box(methodInfo.ReturnType));
|
&& !declaringType.ContainsGenericParameters
|
||||||
|
&& !methodInfo.Module.Assembly.IsDynamic
|
||||||
|
&& methodInfo.GetParameters().Length == 0
|
||||||
|
&& !methodInfo.ContainsGenericParameters
|
||||||
|
&& (methodInfo.CallingConvention & CallingConventions.VarArgs) == 0
|
||||||
|
&& !returnType.IsByRef && !returnType.IsPointer
|
||||||
|
&& !returnType.ContainsGenericParameters
|
||||||
|
&& methodInfo.GetMethodBody() is not null;
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EmitInvokeParameterLoad(ILGenerator il, Type parameterType)
|
private static void EmitInvokeMethodCall(MethodInfo methodInfo, ILGenerator il)
|
||||||
{
|
{
|
||||||
il.Emit(FishTranspiler.LoadElement<object>());
|
il.Emit(FishTranspiler.Call(methodInfo));
|
||||||
|
if (methodInfo.ReturnType == typeof(void))
|
||||||
il.Emit(parameterType.IsValueType
|
il.Emit(FishTranspiler.Null);
|
||||||
? FishTranspiler.Call(typeof(ReflectionCaching),
|
else if (methodInfo.ReturnType.IsValueType)
|
||||||
parameterType.IsNullable() ? nameof(UnboxNullableSafely) : nameof(UnboxSafely),
|
il.Emit(FishTranspiler.Box(methodInfo.ReturnType));
|
||||||
[typeof(object)],
|
|
||||||
[
|
|
||||||
parameterType.IsNullable()
|
|
||||||
? Nullable.GetUnderlyingType(parameterType)
|
|
||||||
: parameterType
|
|
||||||
])
|
|
||||||
: FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastOrConvert),
|
|
||||||
generics: [parameterType]));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void EmitInvokeByRefParameterLoad(ILGenerator il, Type parameterType)
|
|
||||||
{
|
|
||||||
parameterType = parameterType.GetElementType()!;
|
|
||||||
|
|
||||||
il.Emit(FishTranspiler.LoadElementAddress<object>());
|
|
||||||
|
|
||||||
if (parameterType.IsNullable())
|
|
||||||
{
|
|
||||||
il.Emit(FishTranspiler.Call(typeof(ReflectionCaching), nameof(VerifyNullableBoxObject),
|
|
||||||
generics: [Nullable.GetUnderlyingType(parameterType)]));
|
|
||||||
|
|
||||||
il.Emit(FishTranspiler.UnboxAddress(parameterType));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
il.Emit(parameterType.IsValueType
|
|
||||||
? FishTranspiler.Call(typeof(ReflectionCaching), nameof(UnboxRefSafely),
|
|
||||||
generics:
|
|
||||||
[
|
|
||||||
/*parameterType.IsNullable()
|
|
||||||
? Nullable.GetUnderlyingType(parameterType)
|
|
||||||
:*/ parameterType
|
|
||||||
])
|
|
||||||
: FishTranspiler.Call(typeof(ReflectionCaching), nameof(CastRefSafely), generics: [parameterType]));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public record struct MethodInvokerCache : IAsyncCacheable<RuntimeMethodHandle, MethodBase, MethodInvoker>
|
public record struct MethodInvokerCache : IAsyncCacheable<RuntimeMethodHandle, MethodBase, MethodInvoker>
|
||||||
{
|
{
|
||||||
public MethodInvoker? Result { get; set; }
|
public MethodInvoker? Result { get; set; }
|
||||||
public Task<MethodInvoker>? Task { get; set; }
|
public Task<MethodInvoker>? Task { get; set; }
|
||||||
|
public bool EligibilityChecked;
|
||||||
|
public bool Unsupported;
|
||||||
public bool Dirty => Result == null;
|
public bool Dirty => Result == null;
|
||||||
|
|
||||||
public ValueTask<MethodInvoker?> MakeResultAsync(RuntimeMethodHandle key, MethodBase method)
|
public ValueTask<MethodInvoker?> MakeResultAsync(RuntimeMethodHandle key, MethodBase method)
|
||||||
@@ -1119,7 +1423,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
[SecuritySafeCritical]
|
[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 from is TTo or null
|
||||||
? ref Unsafe.As<object?, TTo>(ref from)
|
? ref Unsafe.As<object?, TTo>(ref from)
|
||||||
: ref ThrowInvalidCastException<TTo>();
|
: ref ThrowInvalidCastException<TTo>();
|
||||||
@@ -1131,7 +1435,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
|
|||||||
? ref Unsafe.Unbox<TTo>(from)
|
? ref Unsafe.Unbox<TTo>(from)
|
||||||
: ref Unsafe.Unbox<TTo>(from
|
: ref Unsafe.Unbox<TTo>(from
|
||||||
= from is null
|
= from is null
|
||||||
? default
|
? default(TTo)
|
||||||
: FisheryLib.Convert.Type<object, TTo>(from));
|
: FisheryLib.Convert.Type<object, TTo>(from));
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public static class CellIndexExtensions
|
|||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int CellToIndex(this in IntVec3 cell, CellIndices cellIndices)
|
public static int CellToIndex(this in IntVec3 cell, CellIndices cellIndices)
|
||||||
=> cell.CellToIndex(cellIndices.mapSizeX);
|
=> cell.CellToIndex(GetMapSizeX(cellIndices));
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int CellToIndex(this in IntVec3 cell, int mapSizeX) => (cell.z * mapSizeX) + cell.x;
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int CellToIndex(this IntVec2 cell, CellIndices cellIndices)
|
public static int CellToIndex(this IntVec2 cell, CellIndices cellIndices)
|
||||||
=> cell.CellToIndex(cellIndices.mapSizeX);
|
=> cell.CellToIndex(GetMapSizeX(cellIndices));
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int CellToIndex(this IntVec2 cell, int mapSizeX) => (cell.z * mapSizeX) + cell.x;
|
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);
|
public static int CellToIndex(int x, int z, Map map) => CellToIndex(x, z, map.cellIndices);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int CellToIndex(int x, int z, int mapSizeX) => (z * mapSizeX) + x;
|
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];
|
=> thingGrid.thingGrid[cellIndex.Value];
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static float GetDepth(this SnowGrid snowGrid, CellIndex cellIndex) => snowGrid.depthGrid[cellIndex.Value];
|
public static float GetDepth(this SnowGrid snowGrid, CellIndex cellIndex) => snowGrid.depthGrid[cellIndex.Value];
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static float GetDepth(this SnowGrid snowGrid, int cellIndex) => snowGrid.depthGrid[cellIndex];
|
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)]
|
[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)
|
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;
|
var mapIndex = (uint)thing.mapIndexOrState;
|
||||||
|
|
||||||
return mapIndex < (uint)maps.Count
|
return mapIndex < (uint)maps.Count
|
||||||
@@ -114,7 +117,10 @@ public static class MiscExtensions
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static Map? TryGetMap(this Thing thing)
|
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;
|
var mapIndex = (uint)thing.mapIndexOrState;
|
||||||
|
|
||||||
return mapIndex < (uint)maps.Count ? maps[(int)mapIndex] : null;
|
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];
|
public static Map GetMap(this Region region) => Current.Game.Maps[region.mapIndex];
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int GetLoadID(this Bill bill) => bill.loadID;
|
public static int GetLoadID(this Bill bill) => bill.loadID;
|
||||||
|
|
||||||
public static void InvokeWhenCellIndicesReady(this Map map, Action<Map> action)
|
public static void InvokeWhenCellIndicesReady(this Map map, Action<Map> action)
|
||||||
{
|
{
|
||||||
|
#if V1_6
|
||||||
|
if (map.cellIndices.NumGridCells > 0)
|
||||||
|
#else
|
||||||
if (map.cellIndices != null)
|
if (map.cellIndices != null)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
action(map);
|
action(map);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
map.Events().ComponentsConstructed += action;
|
map.Events().ComponentsConstructed += action;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AddLetterSilently(this LetterStack letterStack, Letter letter, Color color, bool playSound = false,
|
public static void AddLetterSilently(this LetterStack letterStack, Letter letter, Color color, bool playSound = false,
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ public sealed class UtilityPrepatches : ClassWithFishPrepatches
|
|||||||
|
|
||||||
public override MethodBase TargetMethodBase { get; } = methodof(LoadedModManager.CreateModClasses);
|
public override MethodBase TargetMethodBase { get; } = methodof(LoadedModManager.CreateModClasses);
|
||||||
|
|
||||||
|
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||||
|
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||||
|
|
||||||
public static void Prefix()
|
public static void Prefix()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -37,6 +40,59 @@ public sealed class UtilityPrepatches : ClassWithFishPrepatches
|
|||||||
Log.Error($"Exception caught within OnAssembliesLoaded:\n{e}\n{new StackTrace(true)}");
|
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
|
public sealed class NoSteamLogWarning : FishPrepatch
|
||||||
|
|||||||
Reference in New Issue
Block a user