1.5, added stuff, fixed stuff

- mod icon
- fixes for new vanilla clipping issues
- GenClosest optimization improving repairing, wardening, tending jobs
- caching for new ListerBuildings methods
- DynamicDrawManager single threading
- Type.GetFields caching
- StatParts analyzer category
- removed duplicate steam warning and optionally window
- added exception handling for debug menu, incidentworker, Graphic_Multi, InitializeComps, making all those now log what mod broke and not cause black screens or whatever
This commit is contained in:
bbradson
2024-04-27 22:31:07 +02:00
parent be3dae46bb
commit d34bc77869
76 changed files with 3061 additions and 1134 deletions
Binary file not shown.
Binary file not shown.
-42
View File
@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<PerformanceFish.GasDef>
<defName>BlindSmoke</defName>
<label>blind smoke</label>
<dissipationRate>4</dissipationRate>
<diffuses>false</diffuses>
<color>(200,200,200)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>ToxGas</defName>
<label>tox gas</label>
<dissipationRate>3</dissipationRate>
<diffuses>true</diffuses>
<color>(180,214,24)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>RotStink</defName>
<label>rot stink</label>
<dissipationRate>4</dissipationRate>
<diffuses>true</diffuses>
<color>(214,90,24)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>FirstTestGas</defName>
<label>first test gas</label>
<dissipationRate>2</dissipationRate>
<diffuses>false</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>SecondTestGas</defName>
<label>second test gas</label>
<dissipationRate>1</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
</Defs>
Binary file not shown.
Binary file not shown.
+2
View File
@@ -4,8 +4,10 @@
<packageId>bs.performance</packageId> <packageId>bs.performance</packageId>
<supportedVersions> <supportedVersions>
<li>1.4</li> <li>1.4</li>
<li>1.5</li>
</supportedVersions> </supportedVersions>
<author>bradson</author> <author>bradson</author>
<modIconPath IgnoreIfNoMatchingField="True">PerformanceFish/ModIcon</modIconPath>
<url>https://github.com/bbradson/performance-fish</url> <url>https://github.com/bbradson/performance-fish</url>
<loadBefore> <loadBefore>
<li>Dubwise.DubsPerformanceAnalyzer</li> <li>Dubwise.DubsPerformanceAnalyzer</li>
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+314
View File
@@ -0,0 +1,314 @@
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
<PerformanceFish.GasDef>
<defName>BlindSmoke</defName>
<label>blind smoke</label>
<dissipationRate>4</dissipationRate>
<diffuses>false</diffuses>
<color>(200,200,200)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>ToxGas</defName>
<label>tox gas</label>
<dissipationRate>3</dissipationRate>
<diffuses>true</diffuses>
<color>(180,214,24)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>RotStink</defName>
<label>rot stink</label>
<dissipationRate>4</dissipationRate>
<diffuses>true</diffuses>
<color>(214,90,24)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>DeadLifeDust</defName>
<label>dead life dust</label>
<dissipationRate>3</dissipationRate>
<diffuses>true</diffuses>
<color>(3,13,51)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>FirstTestGas</defName>
<label>first test gas</label>
<dissipationRate>2</dissipationRate>
<diffuses>false</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>SecondTestGas</defName>
<label>second test gas</label>
<dissipationRate>1</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>ThirdTestGas</defName>
<label>third test gas</label>
<dissipationRate>1</dissipationRate>
<diffuses>true</diffuses>
<color>(255,113,0,0)</color>
</PerformanceFish.GasDef>
<!-- PerformanceFish.GasDef>
<defName>Test1Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test2Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test3Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test4Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test5Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test6Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test7Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test8Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test9Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test10Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test11Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test12Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test13Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test14Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test15Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test16Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test17Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test18Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test19Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test20Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test21Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test22Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test23Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test24Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test25Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test26Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test27Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test28Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test29Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test30Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test31Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(229,52,235)</color>
</PerformanceFish.GasDef>
<PerformanceFish.GasDef>
<defName>Test32Gas</defName>
<label>test gas</label>
<dissipationRate>0</dissipationRate>
<diffuses>true</diffuses>
<color>(52,113,235)</color>
</PerformanceFish.GasDef -->
</Defs>
+8
View File
@@ -184,6 +184,14 @@
</DerivedMethods> </DerivedMethods>
</RitualObligationTriggers> </RitualObligationTriggers>
<StatParts>
<Tooltip>TransformValue methods of StatParts. These run on every stat lookup, when not cached</Tooltip>
<Category>Tick</Category>
<DerivedMethods>
<li>RimWorld.StatPart:TransformValue</li>
</DerivedMethods>
</StatParts>
<ThingCompTick> <ThingCompTick>
<Tooltip>Tick methods of comps attached to Things. Any ThingWithComps can have these, and any mod can add new ones</Tooltip> <Tooltip>Tick methods of comps attached to Things. Any ThingWithComps can have these, and any mod can add new ones</Tooltip>
<Category>Tick</Category> <Category>Tick</Category>
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutDir>..\..\1.4\Assemblies\</OutDir>
<Copyright>Copyright (c) 2023 Bradson</Copyright>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DefineConstants>DEBUG;V1_4</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DefineConstants>V1_4</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.4.*" />
<PackageReference Include="Lib.Harmony" Version="2.2.*" ExcludeAssets="runtime" />
<Reference Include="1Fishery">
<HintPath>..\..\..\Fishery\1.4\Assemblies\1Fishery.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
</Project>
+21
View File
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutDir>..\..\1.5\Assemblies\</OutDir>
<Copyright>Copyright (c) 2024 Bradson</Copyright>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DefineConstants>DEBUG;V1_5</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DefineConstants>V1_5</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.5.*" />
<PackageReference Include="Lib.Harmony" Version="2.3.*" ExcludeAssets="runtime" />
<Reference Include="1Fishery">
<HintPath>..\..\..\Fishery\1.5\Assemblies\1Fishery.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
</Project>
+44 -7
View File
@@ -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.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq; using System.Linq;
using System.Reflection.Emit; using System.Reflection.Emit;
using System.Xml; using System.Xml;
@@ -305,9 +306,28 @@ public static class AnalyzerFixes
var methods = Reflection.MethodsOfName(AccessTools.TypeByName(array[0]), array[1]); var methods = Reflection.MethodsOfName(AccessTools.TypeByName(array[0]), array[1]);
foreach (var method in methods) foreach (var method in methods)
{ {
yield return method.IsGenericMethodDefinition if (method.IsGenericMethodDefinition || method.ReturnType.IsByRef)
? method.MakeGenericMethod(GetTypeArguments(method)) continue;
: method;
MethodInfo? methodToReturn;
try
{
methodToReturn = method.IsGenericMethodDefinition
? method.MakeGenericMethod(GetTypeArguments(method))
: method;
}
catch (Exception e)
{
methodToReturn = null;
Log.Error($"Exception caught while trying to parse generic method '{
method.FullDescription()}'{
(XmlParser.CurrentModDir is { } currentModDir
? $" in 'DubsAnalyzer.xml' within {currentModDir}"
: "")}:\n{e}");
}
if (methodToReturn != null)
yield return methodToReturn;
} }
} }
@@ -317,9 +337,10 @@ public static class AnalyzerFixes
var resultArray = new Type[genericArguments.Length]; var resultArray = new Type[genericArguments.Length];
for (var i = 0; i < genericArguments.Length; i++) for (var i = 0; i < genericArguments.Length; i++)
{ {
resultArray[i] = genericArguments[i].GetGenericArguments() is var constraints && constraints.Length != 0 var genericArgument = genericArguments[i];
? constraints[0] resultArray[i] = genericArgument.GetGenericArguments() is var constraints && constraints.Length != 0
: typeof(object); ? genericArgument.MakeGenericType(constraints)
: genericArgument;
} }
return resultArray; return resultArray;
@@ -430,6 +451,8 @@ public static class AnalyzerFixes
//Copied from Wiri's analyzer fork, with above fixes integrated. The steam version lacks this functionality //Copied from Wiri's analyzer fork, with above fixes integrated. The steam version lacks this functionality
public static class XmlParser public static class XmlParser
{ {
public static DirectoryInfo? CurrentModDir;
[SuppressMessage("Security", "CA3075")] [SuppressMessage("Security", "CA3075")]
public static void CollectXmlData() public static void CollectXmlData()
{ {
@@ -439,6 +462,8 @@ public static class AnalyzerFixes
if (xmlFiles.Length == 0) if (xmlFiles.Length == 0)
continue; continue;
CurrentModDir = dir;
foreach (var file in xmlFiles) foreach (var file in xmlFiles)
{ {
var doc = new XmlDocument(); var doc = new XmlDocument();
@@ -446,6 +471,8 @@ public static class AnalyzerFixes
Parse(doc); Parse(doc);
} }
CurrentModDir = null;
} }
} }
@@ -516,7 +543,17 @@ public static class AnalyzerFixes
continue; continue;
if (childNode.NodeType != XmlNodeType.Comment) if (childNode.NodeType != XmlNodeType.Comment)
methods.AddRange(func(childNode.InnerText)); {
try
{
methods.AddRange(func(childNode.InnerText));
}
catch (Exception e)
{
Log.Error($"Exception caught while trying to parse DubsAnalyzer.xml at '{
childNode.InnerText}' in '{CurrentModDir}':\n{e}");
}
}
} }
} }
+89 -58
View File
@@ -5,6 +5,7 @@
using JetBrains.Annotations; using JetBrains.Annotations;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
// ReSharper disable WithExpressionModifiesAllMembers
namespace PerformanceFish.Cache; namespace PerformanceFish.Cache;
@@ -25,12 +26,12 @@ public record struct ByInt<T, TResult> where T : notnull where TResult : new()
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => Key; public override int GetHashCode() => Key;
private static List<FishTable<ByInt<T, TResult>, TResult>> _allCaches = [];
private static object _valueInitializerLock = new(); private static readonly object _valueInitializerLock = new();
private static FishTable<ByInt<T, TResult>, TResult> _get = InitializeNew(); private static readonly List<IDictionary> _allCaches = [];
private static readonly FishTable<ByInt<T, TResult>, TResult> _get = InitializeNew();
[ThreadStatic] [ThreadStatic]
private static FishTable<ByInt<T, TResult>, TResult>? _getThreadStatic; private static FishTable<ByInt<T, TResult>, TResult>? _getThreadStatic;
@@ -74,18 +75,12 @@ public record struct ByInt<T, TResult> where T : notnull where TResult : new()
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(ByInt<T, TResult> key) => ref Get.GetOrAddReference(key); public static ref TResult GetOrAddReference(ByInt<T, TResult> key) => ref Get.GetOrAddReference(key);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(ref ByInt<T, TResult> key) => ref Get.GetOrAddReference(ref key);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TResult GetOrAdd(int key) => Get.GetOrAdd(Unsafe.As<int, ByInt<T, TResult>>(ref key)); public static TResult GetOrAdd(int key) => Get.GetOrAdd(Unsafe.As<int, ByInt<T, TResult>>(ref key));
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TResult GetOrAdd(ByInt<T, TResult> key) => Get.GetOrAdd(key); public static TResult GetOrAdd(ByInt<T, TResult> key) => Get.GetOrAdd(key);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TResult GetOrAdd(ref ByInt<T, TResult> key) => Get.GetOrAdd(ref key);
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static ref TResult GetExistingReference(int key) public static ref TResult GetExistingReference(int key)
=> ref Get.GetReference(Unsafe.As<int, ByInt<T, TResult>>(ref key)); => ref Get.GetReference(Unsafe.As<int, ByInt<T, TResult>>(ref key));
@@ -95,23 +90,9 @@ public record struct ByInt<T, TResult> where T : notnull where TResult : new()
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private static FishTable<ByInt<T, TResult>, TResult> InitializeNew() private static FishTable<ByInt<T, TResult>, TResult> InitializeNew()
{ => Utility.AddNew(_allCaches, ValueInitializer);
var newCache = Utility.AddNew(ValueInitializer);
lock (_allCaches)
_allCaches.Add(newCache);
return newCache;
}
public static void Clear() public static void Clear() => Utility.Internal.ClearIn(_allCaches);
{
lock (_allCaches)
{
foreach (var cache in _allCaches)
cache.Clear();
}
}
} }
[PublicAPI] [PublicAPI]
@@ -120,11 +101,11 @@ public record struct ByInt<T1, T2, TResult>
{ {
public int First, Second; public int First, Second;
private static List<FishTable<ByInt<T1, T2, TResult>, TResult>> _allCaches = []; private static readonly object _valueInitializerLock = new();
private static object _valueInitializerLock = new(); private static readonly List<IDictionary> _allCaches = [];
private static FishTable<ByInt<T1, T2, TResult>, TResult> _get = InitializeNew(); private static readonly FishTable<ByInt<T1, T2, TResult>, TResult> _get = InitializeNew();
[ThreadStatic] [ThreadStatic]
private static FishTable<ByInt<T1, T2, TResult>, TResult>? _getThreadStatic; private static FishTable<ByInt<T1, T2, TResult>, TResult>? _getThreadStatic;
@@ -173,7 +154,7 @@ public record struct ByInt<T1, T2, TResult>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(int first, int second) public static ref TResult GetOrAddReference(int first, int second)
=> ref Get.GetOrAddReference(new() { First = first, Second = second }); => ref Get.GetOrAddReference(default(ByInt<T1, T2, TResult>) with { First = first, Second = second });
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(in ByInt<T1, T2, TResult> key) public static ref TResult GetOrAddReference(in ByInt<T1, T2, TResult> key)
@@ -181,14 +162,14 @@ public record struct ByInt<T1, T2, TResult>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TResult GetOrAdd(int first, int second) public static TResult GetOrAdd(int first, int second)
=> Get.GetOrAdd(new() { First = first, Second = second }); => Get.GetOrAdd(default(ByInt<T1, T2, TResult>) with { First = first, Second = second });
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TResult GetOrAdd(in ByInt<T1, T2, TResult> key) => Get.GetOrAdd(ref Unsafe.AsRef(in key)); public static TResult GetOrAdd(in ByInt<T1, T2, TResult> key) => Get.GetOrAdd(ref Unsafe.AsRef(in key));
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static ref TResult GetExistingReference(int first, int second) public static ref TResult GetExistingReference(int first, int second)
=> ref Get.GetReference(new() { First = first, Second = second }); => ref Get.GetReference(default(ByInt<T1, T2, TResult>) with { First = first, Second = second });
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static ref TResult GetExistingReference(T1 first, T2 second) public static ref TResult GetExistingReference(T1 first, T2 second)
@@ -217,23 +198,9 @@ public record struct ByInt<T1, T2, TResult>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private static FishTable<ByInt<T1, T2, TResult>, TResult> InitializeNew() private static FishTable<ByInt<T1, T2, TResult>, TResult> InitializeNew()
{ => Utility.AddNew(_allCaches, ValueInitializer);
var newCache = Utility.AddNew(ValueInitializer);
lock (_allCaches)
_allCaches.Add(newCache);
return newCache;
}
public static void Clear() public static void Clear() => Utility.Internal.ClearIn(_allCaches);
{
lock (_allCaches)
{
foreach (var cache in _allCaches)
cache.Clear();
}
}
} }
[PublicAPI] [PublicAPI]
@@ -242,16 +209,39 @@ public record struct ByInt<T1, T2, T3, TResult>
{ {
public int First, Second, Third; public int First, Second, Third;
private static FishTable<ByInt<T1, T2, T3, TResult>, TResult> _get private static readonly object _valueInitializerLock = new();
= Utility.AddNew<ByInt<T1, T2, T3, TResult>, TResult>();
private static readonly List<IDictionary> _allCaches = [];
private static FishTable<ByInt<T1, T2, T3, TResult>, TResult> _get = InitializeNew();
[ThreadStatic] [ThreadStatic]
private static FishTable<ByInt<T1, T2, T3, TResult>, TResult>? _getThreadStatic; private static FishTable<ByInt<T1, T2, T3, TResult>, TResult>? _getThreadStatic;
private static Func<ByInt<T1, T2, T3, TResult>, TResult>? _valueInitializer;
public static Func<ByInt<T1, T2, T3, TResult>, TResult>? ValueInitializer
{
get
{
lock (_valueInitializerLock)
return _valueInitializer;
}
set
{
lock (_valueInitializerLock)
{
value ??= static _ => Reflection.New<TResult>();
_valueInitializer = value;
_get.ValueInitializer = value;
}
}
}
public static FishTable<ByInt<T1, T2, T3, TResult>, TResult> Get public static FishTable<ByInt<T1, T2, T3, TResult>, TResult> Get
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= Utility.AddNew<ByInt<T1, T2, T3, TResult>, TResult>(); get => _getThreadStatic ??= InitializeNew();
} }
public static FishTable<ByInt<T1, T2, T3, TResult>, TResult> GetDirectly public static FishTable<ByInt<T1, T2, T3, TResult>, TResult> GetDirectly
@@ -263,7 +253,7 @@ public record struct ByInt<T1, T2, T3, TResult>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref TResult GetOrAddReference(int first, int second, int third) public static unsafe ref TResult GetOrAddReference(int first, int second, int third)
{ {
var key = new ByInt<T1, T2, T3, TResult> { First = first, Second = second, Third = third }; var key = default(ByInt<T1, T2, T3, TResult>) with { First = first, Second = second, Third = third };
return ref Get.GetOrAddReference(ref key); return ref Get.GetOrAddReference(ref key);
} }
@@ -273,7 +263,10 @@ public record struct ByInt<T1, T2, T3, TResult>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static ref TResult GetExistingReference(int first, int second, int third) public static ref TResult GetExistingReference(int first, int second, int third)
=> ref Get.GetReference(new() { First = first, Second = second, Third = third }); => ref Get.GetReference(default(ByInt<T1, T2, T3, TResult>) with
{
First = first, Second = second, Third = third
});
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static ref TResult GetExistingReference(T1 first, T2 second, T3 third) public static ref TResult GetExistingReference(T1 first, T2 second, T3 third)
@@ -295,6 +288,12 @@ public record struct ByInt<T1, T2, T3, TResult>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(First, Second, Third); public override int GetHashCode() => HashCode.Combine(First, Second, Third);
[MethodImpl(MethodImplOptions.NoInlining)]
private static FishTable<ByInt<T1, T2, T3, TResult>, TResult> InitializeNew()
=> Utility.AddNew(_allCaches, ValueInitializer);
public static void Clear() => Utility.Internal.ClearIn(_allCaches);
} }
[PublicAPI] [PublicAPI]
@@ -304,16 +303,39 @@ public record struct ByInt<T1, T2, T3, T4, TResult>
{ {
public int First, Second, Third, Fourth; public int First, Second, Third, Fourth;
private static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult> _get private static readonly object _valueInitializerLock = new();
= Utility.AddNew<ByInt<T1, T2, T3, T4, TResult>, TResult>();
private static readonly List<IDictionary> _allCaches = [];
private static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult> _get = InitializeNew();
[ThreadStatic] [ThreadStatic]
private static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult>? _getThreadStatic; private static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult>? _getThreadStatic;
private static Func<ByInt<T1, T2, T3, T4, TResult>, TResult>? _valueInitializer;
public static Func<ByInt<T1, T2, T3, T4, TResult>, TResult>? ValueInitializer
{
get
{
lock (_valueInitializerLock)
return _valueInitializer;
}
set
{
lock (_valueInitializerLock)
{
value ??= static _ => Reflection.New<TResult>();
_valueInitializer = value;
_get.ValueInitializer = value;
}
}
}
public static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult> Get public static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult> Get
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= Utility.AddNew<ByInt<T1, T2, T3, T4, TResult>, TResult>(); get => _getThreadStatic ??= InitializeNew();
} }
public static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult> GetDirectly public static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult> GetDirectly
@@ -325,7 +347,7 @@ public record struct ByInt<T1, T2, T3, T4, TResult>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref TResult GetOrAddReference(int first, int second, int third, int fourth) public static unsafe ref TResult GetOrAddReference(int first, int second, int third, int fourth)
{ {
var key = new ByInt<T1, T2, T3, T4, TResult> var key = default(ByInt<T1, T2, T3, T4, TResult>) with
{ {
First = first, Second = second, Third = third, Fourth = fourth First = first, Second = second, Third = third, Fourth = fourth
}; };
@@ -338,7 +360,10 @@ public record struct ByInt<T1, T2, T3, T4, TResult>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static ref TResult GetExistingReference(int first, int second, int third, int fourth) public static ref TResult GetExistingReference(int first, int second, int third, int fourth)
=> ref Get.GetReference(new() { First = first, Second = second, Third = third, Fourth = fourth }); => ref Get.GetReference(default(ByInt<T1, T2, T3, T4, TResult>) with
{
First = first, Second = second, Third = third, Fourth = fourth
});
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static ref TResult GetExistingReference(T1 first, T2 second, T3 third, T4 fourth) public static ref TResult GetExistingReference(T1 first, T2 second, T3 third, T4 fourth)
@@ -361,5 +386,11 @@ public record struct ByInt<T1, T2, T3, T4, TResult>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(First, Second, Third, Fourth); public override int GetHashCode() => HashCode.Combine(First, Second, Third, Fourth);
[MethodImpl(MethodImplOptions.NoInlining)]
private static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult> InitializeNew()
=> Utility.AddNew(_allCaches, ValueInitializer);
public static void Clear() => Utility.Internal.ClearIn(_allCaches);
} }
#pragma warning restore CS9091 #pragma warning restore CS9091
+378 -151
View File
@@ -8,6 +8,7 @@ using System.Security;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract // ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
// ReSharper disable WithExpressionModifiesAllMembers
namespace PerformanceFish.Cache; namespace PerformanceFish.Cache;
@@ -18,9 +19,9 @@ public static class ByReferenceUnclearable<T, TResult>
{ {
public const int MINIMUM_CALL_COUNT_FOR_STARTING_TASK = 8; public const int MINIMUM_CALL_COUNT_FOR_STARTING_TASK = 8;
public static object SyncLock = new(); public static readonly object SyncLock = new();
private static FishTable<T, TResult> _get = Utility.AddNewUnclearable<T, TResult>(); private static readonly FishTable<T, TResult> _get = Utility.AddNewUnclearable<T, TResult>();
[ThreadStatic] [ThreadStatic]
private static FishTable<T, TResult>? _getThreadStatic; private static FishTable<T, TResult>? _getThreadStatic;
@@ -138,7 +139,8 @@ public static class ByReferenceUnclearable<T, TResult>
} }
[SecuritySafeCritical] [SecuritySafeCritical]
public static Task<TCacheResult>? RequestFromCacheAsync<TCacheValue, TArgument2, TCacheResult>(T key, TArgument2 second) public static Task<TCacheResult>? RequestFromCacheAsync<TCacheValue, TArgument2, TCacheResult>(T key,
TArgument2 second)
where TCacheValue : TResult, IAsyncCacheable<T, TArgument2, TCacheResult> where TCacheValue : TResult, IAsyncCacheable<T, TArgument2, TCacheResult>
{ {
TCacheValue cacheCopy; TCacheValue cacheCopy;
@@ -200,8 +202,8 @@ public static class ByReferenceUnclearable<T, TResult>
return !cache.Result.Equals<TCacheResult>(default); return !cache.Result.Equals<TCacheResult>(default);
} }
private static async Task<TCacheResult> UpdateCacheAsync<TCacheValue, TArgument2, TCacheResult>(T key, TCacheValue cacheCopy, private static async Task<TCacheResult> UpdateCacheAsync<TCacheValue, TArgument2, TCacheResult>(T key,
TArgument2 second) TCacheValue cacheCopy, TArgument2 second)
where TCacheValue : TResult, IAsyncCacheable<T, TArgument2, TCacheResult> where TCacheValue : TResult, IAsyncCacheable<T, TArgument2, TCacheResult>
{ {
TCacheResult? result = default; TCacheResult? result = default;
@@ -240,11 +242,13 @@ public static class ByReferenceUnclearable<T, TResult>
public static class ByReference<T, TResult> public static class ByReference<T, TResult>
where T : notnull where TResult : new() where T : notnull where TResult : new()
{ {
private static object _valueInitializerLock = new(); private static readonly object _valueInitializerLock = new();
public static object SyncLock = new(); public static readonly object SyncLock = new();
private static readonly List<IDictionary> _allCaches = [];
private static FishTable<T, TResult> _get = InitializeNew(); private static readonly FishTable<T, TResult> _get = InitializeNew();
[ThreadStatic] [ThreadStatic]
private static FishTable<T, TResult>? _getThreadStatic; private static FishTable<T, TResult>? _getThreadStatic;
@@ -293,15 +297,8 @@ public static class ByReference<T, TResult>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(ref T key) => ref Get.GetOrAddReference(ref key); public static ref TResult GetOrAddReference(ref T key) => ref Get.GetOrAddReference(ref key);
[MethodImpl(MethodImplOptions.NoInlining)]
public static ref TResult GetExistingReference(T key) => ref Get.GetReference(key); public static ref TResult GetExistingReference(T key) => ref Get.GetReference(key);
public static void Remove(T key)
{
Get.Remove(key);
GetDirectly.Remove(key);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Update<VResult>(T key) where VResult : TResult, ICacheable<T> public static void Update<VResult>(T key) where VResult : TResult, ICacheable<T>
=> Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key); => Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key);
@@ -393,7 +390,8 @@ public static class ByReference<T, TResult>
return result ?? ThrowHelper.ThrowInvalidOperationException<TCacheResult>(); return result ?? ThrowHelper.ThrowInvalidOperationException<TCacheResult>();
} }
public static Task<TCacheResult> RequestFromCacheAsync<TCacheValue, TArgument2, TCacheResult>(T key, TArgument2 second) public static Task<TCacheResult> RequestFromCacheAsync<TCacheValue, TArgument2, TCacheResult>(T key,
TArgument2 second)
where TCacheValue : TResult, IAsyncCacheable<T, TArgument2, TCacheResult> where TCacheValue : TResult, IAsyncCacheable<T, TArgument2, TCacheResult>
{ {
TCacheValue cacheCopy; TCacheValue cacheCopy;
@@ -424,8 +422,8 @@ public static class ByReference<T, TResult>
return !cache.Result.Equals<TCacheResult>(default); return !cache.Result.Equals<TCacheResult>(default);
} }
private static async ValueTask<TCacheResult> UpdateCacheAsync<TCacheValue, TArgument2, TCacheResult>(T key, TCacheValue cacheCopy, private static async ValueTask<TCacheResult> UpdateCacheAsync<TCacheValue, TArgument2, TCacheResult>(T key,
TArgument2 second) TCacheValue cacheCopy, TArgument2 second)
where TCacheValue : TResult, IAsyncCacheable<T, TArgument2, TCacheResult> where TCacheValue : TResult, IAsyncCacheable<T, TArgument2, TCacheResult>
{ {
TCacheResult? result = default; TCacheResult? result = default;
@@ -444,23 +442,14 @@ public static class ByReference<T, TResult>
return result ?? ThrowHelper.ThrowInvalidOperationException<TCacheResult>(); return result ?? ThrowHelper.ThrowInvalidOperationException<TCacheResult>();
} }
public static unsafe void Initialize() public static void Initialize() => Utility.Internal.Initialize<T>();
{
try
{
_ = FisheryLib.FunctionPointers.Equals<T>.Default;
_ = FisheryLib.FunctionPointers.GetHashCode<T>.Default;
}
catch (Exception ex)
{
Log.Error(
$"Performance Fish encountered an exception while trying to initialize {
typeof(ByReference<T, TResult>).FullName}\n{ex}");
}
}
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private static FishTable<T, TResult> InitializeNew() => Utility.AddNew(ValueInitializer); private static FishTable<T, TResult> InitializeNew() => Utility.AddNew(_allCaches, ValueInitializer);
public static void Remove(T key) => Utility.Internal.RemoveIn(_allCaches, key);
public static void Clear() => Utility.Internal.ClearIn(_allCaches);
} }
[PublicAPI] [PublicAPI]
@@ -468,16 +457,43 @@ public static class ByReference<T, TResult>
public record struct ByReference<T1, T2, TResult> : IInitializable<T1, T2> public record struct ByReference<T1, T2, TResult> : IInitializable<T1, T2>
where T1 : notnull where T2 : notnull where TResult : new() where T1 : notnull where T2 : notnull where TResult : new()
{ {
private static FishTable<ByReference<T1, T2, TResult>, TResult> _get public T1 First;
= Utility.AddNew<ByReference<T1, T2, TResult>, TResult>(); public T2 Second;
private static readonly object _valueInitializerLock = new();
private static readonly List<IDictionary> _allCaches = [];
private static readonly FishTable<ByReference<T1, T2, TResult>, TResult> _get
= InitializeNew();
[ThreadStatic] [ThreadStatic]
private static FishTable<ByReference<T1, T2, TResult>, TResult>? _getThreadStatic; private static FishTable<ByReference<T1, T2, TResult>, TResult>? _getThreadStatic;
private static Func<ByReference<T1, T2, TResult>, TResult>? _valueInitializer;
public static Func<ByReference<T1, T2, TResult>, TResult>? ValueInitializer
{
get
{
lock (_valueInitializerLock)
return _valueInitializer;
}
set
{
lock (_valueInitializerLock)
{
value ??= static _ => Reflection.New<TResult>();
_valueInitializer = value;
_get.ValueInitializer = value;
}
}
}
public static FishTable<ByReference<T1, T2, TResult>, TResult> Get public static FishTable<ByReference<T1, T2, TResult>, TResult> Get
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= Utility.AddNew<ByReference<T1, T2, TResult>, TResult>(); get => _getThreadStatic ??= InitializeNew();
} }
public static FishTable<ByReference<T1, T2, TResult>, TResult> GetDirectly public static FishTable<ByReference<T1, T2, TResult>, TResult> GetDirectly
@@ -488,7 +504,7 @@ public record struct ByReference<T1, T2, TResult> : IInitializable<T1, T2>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(T1 first, T2 second) public static ref TResult GetOrAddReference(T1 first, T2 second)
=> ref Get.GetOrAddReference(new() { First = first, Second = second }); => ref Get.GetOrAddReference(default(ByReference<T1, T2, TResult>) with { First = first, Second = second });
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(in ByReference<T1, T2, TResult> key) public static ref TResult GetOrAddReference(in ByReference<T1, T2, TResult> key)
@@ -497,7 +513,7 @@ public record struct ByReference<T1, T2, TResult> : IInitializable<T1, T2>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static unsafe ref TResult GetExistingReference(T1 first, T2 second) public static unsafe ref TResult GetExistingReference(T1 first, T2 second)
{ {
var key = new ByReference<T1, T2, TResult>(first, second); var key = default(ByReference<T1, T2, TResult>) with { First = first, Second = second };
return ref Get.GetReference(ref key); return ref Get.GetReference(ref key);
} }
@@ -509,7 +525,7 @@ public record struct ByReference<T1, T2, TResult> : IInitializable<T1, T2>
public static void Update<VResult>(T1 first, T2 second) public static void Update<VResult>(T1 first, T2 second)
where VResult : TResult, ICacheable<ByReference<T1, T2, TResult>> where VResult : TResult, ICacheable<ByReference<T1, T2, TResult>>
{ {
var key = new ByReference<T1, T2, TResult>(first, second); var key = default(ByReference<T1, T2, TResult>) with { First = first, Second = second };
Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key); Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key);
} }
@@ -517,7 +533,7 @@ public record struct ByReference<T1, T2, TResult> : IInitializable<T1, T2>
public static void Update<VResult, V2>(T1 key1, T2 key2, V2 second) public static void Update<VResult, V2>(T1 key1, T2 key2, V2 second)
where VResult : TResult, ICacheable<ByReference<T1, T2, TResult>, V2> where VResult : TResult, ICacheable<ByReference<T1, T2, TResult>, V2>
{ {
var key = new ByReference<T1, T2, TResult>(key1, key2); var key = default(ByReference<T1, T2, TResult>) with { First = key1, Second = key2 };
Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key, second); Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key, second);
} }
@@ -525,7 +541,7 @@ public record struct ByReference<T1, T2, TResult> : IInitializable<T1, T2>
public static void Update<VResult, V2, V3>(T1 key1, T2 key2, V2 second, V3 third) public static void Update<VResult, V2, V3>(T1 key1, T2 key2, V2 second, V3 third)
where VResult : TResult, ICacheable<ByReference<T1, T2, TResult>, V2, V3> where VResult : TResult, ICacheable<ByReference<T1, T2, TResult>, V2, V3>
{ {
var key = new ByReference<T1, T2, TResult>(key1, key2); var key = default(ByReference<T1, T2, TResult>) with { First = key1, Second = key2 };
Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key, second, third); Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key, second, third);
} }
@@ -533,7 +549,7 @@ public record struct ByReference<T1, T2, TResult> : IInitializable<T1, T2>
public static unsafe ref VResult GetAndCheck<VResult>(T1 first, T2 second) public static unsafe ref VResult GetAndCheck<VResult>(T1 first, T2 second)
where VResult : TResult, ICacheable<ByReference<T1, T2, TResult>> where VResult : TResult, ICacheable<ByReference<T1, T2, TResult>>
{ {
var key = new ByReference<T1, T2, TResult>(first, second); var key = default(ByReference<T1, T2, TResult>) with { First = first, Second = second };
ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)); ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key));
if (cache.Dirty) if (cache.Dirty)
cache.Update(ref key); cache.Update(ref key);
@@ -545,7 +561,7 @@ public record struct ByReference<T1, T2, TResult> : IInitializable<T1, T2>
public static unsafe ref VResult GetAndCheck<VResult, V2>(T1 key1, T2 key2, V2 second) public static unsafe ref VResult GetAndCheck<VResult, V2>(T1 key1, T2 key2, V2 second)
where VResult : TResult, ICacheable<ByReference<T1, T2, TResult>, V2> where VResult : TResult, ICacheable<ByReference<T1, T2, TResult>, V2>
{ {
var key = new ByReference<T1, T2, TResult>(key1, key2); var key = default(ByReference<T1, T2, TResult>) with { First = key1, Second = key2 };
ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)); ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key));
if (cache.Dirty) if (cache.Dirty)
cache.Update(ref key, second); cache.Update(ref key, second);
@@ -557,7 +573,7 @@ public record struct ByReference<T1, T2, TResult> : IInitializable<T1, T2>
public static unsafe ref VResult GetAndCheck<VResult, V2, V3>(T1 key1, T2 key2, V2 second, V3 third) public static unsafe ref VResult GetAndCheck<VResult, V2, V3>(T1 key1, T2 key2, V2 second, V3 third)
where VResult : TResult, ICacheable<ByReference<T1, T2, TResult>, V2, V3> where VResult : TResult, ICacheable<ByReference<T1, T2, TResult>, V2, V3>
{ {
var key = new ByReference<T1, T2, TResult>(key1, key2); var key = default(ByReference<T1, T2, TResult>) with { First = key1, Second = key2 };
ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)); ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key));
if (cache.Dirty) if (cache.Dirty)
cache.Update(ref key, second, third); cache.Update(ref key, second, third);
@@ -579,29 +595,13 @@ public record struct ByReference<T1, T2, TResult> : IInitializable<T1, T2>
Second = second; Second = second;
} }
public static unsafe void Initialize() public static void Initialize()
{ {
try Utility.Internal.Initialize<T1>();
{ Utility.Internal.Initialize<T2>();
_ = FisheryLib.FunctionPointers.Equals<T1>.Default; Utility.Internal.Initialize<ByReference<T1, T2, TResult>>();
_ = FisheryLib.FunctionPointers.Equals<T2>.Default;
_ = FisheryLib.FunctionPointers.Equals<ByReference<T1, T2, TResult>>.Default;
_ = FisheryLib.FunctionPointers.GetHashCode<T1>.Default;
_ = FisheryLib.FunctionPointers.GetHashCode<T2>.Default;
_ = FisheryLib.FunctionPointers.GetHashCode<ByReference<T1, T2, TResult>>.Default;
}
catch (Exception ex)
{
Log.Error(
$"Performance Fish encountered an exception while trying to initialize {
typeof(ByReference<T1, T2, TResult>).FullName}\n{ex}");
}
} }
public T1 First;
public T2 Second;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByReference<T1, T2, TResult> other) public bool Equals(ByReference<T1, T2, TResult> other)
=> First.Equals<T1>(other.First) => First.Equals<T1>(other.First)
@@ -610,6 +610,14 @@ public record struct ByReference<T1, T2, TResult> : IInitializable<T1, T2>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(First, Second); public override int GetHashCode() => HashCode.Combine(First, Second);
[MethodImpl(MethodImplOptions.NoInlining)]
private static FishTable<ByReference<T1, T2, TResult>, TResult> InitializeNew()
=> Utility.AddNew(_allCaches, ValueInitializer);
public static void Remove(ByReference<T1, T2, TResult> key) => Utility.Internal.RemoveIn(_allCaches, key);
public static void Clear() => Utility.Internal.ClearIn(_allCaches);
T1 IMemberCount<T1>.First T1 IMemberCount<T1>.First
{ {
get => First; get => First;
@@ -628,22 +636,49 @@ public record struct ByReference<T1, T2, TResult> : IInitializable<T1, T2>
public record struct ByReference<T1, T2, T3, TResult> public record struct ByReference<T1, T2, T3, TResult>
where T1 : notnull where T2 : notnull where T3 : notnull where TResult : new() where T1 : notnull where T2 : notnull where T3 : notnull where TResult : new()
{ {
// TODO: Switch to FishTable. This is used by some of the reflection patches and currently crashes when not used public T1 First;
// with standard dictionaries public T2 Second;
public T3 Third;
public int HashCode;
private static Dictionary<ByReference<T1, T2, T3, TResult>, TResult> _get // TODO: Fix ReflectionCaching:CustomAttributeCache causing crashes when not used with standard dictionaries
= Utility.AddNew<Dictionary<ByReference<T1, T2, T3, TResult>, TResult>>();
private static readonly object _valueInitializerLock = new();
private static readonly List<IDictionary> _allCaches = [];
private static readonly FishTable<ByReference<T1, T2, T3, TResult>, TResult> _get = InitializeNew();
[ThreadStatic] [ThreadStatic]
private static Dictionary<ByReference<T1, T2, T3, TResult>, TResult>? _getThreadStatic; private static FishTable<ByReference<T1, T2, T3, TResult>, TResult>? _getThreadStatic;
public static Dictionary<ByReference<T1, T2, T3, TResult>, TResult> Get private static Func<ByReference<T1, T2, T3, TResult>, TResult>? _valueInitializer;
public static Func<ByReference<T1, T2, T3, TResult>, TResult>? ValueInitializer
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] get
get => _getThreadStatic ??= Utility.AddNew<Dictionary<ByReference<T1, T2, T3, TResult>, TResult>>(); {
lock (_valueInitializerLock)
return _valueInitializer;
}
set
{
lock (_valueInitializerLock)
{
value ??= static _ => Reflection.New<TResult>();
_valueInitializer = value;
_get.ValueInitializer = value;
}
}
} }
public static Dictionary<ByReference<T1, T2, T3, TResult>, TResult> GetDirectly public static FishTable<ByReference<T1, T2, T3, TResult>, TResult> Get
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= InitializeNew();
}
public static FishTable<ByReference<T1, T2, T3, TResult>, TResult> GetDirectly
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _get; get => _get;
@@ -652,7 +687,7 @@ public record struct ByReference<T1, T2, T3, TResult>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref TResult GetOrAddReference(T1 first, T2 second, T3 third) public static unsafe ref TResult GetOrAddReference(T1 first, T2 second, T3 third)
{ {
var key = new ByReference<T1, T2, T3, TResult> { First = first, Second = second, Third = third }; var key = Create(first, second, third);
return ref Get.GetOrAddReference(ref key); return ref Get.GetOrAddReference(ref key);
} }
@@ -663,7 +698,7 @@ public record struct ByReference<T1, T2, T3, TResult>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static unsafe ref TResult GetExistingReference(T1 first, T2 second, T3 third) public static unsafe ref TResult GetExistingReference(T1 first, T2 second, T3 third)
{ {
var key = new ByReference<T1, T2, T3, TResult>(first, second, third); var key = Create(first, second, third);
return ref Get.GetReference(ref key); return ref Get.GetReference(ref key);
} }
@@ -675,7 +710,7 @@ public record struct ByReference<T1, T2, T3, TResult>
public static void Update<VResult>(T1 first, T2 second, T3 third) public static void Update<VResult>(T1 first, T2 second, T3 third)
where VResult : TResult, ICacheable<ByReference<T1, T2, T3, TResult>> where VResult : TResult, ICacheable<ByReference<T1, T2, T3, TResult>>
{ {
var key = new ByReference<T1, T2, T3, TResult>(first, second, third); var key = Create(first, second, third);
Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key); Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key);
} }
@@ -683,7 +718,7 @@ public record struct ByReference<T1, T2, T3, TResult>
public static void Update<VResult, V2>(T1 key1, T2 key2, T3 key3, V2 second) public static void Update<VResult, V2>(T1 key1, T2 key2, T3 key3, V2 second)
where VResult : TResult, ICacheable<ByReference<T1, T2, T3, TResult>, V2> where VResult : TResult, ICacheable<ByReference<T1, T2, T3, TResult>, V2>
{ {
var key = new ByReference<T1, T2, T3, TResult>(key1, key2, key3); var key = Create(key1, key2, key3);
Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key, second); Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key, second);
} }
@@ -691,7 +726,7 @@ public record struct ByReference<T1, T2, T3, TResult>
public static void Update<VResult, V2, V3>(T1 key1, T2 key2, T3 key3, V2 second, V3 third) public static void Update<VResult, V2, V3>(T1 key1, T2 key2, T3 key3, V2 second, V3 third)
where VResult : TResult, ICacheable<ByReference<T1, T2, T3, TResult>, V2, V3> where VResult : TResult, ICacheable<ByReference<T1, T2, T3, TResult>, V2, V3>
{ {
var key = new ByReference<T1, T2, T3, TResult>(key1, key2, key3); var key = Create(key1, key2, key3);
Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key, second, third); Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key, second, third);
} }
@@ -699,7 +734,7 @@ public record struct ByReference<T1, T2, T3, TResult>
public static unsafe ref VResult GetAndCheck<VResult>(T1 first, T2 second, T3 third) public static unsafe ref VResult GetAndCheck<VResult>(T1 first, T2 second, T3 third)
where VResult : TResult, ICacheable<ByReference<T1, T2, T3, TResult>> where VResult : TResult, ICacheable<ByReference<T1, T2, T3, TResult>>
{ {
var key = new ByReference<T1, T2, T3, TResult>(first, second, third); var key = Create(first, second, third);
ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)); ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key));
if (cache.Dirty) if (cache.Dirty)
cache.Update(ref key); cache.Update(ref key);
@@ -711,7 +746,7 @@ public record struct ByReference<T1, T2, T3, TResult>
public static unsafe ref VResult GetAndCheck<VResult, V2>(T1 key1, T2 key2, T3 key3, V2 second) public static unsafe ref VResult GetAndCheck<VResult, V2>(T1 key1, T2 key2, T3 key3, V2 second)
where VResult : TResult, ICacheable<ByReference<T1, T2, T3, TResult>, V2> where VResult : TResult, ICacheable<ByReference<T1, T2, T3, TResult>, V2>
{ {
var key = new ByReference<T1, T2, T3, TResult>(key1, key2, key3); var key = Create(key1, key2, key3);
ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)); ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key));
if (cache.Dirty) if (cache.Dirty)
cache.Update(ref key, second); cache.Update(ref key, second);
@@ -723,7 +758,7 @@ public record struct ByReference<T1, T2, T3, TResult>
public static unsafe ref VResult GetAndCheck<VResult, V2, V3>(T1 key1, T2 key2, T3 key3, V2 second, V3 third) public static unsafe ref VResult GetAndCheck<VResult, V2, V3>(T1 key1, T2 key2, T3 key3, V2 second, V3 third)
where VResult : TResult, ICacheable<ByReference<T1, T2, T3, TResult>, V2, V3> where VResult : TResult, ICacheable<ByReference<T1, T2, T3, TResult>, V2, V3>
{ {
var key = new ByReference<T1, T2, T3, TResult>(key1, key2, key3); var key = Create(key1, key2, key3);
ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)); ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key));
if (cache.Dirty) if (cache.Dirty)
cache.Update(ref key, second, third); cache.Update(ref key, second, third);
@@ -732,42 +767,33 @@ public record struct ByReference<T1, T2, T3, TResult>
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
// ReSharper disable once ConvertToPrimaryConstructor
public ByReference(T1 first, T2 second, T3 third) public ByReference(T1 first, T2 second, T3 third)
{ {
First = first; First = first;
Second = second; Second = second;
Third = third; Third = third;
_hashCode = HashCode.Combine(first, second, third); HashCode = FisheryLib.HashCode.Combine(first, second, third);
} }
public static unsafe void Initialize() [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ByReference<T1, T2, T3, TResult> Create(T1 first, T2 second, T3 third)
=> default(ByReference<T1, T2, T3, TResult>) with
{
First = first,
Second = second,
Third = third,
HashCode = FisheryLib.HashCode.Combine(first, second, third)
};
public static void Initialize()
{ {
try Utility.Internal.Initialize<T1>();
{ Utility.Internal.Initialize<T2>();
_ = FisheryLib.FunctionPointers.Equals<T1>.Default; Utility.Internal.Initialize<T3>();
_ = FisheryLib.FunctionPointers.Equals<T2>.Default; Utility.Internal.Initialize<ByReference<T1, T2, T3, TResult>>();
_ = FisheryLib.FunctionPointers.Equals<T3>.Default;
_ = FisheryLib.FunctionPointers.Equals<ByReference<T1, T2, T3, TResult>>.Default;
_ = FisheryLib.FunctionPointers.GetHashCode<T1>.Default;
_ = FisheryLib.FunctionPointers.GetHashCode<T2>.Default;
_ = FisheryLib.FunctionPointers.GetHashCode<T3>.Default;
_ = FisheryLib.FunctionPointers.GetHashCode<ByReference<T1, T2, T3, TResult>>.Default;
}
catch (Exception ex)
{
Log.Error($"Performance Fish encountered an exception while trying to initialize {
typeof(ByReference<T1, T2, T3, TResult>).FullName}\n{ex}");
}
} }
public T1 First;
public T2 Second;
public T3 Third;
private int _hashCode;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByReference<T1, T2, T3, TResult> other) public bool Equals(ByReference<T1, T2, T3, TResult> other)
=> First.Equals<T1>(other.First) => First.Equals<T1>(other.First)
@@ -775,7 +801,15 @@ public record struct ByReference<T1, T2, T3, TResult>
&& Third.Equals<T3>(other.Third); && Third.Equals<T3>(other.Third);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => _hashCode; public override int GetHashCode() => HashCode;
[MethodImpl(MethodImplOptions.NoInlining)]
private static FishTable<ByReference<T1, T2, T3, TResult>, TResult> InitializeNew()
=> Utility.AddNew(_allCaches, ValueInitializer);
public static void Remove(ByReference<T1, T2, T3, TResult> key) => Utility.Internal.RemoveIn(_allCaches, key);
public static void Clear() => Utility.Internal.ClearIn(_allCaches);
} }
[PublicAPI] [PublicAPI]
@@ -783,16 +817,45 @@ public record struct ByReference<T1, T2, T3, TResult>
public record struct ByReference<T1, T2, T3, T4, TResult> public record struct ByReference<T1, T2, T3, T4, TResult>
where T1 : notnull where T2 : notnull where T3 : notnull where T4 : notnull where TResult : new() where T1 : notnull where T2 : notnull where T3 : notnull where T4 : notnull where TResult : new()
{ {
private static FishTable<ByReference<T1, T2, T3, T4, TResult>, TResult> _get public T1 First;
= Utility.AddNew<ByReference<T1, T2, T3, T4, TResult>, TResult>(); public T2 Second;
public T3 Third;
public T4 Fourth;
public int HashCode;
private static readonly object _valueInitializerLock = new();
private static readonly List<IDictionary> _allCaches = [];
private static readonly FishTable<ByReference<T1, T2, T3, T4, TResult>, TResult> _get = InitializeNew();
[ThreadStatic] [ThreadStatic]
private static FishTable<ByReference<T1, T2, T3, T4, TResult>, TResult>? _getThreadStatic; private static FishTable<ByReference<T1, T2, T3, T4, TResult>, TResult>? _getThreadStatic;
private static Func<ByReference<T1, T2, T3, T4, TResult>, TResult>? _valueInitializer;
public static Func<ByReference<T1, T2, T3, T4, TResult>, TResult>? ValueInitializer
{
get
{
lock (_valueInitializerLock)
return _valueInitializer;
}
set
{
lock (_valueInitializerLock)
{
value ??= static _ => Reflection.New<TResult>();
_valueInitializer = value;
_get.ValueInitializer = value;
}
}
}
public static FishTable<ByReference<T1, T2, T3, T4, TResult>, TResult> Get public static FishTable<ByReference<T1, T2, T3, T4, TResult>, TResult> Get
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= Utility.AddNew<ByReference<T1, T2, T3, T4, TResult>, TResult>(); get => _getThreadStatic ??= InitializeNew();
} }
public static FishTable<ByReference<T1, T2, T3, T4, TResult>, TResult> GetDirectly public static FishTable<ByReference<T1, T2, T3, T4, TResult>, TResult> GetDirectly
@@ -804,7 +867,7 @@ public record struct ByReference<T1, T2, T3, T4, TResult>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref TResult GetOrAddReference(T1 first, T2 second, T3 third, T4 fourth) public static unsafe ref TResult GetOrAddReference(T1 first, T2 second, T3 third, T4 fourth)
{ {
var key = new ByReference<T1, T2, T3, T4, TResult> { First = first, Second = second, Third = third, Fourth = fourth}; var key = Create(first, second, third, fourth);
return ref Get.GetOrAddReference(ref key); return ref Get.GetOrAddReference(ref key);
} }
@@ -815,7 +878,7 @@ public record struct ByReference<T1, T2, T3, T4, TResult>
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static unsafe ref TResult GetExistingReference(T1 first, T2 second, T3 third, T4 fourth) public static unsafe ref TResult GetExistingReference(T1 first, T2 second, T3 third, T4 fourth)
{ {
var key = new ByReference<T1, T2, T3, T4, TResult>(first, second, third, fourth); var key = Create(first, second, third, fourth);
return ref Get.GetReference(ref key); return ref Get.GetReference(ref key);
} }
@@ -827,7 +890,7 @@ public record struct ByReference<T1, T2, T3, T4, TResult>
public static void Update<VResult>(T1 first, T2 second, T3 third, T4 fourth) public static void Update<VResult>(T1 first, T2 second, T3 third, T4 fourth)
where VResult : TResult, ICacheable<ByReference<T1, T2, T3, T4, TResult>> where VResult : TResult, ICacheable<ByReference<T1, T2, T3, T4, TResult>>
{ {
var key = new ByReference<T1, T2, T3, T4, TResult>(first, second, third, fourth); var key = Create(first, second, third, fourth);
Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key); Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key);
} }
@@ -835,7 +898,7 @@ public record struct ByReference<T1, T2, T3, T4, TResult>
public static void Update<VResult, V2>(T1 key1, T2 key2, T3 key3, T4 key4, V2 second) public static void Update<VResult, V2>(T1 key1, T2 key2, T3 key3, T4 key4, V2 second)
where VResult : TResult, ICacheable<ByReference<T1, T2, T3, T4, TResult>, V2> where VResult : TResult, ICacheable<ByReference<T1, T2, T3, T4, TResult>, V2>
{ {
var key = new ByReference<T1, T2, T3, T4, TResult>(key1, key2, key3, key4); var key = Create(key1, key2, key3, key4);
Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key, second); Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key, second);
} }
@@ -843,7 +906,7 @@ public record struct ByReference<T1, T2, T3, T4, TResult>
public static void Update<VResult, V2, V3>(T1 key1, T2 key2, T3 key3, T4 key4, V2 second, V3 third) public static void Update<VResult, V2, V3>(T1 key1, T2 key2, T3 key3, T4 key4, V2 second, V3 third)
where VResult : TResult, ICacheable<ByReference<T1, T2, T3, T4, TResult>, V2, V3> where VResult : TResult, ICacheable<ByReference<T1, T2, T3, T4, TResult>, V2, V3>
{ {
var key = new ByReference<T1, T2, T3, T4, TResult>(key1, key2, key3, key4); var key = Create(key1, key2, key3, key4);
Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key, second, third); Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key, second, third);
} }
@@ -851,7 +914,7 @@ public record struct ByReference<T1, T2, T3, T4, TResult>
public static unsafe ref VResult GetAndCheck<VResult>(T1 first, T2 second, T3 third, T4 fourth) public static unsafe ref VResult GetAndCheck<VResult>(T1 first, T2 second, T3 third, T4 fourth)
where VResult : TResult, ICacheable<ByReference<T1, T2, T3, T4, TResult>> where VResult : TResult, ICacheable<ByReference<T1, T2, T3, T4, TResult>>
{ {
var key = new ByReference<T1, T2, T3, T4, TResult>(first, second, third, fourth); var key = Create(first, second, third, fourth);
ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)); ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key));
if (cache.Dirty) if (cache.Dirty)
cache.Update(ref key); cache.Update(ref key);
@@ -863,7 +926,7 @@ public record struct ByReference<T1, T2, T3, T4, TResult>
public static unsafe ref VResult GetAndCheck<VResult, V2>(T1 key1, T2 key2, T3 key3, T4 key4, V2 second) public static unsafe ref VResult GetAndCheck<VResult, V2>(T1 key1, T2 key2, T3 key3, T4 key4, V2 second)
where VResult : TResult, ICacheable<ByReference<T1, T2, T3, T4, TResult>, V2> where VResult : TResult, ICacheable<ByReference<T1, T2, T3, T4, TResult>, V2>
{ {
var key = new ByReference<T1, T2, T3, T4, TResult>(key1, key2, key3, key4); var key = Create(key1, key2, key3, key4);
ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)); ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key));
if (cache.Dirty) if (cache.Dirty)
cache.Update(ref key, second); cache.Update(ref key, second);
@@ -876,7 +939,7 @@ public record struct ByReference<T1, T2, T3, T4, TResult>
V3 third) V3 third)
where VResult : TResult, ICacheable<ByReference<T1, T2, T3, T4, TResult>, V2, V3> where VResult : TResult, ICacheable<ByReference<T1, T2, T3, T4, TResult>, V2, V3>
{ {
var key = new ByReference<T1, T2, T3, T4, TResult>(key1, key2, key3, key4); var key = Create(key1, key2, key3, key4);
ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)); ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key));
if (cache.Dirty) if (cache.Dirty)
cache.Update(ref key, second, third); cache.Update(ref key, second, third);
@@ -885,48 +948,36 @@ public record struct ByReference<T1, T2, T3, T4, TResult>
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
// ReSharper disable once ConvertToPrimaryConstructor
public ByReference(T1 first, T2 second, T3 third, T4 fourth) public ByReference(T1 first, T2 second, T3 third, T4 fourth)
{ {
First = first; First = first;
Second = second; Second = second;
Third = third; Third = third;
Fourth = fourth; Fourth = fourth;
_hashCode = HashCode.Combine(first, second, third, fourth); HashCode = FisheryLib.HashCode.Combine(first, second, third, fourth);
} }
public static unsafe void Initialize() [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ByReference<T1, T2, T3, T4, TResult> Create(T1 first, T2 second, T3 third, T4 fourth)
=> default(ByReference<T1, T2, T3, T4, TResult>) with
{
First = first,
Second = second,
Third = third,
Fourth = fourth,
HashCode = FisheryLib.HashCode.Combine(first, second, third, fourth)
};
public static void Initialize()
{ {
try Utility.Internal.Initialize<T1>();
{ Utility.Internal.Initialize<T2>();
_ = FisheryLib.FunctionPointers.Equals<T1>.Default; Utility.Internal.Initialize<T3>();
_ = FisheryLib.FunctionPointers.Equals<T2>.Default; Utility.Internal.Initialize<T4>();
_ = FisheryLib.FunctionPointers.Equals<T3>.Default; Utility.Internal.Initialize<ByReference<T1, T2, T3, T4, TResult>>();
_ = FisheryLib.FunctionPointers.Equals<T4>.Default;
_ = FisheryLib.FunctionPointers.Equals<ByReference<T1, T2, T3, T4, TResult>>.Default;
_ = FisheryLib.FunctionPointers.GetHashCode<T1>.Default;
_ = FisheryLib.FunctionPointers.GetHashCode<T2>.Default;
_ = FisheryLib.FunctionPointers.GetHashCode<T3>.Default;
_ = FisheryLib.FunctionPointers.GetHashCode<T4>.Default;
_ = FisheryLib.FunctionPointers.GetHashCode<ByReference<T1, T2, T3, T4, TResult>>.Default;
}
catch (Exception ex)
{
Log.Error(
$"Performance Fish encountered an exception while trying to initialize {
typeof(ByReference<T1, T2, T3, T4, TResult>).FullName}\n{ex}");
}
} }
public T1 First;
public T2 Second;
public T3 Third;
public T4 Fourth;
private int _hashCode;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByReference<T1, T2, T3, T4, TResult> other) public bool Equals(ByReference<T1, T2, T3, T4, TResult> other)
=> First.Equals<T1>(other.First) => First.Equals<T1>(other.First)
@@ -935,6 +986,182 @@ public record struct ByReference<T1, T2, T3, T4, TResult>
&& Fourth.Equals<T4>(other.Fourth); && Fourth.Equals<T4>(other.Fourth);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => _hashCode; public override int GetHashCode() => HashCode;
[MethodImpl(MethodImplOptions.NoInlining)]
private static FishTable<ByReference<T1, T2, T3, T4, TResult>, TResult> InitializeNew()
=> Utility.AddNew(_allCaches, ValueInitializer);
public static void Remove(ByReference<T1, T2, T3, T4, TResult> key) => Utility.Internal.RemoveIn(_allCaches, key);
public static void Clear() => Utility.Internal.ClearIn(_allCaches);
}
[PublicAPI]
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public record struct ByReferenceClassic<T1, T2, T3, TResult>
where T1 : notnull where T2 : notnull where T3 : notnull where TResult : new()
{
public T1 First;
public T2 Second;
public T3 Third;
public int HashCode;
// TODO: Figure out why this behaves differently compared to FishTables on ReflectionCaching:CustomAttributeCache
private static readonly object _valueInitializerLock = new();
private static readonly List<IDictionary> _allCaches = [];
private static readonly Dictionary<ByReferenceClassic<T1, T2, T3, TResult>, TResult> _get = InitializeNew();
[ThreadStatic]
private static Dictionary<ByReferenceClassic<T1, T2, T3, TResult>, TResult>? _getThreadStatic;
public static Dictionary<ByReferenceClassic<T1, T2, T3, TResult>, TResult> Get
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _getThreadStatic ??= InitializeNew();
}
public static Dictionary<ByReferenceClassic<T1, T2, T3, TResult>, TResult> GetDirectly
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _get;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref TResult GetOrAddReference(T1 first, T2 second, T3 third)
{
var key = Create(first, second, third);
return ref Get.GetOrAddReference(ref key);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TResult GetOrAddReference(in ByReferenceClassic<T1, T2, T3, TResult> key)
=> ref Get.GetOrAddReference(ref Unsafe.AsRef(in key));
[MethodImpl(MethodImplOptions.NoInlining)]
public static unsafe ref TResult GetExistingReference(T1 first, T2 second, T3 third)
{
var key = Create(first, second, third);
return ref Get.GetReference(ref key);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static ref TResult GetExistingReference(in ByReferenceClassic<T1, T2, T3, TResult> key)
=> ref Get.GetReference(ref Unsafe.AsRef(in key));
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Update<VResult>(T1 first, T2 second, T3 third)
where VResult : TResult, ICacheable<ByReferenceClassic<T1, T2, T3, TResult>>
{
var key = Create(first, second, third);
Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Update<VResult, V2>(T1 key1, T2 key2, T3 key3, V2 second)
where VResult : TResult, ICacheable<ByReferenceClassic<T1, T2, T3, TResult>, V2>
{
var key = Create(key1, key2, key3);
Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key, second);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Update<VResult, V2, V3>(T1 key1, T2 key2, T3 key3, V2 second, V3 third)
where VResult : TResult, ICacheable<ByReferenceClassic<T1, T2, T3, TResult>, V2, V3>
{
var key = Create(key1, key2, key3);
Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key)).Update(ref key, second, third);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref VResult GetAndCheck<VResult>(T1 first, T2 second, T3 third)
where VResult : TResult, ICacheable<ByReferenceClassic<T1, T2, T3, TResult>>
{
var key = Create(first, second, third);
ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key));
if (cache.Dirty)
cache.Update(ref key);
return ref cache!;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref VResult GetAndCheck<VResult, V2>(T1 key1, T2 key2, T3 key3, V2 second)
where VResult : TResult, ICacheable<ByReferenceClassic<T1, T2, T3, TResult>, V2>
{
var key = Create(key1, key2, key3);
ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key));
if (cache.Dirty)
cache.Update(ref key, second);
return ref cache!;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe ref VResult GetAndCheck<VResult, V2, V3>(T1 key1, T2 key2, T3 key3, V2 second, V3 third)
where VResult : TResult, ICacheable<ByReferenceClassic<T1, T2, T3, TResult>, V2, V3>
{
var key = Create(key1, key2, key3);
ref var cache = ref Unsafe.As<TResult, VResult>(ref Get.GetOrAddReference(ref key));
if (cache.Dirty)
cache.Update(ref key, second, third);
return ref cache!;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
// ReSharper disable once ConvertToPrimaryConstructor
public ByReferenceClassic(T1 first, T2 second, T3 third)
{
First = first;
Second = second;
Third = third;
HashCode = FisheryLib.HashCode.Combine(first, second, third);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ByReferenceClassic<T1, T2, T3, TResult> Create(T1 first, T2 second, T3 third)
=> default(ByReferenceClassic<T1, T2, T3, TResult>) with
{
First = first,
Second = second,
Third = third,
HashCode = FisheryLib.HashCode.Combine(first, second, third)
};
public static void Initialize()
{
Utility.Internal.Initialize<T1>();
Utility.Internal.Initialize<T2>();
Utility.Internal.Initialize<T3>();
Utility.Internal.Initialize<ByReferenceClassic<T1, T2, T3, TResult>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByReferenceClassic<T1, T2, T3, TResult> other)
=> First.Equals<T1>(other.First)
&& Second.Equals<T2>(other.Second)
&& Third.Equals<T3>(other.Third);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode;
[MethodImpl(MethodImplOptions.NoInlining)]
private static Dictionary<ByReferenceClassic<T1, T2, T3, TResult>, TResult> InitializeNew()
{
var newCache = Utility.AddNew<Dictionary<ByReferenceClassic<T1, T2, T3, TResult>, TResult>>();
lock (_allCaches)
_allCaches.Add(newCache);
return newCache;
}
public static void Remove(ByReferenceClassic<T1, T2, T3, TResult> key) => Utility.Internal.RemoveIn(_allCaches, key);
public static void Clear() => Utility.Internal.ClearIn(_allCaches);
} }
#pragma warning restore CS9091 #pragma warning restore CS9091
+78
View File
@@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Reflection.Emit; using System.Reflection.Emit;
using FisheryLib.FunctionPointers;
using JetBrains.Annotations; using JetBrains.Annotations;
using PerformanceFish.Events; using PerformanceFish.Events;
@@ -218,6 +219,20 @@ public static class Utility
return newCollection; return newCollection;
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
public static FishTable<TKey, TValue> AddNew<TKey, TValue>(List<IDictionary> cacheList,
Func<TKey, TValue>? valueInitializer = null,
Action<KeyValuePair<TKey, TValue>>? onEntryAdded = null,
Action<KeyValuePair<TKey, TValue>>? onEntryRemoved = null)
{
var newCache = AddNew(valueInitializer, onEntryAdded, onEntryRemoved);
lock (cacheList)
cacheList.Add(newCache);
return newCache;
}
private sealed record OnDestroyActionData<TKey, TValue>(TKey Key, FishTable<TKey, TValue> Collection) private sealed record OnDestroyActionData<TKey, TValue>(TKey Key, FishTable<TKey, TValue> Collection)
{ {
public Action<Thing>? Action; public Action<Thing>? Action;
@@ -276,6 +291,69 @@ public static class Utility
All.Add(newCollection); // require different constructors. All.Add(newCollection); // require different constructors.
return newCollection; // Reflection.New<T> can't use arguments return newCollection; // Reflection.New<T> can't use arguments
} // for resolving method calls (yet) } // for resolving method calls (yet)
public static class Internal
{
public static void RemoveIn<T>(List<IDictionary> caches, T key)
{
lock (caches)
{
for (var i = caches.Count; i-- > 0;)
caches[i].Remove(key!);
}
}
public static void ClearIn(List<IDictionary> caches)
{
lock (caches)
{
for (var i = caches.Count; i-- > 0;)
caches[i].Clear();
}
}
public static unsafe void Initialize<T>()
{
try
{
_ = Equals<T>.Default;
_ = EqualsByRef<T>.Default;
_ = GetHashCode<T>.Default;
_ = GetHashCodeByRef<T>.Default;
_ = FisheryLib.Collections.Internal.KeyUtility<T>.Default;
}
catch (Exception ex)
{
string name;
try
{
name = typeof(T).FullDescription();
}
catch
{
try
{
name = typeof(T).FullName ?? typeof(T).Name;
}
catch
{
try
{
name = typeof(T).Name;
}
catch (Exception inner)
{
name = inner.ToString();
}
}
}
Log.Error($"Performance Fish encountered an exception while trying to initialize '{
name}'\n==================================================\n{ex}");
}
}
}
} }
public delegate int IndexGetter<in T>(T item); public delegate int IndexGetter<in T>(T item);
+9 -3
View File
@@ -8,14 +8,20 @@ namespace PerformanceFish;
public record struct CellIndex(int Value) public record struct CellIndex(int Value)
{ {
public int Value = Value; public int Value = Value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CellIndex(IntVec3 cell, Map map) : this(cell, map.cellIndices.mapSizeX) {} public CellIndex(IntVec3 cell, Map map) : this(cell, map.cellIndices.mapSizeX) {}
[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.cellIndices.mapSizeX);
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.ThingsListAtFast(Value); public List<Thing> GetThingList(Map map) => map.thingGrid.thingGrid[Value];
public bool IsFogged(Map map) => map.fogGrid.fogGrid[Value];
public float GetSnowDepth(Map map) => map.snowGrid.depthGrid[Value];
} }
+112
View File
@@ -0,0 +1,112 @@
// Copyright (c) 2023 bradson
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
#if !V1_4
using LudeonTK;
#endif
using Mono.Cecil;
using Mono.Cecil.Cil;
using PerformanceFish.Prepatching;
namespace PerformanceFish;
public sealed class DebugActionFixes : ClassWithFishPrepatches
{
public sealed class InitActionsPatch : FishPrepatch
{
// useless without the other, as they initialize simultaneously
public override List<Type> LinkedPatches { get; } = [typeof(InitOutputsPatch)];
public override string? Description { get; }
= "Adds exception handling to the debug actions menu, allowing it to still generate and open when having "
+ "one mod throw errors. Also logs where that error gets thrown";
public override MethodBase TargetMethodBase { get; } = AccessTools.DeclaredMethod(typeof(DebugTabMenu_Actions),
nameof(DebugTabMenu_Actions.InitActions));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
public static DebugActionNode ReplacementBody(DebugTabMenu_Actions instance, DebugActionNode absRoot)
{
var root = instance.myRoot = new("Actions");
absRoot.AddChild(root);
root.AddChild(instance.moreActionsNode = new("Show more actions")
{
category = "More debug actions",
displayPriority = -999999
});
foreach (var type in GenTypes.AllTypes)
{
try
{
var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var methodInfo in methods)
{
if (methodInfo.TryGetAttribute<DebugActionAttribute>(out var customAttribute))
instance.GenerateCacheForMethod(methodInfo, customAttribute);
if (!methodInfo.TryGetAttribute<DebugActionYielderAttribute>(out _))
continue;
foreach (var item in (IEnumerable<DebugActionNode>)methodInfo.Invoke(null, null))
root.AddChild(item);
}
}
catch (Exception ex)
{
Log.Error($"Exception caught while trying to initialize debug actions for type '{
type.FullDescription()}': {ex}");
}
}
root.TrySort();
return root;
}
}
public sealed class InitOutputsPatch : FishPrepatch
{
public override List<Type> LinkedPatches { get; } = [typeof(InitActionsPatch)];
public override string? Description { get; }
= "Adds exception handling to the debug outputs menu, allowing it to still generate and open when having "
+ "one mod throw errors. Also logs where that error gets thrown";
public override MethodBase TargetMethodBase { get; } = AccessTools.DeclaredMethod(typeof(DebugTabMenu_Output),
nameof(DebugTabMenu_Output.InitActions));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
public static DebugActionNode ReplacementBody(DebugTabMenu_Output instance, DebugActionNode absRoot)
{
var root = instance.myRoot = new("Outputs");
absRoot.AddChild(root);
foreach (var type in GenTypes.AllTypes)
{
try
{
var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var methodInfo in methods)
{
if (methodInfo.TryGetAttribute<DebugOutputAttribute>(out var customAttribute))
instance.GenerateCacheForMethod(methodInfo, customAttribute);
}
}
catch (Exception ex)
{
Log.Error($"Exception caught while trying to initialize debug outputs for type '{
type.FullDescription()}': {ex}");
}
}
root.TrySort();
return root;
}
}
}
@@ -5,7 +5,7 @@
using JetBrains.Annotations; using JetBrains.Annotations;
namespace PerformanceFish; namespace PerformanceFish.Defs;
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public record struct DefPair public record struct DefPair
@@ -0,0 +1,66 @@
// Copyright (c) 2023 bradson
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
using Mono.Cecil;
using Mono.Cecil.Cil;
using PerformanceFish.Prepatching;
namespace PerformanceFish.Defs;
public sealed class IncidentDefFixes : ClassWithFishPrepatches
{
public sealed class WorkerPatch : FishPrepatch
{
public override string? Description { get; }
= "RimWorld has no checks to verify that IncidentDefs include valid classes with their actual "
+ "implementation. This means whenever mods either don't provide one or it simply fails to load for any "
+ "reason, entirely uninformative errors get spammed at the point the storyteller tries starting an "
+ "incident at. This patch here adds a check, logging the erroneous def with mod source when possible and "
+ "disabling the IncidentWorker from running to prevent the error spam.";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredPropertyGetter(typeof(IncidentDef), nameof(IncidentDef.Worker));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(Replacement);
public static IncidentWorker Replacement(IncidentDef instance)
{
if (instance.workerInt == null)
{
if (instance.workerClass is null)
TryFixAndLogError(instance);
instance.workerInt = (IncidentWorker)Activator.CreateInstance(instance.workerClass!);
instance.workerInt.def = instance;
}
return instance.workerInt;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void TryFixAndLogError(IncidentDef instance)
{
instance.workerClass = typeof(IncidentWorker_AlwaysSkip);
Log.Error($"Detected missing workerClass in IncidentDef '{
instance.defName ?? "null"}' from mod '{
instance.modContentPack?.Name ?? "null"}' for IncidentCategoryDef '{
instance.category?.defName ?? "null"}'. Disabling now to avoid further issues.");
}
}
public sealed class IncidentWorker_AlwaysSkip : IncidentWorker
{
#if !V1_4
public override float ChanceFactorNow(IIncidentTarget target) => 0f;
#endif
public override float BaseChanceThisGame => 0f;
protected override bool CanFireNowSub(IncidentParms parms) => true;
protected override bool TryExecuteWorker(IncidentParms parms) => true;
}
}
@@ -7,17 +7,17 @@ using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using PerformanceFish.Prepatching; using PerformanceFish.Prepatching;
namespace PerformanceFish; namespace PerformanceFish.Defs;
public sealed class WorkGiverDefFixes : ClassWithFishPrepatches public sealed class WorkGiverDefFixes : ClassWithFishPrepatches
{ {
public sealed class WorkerPatch : FishPrepatch public sealed class WorkerPatch : FishPrepatch
{ {
public override string? Description { get; } public override string? Description { get; }
= "RimWorld has no checks to verify that workGiverDefs include valid classes with their actual " = "RimWorld has no checks to verify that WorkGiverDefs include valid classes with their actual "
+ "implementation. This means whenever mods either don't provide one or it simply fails to load for any " + "implementation. This means whenever mods either don't provide one or it simply fails to load for any "
+ "reason, entirely uninformative errors get spammed at the point pawns try starting a job at. This patch " + "reason, entirely uninformative errors get spammed at the point pawns try starting a job at. This patch "
+ "here adds a check, logging the erroneous def with mod source when possible and disabling the workGiver " + "here adds a check, logging the erroneous def with mod source when possible and disabling the WorkGiver "
+ "from running to prevent the error spam."; + "from running to prevent the error spam.";
public override MethodBase TargetMethodBase { get; } public override MethodBase TargetMethodBase { get; }
@@ -40,6 +40,7 @@ public sealed class WorkGiverDefFixes : ClassWithFishPrepatches
return instance.workerInt; return instance.workerInt;
} }
[MethodImpl(MethodImplOptions.NoInlining)]
public static void TryFixAndLogError(WorkGiverDef instance) public static void TryFixAndLogError(WorkGiverDef instance)
{ {
instance.giverClass = typeof(WorkGiver_AlwaysSkip); instance.giverClass = typeof(WorkGiver_AlwaysSkip);
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<PropertyGroup>
<RootNamespace>PerformanceFish</RootNamespace>
<AssemblyName>PerformanceFish</AssemblyName>
<Authors>bradson</Authors>
<Version>0.6.0.0</Version>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<TargetFramework>net48</TargetFramework>
<LangVersion>preview</LangVersion>
<PlatformTarget>AnyCPU</PlatformTarget>
<Optimize>true</Optimize>
<DebugSymbols>true</DebugSymbols>
<Nullable>enable</Nullable>
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<AnalysisLevel>preview-recommended</AnalysisLevel>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>5</WarningLevel>
<DebugType>portable</DebugType>
<NoWarn>
CA1000;CA1010;CA1051;CA1304;CA1305;CA1310;CA1311;CA1707;CA1708;CA1710;CA1711;CA1715;CA1716;CA1720;CA1725;
CA1852;CA2201;CS8500;CS9082
</NoWarn>
<BaseIntermediateOutputPath>obj\$(MSBuildProjectName)\$(Configuration)\</BaseIntermediateOutputPath>
<MSBuildProjectExtensionsPath>obj\$(MSBuildProjectName)\$(Configuration)\</MSBuildProjectExtensionsPath>
<DefaultItemExcludes>$(DefaultItemExcludes);obj/**/*.*</DefaultItemExcludes>
<PublicizerClearCacheOnClean>true</PublicizerClearCacheOnClean>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Zetrith.Prepatcher" Version="1.*" ExcludeAssets="runtime" />
<PackageReference Include="PolySharp" Version="1.*" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.*" ExcludeAssets="runtime" />
<PackageReference Include="JetBrains.Annotations" Version="2023.*" Aliases="nuget" ExcludeAssets="runtime" />
<PackageReference Include="Krafs.Publicizer" Version="2.*" />
<Publicize Include="Assembly-CSharp;0Harmony" IncludeVirtualMembers="false" />
<Publicize Include="Assembly-CSharp:RimWorld.JobGiver_Haul.TryGiveJob" />
<!-- DoNotPublicize Include="0Harmony:Mono.Collections.Generic.Collection`1" / -->
<DoNotPublicize Include="0Harmony:System.Memory`1" />
<DoNotPublicize Include="0Harmony:System.ReadOnlyMemory`1" />
<DoNotPublicize Include="0Harmony:System.Span`1" />
<DoNotPublicize Include="0Harmony:System.ReadOnlySpan`1" />
<DoNotPublicize Include="0Harmony:System.ThrowHelper" />
<DoNotPublicize Include="0Harmony:System.HashCode" />
<DoNotPublicize Include="0Harmony:System.StringExtensions" />
<DoNotPublicize Include="0Harmony:System.MemoryExtensions" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.CallerArgumentExpressionAttribute" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.DefaultInterpolatedStringHandler" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.IsByRefLikeAttribute" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.IsExternalInit" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.IsReadOnlyAttribute" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.NativeIntegerAttribute" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.NullableAttribute" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.NullableContextAttribute" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.NullablePublicOnlyAttribute" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.RefSafetyRulesAttribute" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.ModuleInitializerAttribute" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.ITuple" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.Unsafe" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.AllowNullAttribute" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.DisallowNullAttribute" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.ExtraDynamicallyAccessedMemberTypes" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.MaybeNullAttribute" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.MemberNotNullAttribute" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.MemberNotNullWhenAttribute" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.NotNullAttribute" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute" />
<DoNotPublicize Include="0Harmony:System.Diagnostics.CodeAnalysis.UnscopedRefAttribute" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.buckets" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.entries" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.version" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2+Entry" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1._items" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1._version" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1._size" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1.EnsureCapacity" />
<Publicize Include="System.Core:System.Collections.Generic.HashSet`1._version" />
<Publicize Include="mscorlib:System.MonoCustomAttrs.GetCustomAttributes" />
<Publicize Include="mscorlib:System.RuntimeType" />
<Publicize Include="mscorlib:System.Reflection.MonoField" />
<Publicize Include="mscorlib:System.String.m_firstChar" />
<None Include="..\..\LICENSE">
<Pack>True</Pack>
<PackagePath />
</None>
</ItemGroup>
</Project>
@@ -1,31 +0,0 @@
// Copyright (c) 2023 bradson
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
#if false
using System.Runtime.InteropServices;
namespace PerformanceFish.Experimental;
#pragma warning disable CS8500
/// <summary>
/// References within fixed size buffers don't get tracked by the GC. This leads to memory corruption. Don't use
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public unsafe struct InlineArray256<T> where T : class
{
[FieldOffset(0)]
private T _firstEntry;
[FieldOffset(0)]
private fixed ulong _entries[256];
public T this[int index]
{
get => (&_firstEntry)[index];
set => (&_firstEntry)[index] = value;
}
}
#pragma warning restore CS8500
#endif
@@ -5,54 +5,254 @@
#if false #if false
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
// ReSharper disable PrivateFieldCanBeConvertedToLocalVariable
namespace PerformanceFish.Experimental; namespace PerformanceFish.Experimental;
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct UnalignedPointer public unsafe struct UnalignedPointer
{ {
private fixed byte _data[6]; // 48bit // 48 bit
private ushort
// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] // 48bit _top,
// private byte[] _data; _mid,
_bot;
public void* Value public void* Value
{ {
get get
{ {
fixed (void* value = _data) var result = (void*)(Unsafe.As<ushort, ulong>(ref _top) & 0x0000_FFFF_FFFF_FFFF);
{ return result;
return value;
}
} }
} }
public UnalignedPointer(void* pointer) public UnalignedPointer(void* pointer)
{ {
fixed (UnalignedPointer* instance = &this) Unsafe.As<ushort, uint>(ref _top) = (uint)(ulong)pointer;
{ _bot = (ushort)((ulong)pointer >> 32);
instance->_data = (byte*)pointer;
}
} }
} }
public unsafe struct TaggedPointer public unsafe struct TaggedPointer
{ {
private void* _data; private const ulong
LowerBitMask = ~(ulong.MaxValue >> 3),
UpperBitMask = 0x0000_0000_0000_FFFF,
ValueBitMask = ~LowerBitMask & ~UpperBitMask;
private ulong _data;
public void* Value public void* Value
{ {
get get
{ {
var value = _data; var value = (void*)(_data & ValueBitMask);
return value; return value;
} }
set => _data = (_data & ~ValueBitMask) | ((ulong)value & ValueBitMask);
} }
public ushort UpperBits => (ushort)_data; public ushort UpperBits
{
get => (ushort)(_data & UpperBitMask);
set => _data = (_data & ~UpperBitMask) | (value & UpperBitMask);
}
public byte LowerBits => (byte)_data; public byte LowerBits
{
get => (byte)((_data & LowerBitMask) >> 61);
set => _data = (_data & ~LowerBitMask) | (((ulong)value << 61) & LowerBitMask);
}
public TaggedPointer(void* pointer) => _data = pointer; public TaggedPointer(void* pointer) => Value = pointer;
public TaggedPointer(void* pointer, ushort upperBits, byte lowerBits)
=> _data = ((ulong)pointer & ValueBitMask)
| (upperBits & UpperBitMask)
| (((ulong)lowerBits << 61) & LowerBitMask);
}
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct UInt48 : IComparable<UInt48>, IEquatable<UInt48>
{
public const ulong MaxValue = ulong.MaxValue >> 16;
private ushort
_top,
_mid,
_bot;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public UInt48(ulong value)
{
Unsafe.As<ushort, uint>(ref _top) = (uint)value;
_bot = (ushort)(value >> 32);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(UInt48 other) => this == other;
public override bool Equals(object? obj) => obj is UInt48 value && this == value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int CompareTo(UInt48 other) => ((ulong)this).CompareTo(other);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => Unsafe.As<ushort, int>(ref _top) ^ _bot;
public override string ToString() => ((ulong)this).ToString();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(UInt48 left, UInt48 right)
{
var x = left;
var y = right;
return (Unsafe.As<ushort, uint>(ref x._top) == Unsafe.As<ushort, uint>(ref y._top)) & (x._bot == y._bot);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(UInt48 left, UInt48 right) => !(left == right);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator <(UInt48 left, UInt48 right) => (ulong)left < right;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator <=(UInt48 left, UInt48 right) => (ulong)left <= right;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator >(UInt48 left, UInt48 right) => (ulong)left > right;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator >=(UInt48 left, UInt48 right) => (ulong)left >= right;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ulong(UInt48 value)
{
var temp = value;
return Unsafe.As<ushort, ulong>(ref temp._top) & MaxValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator long(UInt48 value)
{
var temp = value;
return Unsafe.As<ushort, long>(ref temp._top) & (long)MaxValue;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator UInt48(ulong value)
{
Unsafe.SkipInit(out UInt48 result);
Unsafe.As<ushort, uint>(ref result._top) = (uint)value;
result._bot = (ushort)(value >> 32);
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator UInt48(long value) => (UInt48)(ulong)value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator UInt48(uint value)
{
Unsafe.SkipInit(out UInt48 result);
Unsafe.As<ushort, uint>(ref result._top) = value;
result._bot = 0;
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator UInt48(int value) => (UInt48)(uint)value;
}
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct Int48 : IComparable<Int48>, IEquatable<Int48>
{
public const long MaxValue = long.MaxValue >> 16;
private ushort
_top,
_mid,
_bot;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Int48(long value)
{
Unsafe.As<ushort, uint>(ref _top) = (uint)value;
_bot = (ushort)((((ulong)value >> 32) & (ulong)short.MaxValue)
| ((ulong)(value & long.MinValue) >> 48));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(Int48 other) => this == other;
public override bool Equals(object? obj) => obj is Int48 value && this == value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int CompareTo(Int48 other) => ((long)this).CompareTo(other);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => Unsafe.As<ushort, int>(ref _top) ^ _bot;
public override string ToString() => ((long)this).ToString();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(Int48 left, Int48 right)
{
var x = left;
var y = right;
return (Unsafe.As<ushort, uint>(ref x._top) == Unsafe.As<ushort, uint>(ref y._top)) & (x._bot == y._bot);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(Int48 left, Int48 right) => !(left == right);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator <(Int48 left, Int48 right) => (long)left < right;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator <=(Int48 left, Int48 right) => (long)left <= right;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator >(Int48 left, Int48 right) => (long)left > right;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator >=(Int48 left, Int48 right) => (long)left >= right;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator ulong(Int48 value) => (ulong)(long)value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator long(Int48 value)
{
var temp = value;
return (Unsafe.As<ushort, long>(ref temp._top) & MaxValue)
| (long)(((ulong)temp._bot << 48) & unchecked((ulong)long.MinValue));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Int48(ulong value) => (Int48)(long)value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Int48(long value)
{
Unsafe.SkipInit(out Int48 result);
Unsafe.As<ushort, uint>(ref result._top) = (uint)value;
result._bot = (ushort)((((ulong)value >> 32) & (ulong)short.MaxValue)
| ((ulong)(value & long.MinValue) >> 48));
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Int48(uint value) => (Int48)(int)value;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Int48(int value)
{
Unsafe.SkipInit(out Int48 result);
Unsafe.As<ushort, uint>(ref result._top) = (uint)(value & int.MaxValue);
result._bot = (ushort)((uint)(value & int.MinValue) >> 16);
return result;
}
} }
#endif #endif
+3
View File
@@ -93,6 +93,9 @@ public sealed class FishSettings : ModSettings
ToggleAllPatches(checkModEnabled); ToggleAllPatches(checkModEnabled);
} }
ls.Gap();
ls.Label("Changes to prepatches require a restart. Harmony patches update immediately when toggled.");
ls.Gap(); ls.Gap();
var curFontStyle = Text.CurFontStyle; var curFontStyle = Text.CurFontStyle;
var curFontSize = curFontStyle.fontSize; var curFontSize = curFontStyle.fontSize;
@@ -19,21 +19,31 @@ public static class IndexGetter
[UsedImplicitly(ImplicitUseTargetFlags.Members)] [UsedImplicitly(ImplicitUseTargetFlags.Members)]
public static class Methods public static class Methods
{ {
public static int HediffSet(HediffSet set) => set.pawn.thingIDNumber; public static int Ability(Ability ability) => ability.Id;
public static int HediffDef(HediffDef def) => def.shortHash;
public static int Bill(Bill bill) => bill.loadID; public static int Bill(Bill bill) => bill.loadID;
public static int Pawn(Pawn pawn) => pawn.thingIDNumber; public static int Def(Def def) => def.shortHash;
public static int Thing(Thing thing) => thing.thingIDNumber;
public static int CompAssignableToPawn_Bed(CompAssignableToPawn_Bed comp) => comp.parent.thingIDNumber;
public static int Map(Map map) => map.uniqueID;
public static int DesignationManager(DesignationManager manager) => manager.map.uniqueID; public static int DesignationManager(DesignationManager manager) => manager.map.uniqueID;
public static int DesignationDef(DesignationDef def) => def.shortHash;
public static int Room(Room room) => room.ID;
public static int Ideo(Ideo ideo) => ideo.id;
public static int RecipeDef(RecipeDef def) => def.shortHash;
public static int RuntimeTypeHandle(RuntimeTypeHandle handle) => (int)handle.Value;
public static int WorldObject(WorldObject worldObject) => worldObject.ID;
public static int Hediff(Hediff hediff) => hediff.loadID; public static int Hediff(Hediff hediff) => hediff.loadID;
public static int HediffComp(HediffComp comp) => comp.parent.loadID;
public static int HediffSet(HediffSet set) => set.pawn.thingIDNumber;
public static int Ideo(Ideo ideo) => ideo.id;
public static int Map(Map map) => map.uniqueID;
public static int Room(Room room) => room.ID;
public static int RuntimeTypeHandle(RuntimeTypeHandle handle) => (int)handle.Value;
public static int Thing(Thing thing) => thing.thingIDNumber;
public static int ThingComp(ThingComp comp) => comp.parent.thingIDNumber;
public static int ThingRequestGroup(ThingRequestGroup group) => (byte)group;
public static int WorldObject(WorldObject worldObject) => worldObject.ID;
public static int WorldObjectComp(WorldObjectComp comp) => comp.parent.ID;
public static int CompAssignableToPawn_Bed(CompAssignableToPawn_Bed comp) => comp.parent.thingIDNumber;
public static int DesignationDef(DesignationDef def) => def.shortHash;
public static int HediffDef(HediffDef def) => def.shortHash;
public static int HediffWithComps(HediffWithComps hediff) => hediff.loadID;
public static int Pawn(Pawn pawn) => pawn.thingIDNumber;
public static int RecipeDef(RecipeDef def) => def.shortHash;
public static int ThingDef(ThingDef def) => def.shortHash;
public static int ThingWithComps(ThingWithComps thing) => thing.thingIDNumber;
} }
internal static Cache.IndexGetter<T> GetDefaultIndexGetter<T>() where T : notnull internal static Cache.IndexGetter<T> GetDefaultIndexGetter<T>() where T : notnull
+6 -1
View File
@@ -15,7 +15,12 @@ public static class GasDefOf
public static GasDef public static GasDef
BlindSmoke, BlindSmoke,
ToxGas, ToxGas,
RotStink; RotStink
#if !V1_4
,
DeadLifeDust
#endif
;
static GasDefOf() => DefOfHelper.EnsureInitializedInCtor(typeof(GasDefOf)); static GasDefOf() => DefOfHelper.EnsureInitializedInCtor(typeof(GasDefOf));
} }
+194 -17
View File
@@ -8,8 +8,15 @@
// #define GAS_DEBUG_L3 // individual cell changes // #define GAS_DEBUG_L3 // individual cell changes
using System.Diagnostics; using System.Diagnostics;
#if V1_4
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
#endif
using System.Linq; using System.Linq;
#if !V1_4
using Gilzoide.ManagedJobs;
using LudeonTK;
using Unity.Jobs;
#endif
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using MonoMod.Utils; using MonoMod.Utils;
@@ -32,16 +39,28 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody); => ilProcessor.ReplaceBodyWith(ReplacementBody);
public static void ReplacementBody(GasGrid __instance, int index, byte smoke, byte toxic, byte rotStink) public static void ReplacementBody(GasGrid __instance, int index, byte smoke, byte toxic, byte rotStink
#if !V1_4
, byte deadlife
#endif
)
{ {
if (!ModsConfig.BiotechActive) if (!ModsConfig.BiotechActive)
toxic = 0; toxic = 0;
#if !V1_4
if (!ModsConfig.AnomalyActive)
deadlife = 0;
#endif
var gasGrids = __instance.ParallelGasGrids(); var gasGrids = __instance.ParallelGasGrids();
gasGrids[0].SetDirect(index, smoke); gasGrids[0].SetDirect(index, smoke);
gasGrids[1].SetDirect(index, toxic); gasGrids[1].SetDirect(index, toxic);
gasGrids[2].SetDirect(index, rotStink); gasGrids[2].SetDirect(index, rotStink);
#if !V1_4
gasGrids[3].SetDirect(index, deadlife);
#endif
} }
} }
@@ -49,7 +68,11 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
{ {
public override List<Type> LinkedPatches { get; } = public override List<Type> LinkedPatches { get; } =
[ [
typeof(SetDirectPatch), typeof(ColorAtPatch), typeof(AddGasPatch), typeof(AnyGasAtPatch), typeof(SetDirectPatch),
#if V1_4
typeof(ColorAtPatch),
#endif
typeof(AddGasPatch), typeof(AnyGasAtPatch),
typeof(DensityAtPatch), typeof(Debug_ClearAllPatch), typeof(Debug_FillAllPatch), typeof(DensityAtPatch), typeof(Debug_ClearAllPatch), typeof(Debug_FillAllPatch),
typeof(Notify_ThingSpawnedPatch), typeof(ExposeDataPatch), typeof(MouseOverReadOutOnGUI), typeof(Notify_ThingSpawnedPatch), typeof(ExposeDataPatch), typeof(MouseOverReadOutOnGUI),
typeof(DebugToolsGeneralPushGas) typeof(DebugToolsGeneralPushGas)
@@ -76,31 +99,47 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
if (gasGrids.Length < 1) if (gasGrids.Length < 1)
return; return;
#if !V1_4
JobHandle handle;
#endif
if (FishSettings.ThreadingEnabled if (FishSettings.ThreadingEnabled
#if V1_4
&& TryGetRegisteredGasGridWorkers(__instance, out var monitorObject)) && TryGetRegisteredGasGridWorkers(__instance, out var monitorObject))
{ {
TryPulseRegisteredGasGridWorkers(gasGrids, monitorObject); TryPulseRegisteredGasGridWorkers(gasGrids, monitorObject);
#else
)
{
handle = new ManagedJobParallelFor(__instance.ParallelJob()).Schedule(gasGrids.Length - 1, 1);
#endif
} }
else else
{ {
#if !V1_4
Unsafe.SkipInit(out handle);
#endif
for (var i = 1; i < gasGrids.Length; i++) for (var i = 1; i < gasGrids.Length; i++)
gasGrids[i].Tick(); gasGrids[i].Tick();
} }
var firstGasGrid = gasGrids[0]; var firstGasGrid = gasGrids[0];
firstGasGrid.Tick(); firstGasGrid.Tick();
__instance.cycleIndexDiffusion = firstGasGrid.CycleIndexDiffusion;
__instance.cycleIndexDissipation = firstGasGrid.CycleIndexDissipation;
if (FishSettings.ThreadingEnabled) if (FishSettings.ThreadingEnabled)
{
#if V1_4
WaitForGasGridWorkers(gasGrids); WaitForGasGridWorkers(gasGrids);
#else
handle.Complete();
#endif
}
} }
#if V1_4
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static bool TryGetRegisteredGasGridWorkers(GasGrid __instance, public static bool TryGetRegisteredGasGridWorkers(GasGrid __instance,
[NotNullWhen(true)] out object? monitorObject) [NotNullWhen(true)] out object? monitorObject)
=> (monitorObject = __instance.map.GasGridMonitorObject()) != null => (monitorObject = __instance.MonitorObject()) != null
&& ParallelNoAlloc.HasAnyRegisteredWorkers(monitorObject); && ParallelNoAlloc.HasAnyRegisteredWorkers(monitorObject);
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
@@ -124,18 +163,33 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
i++; i++;
} }
} }
#endif
} }
#if !V1_4
public class ParallelJob : IJobParallelFor
{
public ParallelGasGrid[] GasGrids = null!;
public void Execute(int index) => GasGrids[index + 1].Tick();
}
#endif
public sealed class ColorAtPatch : FishPrepatch public sealed class ColorAtPatch : FishPrepatch
{ {
public override bool ShowSettings => false; public override bool ShowSettings => false;
public override MethodBase TargetMethodBase { get; } public override MethodBase TargetMethodBase { get; }
#if V1_4
= AccessTools.DeclaredMethod(typeof(GasGrid), nameof(GasGrid.ColorAt)); = AccessTools.DeclaredMethod(typeof(GasGrid), nameof(GasGrid.ColorAt));
#else
= AccessTools.DeclaredMethod(typeof(SectionLayer_Gas), nameof(SectionLayer_Gas.ColorAt));
#endif
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody); => ilProcessor.ReplaceBodyWith(ReplacementBody);
#if V1_4
public static Color ReplacementBody(GasGrid __instance, IntVec3 cell) public static Color ReplacementBody(GasGrid __instance, IntVec3 cell)
{ {
var cellIndex = cell.CellToIndex(__instance.map); var cellIndex = cell.CellToIndex(__instance.map);
@@ -159,6 +213,41 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
return resultColor; return resultColor;
} }
#else
public static Color ReplacementBody(SectionLayer_Gas __instance, IntVec3 cell)
{
if (__instance.GetType() != typeof(SectionLayer_Gas))
return __instance.DensityAt(cell);
var map = __instance.Map;
var cellIndex = cell.CellToIndex(map);
var gasGrids = map.gasGrid.ParallelGasGrids();
var gasGridCount = gasGrids.Length;
Color resultColor;
resultColor.r = gasGrids[0].DensityAt(cellIndex);
resultColor.g = gasGrids[1].DensityAt(cellIndex);
resultColor.b = gasGrids[2].DensityAt(cellIndex);
resultColor.a = gasGrids[3].DensityAt(cellIndex);
if (gasGridCount > 4)
{
for (var i = gasGridCount; i-- > 4;)
{
var density = gasGrids[i].DensityAt(cellIndex);
if (density > 0)
resultColor += gasGrids[i].Color * (density * 0.25f);
}
// Not remotely accurate, but the shader now essentially applies the previous version's formula with
// hardcoded values to each density in the result. High values, especially on a, lead to faded results.
// The choices are basically blind smoke color, tox gas color, rot stink color, dead life dust color, or
// something in between now
}
return resultColor;
}
#endif
} }
public sealed class AddGasPatch : FishPrepatch public sealed class AddGasPatch : FishPrepatch
@@ -231,7 +320,13 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
for (var i = 0; i < gasGrids.Length; i++) for (var i = 0; i < gasGrids.Length; i++)
gasGrids[i].Clear(); gasGrids[i].Clear();
__instance.map.mapDrawer.WholeMapChanged(MapMeshFlag.Gas); __instance.map.mapDrawer.WholeMapChanged(
#if V1_4
MapMeshFlag.Gas
#else
MapMeshFlagDefOf.Gas
#endif
);
} }
} }
@@ -300,7 +395,15 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
} }
if (anyGridDirty) if (anyGridDirty)
mapDrawer.MapMeshDirty(occupiedCell, MapMeshFlag.Gas); {
mapDrawer.MapMeshDirty(occupiedCell,
#if V1_4
MapMeshFlag.Gas
#else
MapMeshFlagDefOf.Gas
#endif
);
}
} }
} }
@@ -324,12 +427,23 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
var smokeGrid = grids[0]; var smokeGrid = grids[0];
var toxicGrid = grids[1]; var toxicGrid = grids[1];
var rotStinkGrid = grids[2]; var rotStinkGrid = grids[2];
#if !V1_4
var deadLifeGrid = grids[3];
#endif
for (var i = smokeGrid.CellCount; i-- > 0;) for (var i = smokeGrid.CellCount; i-- > 0;)
{ {
__instance.gasDensity[i] = (rotStinkGrid.DensityAt(i) << 16) __instance.gasDensity[i]
#if V1_4
= (rotStinkGrid.DensityAt(i) << 16)
| (toxicGrid.DensityAt(i) << 8) | (toxicGrid.DensityAt(i) << 8)
| smokeGrid.DensityAt(i); | smokeGrid.DensityAt(i);
#else
= ((uint)deadLifeGrid.DensityAt(i) << 24)
| ((uint)rotStinkGrid.DensityAt(i) << 16)
| ((uint)toxicGrid.DensityAt(i) << 8)
| smokeGrid.DensityAt(i);
#endif
} }
} }
@@ -342,14 +456,29 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
var smokeGrid = grids[0]; var smokeGrid = grids[0];
var toxicGrid = grids[1]; var toxicGrid = grids[1];
var rotStinkGrid = grids[2]; var rotStinkGrid = grids[2];
#if !V1_4
var deadLifeGrid = grids[3];
#endif
for (var i = smokeGrid.CellCount; i-- > 0;) for (var i = smokeGrid.CellCount; i-- > 0;)
{ {
var gasDensityAtCell = __instance.gasDensity[i]; var gasDensityAtCell = __instance.gasDensity[i];
smokeGrid.SetDirect(i, (byte)gasDensityAtCell); smokeGrid.SetDirect(i, (byte)gasDensityAtCell);
// ReSharper disable once RedundantCast
toxicGrid.SetDirect(i, (byte)((uint)gasDensityAtCell >> 8)); toxicGrid.SetDirect(i, (byte)((uint)gasDensityAtCell >> 8));
// ReSharper disable once RedundantCast
rotStinkGrid.SetDirect(i, (byte)((uint)gasDensityAtCell >> 16)); rotStinkGrid.SetDirect(i, (byte)((uint)gasDensityAtCell >> 16));
#if !V1_4
deadLifeGrid.SetDirect(i, (byte)(gasDensityAtCell >> 24));
#endif
}
for (var i = grids.Length; i-- > 0;)
{
var grid = grids[i];
grid.CycleIndexDiffusion = __instance.cycleIndexDiffusion;
grid.CycleIndexDissipation = __instance.cycleIndexDissipation;
} }
} }
} }
@@ -444,6 +573,8 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
private byte[] _gasDensity; private byte[] _gasDensity;
private volatile int _lastFinishedTick = -1; private volatile int _lastFinishedTick = -1;
private bool _allowedToRun;
public event Action<ParallelGasGrid>? Ticked; public event Action<ParallelGasGrid>? Ticked;
public GasDef GasDef { get; private set; } public GasDef GasDef { get; private set; }
@@ -487,6 +618,16 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
private void Initialize(Map map, GasDef gasDef) private void Initialize(Map map, GasDef gasDef)
{ {
GasDef = gasDef; GasDef = gasDef;
_allowedToRun = true switch
{
_ when gasDef == GasDefOf.ToxGas => ModLister.BiotechInstalled,
#if !V1_4
_ when gasDef == GasDefOf.DeadLifeDust => ModLister.AnomalyInstalled,
#endif
_ => true
};
Map = map; Map = map;
CellCount = map.cellIndices.NumGridCells; CellCount = map.cellIndices.NumGridCells;
_gasDensity = new byte[CellCount]; _gasDensity = new byte[CellCount];
@@ -495,7 +636,7 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
SourceIndicesOfRandomCells = new int[CellCount]; SourceIndicesOfRandomCells = new int[CellCount];
CardinalDirections = new IntVec3[GenAdj.CardinalDirections.Length]; CardinalDirections = new IntVec3[GenAdj.CardinalDirections.Length];
Array.Copy(GenAdj.CardinalDirections, CardinalDirections, GenAdj.CardinalDirections.Length); Array.Copy(GenAdj.CardinalDirections, CardinalDirections, GenAdj.CardinalDirections.Length);
CycleIndexDiffusion = Rand.Range(0, map.Area >> 1); // CycleIndexDiffusion = Rand.Range(0, map.Area >> 1);
_dissipationTicker = new() { GasGrid = this }; _dissipationTicker = new() { GasGrid = this };
_diffusionTicker = new() { GasGrid = this }; _diffusionTicker = new() { GasGrid = this };
@@ -514,8 +655,14 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
// created per map while loading it's stupid // created per map while loading it's stupid
// Also cellsInRandomOrder itself initializes after gasGrid within CreateComponents, but that's easy to // Also cellsInRandomOrder itself initializes after gasGrid within CreateComponents, but that's easy to
// solve // solve
grid.CellsInRandomOrder = grid.Map.cellsInRandomOrder.GetAll(); var map = grid.Map;
grid.CellsInRandomOrder = map.cellsInRandomOrder.GetAll();
grid.UpdateCellIndicesInRandomOrder(); grid.UpdateCellIndicesInRandomOrder();
var mapGasGrid = map.gasGrid;
grid.CycleIndexDiffusion = mapGasGrid.cycleIndexDiffusion;
grid.CycleIndexDissipation = mapGasGrid.cycleIndexDissipation;
grid.Ticked -= _onFirstTickAction; grid.Ticked -= _onFirstTickAction;
} }
@@ -585,7 +732,7 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
public void AddGas(in IntVec3 cell, int amount, bool canOverflow = true) public void AddGas(in IntVec3 cell, int amount, bool canOverflow = true)
{ {
if (amount <= 0 || !GasCanMoveTo(cell)) if (amount <= 0 || !GasCanMoveTo(cell) || !_allowedToRun)
return; return;
anyGasEverAdded = true; anyGasEverAdded = true;
@@ -593,7 +740,13 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
SetDirect(index, AdjustedDensity(_gasDensity[index] + amount, out var overflow)); SetDirect(index, AdjustedDensity(_gasDensity[index] + amount, out var overflow));
Map.mapDrawer.MapMeshDirty(cell, MapMeshFlag.Gas); Map.mapDrawer.MapMeshDirty(cell,
#if V1_4
MapMeshFlag.Gas
#else
MapMeshFlagDefOf.Gas
#endif
);
if (canOverflow && overflow > 0) if (canOverflow && overflow > 0)
Overflow(cell, overflow); Overflow(cell, overflow);
@@ -765,9 +918,15 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
#endif #endif
_dissipationTicker.TickCells(area, (area + 63) >> 6); // Mathf.CeilToInt(area * (1f / 64f)) _dissipationTicker.TickCells(area, (area + 63) >> 6); // Mathf.CeilToInt(area * (1f / 64f))
var mapGasGrid = Map.gasGrid;
mapGasGrid.cycleIndexDissipation = CycleIndexDissipation;
if (Diffuses) if (Diffuses)
{
_diffusionTicker.TickCells(area, (area + 31) >> 5); // Mathf.CeilToInt(area * (1f / 32f)) _diffusionTicker.TickCells(area, (area + 31) >> 5); // Mathf.CeilToInt(area * (1f / 32f))
mapGasGrid.cycleIndexDiffusion = CycleIndexDiffusion;
}
#if GAS_DEBUG_L2 #if GAS_DEBUG_L2
Debug.LogTotalGasChange(this, previousDensity); Debug.LogTotalGasChange(this, previousDensity);
@@ -817,7 +976,13 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
if (densityAfterDissipation == 0) if (densityAfterDissipation == 0)
{ {
Debug.LogMapMeshDirty(this, index); Debug.LogMapMeshDirty(this, index);
Map.mapDrawer.MapMeshDirty(IndexToCell(index), MapMeshFlag.Gas); Map.mapDrawer.MapMeshDirty(IndexToCell(index),
#if V1_4
MapMeshFlag.Gas
#else
MapMeshFlagDefOf.Gas
#endif
);
} }
} }
@@ -844,7 +1009,13 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
continue; continue;
SetDirect(otherCellIndex, (byte)densityOtOtherCell); SetDirect(otherCellIndex, (byte)densityOtOtherCell);
Map.mapDrawer.MapMeshDirty(otherCell, MapMeshFlag.Gas); Map.mapDrawer.MapMeshDirty(otherCell,
#if V1_4
MapMeshFlag.Gas
#else
MapMeshFlagDefOf.Gas
#endif
);
diffusedToAnyCell = true; diffusedToAnyCell = true;
if (densityAtOriginCell < 17) if (densityAtOriginCell < 17)
break; break;
@@ -854,7 +1025,13 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches
return; return;
SetDirect(originCellIndex, (byte)densityAtOriginCell); SetDirect(originCellIndex, (byte)densityAtOriginCell);
Map.mapDrawer.MapMeshDirty(cell, MapMeshFlag.Gas); Map.mapDrawer.MapMeshDirty(cell,
#if V1_4
MapMeshFlag.Gas
#else
MapMeshFlagDefOf.Gas
#endif
);
} }
private static bool TryDiffuseIndividualGas(ref int gasA, ref int gasB) private static bool TryDiffuseIndividualGas(ref int gasA, ref int gasB)
@@ -16,7 +16,7 @@ public sealed class GenLocalDateCaching : ClassWithFishPatches
{ {
public override string Description { get; } public override string Description { get; }
= "Caches results of GenLocalDate.DayTick for the first map. This is similar to Rim73's mind state " = "Caches results of GenLocalDate.DayTick for the first map. This is similar to Rim73's mind state "
+ "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 TranspilerMethodPriority => Priority.First;
@@ -64,7 +64,7 @@ public sealed class GenLocalDateCaching : ClassWithFishPatches
{ {
public override string Description { get; } public override string Description { get; }
= "Caches results of GenLocalDate.DayTick for the first map. This is similar to Rim73's mind state " = "Caches results of GenLocalDate.DayTick for the first map. This is similar to Rim73's mind state "
+ "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<Map, int>)GenLocalDate.DayTick; public override Delegate TargetMethodGroup { get; } = (Func<Map, int>)GenLocalDate.DayTick;
+4 -4
View File
@@ -17,7 +17,7 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches
public override string? Description { get; } public override string? Description { get; }
= "Fix for thread safety. The vanilla method was rarely breaking and preventing the game from loading"; = "Fix for thread safety. The vanilla method was rarely breaking and preventing the game from loading";
public static object Lock = new(); public static readonly object Lock = new();
public override MethodBase TargetMethodBase { get; } public override MethodBase TargetMethodBase { get; }
= AccessTools.PropertyGetter(typeof(GenTypes), nameof(GenTypes.AllTypes)); = AccessTools.PropertyGetter(typeof(GenTypes), nameof(GenTypes.AllTypes));
@@ -34,7 +34,7 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches
public override string? Description { get; } public override string? Description { get; }
= "Fix for thread safety. The vanilla method was rarely breaking and preventing the game from loading"; = "Fix for thread safety. The vanilla method was rarely breaking and preventing the game from loading";
public static object Lock = new(); public static readonly object Lock = new();
public override MethodBase TargetMethodBase { get; } = methodof(GenTypes.AllTypesWithAttribute<Attribute>); public override MethodBase TargetMethodBase { get; } = methodof(GenTypes.AllTypesWithAttribute<Attribute>);
@@ -67,7 +67,7 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches
public override string? Description { get; } public override string? Description { get; }
= "Fix for thread safety. The vanilla method was rarely breaking and preventing the game from loading"; = "Fix for thread safety. The vanilla method was rarely breaking and preventing the game from loading";
public static object Lock = new(); public static readonly object Lock = new();
public override MethodBase TargetMethodBase { get; } = methodof(GenTypes.AllSubclasses); public override MethodBase TargetMethodBase { get; } = methodof(GenTypes.AllSubclasses);
@@ -107,7 +107,7 @@ public sealed class GenTypesPatches : ClassWithFishPrepatches
= "Fix for thread safety, paired with a small optimization for faster loading. The vanilla method was " = "Fix for thread safety, paired with a small optimization for faster loading. The vanilla method was "
+ "rarely breaking and preventing the game from loading"; + "rarely breaking and preventing the game from loading";
public static object Lock = new(); public static readonly object Lock = new();
public override MethodBase TargetMethodBase { get; } = methodof(GenTypes.AllSubclassesNonAbstract); public override MethodBase TargetMethodBase { get; } = methodof(GenTypes.AllSubclassesNonAbstract);
@@ -49,9 +49,6 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
} }
} }
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public static bool Dirty(List<Gene> genes, List<Gene> genesToTick) => genes._version != genesToTick._version;
[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)
=> GetListVersion(instance) != genesToTick._version; => GetListVersion(instance) != genesToTick._version;
@@ -69,7 +66,6 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetListVersion(Pawn_GeneTracker instance) private static int GetListVersion(Pawn_GeneTracker instance)
// => HashCode.Combine(instance.xenogenes._version, instance.endogenes._version);
=> (instance.xenogenes._version << 16) | (instance.endogenes._version & 0b1111_1111_1111_1111); => (instance.xenogenes._version << 16) | (instance.endogenes._version & 0b1111_1111_1111_1111);
private static void AddTickableGenes(List<Gene> genesToTick, List<Gene> genes) private static void AddTickableGenes(List<Gene> genesToTick, List<Gene> genes)
@@ -87,27 +83,6 @@ public sealed class GeneTrackerOptimization : ClassWithFishPrepatches
genesToTick.Add(gene); genesToTick.Add(gene);
} }
} }
// [MethodImpl(MethodImplOptions.NoInlining)]
// public static void Update(List<Gene> genes, List<Gene> genesToTick)
// {
// genesToTick.Clear();
//
// var count = genes.Count;
// for (var i = 0; i < count; i++)
// {
// var gene = genes[i];
// if (SkippableTypes.Contains(gene.GetType())
// && (gene.def.mentalBreakMtbDays <= 0f || gene.def.mentalBreakDef == null))
// {
// continue;
// }
//
// genesToTick.Add(gene);
// }
//
// genesToTick._version = genes._version;
// }
public static HashSet<Type> SkippableTypes public static HashSet<Type> SkippableTypes
= typeof(Gene).SubclassesWithNoMethodOverrideAndSelf(nameof(Gene.Tick)).ToHashSet(); = typeof(Gene).SubclassesWithNoMethodOverrideAndSelf(nameof(Gene.Tick)).ToHashSet();
+65 -2
View File
@@ -138,7 +138,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
+ "faster than the vanilla method. Replaces Performance Optimizer's error prone and slower optimization."; + "faster than the vanilla method. Replaces Performance Optimizer's error prone and slower optimization.";
public override MethodBase TargetMethodBase { get; } public override MethodBase TargetMethodBase { get; }
= AccessTools.Method(typeof(ThingDef), nameof(ThingDef.HasComp)); = AccessTools.DeclaredMethod(typeof(ThingDef), nameof(ThingDef.HasComp), [typeof(Type)]);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(HasComp); => ilProcessor.ReplaceBodyWith(HasComp);
@@ -192,6 +192,69 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
} }
} }
#if !V1_4
public sealed class ThingDefGenericHasCompPatch : FishPrepatch
{
public override string? Description { get; }
= "Optimizes HasComp lookups with a fast custom dictionary implementation. This is often more than 10x "
+ "faster than the vanilla method. Replaces Performance Optimizer's error prone and slower optimization.";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(ThingDef), nameof(ThingDef.HasComp), []);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(HasComp<ThingComp>);
public static bool HasComp<T>(ThingDef thingDef) where T : ThingComp
{
if (thingDef.comps != null)
{
ref var cache = ref Cache.ByInt<ushort, CacheValue<T>>.GetOrAddReference(thingDef.shortHash);
return !cache.IsDirty(thingDef.comps) ? cache.HasComp : UpdateCache(thingDef, ref cache);
}
return false;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool UpdateCache<T>(ThingDef thingDef, ref CacheValue<T> cache) where T : ThingComp
{
var targetType = typeof(T);
for (var i = 0; i < thingDef.comps.Count; i++)
{
var compClass = thingDef.comps[i].compClass;
if (compClass != targetType && !targetType.IsAssignableFrom(compClass))
continue;
cache.Update(thingDef, true);
return true;
}
cache.Update(thingDef, false);
return false;
}
public record struct CacheValue<T>() where T : ThingComp
{
private int _listVersion = -2;
public bool HasComp;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsDirty(List<CompProperties> comps) => comps._version != _listVersion;
[MethodImpl(MethodImplOptions.NoInlining)]
public void Update(ThingDef thingDef, bool hasComp)
{
if (thingDef.shortHash == default)
return;
_listVersion = thingDef.comps._version;
HasComp = hasComp;
}
}
}
#endif
public sealed class HediffCompPatch : FishPrepatch public sealed class HediffCompPatch : FishPrepatch
{ {
public override string? Description { get; } public override string? Description { get; }
@@ -199,7 +262,7 @@ public sealed class GetCompCaching : ClassWithFishPrepatches
+ "faster than the vanilla method. Replaces Performance Optimizer's error prone and slower optimization."; + "faster than the vanilla method. Replaces Performance Optimizer's error prone and slower optimization.";
public override MethodBase TargetMethodBase { get; } public override MethodBase TargetMethodBase { get; }
= methodof(HediffUtility.TryGetComp<HediffComp>); = methodof((Func<Hediff, HediffComp>)HediffUtility.TryGetComp<HediffComp>);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(TryGetComp<HediffComp>); => ilProcessor.ReplaceBodyWith(TryGetComp<HediffComp>);
+3
View File
@@ -4,12 +4,15 @@
// 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 JetBrains.Annotations; using JetBrains.Annotations;
// ReSharper disable InconsistentNaming
namespace PerformanceFish; namespace PerformanceFish;
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public record struct GroupThingPair public record struct GroupThingPair
{ {
public const int MaxValue = 0xFF_FF_FF;
private uint _data; private uint _data;
public ThingRequestGroup ThingRequestGroup public ThingRequestGroup ThingRequestGroup
@@ -3,6 +3,8 @@
// 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.Diagnostics.CodeAnalysis;
namespace PerformanceFish.Hauling; namespace PerformanceFish.Hauling;
public class StorageDistrict public class StorageDistrict
@@ -93,11 +95,18 @@ public class StorageDistrict
public void RemoveThing(Thing thing) public void RemoveThing(Thing thing)
{ {
if (!StoredThingsOfDef(thing.def).Remove(thing)) if (!StoredThingsOfDef(thing.def).Remove(thing))
ThrowHelper.ThrowInvalidOperationException(); {
ThrowForInvalidRemovalAttempt(thing);
}
FreeSlots = TotalSlots - --_storedThingCount; FreeSlots = TotalSlots - --_storedThingCount;
} }
[DoesNotReturn]
private void ThrowForInvalidRemovalAttempt(Thing thing)
=> ThrowHelper.ThrowInvalidOperationException($"Failed removing thing '{
thing}' from district at cells '{Cells.ToStringSafeEnumerable()}'");
public List<Thing> StoredThingsOfDef(ThingDef def) => _storedThingsByDef.GetOrAdd(def.shortHash); public List<Thing> StoredThingsOfDef(ThingDef def) => _storedThingsByDef.GetOrAdd(def.shortHash);
public bool CapacityAllows(Thing t) => FreeSlots > 0 || StoredThingsOfDef(t.def).ContainsThingStackableWith(t); public bool CapacityAllows(Thing t) => FreeSlots > 0 || StoredThingsOfDef(t.def).ContainsThingStackableWith(t);
@@ -206,7 +206,7 @@ public sealed class StorageSettingsPatches : ClassWithFishPatches
public void Update(Thing thing, bool allowedToAccept) public void Update(Thing thing, bool allowedToAccept)
{ {
_value = allowedToAccept.AsInt(); _value = allowedToAccept.AsInt();
_nextUpdateTick = TickHelper.Add(512, thing.thingIDNumber, 128); _nextUpdateTick = TickHelper.Add(2048, thing.thingIDNumber, 512);
} }
} }
@@ -141,11 +141,16 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody); => ilProcessor.ReplaceBodyWith(ReplacementBody);
public static void ReplacementBody(Thing t, Pawn carrier, Map map, Faction faction, SlotGroup? slotGroup, public static void ReplacementBody(Thing t, Pawn carrier, Map map, Faction faction,
#if V1_4
SlotGroup? slotGroup,
#else
ISlotGroup? slotGroup,
#endif
bool needAccurateResult, ref IntVec3 closestSlot, ref float closestDistSquared, bool needAccurateResult, ref IntVec3 closestSlot, ref float closestDistSquared,
ref StoragePriority foundPriority) ref StoragePriority foundPriority)
{ {
if (slotGroup == null || !slotGroup.parent.Accepts(t) || !CapacityAllows(slotGroup, t)) if (slotGroup == null || !slotGroup.Settings.AllowedToAccept(t) || !CapacityAllows(slotGroup, t))
return; return;
var thingPosition = t.SpawnedOrAnyParentSpawned ? t.PositionHeld : carrier.PositionHeld; var thingPosition = t.SpawnedOrAnyParentSpawned ? t.PositionHeld : carrier.PositionHeld;
@@ -194,9 +199,18 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches
} }
} }
#if V1_4
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static StorageDistrict[] GetDistricts(SlotGroup slotGroup) => slotGroup.Districts(); public static StorageDistrict[] GetDistricts(SlotGroup slotGroup) => slotGroup.Districts();
// wrapper for analyzer compatibility, as it otherwise throws on ref returns // wrapper for analyzer compatibility, as it otherwise throws on ref returns
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static StorageDistrict[] GetDistricts(ISlotGroup iSlotGroup)
=> iSlotGroup is SlotGroup slotGroup ? slotGroup.Districts() : StorageDistrict.GetDefaultArray();
#endif
public static bool CapacityAllows(ISlotGroup iSlotGroup, Thing t)
=> iSlotGroup is not SlotGroup slotGroup || CapacityAllows(slotGroup, t);
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static bool CapacityAllows(SlotGroup slotGroup, Thing t) public static bool CapacityAllows(SlotGroup slotGroup, Thing t)
@@ -218,9 +232,9 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches
public sealed class TryFindBestBetterStoreCellForPatch : FishPrepatch public sealed class TryFindBestBetterStoreCellForPatch : FishPrepatch
{ {
public override string? Description { get; } public override string? Description { get; }
= "Optimizes a loop in the method to make use of cached storage priorities for fewer comparisons against " = "Optimizes a loop in the method to treat storage groups as proper groups without duplicate checks and to "
+ "those. Also removes haulables from ListerHaulables if the found best store cell turns out to be the " + "make use of cached storage priorities for fewer comparisons against those. Also removes haulables from "
+ "current cell"; + "ListerHaulables if the found best store cell turns out to be the current cell";
public override MethodBase TargetMethodBase { get; } = methodof(StoreUtility.TryFindBestBetterStoreCellFor); public override MethodBase TargetMethodBase { get; } = methodof(StoreUtility.TryFindBestBetterStoreCellFor);
@@ -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/.
#if V1_4
using static System.Reflection.Emit.OpCodes; using static System.Reflection.Emit.OpCodes;
namespace PerformanceFish.Hediffs; namespace PerformanceFish.Hediffs;
@@ -48,4 +49,5 @@ public sealed class HediffComp_PsychicHarmonizer_Bugfix : ClassWithFishPatches
return codes; return codes;
} }
} }
} }
#endif
@@ -15,6 +15,7 @@ using VisibleHediffCache
using NotMissingPartsCache using NotMissingPartsCache
= PerformanceFish.Cache.ByInt<Verse.HediffSet, = PerformanceFish.Cache.ByInt<Verse.HediffSet,
PerformanceFish.Hediffs.HediffSetCaching.NotMissingPartsCacheValue>; PerformanceFish.Hediffs.HediffSetCaching.NotMissingPartsCacheValue>;
// ReSharper disable WithExpressionModifiesAllMembers
namespace PerformanceFish.Hediffs; namespace PerformanceFish.Hediffs;
@@ -24,26 +25,24 @@ public sealed class HediffSetPrecaching : ClassWithFishPrepatches
{ {
public override string Description { get; } public override string Description { get; }
= "Caches results of the HediffSet.GetFirstHediffOfDef method. Impact scales with hediff count."; = "Caches results of the HediffSet.GetFirstHediffOfDef method. Impact scales with hediff count.";
public override MethodBase TargetMethodBase { get; } public override MethodBase TargetMethodBase { get; }
= SymbolExtensions.GetMethodInfo(static () = AccessTools.DeclaredMethod(typeof(HediffSet), nameof(HediffSet.GetFirstHediffOfDef));
=> default(HediffSet)!.GetFirstHediffOfDef(null, default));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(Replacement); => ilProcessor.ReplaceBodyWith(Replacement);
public static Hediff? Replacement(HediffSet instance, HediffDef? def, bool mustBeVisible) public static Hediff? Replacement(HediffSet instance, HediffDef? def, bool mustBeVisible)
{ {
var canCache = true; bool canCache;
if (instance.hediffs.Count >= 5 if ((instance.hediffs.Count >= 5) & def is not null)
&& def is not null
// && def.GetType() == typeof(HediffDef) // different types have different indexing. Can't use those
)
{ {
ref var cache = ref GetCacheValue(instance, def, mustBeVisible); ref var cache = ref GetCacheValue(instance, def!, mustBeVisible);
if (!cache.Dirty) if (!cache.Dirty)
return cache.Hediff; return cache.Hediff;
canCache = true;
} }
else else
{ {
@@ -75,7 +74,7 @@ public sealed class HediffSetPrecaching : ClassWithFishPrepatches
public static unsafe ref HediffSetCaching.HediffCacheValue GetCacheValue(HediffSet instance, HediffDef def, public static unsafe ref HediffSetCaching.HediffCacheValue GetCacheValue(HediffSet instance, HediffDef def,
bool mustBeVisible) bool mustBeVisible)
{ {
var key = new HediffCache { First = instance.pawn.thingIDNumber, Second = def.shortHash }; var key = default(HediffCache) with { First = instance.pawn.thingIDNumber, Second = def.shortHash };
ref var cache = ref mustBeVisible ref var cache = ref mustBeVisible
? ref Unsafe.As<HediffSetCaching.VisibleHediffCacheValue, HediffSetCaching.HediffCacheValue>( ? ref Unsafe.As<HediffSetCaching.VisibleHediffCacheValue, HediffSetCaching.HediffCacheValue>(
ref VisibleHediffCache.GetOrAddReference(Unsafe.As<HediffCache, VisibleHediffCache>(ref key))) ref VisibleHediffCache.GetOrAddReference(Unsafe.As<HediffCache, VisibleHediffCache>(ref key)))
@@ -98,10 +97,10 @@ public sealed class HediffSetPrecaching : ClassWithFishPrepatches
{ {
public override string Description { get; } public override string Description { get; }
= "Caches results of the HediffSet.HasHediff method. Impact scales with hediff count."; = "Caches results of the HediffSet.HasHediff method. Impact scales with hediff count.";
public override MethodBase TargetMethodBase { get; } public override MethodBase TargetMethodBase { get; }
= SymbolExtensions.GetMethodInfo(static () = AccessTools.DeclaredMethod(typeof(HediffSet), nameof(HediffSet.HasHediff),
=> default(HediffSet)!.HasHediff(null, default)); [typeof(HediffDef), typeof(bool)]);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(Replacement); => ilProcessor.ReplaceBodyWith(Replacement);
@@ -117,7 +116,7 @@ public sealed class HediffSetPrecaching : ClassWithFishPrepatches
= "Caches results of the HediffSet.GetNotMissingParts method. Impact scales with hediff count."; = "Caches results of the HediffSet.GetNotMissingParts method. Impact scales with hediff count.";
public override MethodBase TargetMethodBase { get; } public override MethodBase TargetMethodBase { get; }
= AccessTools.Method(typeof(HediffSet), nameof(HediffSet.GetNotMissingParts)); = AccessTools.DeclaredMethod(typeof(HediffSet), nameof(HediffSet.GetNotMissingParts));
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(HediffSet __instance, BodyPartHeight height, BodyPartDepth depth, BodyPartTagDef? tag, public static bool Prefix(HediffSet __instance, BodyPartHeight height, BodyPartDepth depth, BodyPartTagDef? tag,
@@ -160,11 +159,14 @@ public sealed class HediffSetCaching : ClassWithFishPatches
{ {
public sealed class DirtyCache : FishPatch public sealed class DirtyCache : FishPatch
{ {
public override List<Type> LinkedPatches { get; }
= [typeof(Pawn_PsychicEntropyTrackerOptimization.Psylink_Patch)];
public override string Description { get; } public override string Description { get; }
= "Patched to trigger psylink cache clearing for the psychic entropy optimization."; = "Patched to trigger psylink cache clearing for the psychic entropy optimization";
public override MethodBase TargetMethodInfo { get; } public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(HediffSet), nameof(HediffSet.DirtyCache)); = AccessTools.DeclaredMethod(typeof(HediffSet), nameof(HediffSet.DirtyCache));
public static void Postfix(HediffSet __instance) public static void Postfix(HediffSet __instance)
{ {
@@ -175,9 +177,10 @@ public sealed class HediffSetCaching : ClassWithFishPatches
public record struct NotMissingPartsCacheValue() public record struct NotMissingPartsCacheValue()
{ {
private int _version = -1; private int
_version = -1,
_nextRefreshTick;
private List<Hediff> _hediffsInSet = []; private List<Hediff> _hediffsInSet = [];
private int _nextRefreshTick;
public List<BodyPartRecord> Parts = []; public List<BodyPartRecord> Parts = [];
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -204,9 +207,10 @@ public sealed class HediffSetCaching : ClassWithFishPatches
public record struct HediffCacheValue() public record struct HediffCacheValue()
{ {
private int _version = -1; private int
_version = -1,
_nextRefreshTick;
private List<Hediff> _hediffsInSet = []; private List<Hediff> _hediffsInSet = [];
private int _nextRefreshTick;
public Hediff? Hediff; public Hediff? Hediff;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -9,7 +9,11 @@ public sealed class Pawn_PsychicEntropyTrackerOptimization : ClassWithFishPatche
{ {
public sealed class Psylink_Patch : FirstPriorityFishPatch public sealed class Psylink_Patch : FirstPriorityFishPatch
{ {
public override string Description { get; } = "Caches/throttles the psylink method to only update every 32 ticks."; public override List<Type> LinkedPatches { get; }
= [typeof(HediffSetCaching.DirtyCache)];
public override string Description { get; }
= "Caches/throttles the psylink method to only update on hediff changes or after 128 ticks";
public override MethodBase TargetMethodInfo { get; } public override MethodBase TargetMethodInfo { get; }
= AccessTools.PropertyGetter(typeof(Pawn_PsychicEntropyTracker), = AccessTools.PropertyGetter(typeof(Pawn_PsychicEntropyTracker),
@@ -22,7 +26,7 @@ public sealed class Pawn_PsychicEntropyTrackerOptimization : ClassWithFishPatche
if (__instance.psylinkCachedForTick <= TickHelper.TicksGame) if (__instance.psylinkCachedForTick <= TickHelper.TicksGame)
{ {
__instance.psylinkCached = __instance.pawn.GetMainPsylinkSource(); __instance.psylinkCached = __instance.pawn.GetMainPsylinkSource();
__instance.psylinkCachedForTick = TickHelper.TicksGame + 32; __instance.psylinkCachedForTick = TickHelper.TicksGame + 128;
} }
return __instance.psylinkCached; return __instance.psylinkCached;
@@ -26,14 +26,23 @@ public sealed class IdeoBuildingPresenceDemandOptimization : ClassWithFishPrepat
public static bool ReplacementBody(IdeoBuildingPresenceDemand __instance, Map map) public static bool ReplacementBody(IdeoBuildingPresenceDemand __instance, Map map)
{ {
var buildings = map.listerBuildings.AllBuildingsColonistOfDef(__instance.parent.ThingDef); var parentBuilding = __instance.parent;
var buildings = map.listerBuildings.AllBuildingsColonistOfDef(parentBuilding.ThingDef);
#if !V1_4
for (var i = buildings.Count; i-- > 0;)
{
if (buildings[i].StyleSourcePrecept == parentBuilding)
return true;
}
return false;
#else
if (buildings is IndexedFishSet<Building> fishSet) if (buildings is IndexedFishSet<Building> fishSet)
{ {
var list = fishSet.ReadOnlyList; var list = fishSet.ReadOnlyList;
for (var i = list.Count; i-- > 0;) for (var i = list.Count; i-- > 0;)
{ {
if (list[i].StyleSourcePrecept == __instance.parent) if (list[i].StyleSourcePrecept == parentBuilding)
return true; return true;
} }
@@ -43,17 +52,21 @@ public sealed class IdeoBuildingPresenceDemandOptimization : ClassWithFishPrepat
{ {
return FallbackCheck(__instance, buildings); return FallbackCheck(__instance, buildings);
} }
#endif
} }
#if V1_4
public static bool FallbackCheck(IdeoBuildingPresenceDemand __instance, IEnumerable<Building> buildings) public static bool FallbackCheck(IdeoBuildingPresenceDemand __instance, IEnumerable<Building> buildings)
{ {
var parentBuilding = __instance.parent;
foreach (var building in buildings) foreach (var building in buildings)
{ {
if (building.StyleSourcePrecept == __instance.parent) if (building.StyleSourcePrecept == parentBuilding)
return true; return true;
} }
return false; return false;
} }
#endif
} }
} }
@@ -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/.
#if V1_4
using PerformanceFish.ModCompatibility; using PerformanceFish.ModCompatibility;
namespace PerformanceFish.JobSystem; namespace PerformanceFish.JobSystem;
@@ -27,4 +28,5 @@ public sealed class DisabledWorkTypesOptimization : ClassWithFishPatches
? __instance.cachedDisabledWorkTypesPermanent ? __instance.cachedDisabledWorkTypesPermanent
: __instance.cachedDisabledWorkTypes) is null; : __instance.cachedDisabledWorkTypes) is null;
} }
} }
#endif
@@ -3,227 +3,27 @@
// 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/.
#if disabledForNow using PerformanceFish.Prepatching;
using System.Threading.Tasks;
using Verse.AI;
namespace PerformanceFish.JobSystem; namespace PerformanceFish.JobSystem;
public sealed class GenClosestPatches : ClassWithFishPatches public sealed class GenClosestPatches : ClassWithFishPrepatches
{ {
public sealed class ClosestThing_Global_Patch : FishPatch public sealed class ClosestThingReachablePatch : FishPrepatch
{ {
public override string Description { get; } = "Threading for job scanning. Disabled and unstable."; public override string Description { get; }
public override bool DefaultState => false; = "Job scanning optimization making repairing, wardening and tending use a cache of targets where "
public static bool ThreadingEnabled { get; set; } + "appropriate";
public override MethodBase TargetMethodBase => methodof(GenClosest.ClosestThingReachable);
public override Expression<Action> TargetMethod { get; } public static void Prefix(Map map, ref ThingRequest thingReq, IEnumerable<Thing>? customGlobalSearchSet)
= static () => GenClosest.ClosestThing_Global(default, null, default, null, null);
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
=> Reflection.MakeReplacementCall(ClosestThing_Global_Replacement);
private struct Variables
{ {
public IntVec3 center; if (!thingReq.CanBeFoundInRegion || customGlobalSearchSet is not ICollection<Thing> collection)
public float maxDistanceSquared; return;
public Func<Thing, float>? priorityGetter;
public float closestDistSquared;
public Predicate<Thing>? validator;
}
public static Thing? ClosestThing_Global_Replacement(IntVec3 center, IEnumerable? searchSet, if (map.listerThings.ThingsMatching(thingReq).Count > collection.Count * 5)
float maxDistance = 99999f, Predicate<Thing>? validator = null, Func<Thing, float>? priorityGetter = null) thingReq = ThingRequest.ForUndefined();
{
if (searchSet == null)
return null;
var closestDistSquared = 2.14748365E+09f;
Thing? chosen = null;
var bestPrio = float.MinValue;
//float maxDistanceSquared = maxDistance * maxDistance;
Variables variables = new()
{
center = center,
maxDistanceSquared = maxDistance * maxDistance,
validator = validator,
priorityGetter = priorityGetter,
closestDistSquared = closestDistSquared
};
var predicate = GenClosest_Predicate.GetValidator(validator?.Target)?.Target ?? validator?.Target;
if (ThreadingEnabled
&& validator != null
&& JobGiver_Work_Predicate.GetScanner(predicate) is WorkGiver_DoBill
/*or WorkGiver_ConstructDeliverResources or WorkGiver_ConstructFinishFrames*/
)
{
if (searchSet is IList<Thing> thingList)
{
Log.Message($"Running threading code for {validator.Target.GetType()}");
Exception? exception = null;
try
{
Parallel.For<(IList<Thing> list, Variables vars)>(0, thingList.Count,
() => (thingList, variables),
(i, loop, listAndVars) => (listAndVars.list,
ProcessThreaded(listAndVars.list[i], listAndVars.vars)), static _ => { });
}
catch (Exception ex)
{
exception = ex;
Log.Error($"Caught error while threading:\n{ex}");
}
if (exception is null)
Log.Message("Finished threading code without errors");
return chosen;
}
}
switch (searchSet)
{
case IList<Thing> thingList:
{
for (var i = 0; i < thingList.Count; i++)
Process(thingList[i], variables);
break;
}
case IList<Pawn> pawnList:
{
for (var j = 0; j < pawnList.Count; j++)
Process(pawnList[j], variables);
break;
}
case IList<Building> buildingList:
{
for (var k = 0; k < buildingList.Count; k++)
Process(buildingList[k], variables);
break;
}
case IList<IAttackTarget> attackTargetList:
{
for (var l = 0; l < attackTargetList.Count; l++)
Process((Thing)attackTargetList[l], variables);
break;
}
default:
{
foreach (Thing item in searchSet)
Process(item, variables);
break;
}
}
return chosen;
void Process<T>(T t, Variables variables) where T : Thing
{
if (!t.Spawned)
return;
float num = (variables.center - t.Position).LengthHorizontalSquared;
if (!(num > variables.maxDistanceSquared)
&& (variables.priorityGetter != null || num < closestDistSquared)
&& (variables.validator == null || variables.validator(t)))
{
var num2 = 0f;
if (variables.priorityGetter != null)
{
num2 = variables.priorityGetter(t);
if (num2 < bestPrio || (Math.Abs(num2 - bestPrio) < 0.001f && num >= closestDistSquared))
return;
}
chosen = t;
closestDistSquared = variables.closestDistSquared = num;
bestPrio = num2;
}
}
Variables ProcessThreaded<T>(T t, Variables variables) where T : Thing
{
if (!t.Spawned)
return variables;
float num = (variables.center - t.Position).LengthHorizontalSquared;
if (num > variables.maxDistanceSquared
|| (variables.priorityGetter == null
&& (!(num < variables.closestDistSquared)
|| !(num < (variables.closestDistSquared = closestDistSquared))))
|| (variables.validator != null && !variables.validator(t)))
{
return variables;
}
lock (Lock)
{
var num2 = 0f;
if (variables.priorityGetter != null)
{
num2 = variables.priorityGetter(t);
if (num2 < bestPrio || (Math.Abs(num2 - bestPrio) < 0.001f && num >= closestDistSquared))
return variables;
}
else
{
if (num >= closestDistSquared)
return variables;
}
chosen = t;
closestDistSquared = variables.closestDistSquared = num;
bestPrio = num2;
}
return variables;
}
}
private static object Lock { get; } = new();
private static Type JobGiverWorkPredicateType { get; } = AccessTools.FirstInner(typeof(JobGiver_Work),
t => AccessTools.Field(t, "scanner") != null
&& AccessTools.FirstMethod(t, m => m.Name.Contains("TryIssueJobPackage")) != null);
private static JobGiver_Work_PredicateAccess JobGiver_Work_Predicate { get; }
= (JobGiver_Work_PredicateAccess)Activator.CreateInstance(
typeof(JobGiver_Work_PredicateAccessImplementation<>).MakeGenericType(JobGiverWorkPredicateType));
private static Type GenClosestPredicateType { get; } = AccessTools.FirstInner(typeof(GenClosest),
t => AccessTools.Field(t, "validator") != null
&& AccessTools.FirstMethod(t, m => m.Name.Contains("ClosestThingReachable")) != null);
private static GenClosest_PredicateAccess GenClosest_Predicate { get; }
= (GenClosest_PredicateAccess)Activator.CreateInstance(
typeof(GenClosest_PredicateAccessImplementation<>).MakeGenericType(GenClosestPredicateType));
private abstract class JobGiver_Work_PredicateAccess
{
public abstract WorkGiver_Scanner? GetScanner(object? obj);
}
private class JobGiver_Work_PredicateAccessImplementation<T> : JobGiver_Work_PredicateAccess
{
public override WorkGiver_Scanner? GetScanner(object? obj) => obj is T t ? Scanner(t) : null;
public static AccessTools.FieldRef<T, WorkGiver_Scanner> Scanner { get; }
= AccessTools.FieldRefAccess<T, WorkGiver_Scanner>("scanner");
}
private abstract class GenClosest_PredicateAccess
{
public abstract Predicate<Thing>? GetValidator(object? obj);
}
private class GenClosest_PredicateAccessImplementation<T> : GenClosest_PredicateAccess
{
public override Predicate<Thing>? GetValidator(object? obj) => obj is T t ? Validator(t) : null;
public static AccessTools.FieldRef<T, Predicate<Thing>> Validator { get; }
= AccessTools.FieldRefAccess<T, Predicate<Thing>>("validator");
} }
} }
#endif }
//}
@@ -3,13 +3,8 @@
// 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.Collections.Concurrent; /*using System.Linq;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;
using static System.Reflection.Emit.OpCodes; using static System.Reflection.Emit.OpCodes;
using static PerformanceFish.TranspilerHelpers;
namespace PerformanceFish.JobSystem; namespace PerformanceFish.JobSystem;
@@ -17,9 +12,17 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
{ {
public sealed class TryIssueJobPackage_InnerDelegate_Patch : FishPatch public sealed class TryIssueJobPackage_InnerDelegate_Patch : FishPatch
{ {
public override string Description => "Work scanning optimization. Splits the IsForbidden and HasJobOnThing checks into separate loops, then threads the IsForbidden loop, sorts it, caches the result and skips any following duplicate checks"; public override string Description { get; }
public override MethodBase TargetMethodInfo => AccessTools.FindIncludingInnerTypes(typeof(JobGiver_Work), = "Work scanning optimization. Splits the IsForbidden and HasJobOnThing checks into separate loops, then "
t => AccessTools.Field(t, "scanner") != null ? AccessTools.FirstMethod(t, m => m.Name.Contains("TryIssueJobPackage") && m.ReturnType == typeof(bool)) : null); + "sorts the IsForbidden loop, caches the result and skips any following duplicate checks";
public override MethodBase? TargetMethodInfo { get; }
= AccessTools.FindIncludingInnerTypes(typeof(JobGiver_Work), static t
=> AccessTools.Field(t, "scanner") != null
? AccessTools.FirstMethod(t,
static m => m.Name.Contains("TryIssueJobPackage") && m.ReturnType == typeof(bool))
: null);
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions) public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
{ {
var shouldRemove = true; var shouldRemove = true;
@@ -46,29 +49,40 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
public sealed class TryIssueJobPackage_Patch : FishPatch public sealed class TryIssueJobPackage_Patch : FishPatch
{ {
public override string Description => "Work scanning optimization. Splits the IsForbidden and HasJobOnThing checks into separate loops, then threads the IsForbidden loop, sorts it, caches the result and skips any following duplicate checks"; public override string Description { get; }
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(JobGiver_Work), nameof(JobGiver_Work.TryIssueJobPackage)); = "Work scanning optimization. Splits the IsForbidden and HasJobOnThing checks into separate loops, "
+ "then sorts it, caches the result and skips any following duplicate checks";
public override MethodBase TargetMethodInfo
=> AccessTools.Method(typeof(JobGiver_Work), nameof(JobGiver_Work.TryIssueJobPackage));
public static void Prefix(Pawn pawn) public static void Prefix(Pawn pawn)
{ {
PrefilteredPotentialWorkThingsGlobal.Clear(); PrefilteredPotentialWorkThingsGlobal.Clear();
FishCache.ClearAll(); FishCache.ClearAll();
CurrentPawn = pawn; CurrentPawn = pawn;
} }
public static void Postfix() public static void Postfix()
{ {
PrefilteredPotentialWorkThingsGlobal.Clear(); PrefilteredPotentialWorkThingsGlobal.Clear();
FishCache.ClearAll(); FishCache.ClearAll();
CurrentPawn = null; CurrentPawn = null;
} }
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions, MethodBase original) public static CodeInstructions Transpiler(CodeInstructions CodeInstructions, MethodBase original)
{ {
var pawn = GetLdarg(original, "pawn"); var pawn = FishTranspiler.Argument(original, "pawn");
var scanner_pawn_PotentialWorkThingsGlobal = GetCallvirt(typeof(WorkGiver_Scanner), nameof(WorkGiver_Scanner.PotentialWorkThingsGlobal)); var scanner_pawn_PotentialWorkThingsGlobal = FishTranspiler.Call(typeof(WorkGiver_Scanner),
var potentialWorkThingsGlobal_pawn_TryGetPrefilteredPotentialWorkThingsAnyType = GetCall(() => TryGetPrefilteredPotentialWorkThingsAnyType(null, null)); nameof(WorkGiver_Scanner.PotentialWorkThingsGlobal));
var potentialWorkThingsGlobal_pawn_TryGetPrefilteredPotentialWorkThingsAnyType
= FishTranspiler.Call(static () => TryGetPrefilteredPotentialWorkThingsAnyType(null!, null!));
var scanner_PotentialWorkThingRequest = GetGetPropertyCallvirt(typeof(WorkGiver_Scanner), nameof(WorkGiver_Scanner.PotentialWorkThingRequest)); var scanner_PotentialWorkThingRequest = FishTranspiler.PropertyGetter(typeof(WorkGiver_Scanner),
var scanner_pawn_GetCachedThingRequest = GetCall(() => GetCachedThingRequest(null, null)); nameof(WorkGiver_Scanner.PotentialWorkThingRequest));
var scanner_pawn_GetCachedThingRequest
= FishTranspiler.Call(static () => GetCachedThingRequest(null!, null!));
foreach (var code in CodeInstructions) foreach (var code in CodeInstructions)
{ {
@@ -97,31 +111,35 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
return originalRequest; return originalRequest;
var listerThings = pawn.Map.listerThings.ThingsMatching(originalRequest); var listerThings = pawn.Map.listerThings.ThingsMatching(originalRequest);
var collection = listerThings.Count > 100 ? (scanner.PotentialWorkThingsGlobal(pawn) as ICollection<Thing>) : null; var collection = listerThings.Count > 100
? scanner.PotentialWorkThingsGlobal(pawn) as ICollection<Thing>
: null;
var processedThings = TryGetPrefilteredPotentialWorkThingsAnyType(collection is null || listerThings.Count < collection.Count * 4 ? listerThings : collection, pawn); var processedThings = TryGetPrefilteredPotentialWorkThingsAnyType(
var fishCache = originalRequest.singleDef != null ? FishCache.ForDef(originalRequest.singleDef) : FishCache.ForGroup(originalRequest.group); collection is null || listerThings.Count < collection.Count * 4 ? listerThings : collection, pawn);
fishCache.FilteredThings = processedThings as List<Thing> ?? new(processedThings); var fishCache = originalRequest.singleDef != null
return new ThingRequest() { group = originalRequest.group, singleDef = fishCache }; ? FishCache.ForDef(originalRequest.singleDef)
: FishCache.ForGroup(originalRequest.group);
fishCache.FilteredThings = processedThings as List<Thing> ?? [..processedThings];
return new() { group = originalRequest.group, singleDef = fishCache };
} }
public static IEnumerable<Thing> TryGetPrefilteredPotentialWorkThingsAnyType(IEnumerable<Thing> originalWorkThings, Pawn pawn)
public static IEnumerable<Thing> TryGetPrefilteredPotentialWorkThingsAnyType(
IEnumerable<Thing> originalWorkThings, Pawn pawn)
=> originalWorkThings switch => originalWorkThings switch
{ {
List<Thing> => Generic<Thing>.TryGetPrefilteredPotentialWorkThingsForList(originalWorkThings, pawn), List<Thing> => Generic<Thing>.TryGetPrefilteredPotentialWorkThingsForList(originalWorkThings, pawn),
List<Pawn> => Generic<Pawn>.TryGetPrefilteredPotentialWorkThingsForList(originalWorkThings, pawn), List<Pawn> => Generic<Pawn>.TryGetPrefilteredPotentialWorkThingsForList(originalWorkThings, pawn),
_ => GetPrefilteredPotentialWorkThingsUncached(originalWorkThings, pawn) _ => GetPrefilteredPotentialWorkThingsUncached(originalWorkThings, pawn)
}; };
public static IEnumerable<Thing> GetPrefilteredPotentialWorkThingsUncached(IEnumerable<Thing> originalWorkThings, Pawn pawn)
=> (originalWorkThings is not ICollection<Thing> collection || collection.Count < 2048)
? CheckForbidden(originalWorkThings, pawn)
: Generic<Thing>.Threaded_CheckForbiddenAndAddToAnyCollection(collection, pawn);
public static IEnumerable<Thing> CheckForbidden(IEnumerable<Thing> things, Pawn pawn) public static IEnumerable<Thing>
GetPrefilteredPotentialWorkThingsUncached(IEnumerable<Thing>? originalWorkThings, Pawn pawn)
{ {
if (things is null) if (originalWorkThings is null)
yield break; yield break;
foreach (var thing in things) foreach (var thing in originalWorkThings)
{ {
if (!thing.IsForbidden(pawn)) if (!thing.IsForbidden(pawn))
yield return thing; yield return thing;
@@ -130,7 +148,8 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
public static class Generic<T> where T : Thing public static class Generic<T> where T : Thing
{ {
public static IEnumerable<Thing> TryGetPrefilteredPotentialWorkThingsForList(IEnumerable<Thing> originalWorkThings, Pawn pawn) public static IEnumerable<Thing> TryGetPrefilteredPotentialWorkThingsForList(
IEnumerable<Thing> originalWorkThings, Pawn pawn)
{ {
if (!originalWorkThings.Any()) if (!originalWorkThings.Any())
return originalWorkThings; return originalWorkThings;
@@ -140,19 +159,20 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
return cache; return cache;
} }
public static T[] RecalculatePrefilteredPotentialWorkThings(IEnumerable<Thing> originalWorkThings, Pawn pawn)
public static T[] RecalculatePrefilteredPotentialWorkThings(IEnumerable<Thing> originalWorkThings,
Pawn pawn)
{ {
var inputList = originalWorkThings as List<T>; var inputList = (List<T>)originalWorkThings;
if (inputList.Count < 512) Sequential_CheckForbiddenAndAddToList(inputList, pawn);
Sequential_CheckForbiddenAndAddToList(inputList, pawn);
else
Threaded_CheckForbiddenAndAddToList(inputList, pawn);
var cache = _tempThingList.ToArray(); var cache = _tempThingList.ToArray();
if (cache.Length < 1024) if (cache.Length < 1024)
{ {
var pawnPosition = pawn.Position; var pawnPosition = pawn.Position;
Array.Sort(cache, (a, b) => (pawnPosition - a.Position).LengthHorizontalSquared.CompareTo((pawnPosition - b.Position).LengthHorizontalSquared)); Array.Sort(cache,
(a, b) => (pawnPosition - a.Position).LengthHorizontalSquared.CompareTo(
(pawnPosition - b.Position).LengthHorizontalSquared));
} }
PrefilteredPotentialWorkThingsGlobal[originalWorkThings] = cache; PrefilteredPotentialWorkThingsGlobal[originalWorkThings] = cache;
@@ -160,6 +180,7 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
return cache; return cache;
} }
private static void Sequential_CheckForbiddenAndAddToList(List<T> inputList, Pawn pawn) private static void Sequential_CheckForbiddenAndAddToList(List<T> inputList, Pawn pawn)
{ {
_tempThingList.Clear(); _tempThingList.Clear();
@@ -171,82 +192,20 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
tempThingList.Add(inputList[i]); tempThingList.Add(inputList[i]);
} }
} }
private static void Threaded_CheckForbiddenAndAddToList(List<T> inputList, Pawn pawn) private static readonly List<T> _tempThingList = [];
{
_tempThingList.Clear();
#if DEBUG
DebugLog.Message("Running threading code");
var stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
#endif
var partitioner = Partitioner.Create(0, inputList.Count);
Parallel.ForEach<Tuple<int, int>, (List<T> localList, List<T> sourceList, Pawn pawn)>(partitioner,
() => new(new(), inputList, pawn),
(range, state, vars) =>
{
for (int i = range.Item1; i < range.Item2; i++)
{
var item = vars.sourceList[i];
if (!item.IsForbidden(vars.pawn))
vars.localList.Add(item);
}
return vars;
},
vars =>
{
lock (_lock)
{
_tempThingList.AddRange(vars.localList);
}
});
#if DEBUG
stopwatch.Stop();
DebugLog.Message($"Finished threading code without errors in {stopwatch.Elapsed}");
#endif
}
public static List<T> Threaded_CheckForbiddenAndAddToAnyCollection(ICollection<T> inputCollection, Pawn pawn)
{
_tempThingList.Clear();
#if DEBUG
DebugLog.Message("Running threading code");
var stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
#endif
Parallel.ForEach<T, (List<T> localList, Pawn pawn)>(inputCollection,
() => new(new(), pawn),
(thing, state, vars) =>
{
if (!thing.IsForbidden(vars.pawn))
vars.localList.Add(thing);
return vars;
},
vars =>
{
lock (_lock)
{
_tempThingList.AddRange(vars.localList);
}
});
#if DEBUG
stopwatch.Stop();
DebugLog.Message($"Finished threading code without errors in {stopwatch.Elapsed}");
#endif
return _tempThingList;
}
private static object _lock { get; } = new();
private static List<T> _tempThingList { get; } = new();
} }
public static Dictionary<IEnumerable<Thing>, IEnumerable<Thing>> PrefilteredPotentialWorkThingsGlobal { get; } = new(); public static Dictionary<IEnumerable<Thing>, IEnumerable<Thing>> PrefilteredPotentialWorkThingsGlobal { get; }
= [];
} }
public sealed class ThingRequest_IsUndefined_Patch : FishPatch public sealed class ThingRequest_IsUndefined_Patch : FishPatch
{ {
public override string Description => "Part of the work scanning optimization"; public override string Description => "Part of the work scanning optimization";
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.IsUndefined));
public override MethodBase TargetMethodInfo
=> AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.IsUndefined));
[HarmonyPriority(Priority.Last)] [HarmonyPriority(Priority.Last)]
public static void Postfix(ThingRequest __instance, ref bool __result) public static void Postfix(ThingRequest __instance, ref bool __result)
{ {
@@ -255,6 +214,7 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
__result = NewResult(__instance); __result = NewResult(__instance);
} }
public static bool NewResult(ThingRequest __instance) public static bool NewResult(ThingRequest __instance)
=> __instance.group == ThingRequestGroup.Undefined && !((FishCache)__instance.singleDef).IsSingleDef; => __instance.group == ThingRequestGroup.Undefined && !((FishCache)__instance.singleDef).IsSingleDef;
} }
@@ -262,7 +222,10 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
public sealed class ThingRequest_CanBeFoundInRegion_Patch : FirstPriorityFishPatch public sealed class ThingRequest_CanBeFoundInRegion_Patch : FirstPriorityFishPatch
{ {
public override string Description => "Part of the work scanning optimization"; public override string Description => "Part of the work scanning optimization";
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.CanBeFoundInRegion));
public override MethodBase TargetMethodInfo
=> AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.CanBeFoundInRegion));
public static bool Prefix(ThingRequest __instance, ref bool __result) public static bool Prefix(ThingRequest __instance, ref bool __result)
{ {
if (__instance.singleDef is not FishCache) if (__instance.singleDef is not FishCache)
@@ -271,15 +234,20 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
__result = NewResult(__instance); __result = NewResult(__instance);
return false; return false;
} }
public static bool NewResult(ThingRequest __instance) public static bool NewResult(ThingRequest __instance)
=> ((FishCache)__instance.singleDef).IsSingleDef => ((FishCache)__instance.singleDef).IsSingleDef
|| (__instance.group != ThingRequestGroup.Undefined && (__instance.group == ThingRequestGroup.Nothing || __instance.group.StoreInRegion())); || (__instance.group != ThingRequestGroup.Undefined
&& (__instance.group == ThingRequestGroup.Nothing || __instance.group.StoreInRegion()));
} }
public sealed class ListerThings_ThingsMatching_Patch : FirstPriorityFishPatch public sealed class ListerThings_ThingsMatching_Patch : FirstPriorityFishPatch
{ {
public override string Description => "Part of the work scanning optimization"; public override string Description => "Part of the work scanning optimization";
public override Expression<Action> TargetMethod => () => new ListerThings(default).ThingsMatching(default);
public override MethodBase? TargetMethodInfo { get; }
= AccessTools.DeclaredMethod(typeof(ListerThings), nameof(ListerThings.ThingsMatching));
public static bool Prefix(ListerThings __instance, ThingRequest req, ref List<Thing> __result) public static bool Prefix(ListerThings __instance, ThingRequest req, ref List<Thing> __result)
{ {
if (req.singleDef is not FishCache) if (req.singleDef is not FishCache)
@@ -288,6 +256,7 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
__result = GetCache(__instance, req); __result = GetCache(__instance, req);
return false; return false;
} }
public static List<Thing> GetCache(ListerThings __instance, ThingRequest req) public static List<Thing> GetCache(ListerThings __instance, ThingRequest req)
{ {
var cachedThings = ((FishCache)req.singleDef).FilteredThings ?? ListerThings.EmptyList; var cachedThings = ((FishCache)req.singleDef).FilteredThings ?? ListerThings.EmptyList;
@@ -301,26 +270,35 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
return cachedThings; return cachedThings;
} }
} }
public static List<Thing> GetForRegion(ListerThings __instance, ThingRequest req) public static List<Thing> GetForRegion(ListerThings __instance, ThingRequest req)
=> new(TryIssueJobPackage_Patch.Generic<Thing>.TryGetPrefilteredPotentialWorkThingsForList( =>
(((FishCache)req.singleDef) is var cache && cache.IsSingleDef [
? __instance.listsByDef.TryGetValue(cache.Def, out var value) ? value : null ..TryIssueJobPackage_Patch.Generic<Thing>.TryGetPrefilteredPotentialWorkThingsForList(
: __instance.listsByGroup.TryGetItem((int)req.group, out var item) ? item : null) ?? ListerThings.EmptyList, CurrentPawn)); ((FishCache)req.singleDef is { IsSingleDef: true } cache
? __instance.listsByDef.TryGetValue(cache.Def, out var value) ? value : null
: __instance.listsByGroup.TryGetItem((int)req.group, out var item)
? item
: null)
?? ListerThings.EmptyList, CurrentPawn)
];
} }
public class FishCache : ThingDef public class FishCache : ThingDef
{ {
public bool IsSingleDef => Def != null; public bool IsSingleDef => Def != null;
public ThingDef Def { get; private set; } public ThingDef? Def { get; private init; }
public List<Thing> FilteredThings { get; set; } public List<Thing>? FilteredThings { get; set; }
public static FishCache ForDef(ThingDef def) public static FishCache ForDef(ThingDef def)
{ {
if (_dictOfDefs.TryGetValue(def, out var value)) if (_dictOfDefs.TryGetValue(def, out var value))
return value; return value;
_dictOfDefs[def] = value = new(def.defName) { Def = def }; _dictOfDefs[def] = value = new(def.defName) { Def = def };
return value; return value;
} }
public static FishCache ForGroup(ThingRequestGroup group) public static FishCache ForGroup(ThingRequestGroup group)
{ {
if (_dictOfGroups.TryGetValue(group, out var value)) if (_dictOfGroups.TryGetValue(group, out var value))
@@ -329,15 +307,17 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
_dictOfGroups[group] = value = new(group.ToString()); _dictOfGroups[group] = value = new(group.ToString());
return value; return value;
} }
public static void ClearAll() public static void ClearAll()
{ {
_dictOfGroups.Clear(); _dictOfGroups.Clear();
_dictOfDefs.Clear(); _dictOfDefs.Clear();
} }
private FishCache(string name) => defName = name; private FishCache(string name) => defName = name;
private static Dictionary<ThingRequestGroup, FishCache> _dictOfGroups { get; } = new(); private static readonly Dictionary<ThingRequestGroup, FishCache> _dictOfGroups = new();
private static Dictionary<ThingDef, FishCache> _dictOfDefs { get; } = new(); private static readonly Dictionary<ThingDef, FishCache> _dictOfDefs = new();
} }
private static Pawn CurrentPawn { get; set; } private static Pawn? CurrentPawn { get; set; }
}*/ }*/
@@ -66,11 +66,6 @@ public sealed class WorkGiver_DoBillPrepatches : ClassWithFishPrepatches
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody); => ilProcessor.ReplaceBodyWith(ReplacementBody);
// public static CodeInstructions Transpiler(CodeInstructions codeInstructions, MethodBase method)
// => Reflection.MakeReplacementCall(AccessTools.Method(
// typeof(TryFindBestIngredientsHelper_InnerDelegate_Patch), nameof(ReplacementBody),
// generics: new[] { DelegateInstanceType }));
public static bool ReplacementBody(object __instance, Region r) public static bool ReplacementBody(object __instance, Region r)
{ {
var instance = _delegateInstance.Get(__instance); var instance = _delegateInstance.Get(__instance);
@@ -86,8 +81,11 @@ public sealed class WorkGiver_DoBillPrepatches : ClassWithFishPrepatches
var bill = thingValidator.Bill; var bill = thingValidator.Bill;
if (HasNoFilters(bill) // apparently applies to a rim reborn benches? if (HasNoFilters(bill) // apparently applies to a rim reborn benches?
|| bill.billStack?.billGiver
#if V1_4 #if V1_4
|| bill.billStack?.billGiver is Building_MechGestator is Building_MechGestator
#else
is Building_WorkTableAutonomous
#endif #endif
|| (BlackListedModExtensions.Count > 0 || (BlackListedModExtensions.Count > 0
&& IsBlackListed(bill))) // <-- Fuck these too && IsBlackListed(bill))) // <-- Fuck these too
@@ -532,9 +530,12 @@ public sealed class WorkGiver_DoBillOptimization : ClassWithFishPatches
public static void Prefix(ref bool alreadySorted, Bill? bill) public static void Prefix(ref bool alreadySorted, Bill? bill)
=> alreadySorted = alreadySorted => alreadySorted = alreadySorted
|| (bill?.billStack is { } stack || (bill?.billStack is { } stack
#if V1_4
// ReSharper disable once MergeIntoPattern // ReSharper disable once MergeIntoPattern
&& stack.billGiver is not Building_MechGestator && stack.billGiver is not
#if V1_4
Building_MechGestator
#else
Building_WorkTableAutonomous
#endif #endif
); );
@@ -575,8 +576,11 @@ public sealed class WorkGiver_DoBillOptimization : ClassWithFishPatches
public static void MarkIngredientCountAsFound(IngredientCount ingredientCount, Bill? bill) public static void MarkIngredientCountAsFound(IngredientCount ingredientCount, Bill? bill)
{ {
if (bill?.billStack is not { } billStack if (bill?.billStack is not { } billStack
|| billStack.billGiver is
#if V1_4 #if V1_4
|| billStack.billGiver is Building_MechGestator Building_MechGestator
#else
Building_WorkTableAutonomous
#endif #endif
) )
{ {
+95 -2
View File
@@ -27,6 +27,23 @@ public sealed class Buildings : ClassWithFishPrepatches
=> (IEnumerable<T>)__instance.Cache().ColonistBuildingsByType.GetOrAdd(typeof(T)); => (IEnumerable<T>)__instance.Cache().ColonistBuildingsByType.GetOrAdd(typeof(T));
} }
#if !V1_4
public sealed class AllColonistBuildingsOfTypePatch : FishPrepatch
{
public override string Description { get; }
= "Same as AllBuildingsColonistOfClass, just there as a new copy in 1.5";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(ListerBuildings), nameof(ListerBuildings.AllColonistBuildingsOfType));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody<Building>);
public static IEnumerable<T> ReplacementBody<T>(ListerBuildings __instance)
=> (IEnumerable<T>)__instance.Cache().ColonistBuildingsByType.GetOrAdd(typeof(T));
}
#endif
public sealed class ColonistsHaveResearchBenchPatch : FishPrepatch public sealed class ColonistsHaveResearchBenchPatch : FishPrepatch
{ {
public override string Description { get; } public override string Description { get; }
@@ -60,6 +77,72 @@ public sealed class Buildings : ClassWithFishPrepatches
=> __instance.Cache().ColonistBuildingsByDef.GetOrAdd(def).Count > 0; => __instance.Cache().ColonistBuildingsByDef.GetOrAdd(def).Count > 0;
} }
#if !V1_4
public sealed class AllBuildingsColonistOfGroup_Patch : FishPrepatch
{
public override string Description { get; }
= "Optimizes building lookups by group on ListerBuildings to directly return the result of cached "
+ "data instead of iterating over all buildings on the map trying to find all matches";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(ListerBuildings), nameof(ListerBuildings.AllBuildingsColonistOfGroup));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
public static List<Building> ReplacementBody(ListerBuildings __instance, ThingRequestGroup group)
{
var result = ListerBuildings.allBuildingsColonistOfGroupResult;
result.Clear();
if (group == ThingRequestGroup.Undefined)
return result;
var listerThings = TryGetListerThings(__instance);
if (listerThings != null)
{
var thingsOfGroup = listerThings.ThingsInGroup(group);
var playerFaction = Faction.OfPlayerSilentFail;
for (var i = 0; i < thingsOfGroup.Count; i++)
{
if (thingsOfGroup[i] is Building building && building.Faction == playerFaction)
result.Add(building);
}
}
else
{
FallbackLoop(__instance, group);
}
return result;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void FallbackLoop(ListerBuildings listerBuildings, ThingRequestGroup group)
{
var allBuildingsColonist = listerBuildings.allBuildingsColonist;
for (var i = 0; i < allBuildingsColonist.Count; i++)
{
if (group.Includes(allBuildingsColonist[i].def))
ListerBuildings.allBuildingsColonistOfGroupResult.Add(allBuildingsColonist[i]);
}
}
public static ListerThings? TryGetListerThings(ListerBuildings listerBuildings)
{
var maps = Current.Game.Maps;
for (var i = 0; i < maps.Count; i++)
{
if (maps[i].listerBuildings == listerBuildings)
return maps[i].listerThings;
}
return null;
}
}
#endif
public sealed class AllBuildingsColonistOfDef_Patch : FishPrepatch public sealed class AllBuildingsColonistOfDef_Patch : FishPrepatch
{ {
public override string Description { get; } public override string Description { get; }
@@ -73,8 +156,18 @@ public sealed class Buildings : ClassWithFishPrepatches
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody); => ilProcessor.ReplaceBodyWith(ReplacementBody);
#if !V1_5
public static IEnumerable<Building> ReplacementBody(ListerBuildings __instance, ThingDef def) public static IEnumerable<Building> ReplacementBody(ListerBuildings __instance, ThingDef def)
=> __instance.Cache().ColonistBuildingsByDef.GetOrAdd(def); => __instance.Cache().ColonistBuildingsByDef.GetOrAdd(def);
#else
public static List<Building> ReplacementBody(ListerBuildings __instance, ThingDef def)
{
var result = ListerBuildings.allBuildingsColonistOfDefResult;
result.Clear();
result.AddRange(__instance.Cache().ColonistBuildingsByDef.GetOrAdd(def));
return result;
}
#endif
} }
public sealed class AllBuildingsNonColonistOfDef_Patch : FishPrepatch public sealed class AllBuildingsNonColonistOfDef_Patch : FishPrepatch
@@ -228,8 +321,8 @@ 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 = new(), ColonistBuildingsByDef = [],
NonColonistBuildingsByDef = new(); NonColonistBuildingsByDef = [];
public readonly IndexedFishSet<Building_ResearchBench> ColonistResearchBenches = []; public readonly IndexedFishSet<Building_ResearchBench> ColonistResearchBenches = [];
@@ -97,13 +97,18 @@ public sealed class Haulables : ClassWithFishPrepatches
public sealed class TickPatch : FishPrepatch public sealed class TickPatch : FishPrepatch
{ {
public override string? Description { get; } public override string? Description { get; }
#if V1_4
= "Essentially fixes a bug that was causing the method to tick cells multiple times for storages with less " = "Essentially fixes a bug that was causing the method to tick cells multiple times for storages with less "
+ "than 4 cells. Not ticking multiple times leads to a performance improvement. Also handles things queued " + "than 4 cells. Not ticking multiple times leads to a performance improvement. Also handles things queued "
+ "for removal by the StoreUtilityPrepatches:TryFindBestBetterStoreCellFor patch"; + "for removal by the StoreUtilityPrepatches:TryFindBestBetterStoreCellFor patch";
#else
= "Handles things queued for removal by the StoreUtilityPrepatches:TryFindBestBetterStoreCellFor patch";
#endif
public override MethodBase TargetMethodBase { get; } public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(ListerHaulables), nameof(ListerHaulables.ListerHaulablesTick)); = AccessTools.DeclaredMethod(typeof(ListerHaulables), nameof(ListerHaulables.ListerHaulablesTick));
#if V1_4
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
{ {
var instructions = ilProcessor.instructions; var instructions = ilProcessor.instructions;
@@ -120,6 +125,7 @@ public sealed class Haulables : ClassWithFishPrepatches
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetCellCountToTick(int original, SlotGroup slotGroup) public static int GetCellCountToTick(int original, SlotGroup slotGroup)
=> Math.Min(original, slotGroup.CellsList.Count); => Math.Min(original, slotGroup.CellsList.Count);
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(ListerHaulables __instance) public static void Postfix(ListerHaulables __instance)
@@ -141,6 +147,35 @@ public sealed class Haulables : ClassWithFishPrepatches
} }
} }
#if !V1_4
public sealed class CellsCheckTickPatch : FishPrepatch
{
public override string? Description { get; }
= "Essentially fixes a bug that was causing the method to tick cells multiple times for storages with less "
+ "than 4 cells. Not ticking multiple times leads to a performance improvement";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(ListerHaulables), nameof(ListerHaulables.CellsCheckTick));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
{
var instructions = ilProcessor.instructions;
var fourInstructionIndex = instructions.FirstIndexOf(static code
=> code.OpCode == OpCodes.Ldc_I4_4 || (code.OpCode == OpCodes.Ldc_I4 && code.Operand is 4));
ilProcessor.InsertRange(fourInstructionIndex + 1,
(OpCodes.Ldloc,
ilProcessor.Body.Variables.First(static local => local.VariableType.Is(typeof(SlotGroup)))),
(OpCodes.Call, methodof(GetCellCountToTick)));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetCellCountToTick(int original, SlotGroup slotGroup)
=> Math.Min(original, slotGroup.CellsList.Count);
}
#endif
public record struct Cache() public record struct Cache()
{ {
public readonly FishSet<int> ContainedThings = []; public readonly FishSet<int> ContainedThings = [];
+192 -41
View File
@@ -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.Diagnostics;
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using PerformanceFish.Prepatching; using PerformanceFish.Prepatching;
@@ -26,20 +27,29 @@ public sealed class ThingsPrepatches : ClassWithFishPrepatches
{ {
if (!ListerThings.EverListable(t.def, __instance.use)) if (!ListerThings.EverListable(t.def, __instance.use))
return; return;
AddToDefList(__instance, t);
var allGroups = ThingListGroupHelper.AllGroups; lock (__instance.LockObject())
foreach (var thingRequestGroup in allGroups)
{ {
if ((__instance.use == ListerThingsUse.Region && !thingRequestGroup.StoreInRegion()) AddToDefList(__instance, t);
|| !thingRequestGroup.Includes(t.def))
{
continue;
}
AddToGroupList(__instance, t, thingRequestGroup); var allGroups = ThingListGroupHelper.AllGroups;
foreach (var thingRequestGroup in allGroups)
{
if ((__instance.use == ListerThingsUse.Region && !thingRequestGroup.StoreInRegion())
|| !thingRequestGroup.Includes(t.def))
{
continue;
}
AddToGroupList(__instance, t, thingRequestGroup);
}
AddToTypeList(__instance, t);
} }
#if !V1_4
__instance.thingListChangedCallbacks?.onThingAdded?.Invoke(t);
#endif
} }
} }
@@ -61,57 +71,86 @@ public sealed class ThingsPrepatches : ClassWithFishPrepatches
if (!ListerThings.EverListable(t.def, __instance.use)) if (!ListerThings.EverListable(t.def, __instance.use))
return; return;
RemoveFromDefList(__instance, t); lock (__instance.LockObject())
var allGroups = ThingListGroupHelper.AllGroups;
for (var i = 0; i < allGroups.Length; i++)
{ {
var thingRequestGroup = allGroups[i]; RemoveFromDefList(__instance, t);
if ((__instance.use == ListerThingsUse.Region && !thingRequestGroup.StoreInRegion())
|| !thingRequestGroup.Includes(t.def)) var allGroups = ThingListGroupHelper.AllGroups;
for (var i = 0; i < allGroups.Length; i++)
{ {
continue; var thingRequestGroup = allGroups[i];
if ((__instance.use == ListerThingsUse.Region && !thingRequestGroup.StoreInRegion())
|| !thingRequestGroup.Includes(t.def))
{
continue;
}
RemoveFromGroupList(__instance, t, thingRequestGroup);
} }
RemoveFromGroupList(__instance, t, thingRequestGroup); RemoveFromTypeList(__instance, t);
} }
#if !V1_4
__instance.thingListChangedCallbacks?.onThingRemoved?.Invoke(t);
#endif
} }
} }
public static void AddToDefList(ListerThings lister, Thing thing) public static void AddToDefList(ListerThings lister, Thing thing)
{ {
if (!lister.listsByDef.TryGetValue(thing.def, out var value)) if (!lister.listsByDef.TryGetValue(thing.def, out var value))
{ lister.listsByDef.Add(thing.def, value = []);
value = [];
lister.listsByDef.Add(thing.def, value);
}
value.Add(thing); value.Add(thing);
lister.IndexMapByDef()[thing.GetKey()] = lister.listsByDef[thing.def].Count - 1; lister.IndexMapByDef()[thing.GetKey()] = value.Count - 1;
} }
public static void AddToGroupList(ListerThings lister, Thing thing, ThingRequestGroup thingRequestGroup) public static void AddToGroupList(ListerThings lister, Thing thing, ThingRequestGroup thingRequestGroup)
{ {
var list = lister.listsByGroup[(uint)thingRequestGroup]; ref var list = ref lister.listsByGroup[(uint)thingRequestGroup];
ref var hash = ref lister.stateHashByGroup[(uint)thingRequestGroup];
if (list == null) if (list == null)
{ {
list = []; list = [];
lister.listsByGroup[(uint)thingRequestGroup] = list; hash = 0;
lister.stateHashByGroup[(uint)thingRequestGroup] = 0;
} }
list.Add(thing); list.Add(thing);
lister.stateHashByGroup[(uint)thingRequestGroup]++; hash++;
lister.IndexMapByGroup()[new(thingRequestGroup, thing.GetKey())] lister.IndexMapByGroup()[new(thingRequestGroup, thing.GetKey())] = list.Count - 1;
= lister.listsByGroup[(uint)thingRequestGroup].Count - 1; }
public static void AddToTypeList(ListerThings lister, Thing thing)
{
var thingType = thing.GetType();
if (thingType == typeof(Thing))
return;
var cache = lister.Cache();
do
{
if (thingType == null)
break;
cache.ThingsByType.GetOrAdd(thingType).Add(thing);
}
while ((thingType = thingType.BaseType) != typeof(Thing));
} }
public static void RemoveFromDefList(ListerThings lister, Thing thing) public static void RemoveFromDefList(ListerThings lister, Thing thing)
{ {
var thingKey = thing.GetKey(); var thingKey = thing.GetKey();
var indexMapByDef = lister.IndexMapByDef(); var indexMapByDef = lister.IndexMapByDef();
var listByDef = lister.listsByDef[thing.def];
if (lister.listsByDef.TryGetValue(thing.def) is not { } listByDef)
{
LogFailureToRemoveThingFromLister(lister, thing);
return;
}
var indexByDef = indexMapByDef.TryGetValue(thingKey, out var knownIndexByDef) var indexByDef = indexMapByDef.TryGetValue(thingKey, out var knownIndexByDef)
&& knownIndexByDef < listByDef.Count && knownIndexByDef < listByDef.Count
@@ -127,10 +166,19 @@ public sealed class ThingsPrepatches : ClassWithFishPrepatches
indexMapByDef[listByDef[indexByDef].GetKey()] = indexByDef; indexMapByDef[listByDef[indexByDef].GetKey()] = indexByDef;
} }
[MethodImpl(MethodImplOptions.NoInlining)]
private static void LogFailureToRemoveThingFromLister(ListerThings lister, Thing thing)
=> Log.Error($"Tried and failed removing Thing '{thing}' of map '{
thing.TryGetMap().ToStringSafe()}' from ListerThings of map '{
lister.AllThings?.FirstOrDefault()?.TryGetMap().ToStringSafe()}'.\n{
new StackTrace(2, true)}");
public static void RemoveFromGroupList(ListerThings lister, Thing thing, ThingRequestGroup thingRequestGroup) public static void RemoveFromGroupList(ListerThings lister, Thing thing, ThingRequestGroup thingRequestGroup)
{ {
var indexMapByGroup = lister.IndexMapByGroup(); var indexMapByGroup = lister.IndexMapByGroup();
var listByGroup = lister.listsByGroup[(int)thingRequestGroup];
if (lister.listsByGroup[(int)thingRequestGroup] is not { } listByGroup)
return;
var groupMapKey = new GroupThingPair(thingRequestGroup, thing.GetKey()); var groupMapKey = new GroupThingPair(thingRequestGroup, thing.GetKey());
@@ -151,6 +199,24 @@ public sealed class ThingsPrepatches : ClassWithFishPrepatches
lister.stateHashByGroup[(uint)thingRequestGroup]++; lister.stateHashByGroup[(uint)thingRequestGroup]++;
} }
public static void RemoveFromTypeList(ListerThings lister, Thing thing)
{
var thingType = thing.GetType();
if (thingType == typeof(Thing))
return;
var cache = lister.Cache();
do
{
if (thingType == null)
break;
cache.ThingsByType.GetOrAdd(thingType).Remove(thing);
}
while ((thingType = thingType.BaseType) != typeof(Thing));
}
public sealed class ContainsPatch : FishPrepatch public sealed class ContainsPatch : FishPrepatch
{ {
public override string? Description { get; } public override string? Description { get; }
@@ -163,24 +229,99 @@ public sealed class ThingsPrepatches : ClassWithFishPrepatches
=> ilProcessor.ReplaceBodyWith(ReplacementBody); => ilProcessor.ReplaceBodyWith(ReplacementBody);
public static bool ReplacementBody(ListerThings __instance, Thing t) public static bool ReplacementBody(ListerThings __instance, Thing t)
=> __instance.IndexMapByDef().ContainsKey(t.GetKey()) {
|| (__instance.listsByDef.TryGetValue(t.def)?.Contains(t) ?? false); lock (__instance.LockObject())
{
return __instance.IndexMapByDef().ContainsKey(t.GetKey())
|| (__instance.listsByDef.TryGetValue(t.def)?.Contains(t) ?? false);
}
}
} }
public sealed class ClearPatch : FishPrepatch public sealed class ClearPatch : FishPrepatch
{ {
public override List<Type> LinkedPatches { get; }
= [typeof(AddPatch), typeof(RemovePatch), typeof(ContainsPatch)];
public override string? Description { get; } public override string? Description { get; }
= "Required to keep the ListerThings cache synced"; = "Required to keep the ListerThings cache synced";
public override MethodBase TargetMethodBase { get; } public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(ListerThings), nameof(ListerThings.Clear)); = AccessTools.DeclaredMethod(typeof(ListerThings), nameof(ListerThings.Clear));
public static void Postfix(ListerThings __instance) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
public static void ReplacementBody(ListerThings __instance)
{ {
__instance.IndexMapByDef().Clear(); lock (__instance.LockObject())
__instance.IndexMapByGroup().Clear(); {
__instance.listsByDef.Clear();
var listsByGroup = __instance.listsByGroup;
for (var i = 0; i < listsByGroup.Length; i++)
{
listsByGroup[i]?.Clear();
__instance.stateHashByGroup[i] = 0;
}
__instance.IndexMapByDef().Clear();
__instance.IndexMapByGroup().Clear();
ClearTypeLists(__instance);
}
// no thing __instance.thingListChangedCallbacks?.onThingRemoved?.Invoke(t); calls in original
}
public static void ClearTypeLists(ListerThings lister)
{
var lists = lister.Cache().ThingsByType;
foreach (var (_, list) in lists)
list.Clear();
} }
} }
#if !V1_4
public sealed class GetThingsOfTypePatch : FishPrepatch
{
public override string Description { get; }
= "Optimizes thing lookups for generic types on ListerThings to directly return the result of cached "
+ "data instead of iterating over all things on the map trying to find all matches";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(ListerThings), nameof(ListerThings.GetThingsOfType), Type.EmptyTypes);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody<Thing>);
public static IEnumerable<T> ReplacementBody<T>(ListerThings __instance) where T : Thing
=> typeof(T) != typeof(Thing) // normally logs an error instead of simply returning all things
? (IEnumerable<T>)__instance.Cache().ThingsByType.GetOrAdd(typeof(T))
: (IEnumerable<T>)__instance.AllThings;
}
public sealed class GetThingsOfTypeIntoListPatch : FishPrepatch
{
public override string Description { get; }
= "Optimizes thing lookups for generic types on ListerThings to directly return the result of cached "
+ "data instead of iterating over all things on the map trying to find all matches";
public override MethodBase TargetMethodBase { get; }
= typeof(ListerThings).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.First(static method => method.Name == nameof(ListerThings.GetThingsOfType)
&& method.ReturnType == typeof(void)
&& method.GetParameters().Length == 1);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody<Thing>);
public static void ReplacementBody<T>(ListerThings __instance, List<T> list) where T : Thing
=> list.AddRange(typeof(T) != typeof(Thing) // normally logs an error instead of simply returning all things
? (IEnumerable<T>)__instance.Cache().ThingsByType.GetOrAdd(typeof(T))
: (IEnumerable<T>)__instance.AllThings);
}
#endif
} }
public sealed class Things : ClassWithFishPatches public sealed class Things : ClassWithFishPatches
@@ -193,8 +334,8 @@ public sealed class Things : ClassWithFishPatches
= "Part of the ThingFilter.BestThingRequest optimization. Required by that particular patch and gets " = "Part of the ThingFilter.BestThingRequest optimization. Required by that particular patch and gets "
+ "toggled alongside it"; + "toggled alongside it";
public override Expression<Action> TargetMethod { get; } public override MethodBase? TargetMethodInfo { get; }
= static () => new ListerThings(default).ThingsMatching(default); = AccessTools.DeclaredMethod(typeof(ListerThings), nameof(ListerThings.ThingsMatching));
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(ListerThings __instance, ThingRequest req, ref List<Thing> __result) public static bool Prefix(ListerThings __instance, ThingRequest req, ref List<Thing> __result)
@@ -223,11 +364,12 @@ public sealed class Things : ClassWithFishPatches
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static List<Thing> GetThingsOfDefsListFor(IEnumerable<ThingDef> thingDefs) private static List<Thing> GetThingsOfDefsListFor(IEnumerable<ThingDef> thingDefs)
=> (_thingsOfDefsDictionary ??= Cache.Utility.AddNew<Dictionary<IEnumerable<ThingDef>, List<Thing>>>()) => (_thingsOfDefsDictionary
??= PerformanceFish.Cache.Utility.AddNew<FishTable<IEnumerable<ThingDef>, List<Thing>>>())
.GetOrAdd(thingDefs); .GetOrAdd(thingDefs);
[ThreadStatic] [ThreadStatic]
private static Dictionary<IEnumerable<ThingDef>, List<Thing>>? _thingsOfDefsDictionary; private static FishTable<IEnumerable<ThingDef>, List<Thing>>? _thingsOfDefsDictionary;
#if disabled #if disabled
public sealed class ThingRequest_IsUndefined_Patch : FishPatch public sealed class ThingRequest_IsUndefined_Patch : FishPatch
@@ -412,4 +554,13 @@ public sealed class Things : ClassWithFishPatches
private static Dictionary<ThingDef, FishCache> _dictOfDefs { get; } = new(); private static Dictionary<ThingDef, FishCache> _dictOfDefs { get; } = new();
} }
#endif #endif
public readonly record struct Cache()
{
public readonly FishTable<Type, IList> ThingsByType = new()
{
ValueInitializer = static type
=> (IList)Activator.CreateInstance(typeof(IndexedFishSet<>).MakeGenericType(type))
};
}
} }
+1 -1
View File
@@ -112,7 +112,7 @@ public sealed class MassUtilityCaching : ClassWithFishPatches
public void Update(float mass, Pawn p) public void Update(float mass, Pawn p)
{ {
Mass = mass; Mass = mass;
//shorter refresh interval because of stack sizes not being considered by the listVersion check // shorter refresh interval because of stack sizes not being considered by the listVersion check
_nextRefreshTick = TickHelper.Add(GenTicks.TickRareInterval, p.thingIDNumber); _nextRefreshTick = TickHelper.Add(GenTicks.TickRareInterval, p.thingIDNumber);
_inventoryListsState = GetInventoryListsState(p); _inventoryListsState = GetInventoryListsState(p);
} }
+4 -7
View File
@@ -18,15 +18,12 @@ public sealed class MiscPrepatchedOptimizations : ClassWithFishPrepatches
{ {
public override string Description { get; } public override string Description { get; }
= "Optimization for a basic and frequently used list copying method. Replaces a for loop with an " = "Optimization for a basic and frequently used list copying method. Replaces a for loop with an "
+ "optimized IL instruction.";/*{(ActiveMods.GradientHair + "optimized IL instruction.";
? " Disabled due to incompatibility with Gradient Hair" : "")}";*/
// public override bool Enabled => base.Enabled && !ActiveMods.GradientHair;
public override MethodBase TargetMethodBase { get; } = methodof(GenList.ListFullCopy<object>); public override MethodBase TargetMethodBase { get; } = methodof(GenList.ListFullCopy<object>);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ListFullCopy<object>/*Utility.CollectionExtensions.Copy<object>*/); => ilProcessor.ReplaceBodyWith(ListFullCopy<object>);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static List<T> ListFullCopy<T>(List<T> source) => source.Copy(); public static List<T> ListFullCopy<T>(List<T> source) => source.Copy();
@@ -51,7 +48,7 @@ public sealed class MiscPrepatchedOptimizations : ClassWithFishPrepatches
{ {
public override string? Description { get; } = "Tiny optimization by fixing an incorrect cast"; public override string? Description { get; } = "Tiny optimization by fixing an incorrect cast";
public override MethodBase TargetMethodBase => methodof((Func<IntVec3, Map, bool>)GenGrid.InBounds); public override MethodBase TargetMethodBase { get; } = methodof((Func<IntVec3, Map, bool>)GenGrid.InBounds);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody); => ilProcessor.ReplaceBodyWith(ReplacementBody);
@@ -60,7 +57,7 @@ public sealed class MiscPrepatchedOptimizations : ClassWithFishPrepatches
public static bool ReplacementBody(IntVec3 c, Map map) public static bool ReplacementBody(IntVec3 c, Map map)
{ {
var size = map.Size; var size = map.Size;
return (uint)c.x < (uint)size.x && (uint)c.z < (uint)size.z; return ((uint)c.x < (uint)size.x) & ((uint)c.z < (uint)size.z);
} }
} }
} }
@@ -135,4 +135,23 @@ public static class ActiveMods
private static void WarnForMissingModDependency(string modName) private static void WarnForMissingModDependency(string modName)
=> Log.Warning( => Log.Warning(
$"{modName} mod is missing. Performance Fish may not work correctly without this dependency."); $"{modName} mod is missing. Performance Fish may not work correctly without this dependency.");
}
public static class Installed
{
public static bool[] DLC =
[
ModLister.RoyaltyInstalled, ModLister.IdeologyInstalled, ModLister.BiotechInstalled
#if !V1_4
, ModLister.AnomalyInstalled
#endif
];
}
public enum DLCs
{
Royalty,
Ideology,
Biotech,
Anomaly
} }
@@ -20,6 +20,15 @@ public static class OnAssembliesLoaded
public static void Start() public static void Start()
{ {
Loaded = true; Loaded = true;
try
{
RuntimeHelpers.RunClassConstructor(typeof(ReflectionCaching).TypeHandle);
}
catch (Exception ex)
{
Log.Error(ex.ToString());
}
TryPatchGraphicsSetter(); TryPatchGraphicsSetter();
TryPatchPerformanceOptimizer(); TryPatchPerformanceOptimizer();
@@ -1,92 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<LangVersion>preview</LangVersion>
<PlatformTarget>AnyCPU</PlatformTarget>
<Optimize>true</Optimize>
<Authors>bradson</Authors>
<Copyright>Copyright (c) 2023 bradson</Copyright>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<AnalysisLevel>preview-all</AnalysisLevel>
<DebugSymbols>true</DebugSymbols>
<DebugType>portable</DebugType>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>5</WarningLevel>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<Configurations>v1.4</Configurations>
<Version>0.5.0.0</Version>
<NoWarn>
1701;1702;IL2121;CS8500;CS9082;CA1000;CA1002;CA1003;CA1707;CA1010;CA1715;CA1716;CA1724;CA1031;CA1033;
CA1034;CA1043;CA1051;CA1305;CA1310;CA1710;CA1711;CA1810;CA1812;CA1819;CA1851;CA2002;CA5394;CA1725;CA1062
</NoWarn>
</PropertyGroup>
<Choose>
<When Condition="'$(Configuration)' == 'v1.4'">
<PropertyGroup>
<OutDir>..\..\1.4\Assemblies\</OutDir>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.4.*" />
<Reference Include="1Fishery">
<HintPath>..\..\..\Fishery\1.4\Assemblies\1Fishery.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
</When>
<!-- When Condition="'$(Configuration)' == 'v1.3'">
<PropertyGroup>
<OutDir>..\..\1.3\Assemblies\</OutDir>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.3.*" />
<Reference Include="1Fishery">
<HintPath>..\..\..\Fishery\1.3\Assemblies\1Fishery.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
</When -->
</Choose>
<ItemGroup>
<PackageReference Include="Lib.Harmony" Version="2.2.*" ExcludeAssets="runtime" />
<PackageReference Include="Zetrith.Prepatcher" Version="1.*" ExcludeAssets="runtime" />
<PackageReference Include="Krafs.Publicizer" Version="2.*" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.*" ExcludeAssets="runtime" /><!-- Version included in Unity is missing IsNullRef -->
<PackageReference Include="PolySharp" Version="1.*" />
<PackageReference Include="JetBrains.Annotations" Version="2022.*" Aliases="nuget" ExcludeAssets="runtime" />
<!-- PackageReference Include="Mono.Cecil" Version="0.*" /-->
<!-- PackageReference Include="Mono.Reflection" Version="2.*" /-->
<Publicize Include="Assembly-CSharp;0Harmony" IncludeVirtualMembers="false" />
<Publicize Include="Assembly-CSharp:RimWorld.JobGiver_Haul.TryGiveJob" />
<DoNotPublicize Include="0Harmony:System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute" />
<DoNotPublicize Include="0Harmony:Mono.Collections.Generic.Collection`1" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.buckets" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.entries" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2.version" />
<Publicize Include="mscorlib:System.Collections.Generic.Dictionary`2+Entry" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1._items" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1._version" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1._size" />
<Publicize Include="mscorlib:System.Collections.Generic.List`1.EnsureCapacity" />
<Publicize Include="System.Core:System.Collections.Generic.HashSet`1._version" />
<Publicize Include="mscorlib:System.MonoCustomAttrs.GetCustomAttributes" />
<Publicize Include="mscorlib:System.RuntimeType" />
<Publicize Include="mscorlib:System.Reflection.MonoField" />
<Publicize Include="mscorlib:System.String.m_firstChar" />
<None Include="..\..\LICENSE">
<Pack>True</Pack>
<PackagePath />
</None>
</ItemGroup>
<Target Name="ChangeAliasesOfStrongNameAssemblies" BeforeTargets="FindReferenceAssembliesForReferences;ResolveReferences">
<ItemGroup>
<ReferencePath Condition="'%(FileName)' == 'JetBrains.Annotations'">
<Aliases>nuget</Aliases><!-- Rider bug, the earlier Aliases="nuget" attribute should cover this -->
</ReferencePath>
</ItemGroup>
</Target>
</Project>
+15 -9
View File
@@ -3,25 +3,31 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.1.31911.260 VisualStudioVersion = 17.1.31911.260
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerformanceFish", "PerformanceFish.csproj", "{E8A3D63E-5850-4866-AB06-DBD44B11F67B}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "1.5", "1.5.csproj", "{E8A3D63E-5850-4866-AB06-DBD44B11F67B}"
EndProject
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}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig .editorconfig = .editorconfig
..\..\1.4\Defs\GasDefs.xml = ..\..\1.4\Defs\GasDefs.xml ..\..\Defs\GasDefs.xml = ..\..\Defs\GasDefs.xml
..\..\1.4\Defs\MainButtonDefs.xml = ..\..\1.4\Defs\MainButtonDefs.xml ..\..\Defs\MainButtonDefs.xml = ..\..\Defs\MainButtonDefs.xml
EndProjectSection EndProjectSection
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
v1.3|Any CPU = v1.3|Any CPU Debug|Any CPU = Debug|Any CPU
v1.4|Any CPU = v1.4|Any CPU Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.v1.3|Any CPU.ActiveCfg = v1.3|Any CPU {E8A3D63E-5850-4866-AB06-DBD44B11F67B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.v1.3|Any CPU.Build.0 = v1.3|Any CPU {E8A3D63E-5850-4866-AB06-DBD44B11F67B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.v1.4|Any CPU.ActiveCfg = v1.4|Any CPU {E8A3D63E-5850-4866-AB06-DBD44B11F67B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8A3D63E-5850-4866-AB06-DBD44B11F67B}.v1.4|Any CPU.Build.0 = v1.4|Any CPU {E8A3D63E-5850-4866-AB06-DBD44B11F67B}.Release|Any CPU.Build.0 = Release|Any CPU
{0257ABCD-7009-4C7D-9BD8-599FC47595DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0257ABCD-7009-4C7D-9BD8-599FC47595DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0257ABCD-7009-4C7D-9BD8-599FC47595DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0257ABCD-7009-4C7D-9BD8-599FC47595DB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
+5 -2
View File
@@ -48,19 +48,22 @@ public sealed class PerformanceFishMod : Mod
PACKAGE_ID = PackageIDs.PERFORMANCE_FISH; PACKAGE_ID = PackageIDs.PERFORMANCE_FISH;
[Obsolete("I am not incrementing this version number")] [Obsolete("I am not incrementing this version number")]
public const decimal VERSION = 0.5M; public const decimal VERSION = 0.6M;
public static Version LoadedVersion => typeof(PerformanceFishMod).Assembly.GetLoadedVersion(); public static Version LoadedVersion => typeof(PerformanceFishMod).Assembly.GetLoadedVersion();
public static FishSettings? Settings { get; internal set; } public static FishSettings? Settings { get; internal set; }
public static PerformanceFishMod? Mod { get; private set; } public static PerformanceFishMod? Mod { get; private set; }
public static Harmony Harmony { get; private set; } = new(PACKAGE_ID);
public static Harmony Harmony => _harmony.Value;
public static IHasFishPatch[]? AllPatchClasses { get; internal set; } public static IHasFishPatch[]? AllPatchClasses { get; internal set; }
public static ClassWithFishPrepatches[]? AllPrepatchClasses { get; internal set; } public static ClassWithFishPrepatches[]? AllPrepatchClasses { get; internal set; }
private static FishPatch[]? _allFishPatches; private static FishPatch[]? _allFishPatches;
private static FishPrepatchBase[]? _allFishPrepatches; private static FishPrepatchBase[]? _allFishPrepatches;
private static readonly Lazy<Harmony> _harmony = new(static () => new(PACKAGE_ID), true);
public static IEnumerable<FishPatch> AllHarmonyPatches public static IEnumerable<FishPatch> AllHarmonyPatches
=> _allFishPatches ??= AllPatchClasses?.SelectMany(static patchClass => patchClass.Patches.All.Values).ToArray() => _allFishPatches ??= AllPatchClasses?.SelectMany(static patchClass => patchClass.Patches.All.Values).ToArray()
+83 -23
View File
@@ -14,16 +14,21 @@ namespace PerformanceFish;
public static class PrepatcherFields public static class PrepatcherFields
{ {
[PrepatcherField]
[ValueInitializer(nameof(CreateObject))]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern object LockObject(this ListerThings listerThings);
[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 MapEvents.Instanced Events(this Map map);
[PrepatcherField] [PrepatcherField]
[ValueInitializer(nameof(CreateThingEvents))] [ValueInitializer(nameof(CreateThingEvents))]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern ThingEvents.Instanced Events(this Thing thing); public static extern ThingEvents.Instanced Events(this Thing thing);
[PrepatcherField] [PrepatcherField]
[ValueInitializer(nameof(CreateGeneList))] [ValueInitializer(nameof(CreateGeneList))]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -69,16 +74,24 @@ public static class PrepatcherFields
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern ref StorageSettingsPatches.StorageSettingsCache Cache(this StorageSettings storageSettings); public static extern ref StorageSettingsPatches.StorageSettingsCache Cache(this StorageSettings storageSettings);
#if V1_4
[PrepatcherField]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern ref object? MonitorObject(this GasGrid gasGrid);
#endif
#if !V1_4
[PrepatcherField]
[ValueInitializer(nameof(CreateParallelGasJob))]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern GasGridOptimization.ParallelJob ParallelJob(this GasGrid gasGrid);
#endif
[PrepatcherField] [PrepatcherField]
[ValueInitializer(nameof(CreateParallelGasGridArray))] [ValueInitializer(nameof(CreateParallelGasGridArray))]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern GasGridOptimization.ParallelGasGrid[] ParallelGasGrids(this GasGrid gasGrid); public static extern GasGridOptimization.ParallelGasGrid[] ParallelGasGrids(this GasGrid gasGrid);
[PrepatcherField]
[ValueInitializer(nameof(CreateGroupMonitorObject))]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern object GasGridMonitorObject(this Map map);
[PrepatcherField] [PrepatcherField]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern ref Texture2D? ExpandingIconCache(this WorldObject worldObject); public static extern ref Texture2D? ExpandingIconCache(this WorldObject worldObject);
@@ -102,6 +115,11 @@ public static class PrepatcherFields
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern ref Listers.Buildings.Cache Cache(this ListerBuildings listerBuildings); public static extern ref Listers.Buildings.Cache Cache(this ListerBuildings listerBuildings);
[PrepatcherField]
[ValueInitializer(nameof(CreateListerThingsCache))]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern ref Listers.Things.Cache Cache(this ListerThings listerThings);
[PrepatcherField] [PrepatcherField]
[ValueInitializer(nameof(CreateListerHaulablesCache))] [ValueInitializer(nameof(CreateListerHaulablesCache))]
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -122,10 +140,6 @@ public static class PrepatcherFields
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern ref float[] StatsCache(this BuildableDef buildableDef); public static extern ref float[] StatsCache(this BuildableDef buildableDef);
[PrepatcherField]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static extern ref object MonitorObject(this GasGrid gasGrid);
// [PrepatcherField] // [PrepatcherField]
// [ValueInitializer(nameof(CreateHediffCompList))] // [ValueInitializer(nameof(CreateHediffCompList))]
// [MethodImpl(MethodImplOptions.AggressiveInlining)] // [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -149,7 +163,7 @@ public static class PrepatcherFields
public static List<Hediff> CreateHediffList() => new(); public static List<Hediff> CreateHediffList() => new();
#endif #endif
public static List<Gene> CreateGeneList() => []; public static List<Gene> CreateGeneList() => [];
public static WorldPawnsOptimization.Cache CreateWorldPawnsCache() => new(); public static WorldPawnsOptimization.Cache CreateWorldPawnsCache() => new();
public static HashSet<Building_Bed> CreateBedHashSet() => []; public static HashSet<Building_Bed> CreateBedHashSet() => [];
@@ -163,9 +177,14 @@ public static class PrepatcherFields
public static StorageDistrict[] GetDefaultDistrictArray() => StorageDistrict.GetDefaultArray(); public static StorageDistrict[] GetDefaultDistrictArray() => StorageDistrict.GetDefaultArray();
public static Cache.CellGrid<StorageDistrict> CreateStorageDistrictGrid(Map map) => new(map); public static Cache.CellGrid<StorageDistrict> CreateStorageDistrictGrid(Map map) => new(map);
public static Listers.Buildings.Cache CreateListerBuildingsCache() => new(); public static Listers.Buildings.Cache CreateListerBuildingsCache() => new();
public static Listers.Things.Cache CreateListerThingsCache() => new();
public static Listers.Haulables.Cache CreateListerHaulablesCache() => new(); public static Listers.Haulables.Cache CreateListerHaulablesCache() => new();
public static HaulDestinationManagerCache CreateHaulDestinationManagerCache() => new(); public static HaulDestinationManagerCache CreateHaulDestinationManagerCache() => new();
public static float[] GetEmptyArray() => Array.Empty<float>(); public static float[] GetEmptyArray() => Array.Empty<float>();
public static object CreateObject() => new();
#if !V1_4
public static GasGridOptimization.ParallelJob CreateParallelGasJob() => new();
#endif
public static float[] CreateBaseStatsCache() public static float[] CreateBaseStatsCache()
{ {
@@ -180,18 +199,36 @@ public static class PrepatcherFields
public static GasGridOptimization.ParallelGasGrid[] CreateParallelGasGridArray(GasGrid gasGrid) public static GasGridOptimization.ParallelGasGrid[] CreateParallelGasGridArray(GasGrid gasGrid)
{ {
DefDatabase<GasDef>.SetIndices(); DefDatabase<GasDef>.SetIndices();
var map = gasGrid.map; var map = gasGrid.map;
var gasDefs = DefDatabase<GasDef>.AllDefsListForReading; var gasDefs = DefDatabase<GasDef>.AllDefsListForReading;
if (gasDefs.Count < 3) if (gasDefs.Count
#if V1_4
< 3
#else
< 4
#endif
)
{ {
Log.Error($"GasDefs are missing! Expected are at least 3, DefDatabase contains { Log.Error($"GasDefs are missing! Expected are at least "
gasDefs.Count} instead. ParallelGasGrid won't work correctly in this state."); #if V1_4
+ $"3"
#else
+ $"4"
#endif
+ $", DefDatabase contains {
gasDefs.Count} instead. ParallelGasGrid won't work correctly in this state.");
} }
else else
{ {
if (gasDefs[0] != GasDefOf.BlindSmoke || gasDefs[1] != GasDefOf.ToxGas || gasDefs[2] != GasDefOf.RotStink) if (gasDefs[0] != GasDefOf.BlindSmoke
|| gasDefs[1] != GasDefOf.ToxGas
|| gasDefs[2] != GasDefOf.RotStink
#if !V1_4
|| gasDefs[3] != GasDefOf.DeadLifeDust
#endif
)
{ {
Log.Warning("GasDefs out of order. Sorting now to fix."); Log.Warning("GasDefs out of order. Sorting now to fix.");
var allGasDefs = gasDefs.ToList(); var allGasDefs = gasDefs.ToList();
@@ -199,29 +236,52 @@ public static class PrepatcherFields
allGasDefs.Remove(gasDefs[0] = GasDefOf.BlindSmoke); allGasDefs.Remove(gasDefs[0] = GasDefOf.BlindSmoke);
allGasDefs.Remove(gasDefs[1] = GasDefOf.ToxGas); allGasDefs.Remove(gasDefs[1] = GasDefOf.ToxGas);
allGasDefs.Remove(gasDefs[2] = GasDefOf.RotStink); allGasDefs.Remove(gasDefs[2] = GasDefOf.RotStink);
#if !V1_4
allGasDefs.Remove(gasDefs[3] = GasDefOf.DeadLifeDust);
#endif
for (var i = 0; i < allGasDefs.Count; i++) for (var i = 0; i < allGasDefs.Count; i++)
gasDefs[i + 3] = allGasDefs[i]; {
gasDefs[i
#if V1_4
+ 3
#else
+ 4
#endif
] = allGasDefs[i];
}
DefDatabase<GasDef>.SetIndices(); DefDatabase<GasDef>.SetIndices();
} }
} }
var grids = new GasGridOptimization.ParallelGasGrid[gasDefs.Count]; var grids = new GasGridOptimization.ParallelGasGrid[gasDefs.Count];
for (var i = 0; i < grids.Length; i++) for (var i = 0; i < grids.Length; i++)
grids[i] = new(map, gasDefs[i]); grids[i] = new(map, gasDefs[i]);
#if V1_4
if (FishSettings.ThreadingEnabled) if (FishSettings.ThreadingEnabled)
{ {
var monitorObject = map.GasGridMonitorObject(); var monitorObject = gasGrid.MonitorObject() ??= CreateGroupMonitorObject();
for (var i = 1; i < grids.Length; i++) for (var i = 1; i < grids.Length; i++)
{ {
var grid = grids[i]; var grid = grids[i];
ParallelNoAlloc.RegisterBackgroundWaitingWorker(monitorObject, () => grid.Tick()); ParallelNoAlloc.RegisterBackgroundWaitingWorker(monitorObject, () => grid.Tick());
} }
} }
#else
var parallelJob = gasGrid.ParallelJob();
if (parallelJob != null!)
{
parallelJob.GasGrids = grids;
}
else
{
Log.Error(
"Failed to initialize GasGrid parallel job. Map components may have loaded in an incorrect order.");
}
#endif
return grids; return grids;
} }
@@ -5,7 +5,7 @@
using PerformanceFish.Prepatching; using PerformanceFish.Prepatching;
namespace PerformanceFish; namespace PerformanceFish.Rendering;
public sealed class ContentFinderCaching : ClassWithFishPrepatches public sealed class ContentFinderCaching : ClassWithFishPrepatches
{ {
@@ -22,7 +22,7 @@ public sealed class ContentFinderCaching : ClassWithFishPrepatches
{ {
ref var cache = ref Cache.ByReference<string, CacheValue<T>>.GetOrAddReference(itemPath); ref var cache = ref Cache.ByReference<string, CacheValue<T>>.GetOrAddReference(itemPath);
if (!cache.Cached || !OnStartup.State.Initialized) if (!cache.Cached)
return __state = true; return __state = true;
__result = cache.Value; __result = cache.Value;
@@ -42,6 +42,9 @@ public sealed class ContentFinderCaching : ClassWithFishPrepatches
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateCache<T>(string itemPath, T? __result) private static void UpdateCache<T>(string itemPath, T? __result)
{ {
if (!OnStartup.State.Initialized)
return;
ref var cache = ref Cache.ByReference<string, CacheValue<T>>.GetExistingReference(itemPath); ref var cache = ref Cache.ByReference<string, CacheValue<T>>.GetExistingReference(itemPath);
cache.Cached = true; cache.Cached = true;
@@ -5,7 +5,7 @@
using System.IO; using System.IO;
namespace PerformanceFish; namespace PerformanceFish.Rendering;
//DDS importing from // https://github.com/sarbian/DDSLoader/blob/master/DatabaseLoaderTexture_DDS.cs //DDS importing from // https://github.com/sarbian/DDSLoader/blob/master/DatabaseLoaderTexture_DDS.cs
public static class DDSLoader public static class DDSLoader
@@ -0,0 +1,188 @@
// Copyright (c) 2023 bradson
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
#if !V1_4
using Gilzoide.ManagedJobs;
using Mono.Cecil;
using Mono.Cecil.Cil;
using PerformanceFish.Prepatching;
using Unity.Jobs;
namespace PerformanceFish.Rendering;
public sealed class DynamicDrawManagerPatches : ClassWithFishPrepatches
{
public sealed class DrawDynamicThingsPatch : FishPrepatch
{
public override string? Description { get; }
= "Optimizes rendering by reverting most of the threading added in 1.5, making 5 of the 6 parallel loops "
+ "in DrawDynamicThings run on the main thread again. This generally improves rendering performance by "
+ "about 10% or so";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(DynamicDrawManager), nameof(DynamicDrawManager.DrawDynamicThings));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
public static void ReplacementBody(DynamicDrawManager instance)
{
if (!DebugViewSettings.drawThingsDynamic || instance.map.Disposed)
return;
instance.drawingNow = true;
try
{
CullAndInitializeThings(instance);
var hasAnyThingsToDraw = ThingsToDraw.Count > 0;
if (hasAnyThingsToDraw)
{
if (!DebugViewSettings.singleThreadedDrawing)
DoPreDrawThings();
DrawThingsNow();
}
if (PawnShadowsToDraw.Count > 0)
DrawShadowsNow();
if (SilhouetteUtility.CanHighlightAny() && hasAnyThingsToDraw)
DrawSilhouettes(instance);
}
catch (Exception ex)
{
Log.Error("Exception drawing dynamic things: " + ex);
}
finally
{
ThingsToDraw.Clear();
PawnShadowsToDraw.Clear();
instance.drawingNow = false;
}
}
public static void CullAndInitializeThings(DynamicDrawManager instance)
{
var map = instance.map;
var mapSizeX = map.SizeX();
var fogGrid = map.fogGrid.fogGrid;
var snowGrid = map.snowGrid.depthGrid;
var checkShadows = MatBases.SunShadow.shader.isSupported;
var currentViewRect = Find.CameraDriver.CurrentViewRect.ClipInsideMap(map).ExpandedBy(1);
var shadowViewRect = SectionLayer_SunShadows.GetSunShadowsViewRect(map, currentViewRect);
instance.drawThings.UnwrapArray(out var drawThings, out var drawThingsCount);
for (var i = 0; i < drawThingsCount; i++)
{
var drawThing = drawThings.UnsafeLoad(i);
var position = drawThing.Position;
var drawThingDef = drawThing.def;
var cellIndex = position.CellToIndex(mapSizeX);
if ((!fogGrid[cellIndex] || drawThingDef.seeThroughFog)
&& (drawThingDef.hideAtSnowDepth >= 1f
|| drawThingDef.hideAtSnowDepth >= snowGrid[cellIndex]))
{
if (currentViewRect.Contains(position)
|| currentViewRect.Overlaps(drawThing.OccupiedDrawRect()))
{
ThingsToDraw.Add(drawThing);
if (!DebugViewSettings.singleThreadedDrawing)
drawThing.DynamicDrawPhase(DrawPhase.EnsureInitialized);
}
else if (drawThing is Pawn pawn && checkShadows && shadowViewRect.Contains(drawThing.Position))
{
PawnShadowsToDraw.Add(pawn);
}
}
}
}
public static void DrawShadowsNow()
{
PawnShadowsToDraw.UnwrapArray(out var pawnShadowsToDraw, out var pawnShadowsToDrawCount);
for (var i = 0; i < pawnShadowsToDrawCount; i++)
{
var pawn = pawnShadowsToDraw.UnsafeLoad(i);
try
{
pawn.DrawShadowAt(pawn.DrawPos);
}
catch (Exception ex)
{
Log.Error($"Exception drawing shadow for '{pawn}': {ex}");
}
}
}
public static void DrawThingsNow()
{
ThingsToDraw.UnwrapArray(out var thingsToDraw, out var thingsToDrawCount);
for (var i = 0; i < thingsToDrawCount; i++)
{
var drawThing = thingsToDraw.UnsafeLoad(i);
try
{
drawThing.DynamicDrawPhase(DrawPhase.Draw);
}
catch (Exception ex)
{
Log.Error($"Exception drawing '{drawThing}': {ex}");
}
}
}
public static void DoPreDrawThings()
{
ThingsToDraw.UnwrapArray(out PreDrawThingsJob.thingsToDraw, out var thingsToDrawCount);
new ManagedJobParallelFor(PreDrawThingsJob)
.Schedule(thingsToDrawCount, UnityData.GetIdealBatchCount(thingsToDrawCount)).Complete();
}
public static readonly List<Thing> ThingsToDraw = [];
public static readonly List<Pawn> PawnShadowsToDraw = [];
public static readonly PreDrawThings PreDrawThingsJob = new();
[MethodImpl(MethodImplOptions.NoInlining)]
public static void DrawSilhouettes(DynamicDrawManager instance)
{
ThingsToDraw.UnwrapArray(out var thingsToDraw, out var thingsToDrawCount);
var inverseFovScale = Find.CameraDriver.InverseFovScale;
var altitude = AltitudeLayer.Silhouettes.AltitudeFor();
var angleAxis = Quaternion.AngleAxis(0f, Vector3.up);
for (var i = 0; i < thingsToDrawCount; i++)
{
if (thingsToDraw.UnsafeLoad(i) is not Pawn pawn || !SilhouetteUtility.ShouldDrawSilhouette(pawn))
continue;
var pawnRenderer = pawn.Drawer.renderer;
var drawSize = pawnRenderer.SilhouetteGraphic.drawSize;
drawSize.x = drawSize.x < 2.5f ? drawSize.x + SilhouetteUtility.AdjustScale(drawSize.x) : drawSize.x;
drawSize.y = drawSize.y < 2.5f ? drawSize.y + SilhouetteUtility.AdjustScale(drawSize.y) : drawSize.y;
var pos = pawnRenderer.SilhouettePos;
pos.y = altitude;
SilhouetteUtility.DrawSilhouetteJob(pawn, Matrix4x4.TRS(pos, angleAxis,
inverseFovScale with { x = inverseFovScale.x * drawSize.x, z = inverseFovScale.z * drawSize.y }));
}
}
public class PreDrawThings : IJobParallelFor
{
public Thing[] thingsToDraw = Array.Empty<Thing>();
public void Execute(int index) => thingsToDraw[index].DynamicDrawPhase(DrawPhase.ParallelPreDraw);
}
}
}
#endif
@@ -1,47 +0,0 @@
// Copyright (c) 2023 bradson
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
/*namespace PerformanceFish.Rendering;
public sealed class GenDrawPatches : ClassWithFishPatches
{
public sealed class DrawMeshNowOrLater_Patch : FishPatch
{
public override Delegate TargetMethodGroup { get; }
= (Action<Mesh, Vector3, Quaternion, Material, bool>)GenDraw.DrawMeshNowOrLater;
public static void Replacement(Mesh mesh, Vector3 loc, Quaternion quat, Material mat, bool drawNow)
{
if (drawNow)
{
if (!mat.SetPass(0))
Log.Error("SetPass(0) call failed on material " + mat.name + " with shader " + mat.shader.name);
Graphics.DrawMeshNow(mesh, loc, quat);
}
else
{
Graphics.DrawMesh(mesh, loc, quat, mat, 0);
}
}
}
public sealed class DrawMeshNowOrLaterWithMatrix_Patch : FishPatch
{
public override Delegate TargetMethodGroup { get; }
= (Action<Mesh, Matrix4x4, Material, bool>)GenDraw.DrawMeshNowOrLater;
public static void Replacement(Mesh mesh, Matrix4x4 matrix, Material mat, bool drawNow)
{
if (drawNow)
{
mat.SetPass(0);
Graphics.DrawMeshNow(mesh, matrix);
}
else
{
Graphics.DrawMesh(mesh, matrix, mat, 0);
}
}
}
}*/
@@ -3,40 +3,39 @@
// 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/.
/*namespace PerformanceFish.Rendering; using PerformanceFish.Prepatching;
public sealed class GraphicPatches : ClassWithFishPatches
namespace PerformanceFish.Rendering;
public sealed class GraphicPatches : ClassWithFishPrepatches
{ {
public sealed class DrawMeshInt_Patch : FishPatch public sealed class MultiInitFix : FishPrepatch
{ {
public override MethodBase TargetMethodInfo { get; } public override string? Description { get; }
= AccessTools.DeclaredMethod(typeof(Graphic), nameof(Graphic.DrawMeshInt)); = "Fixes a bug in this type of Graphic that causes it to return with null material when failing to find a "
+ "texture, instead of the usual pink square seen in all other Graphics";
public static void Replacement(Graphic __instance, Mesh mesh, Vector3 loc, Quaternion quat, Material mat) public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(Graphic_Multi), nameof(Graphic_Multi.Init));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(Graphic_Multi __instance)
{ {
Graphics.DrawMesh(mesh, loc, quat, mat, 0); if (__instance.MatSingle != null)
}
}
public sealed class Shadow_DrawWorker_Patch : FishPatch
{
public override MethodBase TargetMethodInfo { get; }
= AccessTools.DeclaredMethod(typeof(Graphic_Shadow), nameof(Graphic_Shadow.DrawWorker));
public static void Replacement(Graphic_Shadow __instance, Vector3 loc, Rot4 rot, ThingDef? thingDef, Thing thing,
float extraRotation)
{
if (__instance.shadowMesh is null
|| thingDef is null
|| __instance.shadowInfo is null
|| (Find.CurrentMap is { } map && loc.ToIntVec3().InBounds(map) && map.roofGrid.Roofed(loc.ToIntVec3()))
|| !DebugViewSettings.drawShadows)
{
return; return;
}
var position = loc + __instance.shadowInfo.offset; AssignBadMats(__instance);
position.y = AltitudeLayer.Shadows.AltitudeFor(); }
Graphics.DrawMesh(__instance.shadowMesh, position, rot.AsQuat, MatBases.SunShadowFade, 0);
[MethodImpl(MethodImplOptions.NoInlining)]
private static void AssignBadMats(Graphic_Multi instance)
{
var mats = instance.mats;
for (var i = mats.Length; i-- > 0;)
{
if (mats[i] == null)
mats[i] = BaseContent.BadMat;
}
} }
} }
}*/ }
@@ -10,7 +10,7 @@ using FisheryLib.Pools;
using PerformanceFish.Prepatching; using PerformanceFish.Prepatching;
using RimWorld.IO; using RimWorld.IO;
namespace PerformanceFish; namespace PerformanceFish.Rendering;
public sealed class TextureLoadingPatches : ClassWithFishPrepatches public sealed class TextureLoadingPatches : ClassWithFishPrepatches
{ {
@@ -93,6 +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);
} }
if (!isDds && Prefs.TextureCompression) if (!isDds && Prefs.TextureCompression)
@@ -101,7 +102,10 @@ public sealed class TextureLoadingPatches : ClassWithFishPrepatches
texture2D.name = Path.GetFileNameWithoutExtension(file.Name); texture2D.name = Path.GetFileNameWithoutExtension(file.Name);
texture2D.filterMode = FilterMode.Trilinear; texture2D.filterMode = FilterMode.Trilinear;
texture2D.anisoLevel = 0; texture2D.anisoLevel = 0;
texture2D.mipMapBias = -0.7f;
if (texture2D.mipmapCount > 1)
texture2D.mipMapBias = -0.7f;
texture2D.Apply(!hasStoredMipMaps, true); texture2D.Apply(!hasStoredMipMaps, true);
return texture2D; return texture2D;
} }
@@ -116,9 +120,25 @@ 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);
return texture2D; return texture2D;
} }
public static void FixMipMapsIfNeeded(Texture2D texture2D, byte[] data, VirtualFile file)
{
if (((texture2D.width & 3) == 0) & ((texture2D.height & 3) == 0))
return;
if (Prefs.LogVerbose)
{
Log.Warning($"Texture does not support mipmapping, needs to be divisible by 4 ({
texture2D.width}x{texture2D.height}) for '{file.Name}'");
}
texture2D = new(2, 2, TextureFormat.Alpha8, mipChain: false);
texture2D.LoadImage(data);
}
private static byte[]? ReadBytesForFallbackFormat(VirtualFile file) private static byte[]? ReadBytesForFallbackFormat(VirtualFile file)
{ {
byte[]? data = null; byte[]? data = null;
+84 -82
View File
@@ -3,8 +3,12 @@
// 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.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection.Emit; using System.Reflection.Emit;
using Mono.Cecil;
using Mono.Cecil.Cil;
using PerformanceFish.Prepatching;
using RoomOwnersCache using RoomOwnersCache
= PerformanceFish.Cache.ByInt<Verse.Room, PerformanceFish.RoomOptimizations.Owners_Patch.CacheValue>; = PerformanceFish.Cache.ByInt<Verse.Room, PerformanceFish.RoomOptimizations.Owners_Patch.CacheValue>;
using RoomRoleCache using RoomRoleCache
@@ -27,7 +31,7 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
+ "instead of every time anything at all changes or moves"; + "instead of every time anything at all changes or moves";
public override MethodBase TargetMethodInfo { get; } public override MethodBase TargetMethodInfo { get; }
= AccessTools.PropertyGetter(typeof(Room), nameof(Room.Role)); = AccessTools.DeclaredPropertyGetter(typeof(Room), nameof(Room.Role));
public static CodeInstructions Transpiler(CodeInstructions instructions) public static CodeInstructions Transpiler(CodeInstructions instructions)
{ {
@@ -55,13 +59,11 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ShouldUpdate(Room __instance) public static bool ShouldUpdate(Room __instance)
{ {
if (__instance.role is not null if (__instance.role is not null || !RoomRoleCache.GetOrAddReference(__instance.ID).Dirty)
|| !RoomRoleCache.GetOrAddReference(__instance.ID).Dirty)
{
return false; return false;
}
UpdateCache(__instance); UpdateCache(__instance);
return true; return true;
@@ -92,7 +94,7 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
+ "quite expensive"; + "quite expensive";
public override MethodBase TargetMethodInfo { get; } public override MethodBase TargetMethodInfo { get; }
= AccessTools.PropertyGetter(typeof(Room), nameof(Room.Owners)); = AccessTools.DeclaredPropertyGetter(typeof(Room), nameof(Room.Owners));
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Room __instance, ref IEnumerable<Pawn> __result, out bool __state) public static bool Prefix(Room __instance, ref IEnumerable<Pawn> __result, out bool __state)
@@ -111,7 +113,8 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
return __state = false; return __state = false;
} }
private static bool CanCache(Room __instance) [MethodImpl(MethodImplOptions.NoInlining)]
public static bool CanCache(Room __instance)
=> __instance is { TouchesMapEdge: false, IsHuge: false } => __instance is { TouchesMapEdge: false, IsHuge: false }
&& (!__instance.statsAndRoleDirty || __instance.ContainedBeds.Any()); && (!__instance.statsAndRoleDirty || __instance.ContainedBeds.Any());
@@ -157,29 +160,6 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
} }
} }
public sealed class Regions_Patch : FirstPriorityFishPatch
{
public override string Description { get; } = "Minor optimization";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.PropertyGetter(typeof(Room), nameof(Room.Regions));
public static CodeInstructions Transpiler(CodeInstructions codeInstructions)
=> Reflection.MakeReplacementCall(Regions_Replacement);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static List<Region> Regions_Replacement(Room instance)
{
var tmpRegions = instance.tmpRegions;
var districts = instance.districts;
tmpRegions.Clear();
for (var i = 0; i < districts.Count; i++)
tmpRegions.AddRangeFast(districts[i].Regions);
return tmpRegions;
}
}
public sealed class ContainedBeds_Patch : FirstPriorityFishPatch public sealed class ContainedBeds_Patch : FirstPriorityFishPatch
{ {
public override string Description { get; } public override string Description { get; }
@@ -188,7 +168,7 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
+ "against spikes"; + "against spikes";
public override MethodBase TargetMethodInfo { get; } public override MethodBase TargetMethodInfo { get; }
= AccessTools.PropertyGetter(typeof(Room), nameof(Room.ContainedBeds)); = AccessTools.DeclaredPropertyGetter(typeof(Room), nameof(Room.ContainedBeds));
public static CodeInstructions Transpiler(CodeInstructions codeInstructions, ILGenerator generator) public static CodeInstructions Transpiler(CodeInstructions codeInstructions, ILGenerator generator)
=> Reflection.GetCodeInstructions(ContainedBeds_Replacement, generator); => Reflection.GetCodeInstructions(ContainedBeds_Replacement, generator);
@@ -219,7 +199,7 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
public override string Description { get; } = "Caching of room content info. Variable impact"; public override string Description { get; } = "Caching of room content info. Variable impact";
public override MethodBase TargetMethodInfo { get; } public override MethodBase TargetMethodInfo { get; }
= AccessTools.PropertyGetter(typeof(Room), nameof(Room.ContainedAndAdjacentThings)); = AccessTools.DeclaredPropertyGetter(typeof(Room), nameof(Room.ContainedAndAdjacentThings));
public static CodeInstructions Transpiler(CodeInstructions codeInstructions) public static CodeInstructions Transpiler(CodeInstructions codeInstructions)
=> Reflection.MakeReplacementCall(ContainedAndAdjacentThings_Replacement); => Reflection.MakeReplacementCall(ContainedAndAdjacentThings_Replacement);
@@ -236,18 +216,11 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
if (cache.ListVersions.Count == regions.Count && !cache.Dirty) if (cache.ListVersions.Count == regions.Count && !cache.Dirty)
{ {
var cacheIsAccurate = true; var cacheIsAccurate = true;
for (var i = 0; i < regions.Count; i++)
for (var i = regions.Count; i-- > 0;)
{ {
var allThings = regions[i].listerThings.AllThings; if (cache.ListVersions[i] != (regions[i]?.listerThings.AllThings?._version ?? -1))
if (allThings == null)
{
if (cache.ListVersions[i] != -1)
cacheIsAccurate = false;
}
else if (cache.ListVersions[i] != allThings._version)
{
cacheIsAccurate = false; cacheIsAccurate = false;
}
} }
if (cacheIsAccurate) if (cacheIsAccurate)
@@ -260,6 +233,7 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
return RefreshContainedAndAdjacentThings(instance, regions, ref cache); return RefreshContainedAndAdjacentThings(instance, regions, ref cache);
} }
[MethodImpl(MethodImplOptions.NoInlining)]
private static List<Thing> RefreshContainedAndAdjacentThings(Room instance, List<Region> regions, private static List<Thing> RefreshContainedAndAdjacentThings(Room instance, List<Region> regions,
ref CacheValue cache) ref CacheValue cache)
{ {
@@ -270,8 +244,7 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
for (var i = 0; i < regions.Count; i++) for (var i = 0; i < regions.Count; i++)
{ {
var allThings = regions[i].ListerThings.AllThings; if (regions[i]?.ListerThings.AllThings is not { } allThings)
if (allThings == null)
{ {
cache.ListVersions.Add(-1); cache.ListVersions.Add(-1);
continue; continue;
@@ -281,67 +254,66 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
for (var j = 0; j < count; j++) for (var j = 0; j < count; j++)
uniqueContainedThingsSet.Add(allThings[j]); uniqueContainedThingsSet.Add(allThings[j]);
cache.ListVersions.Add(allThings.Version()); cache.ListVersions.Add(allThings._version);
} }
uniqueContainedThings.AddRange(uniqueContainedThingsSet); uniqueContainedThings.AddRange(uniqueContainedThingsSet);
if (cache.Things is { } list)
list.ReplaceContentsWith(uniqueContainedThings);
else
cache.Things = [..uniqueContainedThings];
cache.Things.ReplaceContentsWith(uniqueContainedThings);
cache.SetDirty(false, instance); cache.SetDirty(false, instance);
uniqueContainedThingsSet.Clear(); uniqueContainedThingsSet.Clear();
return uniqueContainedThings; return uniqueContainedThings;
} }
/*private static List<Thing> RefreshContainedAndAdjacentThingsAlternative(Room instance, List<Region> regions, /*[MethodImpl(MethodImplOptions.NoInlining)]
FishThingsInfo cache) private static List<Thing> RefreshContainedAndAdjacentThingsAlternative(Room instance, List<Region> regions,
ref CacheValue cache)
{ {
var uniqueContainedThings = instance.uniqueContainedThings; var uniqueContainedThings = instance.uniqueContainedThings;
(cache.listVersions ??= new()).Clear(); cache.ListVersions.Clear();
var map = instance.Map; var map = instance.Map;
var regionGrid = map.regionGrid.regionGrid; var regionGrid = map.regionGrid.regionGrid;
var mapSizeX = map.cellIndices.mapSizeX; var mapSizeX = map.cellIndices.mapSizeX;
var adjacentThings = new List<Thing>(); using var pooledAdjacentThings = new Pools.PooledIList<List<Thing>>();
var adjacentThings = pooledAdjacentThings.List;
var regionCount = regions.Count;
using var pooledRegionIDs = new PooledArray<int>(regionCount);
var regionIDs = pooledRegionIDs.BackingArray;
for (var i = regionCount; i-- > 0;)
regionIDs[i] = regions[i].id;
for (var i = 0; i < regions.Count; i++) for (var i = 0; i < regionCount; i++)
{ {
var region = regions[i]; var region = regions[i];
var allThings = region.ListerThings.AllThings; if (region.ListerThings.AllThings is not { } allThings)
if (allThings == null)
{ {
cache.listVersions.Add(-1); cache.ListVersions.Add(-1);
continue; continue;
} }
var count = allThings.Count; var count = allThings.Count;
for (var j = 0; j < count; j++) for (var j = 0; j < count; j++)
{ {
if (regionGrid[allThings[j].Position.CellToIndex(mapSizeX)] == region) var thing = allThings[j];
uniqueContainedThings.Add(allThings[j]); var thingRegionId = regionGrid[thing.Position.CellToIndex(mapSizeX)].id;
else
adjacentThings.Add(allThings[j]); if (thingRegionId == region.id)
uniqueContainedThings.Add(thing);
else if (!regionIDs.Contains(thingRegionId) && !adjacentThings.Contains(thing))
adjacentThings.Add(thing);
} }
cache.listVersions.Add(allThings._version); cache.ListVersions.Add(allThings._version);
} }
for (var i = 0; i < adjacentThings.Count; i++) for (var i = adjacentThings.Count; i-- > 0;)
{ uniqueContainedThings.Add(adjacentThings[i]);
if (!uniqueContainedThings.Contains(adjacentThings[i]))
uniqueContainedThings.Add(adjacentThings[i]);
}
if (cache.things is List<Thing> list) cache.Things.ReplaceContentsWith(uniqueContainedThings);
list.ReplaceContentsWith(uniqueContainedThings); cache.SetDirty(false, instance);
else
cache.things = new List<Thing>(uniqueContainedThings);
cache.ShouldRefreshNow = false;
CachedThingLists[instance] = cache;
return uniqueContainedThings; return uniqueContainedThings;
}*/ }*/
@@ -349,8 +321,8 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
public record struct CacheValue() public record struct CacheValue()
{ {
private int _nextRefreshTick = -2; private int _nextRefreshTick = -2;
public List<int> ListVersions = []; public readonly List<int> ListVersions = [];
public List<Thing> Things = []; public readonly List<Thing> Things = [];
public void SetDirty(bool value, Room room) public void SetDirty(bool value, Room room)
=> _nextRefreshTick = value ? 0 : TickHelper.Add(3072, room.ID, 2048); => _nextRefreshTick = value ? 0 : TickHelper.Add(3072, room.ID, 2048);
@@ -382,20 +354,50 @@ public sealed class RoomOptimizations : ClassWithFishPatches, IHasDescription
return null; return null;
var regionAndRoomUpdater = map.regionAndRoomUpdater; var regionAndRoomUpdater = map.regionAndRoomUpdater;
if (regionAndRoomUpdater.Enabled) if (regionAndRoomUpdater.Enabled)
{
regionAndRoomUpdater.TryRebuildDirtyRegionsAndRooms(); regionAndRoomUpdater.TryRebuildDirtyRegionsAndRooms();
}
else if (regionAndRoomUpdater.AnythingToRebuild) else if (regionAndRoomUpdater.AnythingToRebuild)
{ LogIncorrectResultWarning(c);
Verse.Log.Warning($"Trying to get valid region at {
c.ToString()} but RegionAndRoomUpdater is disabled. The result may be incorrect.");
}
var region = map.regionGrid.regionGrid[(c.z * cellIndices.mapSizeX) + c.x]; var region = map.regionGrid.regionGrid[(c.z * cellIndices.mapSizeX) + c.x];
return region is null || !region.valid || (region.type & allowedRegionTypes) == 0 return region is null || !region.valid || (region.type & allowedRegionTypes) == 0
? null ? null
: region.District?.Room; : region.District?.Room;
} }
[MethodImpl(MethodImplOptions.NoInlining)]
public static void LogIncorrectResultWarning(IntVec3 c)
=> Log.Warning($"Trying to get valid region at {
c.ToString()} but RegionAndRoomUpdater is disabled. The result may be incorrect.\nThis commonly "
+ $"happens when incorrectly accessing a region within a method that itself gets accessed by the "
+ $"RegionAndRoomUpdater when computing the region grid. The updater gets disabled to avoid a stack "
+ $"overflow.\n{new StackTrace(1, true)}");
}
}
public sealed class RoomPrepatches : ClassWithFishPrepatches
{
public sealed class Regions_Patch : FishPrepatch
{
public override string Description { get; } = "Minor optimization by simplifying instructions";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredPropertyGetter(typeof(Room), nameof(Room.Regions));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(Regions_Replacement);
public static List<Region> Regions_Replacement(Room instance)
{
var tmpRegions = instance.tmpRegions;
var districts = instance.districts;
tmpRegions.Clear();
for (var i = 0; i < districts.Count; i++)
tmpRegions.AddRangeFast(districts[i].Regions);
return tmpRegions;
}
} }
} }
+1
View File
@@ -5,6 +5,7 @@
using Mono.Cecil; using Mono.Cecil;
using Mono.Cecil.Cil; using Mono.Cecil.Cil;
using PerformanceFish.Defs;
using PerformanceFish.Prepatching; using PerformanceFish.Prepatching;
using StatsCache = PerformanceFish.Cache.ByInt<uint, ushort, PerformanceFish.StatCaching.CachedStatValue>; using StatsCache = PerformanceFish.Cache.ByInt<uint, ushort, PerformanceFish.StatCaching.CachedStatValue>;
@@ -86,7 +86,7 @@ public sealed class EquatableValueTypeEqualityComparer<T> : EqualityComparer<T>
/// <summary> /// <summary>
/// Equals method for the comparer itself. /// Equals method for the comparer itself.
/// </summary> /// </summary>
public override bool Equals(object? obj) => obj is EquatableValueTypeEqualityComparer<T>; public override bool Equals(object? obj) => obj?.GetType() == typeof(EquatableValueTypeEqualityComparer<T>);
public override int GetHashCode() => GetType().Name.GetHashCode(); public override int GetHashCode() => GetType().Name.GetHashCode();
} }
@@ -106,7 +106,7 @@ public sealed class EquatableNullableEqualityComparer<T> : EqualityComparer<T?>
/// <summary> /// <summary>
/// Equals method for the comparer itself. /// Equals method for the comparer itself.
/// </summary> /// </summary>
public override bool Equals(object? obj) => obj is EquatableNullableEqualityComparer<T>; public override bool Equals(object? obj) => obj?.GetType() == typeof(EquatableNullableEqualityComparer<T>);
public override int GetHashCode() => GetType().Name.GetHashCode(); public override int GetHashCode() => GetType().Name.GetHashCode();
} }
@@ -126,7 +126,7 @@ public sealed class NullableEqualityComparer<T> : EqualityComparer<T?> where T :
/// <summary> /// <summary>
/// Equals method for the comparer itself. /// Equals method for the comparer itself.
/// </summary> /// </summary>
public override bool Equals(object? obj) => obj is NullableEqualityComparer<T>; public override bool Equals(object? obj) => obj?.GetType() == typeof(NullableEqualityComparer<T>);
public override int GetHashCode() => GetType().Name.GetHashCode(); public override int GetHashCode() => GetType().Name.GetHashCode();
} }
@@ -135,7 +135,6 @@ public sealed class NullableEqualityComparer<T> : EqualityComparer<T?> where T :
public sealed class StringEqualityComparer : EqualityComparer<string?> public sealed class StringEqualityComparer : EqualityComparer<string?>
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
[SuppressMessage("Globalization", "CA1307")]
[SuppressMessage("Globalization", "CA1309")] [SuppressMessage("Globalization", "CA1309")]
public override bool Equals(string? x, string? y) public override bool Equals(string? x, string? y)
=> string.Equals(x, y); // already does a null check, so this skips the extra check that'd otherwise happen => string.Equals(x, y); // already does a null check, so this skips the extra check that'd otherwise happen
@@ -146,7 +145,7 @@ public sealed class StringEqualityComparer : EqualityComparer<string?>
/// <summary> /// <summary>
/// Equals method for the comparer itself. /// Equals method for the comparer itself.
/// </summary> /// </summary>
public override bool Equals(object? obj) => obj is StringEqualityComparer; public override bool Equals(object? obj) => obj?.GetType() == typeof(StringEqualityComparer);
public override int GetHashCode() => GetType().Name.GetHashCode(); public override int GetHashCode() => GetType().Name.GetHashCode();
} }
@@ -163,7 +162,7 @@ public sealed class IntEqualityComparer : EqualityComparer<int>
/// <summary> /// <summary>
/// Equals method for the comparer itself. /// Equals method for the comparer itself.
/// </summary> /// </summary>
public override bool Equals(object? obj) => obj is IntEqualityComparer; public override bool Equals(object? obj) => obj?.GetType() == typeof(IntEqualityComparer);
public override int GetHashCode() => GetType().Name.GetHashCode(); public override int GetHashCode() => GetType().Name.GetHashCode();
} }
@@ -180,7 +179,7 @@ public sealed class UshortEqualityComparer : EqualityComparer<ushort>
/// <summary> /// <summary>
/// Equals method for the comparer itself. /// Equals method for the comparer itself.
/// </summary> /// </summary>
public override bool Equals(object? obj) => obj is UshortEqualityComparer; public override bool Equals(object? obj) => obj?.GetType() == typeof(UshortEqualityComparer);
public override int GetHashCode() => GetType().Name.GetHashCode(); public override int GetHashCode() => GetType().Name.GetHashCode();
} }
@@ -199,7 +198,7 @@ public sealed class ReferenceEqualityComparer<T> : EqualityComparer<T>
/// <summary> /// <summary>
/// Equals method for the comparer itself. /// Equals method for the comparer itself.
/// </summary> /// </summary>
public override bool Equals(object? obj) => obj is ReferenceEqualityComparer<T>; public override bool Equals(object? obj) => obj?.GetType() == typeof(ReferenceEqualityComparer<T>);
public override int GetHashCode() => GetType().Name.GetHashCode(); public override int GetHashCode() => GetType().Name.GetHashCode();
} }
@@ -218,7 +217,7 @@ public sealed class ValueTypeEqualityComparer<T> : EqualityComparer<T> where T :
/// <summary> /// <summary>
/// Equals method for the comparer itself. /// Equals method for the comparer itself.
/// </summary> /// </summary>
public override bool Equals(object? obj) => obj is ReferenceEqualityComparer<T>; public override bool Equals(object? obj) => obj?.GetType() == typeof(ReferenceEqualityComparer<T>);
public override int GetHashCode() => GetType().Name.GetHashCode(); public override int GetHashCode() => GetType().Name.GetHashCode();
} }
@@ -6,14 +6,13 @@
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection.Emit; using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Security; using System.Security;
using System.Threading.Tasks; using System.Threading.Tasks;
using PerformanceFish.Cache; using PerformanceFish.Cache;
using PerformanceFish.ModCompatibility; using PerformanceFish.ModCompatibility;
using ConstructorInfoCache using ConstructorInfoCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags,
PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>, PerformanceFish.System.ReflectionCaching.RecordArray<System.Type>,
PerformanceFish.System.ReflectionCaching.TypePatches.ConstructorInfoCacheValue>; PerformanceFish.System.ReflectionCaching.TypePatches.ConstructorInfoCacheValue>;
using FieldGetters using FieldGetters
= PerformanceFish.Cache.ByReferenceUnclearable<System.RuntimeFieldHandle, = PerformanceFish.Cache.ByReferenceUnclearable<System.RuntimeFieldHandle,
@@ -21,6 +20,9 @@ using FieldGetters
using FieldInfoCache using FieldInfoCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string,
PerformanceFish.System.ReflectionCaching.TypePatches.FieldInfoCacheValue>; PerformanceFish.System.ReflectionCaching.TypePatches.FieldInfoCacheValue>;
using FieldInfosCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags,
PerformanceFish.System.ReflectionCaching.TypePatches.FieldInfosCacheValue>;
using FieldSetters using FieldSetters
= PerformanceFish.Cache.ByReferenceUnclearable<System.RuntimeFieldHandle, = PerformanceFish.Cache.ByReferenceUnclearable<System.RuntimeFieldHandle,
PerformanceFish.System.ReflectionCaching.FieldInfoPatches.FieldInfoSetterCache>; PerformanceFish.System.ReflectionCaching.FieldInfoPatches.FieldInfoSetterCache>;
@@ -29,11 +31,11 @@ using MethodInfoCache
PerformanceFish.System.ReflectionCaching.TypePatches.MethodInfoCacheValue>; PerformanceFish.System.ReflectionCaching.TypePatches.MethodInfoCacheValue>;
using MethodInfoWithTypesAndFlagsCache using MethodInfoWithTypesAndFlagsCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string,
PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>, PerformanceFish.System.ReflectionCaching.RecordArray<System.Type>,
PerformanceFish.System.ReflectionCaching.TypePatches.MethodInfoCacheValue>; PerformanceFish.System.ReflectionCaching.TypePatches.MethodInfoCacheValue>;
using MethodInfoWithTypesCache using MethodInfoWithTypesCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, string, = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, string,
PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>, PerformanceFish.System.ReflectionCaching.RecordArray<System.Type>,
PerformanceFish.System.ReflectionCaching.TypePatches.MethodInfoCacheValue>; PerformanceFish.System.ReflectionCaching.TypePatches.MethodInfoCacheValue>;
using MethodInvokers using MethodInvokers
= PerformanceFish.Cache.ByReferenceUnclearable<System.RuntimeMethodHandle, = PerformanceFish.Cache.ByReferenceUnclearable<System.RuntimeMethodHandle,
@@ -41,9 +43,6 @@ using MethodInvokers
using PropertyInfoCache using PropertyInfoCache
= PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string, = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, System.Reflection.BindingFlags, string,
PerformanceFish.System.ReflectionCaching.TypePatches.PropertyInfoCacheValue>; PerformanceFish.System.ReflectionCaching.TypePatches.PropertyInfoCacheValue>;
using CustomAttributeCache
= PerformanceFish.Cache.ByReference<System.Reflection.ICustomAttributeProvider, System.RuntimeTypeHandle, bool,
PerformanceFish.System.ReflectionCaching.MonoCustomAttrs.CustomAttributeCacheValue>;
using TypeFullNameCache using TypeFullNameCache
= PerformanceFish.Cache.ByReferenceUnclearable<System.RuntimeTypeHandle, = PerformanceFish.Cache.ByReferenceUnclearable<System.RuntimeTypeHandle,
PerformanceFish.System.ReflectionCaching.TypePatches.TypeFullNameCacheValue>; PerformanceFish.System.ReflectionCaching.TypePatches.TypeFullNameCacheValue>;
@@ -54,6 +53,9 @@ using ActivatorCache
// = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle, // = PerformanceFish.Cache.ByReference<System.RuntimeTypeHandle,
// PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>, // PerformanceFish.System.ReflectionCaching.ByValueComparableArray<System.Type>,
// PerformanceFish.System.ReflectionCaching.ActivatorPatches.ActivatorCacheValue>; // PerformanceFish.System.ReflectionCaching.ActivatorPatches.ActivatorCacheValue>;
using CustomAttributeCache
= PerformanceFish.Cache.ByReferenceClassic<System.Reflection.ICustomAttributeProvider, System.RuntimeTypeHandle, bool,
PerformanceFish.System.ReflectionCaching.MonoCustomAttrs.CustomAttributeCacheValue>;
namespace PerformanceFish.System; namespace PerformanceFish.System;
@@ -66,12 +68,17 @@ public sealed class ReflectionCaching : ClassWithFishPatches
ConstructorInfoCache.Initialize(); ConstructorInfoCache.Initialize();
FieldGetters.Initialize(); FieldGetters.Initialize();
FieldInfoCache.Initialize(); FieldInfoCache.Initialize();
FieldInfosCache.Initialize();
FieldSetters.Initialize(); FieldSetters.Initialize();
MethodInfoCache.Initialize(); MethodInfoCache.Initialize();
MethodInfoWithTypesAndFlagsCache.Initialize(); MethodInfoWithTypesAndFlagsCache.Initialize();
MethodInfoWithTypesCache.Initialize(); MethodInfoWithTypesCache.Initialize();
MethodInvokers.Initialize(); MethodInvokers.Initialize();
PropertyInfoCache.Initialize(); PropertyInfoCache.Initialize();
TypeFullNameCache.Initialize();
ActivatorCache.Initialize();
// ActivatorWithArgumentsCache.Initialize();
CustomAttributeCache.Initialize();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -104,16 +111,17 @@ public sealed class ReflectionCaching : ClassWithFishPatches
ToLowerIfNeededForBindingFlags(name, bindingAttr)); ToLowerIfNeededForBindingFlags(name, bindingAttr));
__result = FieldInfoCache.Get.GetOrAdd(ref key).Info; __result = FieldInfoCache.Get.GetOrAdd(ref key).Info;
__state.Flags = bindingAttr;
if (__result is null) if (__result != null)
{ {
__state = new() { State = true, Flags = bindingAttr }; __state.State = false;
return true; return false;
} }
else else
{ {
__state = new() { State = false, Flags = bindingAttr }; __state.State = true;
return false; return true;
} }
} }
@@ -132,6 +140,47 @@ public sealed class ReflectionCaching : ClassWithFishPatches
ToLowerIfNeededForBindingFlags(name, __state.Flags)).Info ToLowerIfNeededForBindingFlags(name, __state.Flags)).Info
= __result; = __result;
} }
public sealed class GetFieldsPatch : FishPatch
{
public override string Description { get; } = "Caches GetFields lookups";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(RuntimeType), nameof(RuntimeType.GetFields), [typeof(BindingFlags)])!;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(Type __instance, BindingFlags bindingAttr, out FieldInfo[]? __result,
out bool __state)
{
var key = new FieldInfosCache(__instance.TypeHandle, bindingAttr);
__result = FieldInfosCache.Get.GetOrAdd(ref key).Info;
if (__result != null)
{
__state = false;
return false;
}
else
{
__state = true;
return true;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Postfix(Type __instance, FieldInfo[] __result, BindingFlags bindingAttr, bool __state)
{
if (!__state)
return;
UpdateCache(__instance, __result, bindingAttr);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static void UpdateCache(Type __instance, FieldInfo[] __result, BindingFlags bindingAttr)
=> FieldInfosCache.GetExistingReference(__instance.TypeHandle, bindingAttr).Info = __result;
}
public sealed class GetMethodWithFlags_Patch : FishPatch public sealed class GetMethodWithFlags_Patch : FishPatch
{ {
@@ -400,6 +449,11 @@ public sealed class ReflectionCaching : ClassWithFishPatches
public FieldInfo? Info; public FieldInfo? Info;
} }
public record struct FieldInfosCacheValue
{
public FieldInfo[]? Info;
}
public record struct MethodInfoCacheValue public record struct MethodInfoCacheValue
{ {
public MethodInfo? Info; public MethodInfo? Info;
@@ -506,33 +560,35 @@ public sealed class ReflectionCaching : ClassWithFishPatches
} }
} }
[StructLayout(LayoutKind.Sequential, Pack = 1)] public record struct RecordArray<T>
public record struct ByValueComparableArray<T>
where T : class where T : class
{ {
public byte Length;
private int _hashCode; private int _hashCode;
public T[] Array; public T[]? Array;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ByValueComparableArray(T[] array) public RecordArray(T[] array)
{ {
Guard.IsNotNull(array); Guard.IsNotNull(array);
Guard.IsLessThanOrEqualTo(array.Length, byte.MaxValue); Guard.IsLessThanOrEqualTo(array.Length, byte.MaxValue);
Array = array; Array = array;
Length = (byte)array.Length; var length = array.Length;
_hashCode = HashCode.Combine((int)Length, Length > 0 && Array[0] is { } item ? item.GetHashCode() : 0); _hashCode = HashCode.Combine(length, length > 0 && Array[0] is { } item ? item.GetHashCode() : 0);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Equals(ByValueComparableArray<T> other) public bool Equals(RecordArray<T> other)
{ {
if (Length != other.Length) if (Array is null)
return other.Array is null;
if (other.Array is null || Array.Length != other.Array.Length)
return false; return false;
for (var i = 0; i < Length; i++) var length = Array.Length;
for (var i = 0; i < length; i++)
{ {
if (!Array[i].Equals(other.Array[i])) if (!Array[i].Equals(other.Array![i]))
return false; return false;
} }
@@ -540,8 +596,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() public override int GetHashCode() => _hashCode;
=> _hashCode;
} }
public sealed class ActivatorPatches public sealed class ActivatorPatches
@@ -744,7 +799,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches
cache.Task = _fieldSetterFailureTask; cache.Task = _fieldSetterFailureTask;
Log.Error($"FieldInfo.SetValue has been called on '{ Log.Error($"FieldInfo.SetValue has been called on '{
info.FullDescription()}'. This is a const that gets copied at compile time. An attempt at " info.FullDescription()}'. This is a const that gets copied at compile time. An attempt at "
+ $"setting a value will always fail.\n{new StackTrace(true)}"); + $"setting a value will always fail.\n{new StackTrace(2, true)}");
return false; return false;
} }
+1 -1
View File
@@ -207,7 +207,7 @@ public sealed class ThingFilterPatches : ClassWithFishPatches
public record struct AllowedDefsListCacheValue() : ICacheable<ThingFilter>, ICacheable<ThingFilter, int> public record struct AllowedDefsListCacheValue() : ICacheable<ThingFilter>, ICacheable<ThingFilter, int>
{ {
public List<ThingDef> Defs = []; public readonly List<ThingDef> Defs = [];
private int _nextRefreshTick = -2; private int _nextRefreshTick = -2;
public bool Dirty public bool Dirty
+38 -2
View File
@@ -48,7 +48,7 @@ public sealed class ThingPatches : ClassWithFishPrepatches
__instance.SetStuffDirect(GenStuff.DefaultStuffFor(def)); __instance.SetStuffDirect(GenStuff.DefaultStuffFor(def));
} }
} }
public sealed class SpawnedPatch : FishPrepatch public sealed class SpawnedPatch : FishPrepatch
{ {
public override string? Description { get; } public override string? Description { get; }
@@ -60,7 +60,7 @@ public sealed class ThingPatches : ClassWithFishPrepatches
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(MiscExtensions.IsSpawned); => ilProcessor.ReplaceBodyWith(MiscExtensions.IsSpawned);
} }
public sealed class MapHeldPatch : FishPrepatch public sealed class MapHeldPatch : FishPrepatch
{ {
public override string? Description { get; } public override string? Description { get; }
@@ -72,4 +72,40 @@ public sealed class ThingPatches : ClassWithFishPrepatches
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(MiscExtensions.TryGetMapHeld); => ilProcessor.ReplaceBodyWith(MiscExtensions.TryGetMapHeld);
} }
public sealed class InitializeCompsFix : FishPrepatch
{
public override string? Description { get; }
= "Improves the error message in ThingWithComps.InitializeComps to include thing, props and mod";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(ThingWithComps), nameof(ThingWithComps.InitializeComps));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
{
var codes = ilProcessor.instructions;
var errorMessageIndex = codes.FirstIndexOf(static code
=> code.Operand is "Could not instantiate or initialize a ThingComp: ");
codes[errorMessageIndex].OpCode = OpCodes.Ldarg_0;
codes[errorMessageIndex].Operand = null;
ilProcessor.InsertRange(errorMessageIndex + 1,
(OpCodes.Ldloca,
ilProcessor.Body.Variables.First(local => local.VariableType == module.TypeSystem.Int32)),
(OpCodes.Call, methodof(MakeErrorMessage)));
}
public static string MakeErrorMessage(ThingWithComps thing, ref int index)
{
var compProps = thing.def.comps;
if (compProps.TryGetItem(index, out var props))
compProps.RemoveAt(index--);
return $"Could not instantiate or initialize a ThingComp with props '{
(props != null! ? props.GetType().FullName : "null")}' on thing '{thing}' from mod '{
thing.def.modContentPack?.Name ?? "null"}': ";
}
}
} }
@@ -35,4 +35,17 @@ public static class CellIndexExtensions
[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;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static List<Thing> ThingsListAtFast(this ThingGrid thingGrid, CellIndex cellIndex)
=> thingGrid.thingGrid[cellIndex.Value];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsFogged(this FogGrid fogGrid, CellIndex cellIndex) => fogGrid.fogGrid[cellIndex.Value];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float GetDepth(this SnowGrid snowGrid, CellIndex cellIndex) => snowGrid.depthGrid[cellIndex.Value];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float GetDepth(this SnowGrid snowGrid, int cellIndex) => snowGrid.depthGrid[cellIndex];
} }
@@ -5,6 +5,8 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace PerformanceFish.Utility; namespace PerformanceFish.Utility;
@@ -308,6 +310,28 @@ public static class CollectionExtensions
} }
public static List<T> AsOrToList<T>(this IEnumerable<T> enumerable) => enumerable as List<T> ?? enumerable.ToList(); public static List<T> AsOrToList<T>(this IEnumerable<T> enumerable) => enumerable as List<T> ?? enumerable.ToList();
public static unsafe ref T GetReference<T>(this NativeArray<T> nativeArray, int index) where T : struct
=> ref Unsafe.AsRef<T>(Unsafe.Add<T>(NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(nativeArray),
index));
public static void UnwrapArray<T>(this List<T> list, out T[] array, out int count)
{
array = list._items;
count = list._size;
Guard.IsLessThanOrEqualTo(count, array.Length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetSize<T>(this List<T> list, int size) => list._size = size;
public static void AddRange<T>(this List<T> list, IndexedFishSet<T> set)
{
list.EnsureCapacity(list._size + set.Count);
set.CopyTo(list._items, list._size);
list._size += set.Count;
list._version++;
}
[DoesNotReturn] [DoesNotReturn]
private static T ThrowForNoElements<T>() private static T ThrowForNoElements<T>()
@@ -5,16 +5,19 @@
using System.Linq; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
#if V1_5
using LudeonTK;
#endif
using UnityEngine.Rendering; using UnityEngine.Rendering;
namespace PerformanceFish.Utility; namespace PerformanceFish.Utility;
public static class PipetteTool public static class DebugActions
{ {
[UsedImplicitly] [UsedImplicitly]
[DebugAction("Misc", "Pipette tool", actionType = DebugActionType.ToolMap, [DebugAction("Misc", "Pipette tool", actionType = DebugActionType.ToolMap,
allowedGameStates = AllowedGameStates.PlayingOnMap)] allowedGameStates = AllowedGameStates.PlayingOnMap)]
public static void Action() public static void PipetteTool()
{ {
var things = UI.MouseCell().GetThingList(Find.CurrentMap).ToList(); var things = UI.MouseCell().GetThingList(Find.CurrentMap).ToList();
things.Sort(static (x, y) => ((int)x.def.altitudeLayer).CompareTo((int)y.def.altitudeLayer)); things.Sort(static (x, y) => ((int)x.def.altitudeLayer).CompareTo((int)y.def.altitudeLayer));
@@ -77,4 +80,11 @@ public static class PipetteTool
private static byte GetAverage(IEnumerable<Color32> colors, Func<Color32, int> selector) private static byte GetAverage(IEnumerable<Color32> colors, Func<Color32, int> selector)
=> (byte)Mathf.RoundToInt((float)colors.Average(selector)); => (byte)Mathf.RoundToInt((float)colors.Average(selector));
#if testing
[UsedImplicitly]
[DebugAction("Misc", "Test ListerThings", actionType = DebugActionType.ToolMap,
allowedGameStates = AllowedGameStates.PlayingOnMap)]
public static void TestListerThings() => Find.CurrentMap.listerThings.Remove(ThingMaker.MakeThing(ThingDefOf.Beer));
#endif
} }
@@ -95,6 +95,9 @@ public static class MiscExtensions
return false; return false;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int SizeX(this Map map) => map.cellIndices.mapSizeX;
public static Map? TryGetMapHeld(this Thing thing) public static Map? TryGetMapHeld(this Thing thing)
{ {
var maps = Current.Game.Maps; var maps = Current.Game.Maps;
@@ -24,4 +24,12 @@ public static class StringExtensions
public static string AppendWhenNotNull(this string instance, object? value) public static string AppendWhenNotNull(this string instance, object? value)
=> value != null ? instance + value : instance; => value != null ? instance + value : instance;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Contains(this string self, string value, StringComparison comparison)
{
Guard.IsNotNull(self);
Guard.IsNotNull(value);
return self.IndexOf(value, comparison) >= 0;
}
} }
+119 -2
View File
@@ -4,7 +4,11 @@
// 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 Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod.Utils;
using PerformanceFish.Prepatching; using PerformanceFish.Prepatching;
using Verse.Steam;
namespace PerformanceFish; namespace PerformanceFish;
@@ -20,8 +24,7 @@ public sealed class UtilityPrepatches : ClassWithFishPrepatches
public override bool ShowSettings => false; public override bool ShowSettings => false;
public override MethodBase TargetMethodBase { get; } public override MethodBase TargetMethodBase { get; } = methodof(LoadedModManager.CreateModClasses);
= methodof(LoadedModManager.CreateModClasses);
public static void Prefix() public static void Prefix()
{ {
@@ -35,4 +38,118 @@ public sealed class UtilityPrepatches : ClassWithFishPrepatches
} }
} }
} }
public sealed class BetterPrinting : FishPrepatch
{
public override string? Description { get; }
= "Attempt at fixing the clipping between textures by slightly adjusting their angle to have the top left "
+ "placed closer to the camera than the bottom right";
public override MethodBase TargetMethodBase { get; } = methodof(Printer_Plane.PrintPlane);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
{
var codes = ilProcessor.instructions;
var altitudeBiasParameter = ilProcessor.GetParameter("topVerticesAltitudeBias");
var sizeParameter = ilProcessor.GetParameter("size");
var altitudeBiasIndex = codes.FirstIndexOf(code => code.Operand == altitudeBiasParameter);
LoadSizeAtAndCall(altitudeBiasIndex, TopLeftAdjustment);
while (codes[++altitudeBiasIndex].Operand != altitudeBiasParameter)
;
LoadSizeAtAndCall(altitudeBiasIndex, TopRightAdjustment);
while (codes[--altitudeBiasIndex].Operand is not 0f)
;
LoadSizeAtAndCall(altitudeBiasIndex, BottomLeftAdjustment);
while (codes[++altitudeBiasIndex].Operand is not 0f)
;
LoadSizeAtAndCall(altitudeBiasIndex, BottomRightAdjustment);
void LoadSizeAtAndCall(int instructionIndex, Delegate method)
{
codes[instructionIndex].OpCode = OpCodes.Ldarga;
codes[instructionIndex].Operand = sizeParameter;
ilProcessor.InsertAt(instructionIndex + 1, (OpCodes.Call, method.Method));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float TopLeftAdjustment(in Vector2 size)
=> (size.y * 0.0045f) + (size.x * 0.0005f);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float TopRightAdjustment(in Vector2 size)
=> (size.y * 0.0045f) - (size.x * 0.0005f);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float BottomLeftAdjustment(in Vector2 size)
=> (size.y * -0.0045f) + (size.x * 0.0005f);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float BottomRightAdjustment(in Vector2 size)
=> (size.y * -0.0045f) - (size.x * 0.0005f);
}
public sealed class NoSteamLogWarning : FishPrepatch
{
public override string? Description { get; }
= "Disables the \"[Steamworks.NET] SteamAPI.Init() failed\" warning. It's duplicated with prepatcher "
+ "anyway";
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(SteamManager), nameof(SteamManager.InitIfNeeded));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
{
var codes = ilProcessor.instructions;
var warningIndex = codes.FirstIndexOf(static code
=> code.Operand is string text && text.Contains("SteamAPI.Init() failed"));
if (warningIndex < 0)
return;
codes[warningIndex].OpCode = OpCodes.Nop;
codes[warningIndex].Operand = null;
codes[++warningIndex].OpCode = OpCodes.Nop;
codes[warningIndex].Operand = null;
}
}
public sealed class NoSteamFailWindow : FishPrepatch
{
public override string? Description { get; }
= "Disables the window that opens when running the steam version of rimworld without steam. Off by default";
public override bool DefaultState => false;
public override MethodBase TargetMethodBase { get; }
= AccessTools.DeclaredMethod(typeof(UIRoot_Entry), nameof(UIRoot_Entry.Init));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
{
var codes = ilProcessor.instructions;
var steamInitializedProperty
= AccessTools.PropertyGetter(typeof(SteamManager), nameof(SteamManager.Initialized));
var instruction = codes.FirstOrDefault(code
=> code.Operand is MethodReference method && method.Is(steamInitializedProperty));
if (instruction is null)
return;
instruction.OpCode = OpCodes.Ldc_I4_1;
instruction.Operand = null;
}
}
} }
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB