Lots of new optimizations utilizing prepatcher
To start, this mod now requires Prepatcher and Fishery. New optimizations include: gene ticking improvements, world object improvements, hediff optimizations, world pawn gc optimizations, better mothballing, dds support, ContentFinder caching, additions and changes to reflection caching, as well as various other additions. Multiplayer support has been improved too, but should be considered highly experimental. The WorkGiver_DoBill bug is fixed in this build. Multiple patches aiming to improve RimWorld's load time have been added too.
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+32
-2
@@ -15,13 +15,43 @@
|
||||
<li>Ludeon.RimWorld.Ideology</li>
|
||||
<li>Ludeon.RimWorld.Biotech</li>
|
||||
<li>UnlimitedHugs.HugsLib</li>
|
||||
<li>OskarPotocki.VanillaFactionsExpanded.Core</li>
|
||||
<li>Taranchuk.PerformanceOptimizer</li>
|
||||
</loadBefore>
|
||||
<loadAfter>
|
||||
<li>brrainz.harmony</li>
|
||||
<li>me.samboycoding.betterloading</li>
|
||||
<li>zetrith.prepatcher</li>
|
||||
<li>brrainz.harmony</li>
|
||||
<li>bs.fishery</li>
|
||||
<li>me.samboycoding.betterloading</li>
|
||||
<li>Ludeon.RimWorld</li>
|
||||
<li>bs.betterlog</li>
|
||||
</loadAfter>
|
||||
<modDependencies>
|
||||
<li>
|
||||
<packageId>brrainz.harmony</packageId>
|
||||
<displayName>Harmony</displayName>
|
||||
<steamWorkshopUrl>steam://url/CommunityFilePage/2009463077</steamWorkshopUrl>
|
||||
<downloadUrl>https://github.com/pardeike/HarmonyRimWorld/releases/latest</downloadUrl>
|
||||
</li>
|
||||
<li>
|
||||
<packageId>zetrith.prepatcher</packageId>
|
||||
<displayName>Prepatcher</displayName>
|
||||
<steamWorkshopUrl>steam://url/CommunityFilePage/2934420800</steamWorkshopUrl>
|
||||
<downloadUrl>https://github.com/Zetrith/Prepatcher</downloadUrl>
|
||||
</li>
|
||||
<li>
|
||||
<packageId>bs.fishery</packageId>
|
||||
<displayName>Fishery</displayName>
|
||||
<steamWorkshopUrl>n/a</steamWorkshopUrl>
|
||||
<downloadUrl>https://github.com/bbradson/Fishery</downloadUrl>
|
||||
</li>
|
||||
</modDependencies>
|
||||
<incompatibleWith>
|
||||
<li>majorhoff.rimthreaded</li> <!-- bad threading -->
|
||||
<li>telardo.NoLaggyBed</li> <!-- overlap in patches -->
|
||||
<li>GwinnBleidd.MothballedAndDeadPawns</li> <!-- redundant -->
|
||||
<li>VSE.OskarObnoxious</li> <!-- crashes reported -->
|
||||
<li>vanillaexpanded.storytellers.rick</li> <!-- sussy -->
|
||||
</incompatibleWith>
|
||||
<description>download more tps</description>
|
||||
</ModMetaData>
|
||||
+45
-2
@@ -118,7 +118,17 @@
|
||||
</NestedTypes-->
|
||||
</PerformanceFish>
|
||||
|
||||
<ThingComps>
|
||||
<ThingCompTick>
|
||||
<Tooltip>Tick methods of comps attached to Things. Any ThingWithComps can have these, and any mod can add new ones</Tooltip>
|
||||
<Category>Tick</Category>
|
||||
<DerivedMethods>
|
||||
<li>Verse.ThingComp:CompTick</li>
|
||||
<li>Verse.ThingComp:CompTickRare</li>
|
||||
<li>Verse.ThingComp:CompTickLong</li>
|
||||
</DerivedMethods>
|
||||
</ThingCompTick>
|
||||
|
||||
<ThingCompUpdate>
|
||||
<Tooltip>Update methods of comps attached to Things. Any ThingWithComps can have these, and any mod can add new ones</Tooltip>
|
||||
<Category>Update</Category>
|
||||
<DerivedMethods>
|
||||
@@ -127,7 +137,7 @@
|
||||
<li>Verse.ThingComp:PostDrawExtraSelectionOverlays</li>
|
||||
<li>Verse.ThingComp:PostPrintOnto</li>
|
||||
</DerivedMethods>
|
||||
</ThingComps>
|
||||
</ThingCompUpdate>
|
||||
|
||||
<Hediffs>
|
||||
<Tooltip>Ticking health stuff ond other things implanted in pawn bodies</Tooltip>
|
||||
@@ -138,6 +148,31 @@
|
||||
</DerivedMethods>
|
||||
</Hediffs>
|
||||
|
||||
<Abilities>
|
||||
<Tooltip>Ticking methods of abilities. Cooldown, effects, combat</Tooltip>
|
||||
<Category>Tick</Category>
|
||||
<DerivedMethods>
|
||||
<li>RimWorld.Ability:AbilityTick</li>
|
||||
<li MayRequire="OskarPotocki.VanillaFactionsExpanded.Core">VFECore.Abilities.Ability:Tick</li>
|
||||
</DerivedMethods>
|
||||
</Abilities>
|
||||
|
||||
<Precepts>
|
||||
<Tooltip>Ticking methods of precepts. Ideology stuff.</Tooltip>
|
||||
<Category>Tick</Category>
|
||||
<DerivedMethods>
|
||||
<li MayRequire="Ludeon.RimWorld.Ideology">RimWorld.Precept:Tick</li>
|
||||
</DerivedMethods>
|
||||
</Precepts>
|
||||
|
||||
<Genes>
|
||||
<Tooltip>Ticking methods of genes. Yes, all the genes tick.</Tooltip>
|
||||
<Category>Tick</Category>
|
||||
<DerivedMethods>
|
||||
<li MayRequire="Ludeon.RimWorld.Biotech">Verse.Gene:Tick</li>
|
||||
</DerivedMethods>
|
||||
</Genes>
|
||||
|
||||
<HediffComps>
|
||||
<Tooltip>Comps attached to Hediffs. Often responsible for the actual logic</Tooltip>
|
||||
<Category>Tick</Category>
|
||||
@@ -146,6 +181,14 @@
|
||||
</DerivedMethods>
|
||||
</HediffComps>
|
||||
|
||||
<AbilityComps>
|
||||
<Tooltip>Comps attached to abilities</Tooltip>
|
||||
<Category>Tick</Category>
|
||||
<DerivedMethods>
|
||||
<li>RimWorld.AbilityComp:CompTick</li>
|
||||
</DerivedMethods>
|
||||
</AbilityComps>
|
||||
|
||||
<WorldObjects>
|
||||
<Tooltip>Settlements, caravans etc</Tooltip>
|
||||
<Category>Tick</Category>
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||

|
||||
|
||||
Performance Mod for Rimworld.
|
||||
Requires Prepatcher and Fishery.
|
||||
|
||||
Performance Fish attempts to improve overall framerates and tick times by patching various methods for improved efficiency, while keeping functionality identical. Designed to be used alongside other performance mods, like RocketMan, and intended to be compatible with very large modlists. Most patches become more impactful as the game progresses further into the lategame.
|
||||
|
||||
A settings menu includes short descriptions for every patch, and each of them can be freely toggled in there. They get entirely unpatched in a disabled state, immediately. Nothing is stored in savefiles of specific game-sessions, ensuring that no errors get thrown when removing this mod.
|
||||
Special patches exist to add new entries and features to Dub's Performance Analyzer, including a right-click function allowing to profile overrides of functions.
|
||||
|
||||
Multiplayer and RimThreaded currently aren't supported. All other mods, including Combat Extended, Vanilla Expanded, RocketMan and Performance Optimizer, are compatible.
|
||||
Almost all mods, including Combat Extended, Vanilla Expanded, RocketMan and Performance Optimizer, are compatible.
|
||||
Multiplayer needs testing.
|
||||
RimThreaded, RimWorld Rick, Oskar Obnoxious, No Laggy Beds and Better GC are currently marked as incompatible.
|
||||
|
||||
Licensed under [MPL-2.0](https://tldrlegal.com/license/mozilla-public-license-2.0-(mpl-2))
|
||||
@@ -58,8 +58,30 @@
|
||||
</Choose>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lib.Harmony" Version="2.*" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.*" />
|
||||
<PackageReference Include="Krafs.Publicizer" 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" />
|
||||
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
|
||||
<PackageReference Include="xunit" Version="2.*" />
|
||||
<PackageReference Include="xunit.analyzers" Version="1.*" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.*">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
||||
@@ -2,34 +2,242 @@ global using System;
|
||||
global using System.Collections.Generic;
|
||||
global using Xunit;
|
||||
global using Xunit.Abstractions;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
using System.Security.Permissions;
|
||||
using System.Threading;
|
||||
using FisheryLib;
|
||||
using HarmonyLib;
|
||||
using JetBrains.Annotations;
|
||||
using PerformanceFish.Collections;
|
||||
using Verse;
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
//[assembly: AllowPartiallyTrustedCallers]
|
||||
//[assembly: SecurityTransparent]
|
||||
[assembly: SecurityRules(SecurityRuleSet.Level2, SkipVerificationInFullTrust = true)]
|
||||
[assembly: Debuggable(false, false)]
|
||||
|
||||
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
|
||||
[assembly: SecurityCritical(SecurityCriticalScope.Everything)]
|
||||
|
||||
namespace PerformanceFish.Tests;
|
||||
|
||||
[UsedImplicitly]
|
||||
public class ReflectionCaching
|
||||
{
|
||||
internal static void InitializeLog(ITestOutputHelper output)
|
||||
{
|
||||
Log.Config.Message = output.WriteLine;
|
||||
Log.Config.Warning = output.WriteLine;
|
||||
Log.Config.Error = message => throw new(message);
|
||||
Log.Config.Message = output.WriteLine; // message => output.WriteLine("\u001b[33m" + message);
|
||||
Log.Config.Warning = output.WriteLine; // message => output.WriteLine("\u001b[33m" + message);
|
||||
Log.Config.Error = static message => throw new(message);
|
||||
}
|
||||
|
||||
// [Collection("Fish")]
|
||||
// public class FishTableTest
|
||||
// {
|
||||
// public FishTableTest(ITestOutputHelper output) => InitializeLog(output);
|
||||
//
|
||||
// [Fact]
|
||||
// public void Test()
|
||||
// {
|
||||
// const int COUNT = 538762;
|
||||
//
|
||||
// var randomBuffer = new byte[4 * COUNT];
|
||||
// new Random(42).NextBytes(randomBuffer);
|
||||
// var randomNumbers = new int[COUNT];
|
||||
// Unsafe.CopyBlock(ref randomBuffer[0], ref Unsafe.As<int, byte>(ref randomNumbers[0]), 4 * COUNT);
|
||||
//
|
||||
// var fishTable = new FishTable<int, int>();
|
||||
// foreach (var i in randomNumbers)
|
||||
// {
|
||||
// fishTable.GetOrAddReference(i) = -i;
|
||||
// }
|
||||
//
|
||||
// foreach (var i in randomNumbers)
|
||||
// {
|
||||
// Assert.Equal(-i, fishTable.GetOrAddReference(i));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#if false
|
||||
[Collection("Fish")]
|
||||
public class FishTableTest2
|
||||
{
|
||||
public FishTableTest2(ITestOutputHelper output) => InitializeLog(output);
|
||||
|
||||
[Fact]
|
||||
public void Test()
|
||||
{
|
||||
var allTypes = GenTypes.AllTypes.ToArray();
|
||||
var allTypeHandles = Array.ConvertAll(allTypes, static type => type.TypeHandle);
|
||||
var hashCodes = Array.ConvertAll(allTypes, static type => type.TypeHandle.GetHashCode());
|
||||
|
||||
var hashCodeCounts = new Dictionary<int, uint>();
|
||||
foreach (var code in hashCodes)
|
||||
hashCodeCounts.GetOrAddReference(code)++;
|
||||
|
||||
var count = allTypes.Length;
|
||||
|
||||
Log.Message($"allTypes: {count}, hashCodes: {hashCodeCounts.Count}, max: {
|
||||
hashCodeCounts.Max(static pair => pair.Value)}{Environment.NewLine}");
|
||||
|
||||
var random = new Random(42);
|
||||
var randomNumbers = new int[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var randomNumber = random.Next();
|
||||
|
||||
while (randomNumber == 0)
|
||||
randomNumber = random.Next();
|
||||
|
||||
randomNumbers[i] = randomNumber;
|
||||
}
|
||||
|
||||
var success = true;
|
||||
|
||||
var fishTable = new FishTable<IntPtr, int>();
|
||||
var addedEntries = 0;
|
||||
|
||||
fishTable.EntryAdded += _ => addedEntries++;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
Exception caughtException = null;
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
fishTable.GetOrAddReference(allTypes[i].TypeHandle.Value) = randomNumbers[i];
|
||||
|
||||
try
|
||||
{
|
||||
VerifyCount(fishTable, i + 1);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
caughtException = e;
|
||||
|
||||
VerifyNumbers(randomNumbers, allTypeHandles, fishTable, i);
|
||||
|
||||
throw;
|
||||
}
|
||||
VerifyNumbers(randomNumbers, allTypeHandles, fishTable, i);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new($"Inserted number: {randomNumbers[i]}, Retrieved number: {
|
||||
fishTable.GetOrAddReference(allTypes[i].TypeHandle.Value)} {e}\n\n{caughtException}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning($"{ex}");
|
||||
Log.Message(Environment.NewLine);
|
||||
success = false;
|
||||
|
||||
// throw;
|
||||
}
|
||||
}
|
||||
|
||||
var failureCount = 0;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
Assert.Equal(randomNumbers[i], fishTable.GetOrAddReference(allTypes[i].TypeHandle.Value));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
failureCount++;
|
||||
|
||||
Log.Warning($"{ex}");
|
||||
Log.Message(Environment.NewLine);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
Log.Message($"FailureCount: {failureCount}");
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
Assert.True(hashCodes[i].Equals<int>(allTypes[i].TypeHandle.GetHashCode()));
|
||||
}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
Assert.True(allTypeHandles[i].Equals<RuntimeTypeHandle>(allTypes[i].TypeHandle));
|
||||
}
|
||||
|
||||
Assert.True(success);
|
||||
|
||||
VerifyCount(fishTable, allTypeHandles.Length);
|
||||
|
||||
Assert.Equal(count, addedEntries);
|
||||
}
|
||||
|
||||
private static void VerifyNumbers(int[] expectedNumbers, RuntimeTypeHandle[] types,
|
||||
FishTable<IntPtr, int> fishTable, int count)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var number = expectedNumbers[i];
|
||||
var type = types[i];
|
||||
|
||||
try
|
||||
{
|
||||
Assert.True(number.Equals<int>(fishTable[type.Value]));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new($"number: {number}, hashCode: {type.GetHashCode()}, i: {i}, size: {
|
||||
fishTable.Count} {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void VerifyCount(FishTable<IntPtr, int> fishTable, int expectedCount)
|
||||
{
|
||||
var realCount = 0;
|
||||
var returnedCount = fishTable.Count;
|
||||
|
||||
foreach (var _ in fishTable)
|
||||
{
|
||||
realCount++;
|
||||
}
|
||||
|
||||
if (realCount != returnedCount)
|
||||
{
|
||||
throw new($"Invalid count! real: {realCount}, returned: {returnedCount}, expected: {
|
||||
expectedCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
[Collection("Fish")]
|
||||
public class GetValue
|
||||
{
|
||||
public GetValue(ITestOutputHelper output) => InitializeLog(output);
|
||||
|
||||
private const int COUNT = 500;
|
||||
private const int COUNT = 50000;
|
||||
|
||||
public static IEnumerable<object[]> Data
|
||||
=> new object[][]
|
||||
=> new[]
|
||||
{
|
||||
new[] { nameof(_enum) },
|
||||
new[] { nameof(_nullableWithoutValue) },
|
||||
new[] { nameof(_nullableWithValue) },
|
||||
new[] { nameof(_primitive) },
|
||||
new[] { nameof(_reference) },
|
||||
new[] { nameof(CONST) }
|
||||
new object[] { nameof(_enum) },
|
||||
new object[] { nameof(_nullableWithoutValue) },
|
||||
new object[] { nameof(_nullableWithValue) },
|
||||
new object[] { nameof(_primitive) },
|
||||
new object[] { nameof(_reference) },
|
||||
new object[] { nameof(CONST) }
|
||||
};
|
||||
|
||||
[Theory]
|
||||
@@ -48,9 +256,9 @@ public class ReflectionCaching
|
||||
object result = null;
|
||||
|
||||
for (var i = 0; i < COUNT; i++)
|
||||
System.ReflectionCaching.FieldInfoPatches.GetValue_Patch.Prefix(field, null, ref result);
|
||||
System.ReflectionCaching.Test.GetValue(field, null, ref result);
|
||||
|
||||
Assert.Equal(result, field.GetValue(null));
|
||||
Assert.Equal(field.GetValue(null), result);
|
||||
}
|
||||
|
||||
private static TypeCode _enum = TypeCode.Int32;
|
||||
@@ -62,6 +270,7 @@ public class ReflectionCaching
|
||||
private static Type _type = typeof(GetValue);
|
||||
}
|
||||
|
||||
[Collection("Fish")]
|
||||
public class SetValue
|
||||
{
|
||||
public SetValue(ITestOutputHelper output) => InitializeLog(output);
|
||||
@@ -69,7 +278,7 @@ public class ReflectionCaching
|
||||
private const int COUNT = 500;
|
||||
|
||||
public static IEnumerable<object[]> Data
|
||||
=> new object[][]
|
||||
=> new[]
|
||||
{
|
||||
new object[] { nameof(_enum), TypeCode.Byte },
|
||||
new object[] { nameof(_nullableWithoutValue), 1 },
|
||||
@@ -86,11 +295,87 @@ public class ReflectionCaching
|
||||
Assert.NotNull(field);
|
||||
|
||||
for (var i = 0; i < COUNT; i++)
|
||||
System.ReflectionCaching.FieldInfoPatches.SetValue_Patch.Prefix(field, null, input);
|
||||
System.ReflectionCaching.Test.SetValue(field, null, input);
|
||||
|
||||
Assert.Equal(input, field.GetValue(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test2()
|
||||
{
|
||||
SimpleThingDefOf.Initialize();
|
||||
var def = SimpleThingDefOf.Instance;
|
||||
|
||||
Assert.NotNull(def.Description);
|
||||
a(def, "descriptionDetailedCached", null);
|
||||
Assert.Null(def.Description);
|
||||
|
||||
Assert.NotEqual(default, def.Label);
|
||||
a(def, "cachedLabelCap", null);
|
||||
Assert.Equal(default, def.Label);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test3()
|
||||
{
|
||||
for (var i = 0; i < COUNT; i++)
|
||||
{
|
||||
SimpleThingDefOf.Initialize();
|
||||
var def = SimpleThingDefOf.Instance;
|
||||
|
||||
Assert.NotNull(def.Description);
|
||||
a_Optimized(def, "descriptionDetailedCached", null);
|
||||
Assert.Null(def.Description);
|
||||
|
||||
Assert.NotEqual(default, def.Label);
|
||||
a_Optimized(def, "cachedLabelCap", null);
|
||||
Assert.Equal(default, def.Label);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test4()
|
||||
{
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
_primitive = 0;
|
||||
var field = GetType().GetField(nameof(_primitive), AccessTools.all);
|
||||
|
||||
IntPtr value = (nint)(-476);
|
||||
|
||||
var failed = false;
|
||||
try
|
||||
{
|
||||
if (i == 0)
|
||||
field.SetValue(null, value);
|
||||
else
|
||||
System.ReflectionCaching.Test.SetValue(field, null, value);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
failed = true;
|
||||
Log.Message(e + "\n");
|
||||
}
|
||||
|
||||
Assert.True(failed);
|
||||
|
||||
// Assert.Equal(-476, field.GetValue(null));
|
||||
}
|
||||
}
|
||||
|
||||
private static void a(object A_0, string A_1, object A_2)
|
||||
=> (A_0?.GetType().GetField(A_1, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
||||
?.SetValue(A_0, A_2);
|
||||
|
||||
private static void a_Optimized(object A_0, string A_1, object A_2)
|
||||
{
|
||||
if (A_0?.GetType().GetField(A_1, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
is { } field)
|
||||
{
|
||||
System.ReflectionCaching.Test.SetValue(field, A_0, A_2);
|
||||
}
|
||||
}
|
||||
|
||||
private static TypeCode _enum = TypeCode.Int32;
|
||||
private static int? _nullableWithoutValue = null;
|
||||
private static int? _nullableWithValue = 1;
|
||||
@@ -98,4 +383,138 @@ public class ReflectionCaching
|
||||
private static object _reference = new();
|
||||
private static Type _type = typeof(SetValue);
|
||||
}
|
||||
|
||||
public class SimpleThingDef
|
||||
{
|
||||
private string descriptionDetailedCached;
|
||||
private TaggedString cachedLabelCap;
|
||||
|
||||
public string Description => descriptionDetailedCached;
|
||||
|
||||
public TaggedString Label
|
||||
{
|
||||
get => cachedLabelCap;
|
||||
set => cachedLabelCap = value;
|
||||
}
|
||||
|
||||
public SimpleThingDef(string description, string label)
|
||||
{
|
||||
descriptionDetailedCached = description;
|
||||
cachedLabelCap = label;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SimpleThingDefOf
|
||||
{
|
||||
public static SimpleThingDef Instance;
|
||||
|
||||
public static void Initialize() => Instance = new("InitialDescription", "InitialLabel");
|
||||
}
|
||||
|
||||
[Collection("Fish")]
|
||||
public class Invoke
|
||||
{
|
||||
public Invoke(ITestOutputHelper output) => InitializeLog(output);
|
||||
|
||||
private const int COUNT = 500;
|
||||
|
||||
public static IEnumerable<object[]> Data
|
||||
=> new[]
|
||||
{
|
||||
new object[]
|
||||
{
|
||||
AccessTools.Method(typeof(Scribe_Values), nameof(Scribe_Values.Look),
|
||||
generics: new[] { typeof(string) }),
|
||||
new object[] { "", "", "", false }
|
||||
}
|
||||
};
|
||||
|
||||
// [Theory]
|
||||
// [MemberData(nameof(Data))]
|
||||
// public void Test(MethodBase method, object[] input)
|
||||
// {
|
||||
// Assert.NotNull(method);
|
||||
//
|
||||
// object result = null;
|
||||
//
|
||||
// for (var i = 0; i < COUNT; i++)
|
||||
// System.ReflectionCaching.Test.Invoke(method, null, input, ref result);
|
||||
// }
|
||||
|
||||
[Fact]
|
||||
public void TestString()
|
||||
{
|
||||
var method = AccessTools.Method(GetType(), nameof(Look), generics: new[] { typeof(string) });
|
||||
|
||||
var value = "Input";
|
||||
var label = "Label";
|
||||
var defaultValue = "DefaultValue";
|
||||
var forceSave = true;
|
||||
|
||||
var parameters = new object[] { value, label, defaultValue, forceSave };
|
||||
|
||||
var input = MakeString(value, label, defaultValue, forceSave);
|
||||
|
||||
object output = null;
|
||||
|
||||
System.ReflectionCaching.Test.Invoke(method, null, parameters, ref output);
|
||||
|
||||
Assert.Equal(input, output);
|
||||
Assert.Equal("Test", parameters[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestIntOptimized()
|
||||
{
|
||||
var method = AccessTools.Method(GetType(), nameof(Look), generics: new[] { typeof(int) });
|
||||
|
||||
var value = 5;
|
||||
var label = "Label";
|
||||
var defaultValue = 0;
|
||||
var forceSave = false;
|
||||
|
||||
var parameters = new object[] { value, label, defaultValue, forceSave };
|
||||
|
||||
var input = MakeString(value, label, defaultValue, forceSave);
|
||||
|
||||
object output = null;
|
||||
|
||||
System.ReflectionCaching.Test.Invoke(method, null, parameters, ref output);
|
||||
|
||||
Assert.Equal(input, output);
|
||||
Assert.Equal(17, parameters[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestIntExpected()
|
||||
{
|
||||
var value = 5;
|
||||
var label = "Label";
|
||||
var defaultValue = 0;
|
||||
var forceSave = false;
|
||||
|
||||
var input = MakeString(value, label, defaultValue, forceSave);
|
||||
|
||||
var output = Look(ref value, label, defaultValue, forceSave);
|
||||
|
||||
Assert.Equal(input, output);
|
||||
Assert.Equal(17, value);
|
||||
}
|
||||
|
||||
private static string Look<T>(ref T value, string label, T defaultValue = default, params object[] ctorArgs)
|
||||
{
|
||||
var input = MakeString(value, label, defaultValue, ctorArgs);
|
||||
|
||||
if (typeof(string).IsAssignableFrom(typeof(T)))
|
||||
value = (T)(object)"Test";
|
||||
else if (typeof(int).IsAssignableFrom(typeof(T)))
|
||||
value = (T)(object)17;
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private static string MakeString<T>(T value, string label, T defaultValue, params object[] ctorArgs)
|
||||
=> $"value: {value}, label: {label}, defaultValue: {defaultValue}, ctorArgs: {
|
||||
ctorArgs.ToStringSafeEnumerable()}";
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[*.cs]
|
||||
|
||||
file_header_template = Copyright (c) 2022 bradson\nThis Source Code Form is subject to the terms of the Mozilla Public\nLicense, v. 2.0. If a copy of the MPL was not distributed with this\nfile, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
file_header_template = Copyright (c) 2023 bradson\nThis Source Code Form is subject to the terms of the Mozilla Public\nLicense, v. 2.0. If a copy of the MPL was not distributed with this\nfile, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
# CA2211: Non-constant fields should not be visible
|
||||
dotnet_diagnostic.CA2211.severity = none
|
||||
@@ -126,7 +126,7 @@ dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity =
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class AccessToolsCaching : ClassWithFishPatches
|
||||
{
|
||||
public class AllTypes : FishPatch
|
||||
{
|
||||
public override string? Description { get; } = "Caches AccessTools.AllTypes to only recalculate when the "
|
||||
+ "loaded assembly count changes. Improves load times.";
|
||||
|
||||
public override Delegate? TargetMethodGroup { get; } = AccessTools.AllTypes;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(ref IEnumerable<Type> __result, out bool __state)
|
||||
{
|
||||
var currentAssemblyCount = AppDomain.CurrentDomain.GetAssemblies().Length;
|
||||
if (currentAssemblyCount != _cachedAssemblyCount)
|
||||
{
|
||||
_cachedAssemblyCount = currentAssemblyCount;
|
||||
return __state = true;
|
||||
}
|
||||
|
||||
__result = _cachedTypes;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(ref IEnumerable<Type> __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
UpdateCache(ref __result);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void UpdateCache(ref IEnumerable<Type> __result) => __result = _cachedTypes = __result.ToArray();
|
||||
|
||||
private static Type[] _cachedTypes = Type.EmptyTypes;
|
||||
private static int _cachedAssemblyCount;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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/.
|
||||
@@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using System.Xml;
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
@@ -14,22 +15,27 @@ public static class AnalyzerFixes
|
||||
{
|
||||
public static void Patch()
|
||||
{
|
||||
var modBase = AccessTools.Constructor(Reflection.Type("PerformanceAnalyzer", "Analyzer.Modbase"), new[] { typeof(ModContentPack) });
|
||||
var modBase = AccessTools.Constructor(Reflection.Type("PerformanceAnalyzer", "Analyzer.Modbase"),
|
||||
new[] { typeof(ModContentPack) });
|
||||
if (modBase is null)
|
||||
return /*false*/;
|
||||
|
||||
var success = PerformanceFishMod.Harmony.Patch(modBase, postfix: new(methodof(XmlParser.CollectXmlData))) != null;
|
||||
var success = PerformanceFishMod.Harmony!.Patch(modBase, postfix: new(methodof(XmlParser.CollectXmlData)))
|
||||
!= null;
|
||||
|
||||
var methodTransplanting = Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.MethodTransplanting", "UpdateMethod",
|
||||
var methodTransplanting = Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.MethodTransplanting",
|
||||
"UpdateMethod",
|
||||
new[] { typeof(Type), typeof(MethodInfo) });
|
||||
|
||||
if (methodTransplanting is null
|
||||
|| PerformanceFishMod.Harmony.Patch(methodTransplanting, transpiler: new(methodof(UpdateMethod_Transpiler))) is null)
|
||||
|| PerformanceFishMod.Harmony.Patch(methodTransplanting, transpiler: new(methodof(UpdateMethod_Transpiler)))
|
||||
is null)
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
|
||||
var panelLogs = Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Panel_Logs", "RightClickDropDown");
|
||||
var panelLogs
|
||||
= Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Panel_Logs", "RightClickDropDown");
|
||||
|
||||
if (panelLogs is null
|
||||
|| PerformanceFishMod.Harmony.Patch(panelLogs, postfix: new(methodof(PanelLogs_Postfix))) is null)
|
||||
@@ -47,15 +53,23 @@ public static class AnalyzerFixes
|
||||
{
|
||||
yield return FishTranspiler.ArgumentAddress(method, "meth");
|
||||
yield return FishTranspiler.Call(UpdateMethod_Prefix);
|
||||
|
||||
foreach (var code in codes)
|
||||
yield return code;
|
||||
}
|
||||
|
||||
public static void UpdateMethod_Prefix(ref MethodInfo meth)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (AccessTools.EnumeratorMoveNext(meth) is { } moveNext)
|
||||
meth = moveNext;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Exception thrown while trying to profile method {meth.FullDescription()}:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<FloatMenuOption> PanelLogs_Postfix(IEnumerable<FloatMenuOption> values, object log)
|
||||
{
|
||||
@@ -64,6 +78,7 @@ public static class AnalyzerFixes
|
||||
foreach (var value in values)
|
||||
{
|
||||
yield return value;
|
||||
|
||||
if (IsValidMethodForOverriding(method) && !returned && value.Label == "Profile the internal methods of")
|
||||
{
|
||||
returned = true;
|
||||
@@ -86,12 +101,16 @@ public static class AnalyzerFixes
|
||||
Modder
|
||||
}
|
||||
|
||||
public static void PatchOverridesForMethod(MethodInfo method, Category category)
|
||||
public static void PatchOverridesForMethod(MethodInfo? method, Category category)
|
||||
{
|
||||
if (method is null)
|
||||
return;
|
||||
|
||||
if (OverridingMethodUtility.PatchedOverrides.Contains(method))
|
||||
{
|
||||
var signature = Utility_GetSignature(method, true);
|
||||
Messages.Message("Have already patched overrides for the method " + signature, MessageTypeDefOf.CautionInput, false);
|
||||
Messages.Message("Have already patched overrides for the method " + signature,
|
||||
MessageTypeDefOf.CautionInput, false);
|
||||
Utility_Warn("Trying to repeat patching already profiled overrides of method - " + signature);
|
||||
}
|
||||
else
|
||||
@@ -141,7 +160,8 @@ public static class AnalyzerFixes
|
||||
|
||||
foreach (var subClass in subClasses)
|
||||
{
|
||||
var overridingMethod = Array.Find(subClass.GetMethods(AccessTools.allDeclared), m => m.GetBaseDefinition() == method.GetBaseDefinition());
|
||||
var overridingMethod = Array.Find(subClass.GetMethods(AccessTools.allDeclared),
|
||||
m => m.GetBaseDefinition() == method.GetBaseDefinition());
|
||||
if (overridingMethod is null || overridingMethod.IsAbstract)
|
||||
continue;
|
||||
|
||||
@@ -159,55 +179,70 @@ public static class AnalyzerFixes
|
||||
|
||||
public static AccessTools.FieldRef<object, MethodBase> ProfileLog_meth
|
||||
=> _profileLog_meth
|
||||
??= AccessTools.FieldRefAccess<object, MethodBase>(Reflection.FieldInfo("PerformanceAnalyzer", "Analyzer.Profiling.ProfileLog", "meth"));
|
||||
??= AccessTools.FieldRefAccess<object, MethodBase>(Reflection.FieldInfo("PerformanceAnalyzer",
|
||||
"Analyzer.Profiling.ProfileLog", "meth"));
|
||||
|
||||
private static AccessTools.FieldRef<object, MethodBase>? _profileLog_meth;
|
||||
|
||||
public static Func<Harmony> Modbase_Harmony
|
||||
=> _modbase_Harmony
|
||||
??= (Func<Harmony>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Modbase", "get_Harmony")
|
||||
??= (Func<Harmony>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Modbase", "get_Harmony")!
|
||||
.CreateDelegate(typeof(Func<Harmony>));
|
||||
|
||||
private static Func<Harmony>? _modbase_Harmony;
|
||||
|
||||
public static Action<string, int> GUIController_AddEntry
|
||||
=> _guiController_AddEntry
|
||||
??= (Action<string, int>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "AddEntry")
|
||||
??= (Action<string, int>)Reflection
|
||||
.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "AddEntry")!
|
||||
.CreateDelegate(typeof(Action<string, int>));
|
||||
|
||||
private static Action<string, int>? _guiController_AddEntry;
|
||||
|
||||
public static Action<string> GUIController_SwapToEntry
|
||||
=> _guiController_SwapToEntry
|
||||
??= (Action<string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "SwapToEntry")
|
||||
??= (Action<string>)Reflection
|
||||
.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "SwapToEntry")!
|
||||
.CreateDelegate(typeof(Action<string>));
|
||||
|
||||
private static Action<string>? _guiController_SwapToEntry;
|
||||
|
||||
public static Func<MethodBase, bool, string> Utility_GetSignature
|
||||
=> _utility_GetSignature
|
||||
??= (Func<MethodBase, bool, string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "GetSignature")
|
||||
??= (Func<MethodBase, bool, string>)Reflection
|
||||
.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "GetSignature")!
|
||||
.CreateDelegate(typeof(Func<MethodBase, bool, string>));
|
||||
|
||||
private static Func<MethodBase, bool, string>? _utility_GetSignature;
|
||||
|
||||
public static Func<int> GUIController_CurrentCategory
|
||||
=> _guiController_CurrentCategory
|
||||
??= (Func<int>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "get_CurrentCategory")
|
||||
??= (Func<int>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController",
|
||||
"get_CurrentCategory")!
|
||||
.CreateDelegate(typeof(Func<int>));
|
||||
|
||||
private static Func<int>? _guiController_CurrentCategory;
|
||||
|
||||
public static Action<string> Utility_Warn
|
||||
=> _utility_Warn
|
||||
??= (Action<string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "Warn")
|
||||
??= (Action<string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "Warn")!
|
||||
.CreateDelegate(typeof(Action<string>));
|
||||
|
||||
private static Action<string>? _utility_Warn;
|
||||
|
||||
public static Action<Exception, string> Utility_ReportException
|
||||
=> _utility_ReportException
|
||||
??= (Action<Exception, string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "ReportException")
|
||||
??= (Action<Exception, string>)Reflection
|
||||
.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "ReportException")!
|
||||
.CreateDelegate(typeof(Action<Exception, string>));
|
||||
|
||||
private static Action<Exception, string>? _utility_ReportException;
|
||||
|
||||
public static Action<string> Utility_Error
|
||||
=> _utility_Error
|
||||
??= (Action<string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "Error")
|
||||
??= (Action<string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "Error")!
|
||||
.CreateDelegate(typeof(Action<string>));
|
||||
|
||||
private static Action<string>? _utility_Error;
|
||||
|
||||
public static CodeInstructions Parse_Transpiler(CodeInstructions CodeInstructions)
|
||||
@@ -222,7 +257,9 @@ public static class AnalyzerFixes
|
||||
{
|
||||
yield return new(codes[i - 2]); //Ldloc xmlNode
|
||||
yield return FishTranspiler.Call(ParseMethod_Fixed);
|
||||
yield return FishTranspiler.Call<Action<HashSet<MethodInfo>, IEnumerable<MethodInfo>>>(GenCollection.AddRange);
|
||||
yield return FishTranspiler.Call<Action<HashSet<MethodInfo>, IEnumerable<MethodInfo>>>(GenCollection
|
||||
.AddRange);
|
||||
|
||||
i += 2; //removing OpCodes.Pop
|
||||
}
|
||||
else if (codes[i] == errorMethod)
|
||||
@@ -246,17 +283,34 @@ public static class AnalyzerFixes
|
||||
{
|
||||
var array = info.Split(':');
|
||||
if (array.Length != 2)
|
||||
throw new ArgumentException($"Method must be specified as 'Namespace.Type1.Type2:MethodName. Got {info} instead.", nameof(info));
|
||||
{
|
||||
throw new ArgumentException($"Method must be specified as 'Namespace.Type1.Type2:MethodName. Got {
|
||||
info} instead.", nameof(info));
|
||||
}
|
||||
|
||||
var methods = Reflection.MethodsOfName(AccessTools.TypeByName(array[0]), array[1]);
|
||||
foreach (var method in methods)
|
||||
{
|
||||
yield return method.IsGenericMethodDefinition
|
||||
? method.MakeGenericMethod(method.GetGenericArguments().Select(t => t.GetGenericParameterConstraints() is var constraints2 && constraints2.Length != 0 ? constraints2[0] : typeof(object)).ToArray())
|
||||
? method.MakeGenericMethod(GetTypeArguments(method))
|
||||
: method;
|
||||
}
|
||||
}
|
||||
|
||||
private static Type[] GetTypeArguments(MethodBase method)
|
||||
{
|
||||
var genericArguments = method.GetGenericArguments();
|
||||
var resultArray = new Type[genericArguments.Length];
|
||||
for (var i = 0; i < genericArguments.Length; i++)
|
||||
{
|
||||
resultArray[i] = genericArguments[i].GetGenericArguments() is var constraints && constraints.Length != 0
|
||||
? constraints[0]
|
||||
: typeof(object);
|
||||
}
|
||||
|
||||
return resultArray;
|
||||
}
|
||||
|
||||
public static void ErrorFix(string message, XmlNode node)
|
||||
{
|
||||
if (node.NodeType == XmlNodeType.Comment)
|
||||
@@ -267,29 +321,40 @@ public static class AnalyzerFixes
|
||||
|
||||
public static Action<string> ThreadSafeLogger_Error
|
||||
=> _threadSafeLogger_Error
|
||||
??= (Action<string>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.ThreadSafeLogger", "Error")
|
||||
??= (Action<string>)Reflection
|
||||
.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.ThreadSafeLogger", "Error")!
|
||||
.CreateDelegate(typeof(Action<string>));
|
||||
|
||||
private static Action<string>? _threadSafeLogger_Error;
|
||||
|
||||
public static Func<string, HashSet<MethodInfo>, Type> DynamicTypeBuilder_CreateType
|
||||
=> _dynamicTypeBuilder_CreateType
|
||||
??= (Func<string, HashSet<MethodInfo>, Type>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.DynamicTypeBuilder", "CreateType")
|
||||
??= (Func<string, HashSet<MethodInfo>, Type>)Reflection.MethodInfo("PerformanceAnalyzer",
|
||||
"Analyzer.Profiling.DynamicTypeBuilder", "CreateType")!
|
||||
.CreateDelegate(typeof(Func<string, HashSet<MethodInfo>, Type>));
|
||||
|
||||
private static Func<string, HashSet<MethodInfo>, Type>? _dynamicTypeBuilder_CreateType;
|
||||
|
||||
public static Func<Type, bool, IEnumerable<MethodInfo>> Utility_GetTypeMethods
|
||||
=> _utility_GetTypeMethods
|
||||
??= (Func<Type, bool, IEnumerable<MethodInfo>>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "GetTypeMethods")
|
||||
??= (Func<Type, bool, IEnumerable<MethodInfo>>)Reflection.MethodInfo("PerformanceAnalyzer",
|
||||
"Analyzer.Profiling.Utility", "GetTypeMethods")!
|
||||
.CreateDelegate(typeof(Func<Type, bool, IEnumerable<MethodInfo>>));
|
||||
|
||||
private static Func<Type, bool, IEnumerable<MethodInfo>>? _utility_GetTypeMethods;
|
||||
|
||||
public static AccessTools.FieldRef<object, string> Entry_tip
|
||||
=> _entry_tip
|
||||
??= AccessTools.FieldRefAccess<string>(Type.GetType("Analyzer.Profiling.Entry, PerformanceAnalyzer"), "tip");
|
||||
??= AccessTools.FieldRefAccess<string>(Type.GetType("Analyzer.Profiling.Entry, PerformanceAnalyzer"),
|
||||
"tip");
|
||||
|
||||
private static AccessTools.FieldRef<object, string>? _entry_tip;
|
||||
|
||||
public static Action<int, object, Type> GUIController_AddSpecificEntry => _guiController_AddSpecificEntry ??= MakeGUIController_AddEntryMethod();
|
||||
public static Action<int, object, Type> GUIController_AddSpecificEntry
|
||||
=> _guiController_AddSpecificEntry ??= MakeGUIController_AddEntryMethod();
|
||||
|
||||
private static Action<int, object, Type>? _guiController_AddSpecificEntry;
|
||||
|
||||
private static Action<int, object, Type> MakeGUIController_AddEntryMethod()
|
||||
{
|
||||
var analyzer_Profiling_EntryType = Reflection.Type("PerformanceAnalyzer", "Analyzer.Profiling.Entry");
|
||||
@@ -312,12 +377,14 @@ public static class AnalyzerFixes
|
||||
if (entry_Create == null)
|
||||
Log.Error("entry_Create is null");*/
|
||||
|
||||
var dictionary_Add = AccessTools.Method(typeof(Dictionary<,>).MakeGenericType(new[] { analyzer_Profiling_EntryType, typeof(Type) }),
|
||||
var dictionary_Add = AccessTools.Method(
|
||||
typeof(Dictionary<,>).MakeGenericType(new[] { analyzer_Profiling_EntryType, typeof(Type) }),
|
||||
"Add", new[] { analyzer_Profiling_EntryType, typeof(Type) });
|
||||
if (dictionary_Add == null)
|
||||
Log.Error("dictionary_Add is null");
|
||||
|
||||
var dynamicMethod = new DynamicMethod("GUIController_AddEntryMethod", typeof(void), new[] { typeof(int), typeof(object), typeof(Type) });
|
||||
var dynamicMethod = new DynamicMethod("GUIController_AddEntryMethod", typeof(void),
|
||||
new[] { typeof(int), typeof(object), typeof(Type) });
|
||||
var il = dynamicMethod.GetILGenerator();
|
||||
|
||||
il.Emit(FishTranspiler.Argument(0)); //Category
|
||||
@@ -338,8 +405,12 @@ public static class AnalyzerFixes
|
||||
return (Action<int, object, Type>)dynamicMethod.CreateDelegate(typeof(Action<int, object, Type>));
|
||||
}
|
||||
|
||||
public static Func<string, int, Type, bool, bool, object> Entry_Create => _entry_Create
|
||||
??= (Func<string, int, Type, bool, bool, object>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Entry", "Create").CreateDelegate(typeof(Func<string, int, Type, bool, bool, object>));
|
||||
public static Func<string, int, Type, bool, bool, object> Entry_Create
|
||||
=> _entry_Create
|
||||
??= (Func<string, int, Type, bool, bool, object>)Reflection
|
||||
.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Entry", "Create")!
|
||||
.CreateDelegate(typeof(Func<string, int, Type, bool, bool, object>));
|
||||
|
||||
private static Func<string, int, Type, bool, bool, object>? _entry_Create;
|
||||
|
||||
//Copied from Wiri's analyzer fork, with above fixes integrated. The steam version lacks this functionality
|
||||
@@ -350,8 +421,9 @@ public static class AnalyzerFixes
|
||||
foreach (var dir in ModLister.AllActiveModDirs)
|
||||
{
|
||||
var xmlFiles = dir.GetFiles("DubsAnalyzer.xml");
|
||||
if (xmlFiles.Length != 0)
|
||||
{
|
||||
if (xmlFiles.Length == 0)
|
||||
continue;
|
||||
|
||||
foreach (var file in xmlFiles)
|
||||
{
|
||||
var doc = new XmlDocument();
|
||||
@@ -361,61 +433,81 @@ public static class AnalyzerFixes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterates through each child element in the document and attempts to extract method(s) from the strings inside the children
|
||||
private static void Parse(XmlDocument doc)
|
||||
=> ForEach(doc.DocumentElement.ChildNodes, node =>
|
||||
=> ForEach(doc.DocumentElement!.ChildNodes, ParseChildNodes);
|
||||
|
||||
private static void ParseChildNodes(XmlNode node)
|
||||
{
|
||||
var methods = new HashSet<MethodInfo>();
|
||||
var tip = string.Empty;
|
||||
var category = (int)Category.Modder;
|
||||
|
||||
ForEach(node.ChildNodes, child =>
|
||||
{
|
||||
switch (child.Name.ToLowerInvariant())
|
||||
switch (child.Name.ToUpperInvariant())
|
||||
{
|
||||
case "category":
|
||||
category = (int)Enum.Parse(typeof(Category), child.InnerText, true); //child.InnerText;
|
||||
case "CATEGORY":
|
||||
category = (int)Enum.Parse(typeof(Category), child.InnerText, true);
|
||||
break;
|
||||
case "tooltip":
|
||||
case "tip":
|
||||
case "description":
|
||||
case "TOOLTIP":
|
||||
case "TIP":
|
||||
case "DESCRIPTION":
|
||||
tip += child.InnerText;
|
||||
break;
|
||||
case "methods":
|
||||
case "method":
|
||||
case "METHODS":
|
||||
case "METHOD":
|
||||
AddMethodsFromChildNodes(child, methods, ParseMethod);
|
||||
break;
|
||||
case "derivedmethods":
|
||||
case "DERIVEDMETHODS":
|
||||
AddMethodsFromChildNodes(child, methods, ParseDerivedMethods);
|
||||
break;
|
||||
case "types":
|
||||
case "type":
|
||||
case "TYPES":
|
||||
case "TYPE":
|
||||
AddMethodsFromChildNodes(child, methods, ParseTypeMethods);
|
||||
break;
|
||||
case "derivedtypes":
|
||||
case "DERIVEDTYPES":
|
||||
AddMethodsFromChildNodes(child, methods, ParseDerivedTypeMethods);
|
||||
break;
|
||||
case "nestedtype":
|
||||
case "nestedtypes":
|
||||
case "NESTEDTYPE":
|
||||
case "NESTEDTYPES":
|
||||
AddMethodsFromChildNodes(child, methods, ParseNestedTypeMethods);
|
||||
break;
|
||||
default:
|
||||
ThreadSafeLogger_Error($"[Analyzer] Attempting to read unknown value from a DubsAnalyzer.xml, the given input was {child.Name}, it should have been either 'methods', 'types', 'derivedMethods', 'derivedTypes', 'nestedTypes', 'category' or 'tooltip'");
|
||||
ThreadSafeLogger_Error("[Analyzer] Attempting to read unknown value from a DubsAnalyzer.xml, "
|
||||
+ $"the given input was {child.Name}, it should have been either 'methods', 'types', "
|
||||
+ "'derivedMethods', 'derivedTypes', 'nestedTypes', 'category' or 'tooltip'");
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
var type = DynamicTypeBuilder_CreateType(node.Name, methods);
|
||||
|
||||
var entry = Entry_Create(node.Name, category, type, false, true);
|
||||
if (tip != string.Empty)
|
||||
Entry_tip(entry) = tip;
|
||||
GUIController_AddSpecificEntry(category, entry, type);
|
||||
});
|
||||
|
||||
private static void AddMethodsFromChildNodes(XmlNode node, HashSet<MethodInfo> methods, Func<string, IEnumerable<MethodInfo>> func)
|
||||
=> ForEach(node.ChildNodes, childNode
|
||||
=> methods.AddRange(func(childNode.InnerText)));
|
||||
GUIController_AddSpecificEntry(category, entry, type);
|
||||
}
|
||||
|
||||
private static void AddMethodsFromChildNodes(XmlNode node, HashSet<MethodInfo> methods,
|
||||
Func<string, IEnumerable<MethodInfo>> func)
|
||||
{
|
||||
var nodes = node.ChildNodes;
|
||||
|
||||
foreach (XmlNode childNode in nodes)
|
||||
{
|
||||
if (MissingRequiredMod(childNode.Attributes))
|
||||
continue;
|
||||
|
||||
if (childNode.NodeType != XmlNodeType.Comment)
|
||||
methods.AddRange(func(childNode.InnerText));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool MissingRequiredMod(XmlAttributeCollection? attributes)
|
||||
=> attributes?["MayRequire"] is { } mayRequireAttribute
|
||||
&& ModLister.GetActiveModWithIdentifier(mayRequireAttribute.Value, true) is null;
|
||||
|
||||
private static void ForEach(XmlNodeList nodes, Action<XmlNode> action)
|
||||
{
|
||||
@@ -434,7 +526,8 @@ public static class AnalyzerFixes
|
||||
|
||||
foreach (var subClass in subClasses)
|
||||
{
|
||||
var overridingMethod = Array.Find(subClass.GetMethods(AccessTools.allDeclared), m => m.GetBaseDefinition() == method.GetBaseDefinition());
|
||||
var overridingMethod = Array.Find(subClass.GetMethods(AccessTools.allDeclared),
|
||||
m => m.GetBaseDefinition() == method.GetBaseDefinition());
|
||||
if (overridingMethod is null || overridingMethod.IsAbstract)
|
||||
continue;
|
||||
|
||||
@@ -443,7 +536,8 @@ public static class AnalyzerFixes
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<MethodInfo> ParseTypeMethods(string str) => Utility_GetTypeMethods(AccessTools.TypeByName(str), false);
|
||||
private static IEnumerable<MethodInfo> ParseTypeMethods(string str)
|
||||
=> Utility_GetTypeMethods(AccessTools.TypeByName(str), false);
|
||||
|
||||
private static IEnumerable<MethodInfo> ParseNestedTypeMethods(string str)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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/.
|
||||
@@ -9,15 +9,21 @@ using System.Linq;
|
||||
namespace PerformanceFish.Cache;
|
||||
public class ByIndex<T_out> : IList<T_out?>, ICollection
|
||||
{
|
||||
private static ByIndex<T_out> _get = new();
|
||||
private static ByIndex<T_out> _get = Utility.AddNew<ByIndex<T_out>>();
|
||||
[ThreadStatic]
|
||||
private static ByIndex<T_out>? _getThreadStatic;
|
||||
|
||||
public static ByIndex<T_out> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<ByIndex<T_out>>();
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _getThreadStatic ??= Utility.AddNew<ByIndex<T_out>>();
|
||||
}
|
||||
|
||||
public static ByIndex<T_out> GetDirectly => _get;
|
||||
public static ByIndex<T_out> GetDirectly
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _get;
|
||||
}
|
||||
|
||||
static ByIndex() => Utility.All.Add(_get);
|
||||
|
||||
@@ -112,15 +118,21 @@ public class ByIndex<T_out> : IList<T_out?>, ICollection
|
||||
public class ByIndex<T_in, T_out> : ByIndex<T_out>
|
||||
where T_in : notnull
|
||||
{
|
||||
private static ByIndex<T_in, T_out> _get = new();
|
||||
private static ByIndex<T_in, T_out> _get = Utility.AddNew<ByIndex<T_in, T_out>>();
|
||||
[ThreadStatic]
|
||||
private static ByIndex<T_in, T_out>? _getThreadStatic;
|
||||
|
||||
public new static ByIndex<T_in, T_out> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<ByIndex<T_in, T_out>>();
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _getThreadStatic ??= Utility.AddNew<ByIndex<T_in, T_out>>();
|
||||
}
|
||||
|
||||
public new static ByIndex<T_in, T_out> GetDirectly => _get;
|
||||
public new static ByIndex<T_in, T_out> GetDirectly
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _get;
|
||||
}
|
||||
|
||||
static ByIndex() => Utility.All.Add(_get);
|
||||
|
||||
@@ -142,7 +154,6 @@ public class ByIndex<T_in, T_out> : ByIndex<T_out>
|
||||
[return: MaybeNull]
|
||||
public unsafe ref T_out GetReference(T_in key)
|
||||
=> ref GetReference(FunctionPointers.IndexGetter<T_in>.Default(key))!;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -151,15 +162,21 @@ public class ByIndex<T_in, T_out> : ByIndex<T_out>
|
||||
public class ByIndex<T_first, T_second, T_out> : ByIndex<T_first, ByIndex<T_second, T_out>>
|
||||
where T_first : notnull where T_second : notnull
|
||||
{
|
||||
private static ByIndex<T_first, T_second, T_out> _get = new();
|
||||
private static ByIndex<T_first, T_second, T_out> _get = Utility.AddNew<ByIndex<T_first, T_second, T_out>>();
|
||||
[ThreadStatic]
|
||||
private static ByIndex<T_first, T_second, T_out>? _getThreadStatic;
|
||||
|
||||
public new static ByIndex<T_first, T_second, T_out> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<ByIndex<T_first, T_second, T_out>>();
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _getThreadStatic ??= Utility.AddNew<ByIndex<T_first, T_second, T_out>>();
|
||||
}
|
||||
|
||||
public new static ByIndex<T_first, T_second, T_out> GetDirectly => _get;
|
||||
public new static ByIndex<T_first, T_second, T_out> GetDirectly
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _get;
|
||||
}
|
||||
|
||||
static ByIndex() => Utility.All.Add(_get);
|
||||
|
||||
@@ -179,7 +196,7 @@ public class ByIndex<T_first, T_second, T_out> : ByIndex<T_first, ByIndex<T_seco
|
||||
ref var inner = ref _items[firstIndex];
|
||||
inner ??= new();
|
||||
|
||||
return inner!;
|
||||
return inner;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -1,291 +1,268 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
public struct ByInt<T_in, T_result>
|
||||
where T_in : notnull
|
||||
where T_result : IIsRefreshable<T_in, T_result>
|
||||
|
||||
#pragma warning disable CS9091
|
||||
[PublicAPI]
|
||||
public record struct ByInt<T, TResult> where T : notnull where TResult : new()
|
||||
{
|
||||
private static Dictionary<ByInt<T_in, T_result>, T_result> _get = new();
|
||||
private static FishTable<ByInt<T, TResult>, TResult> _get
|
||||
= Utility.AddNew<ByInt<T, TResult>, TResult>();
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByInt<T_in, T_result>, T_result>? _getThreadStatic;
|
||||
private static FishTable<ByInt<T, TResult>, TResult>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByInt<T_in, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByInt<T_in, T_result>, T_result>>();
|
||||
public static FishTable<ByInt<T, TResult>, TResult> Get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _getThreadStatic ??= Utility.AddNew<ByInt<T, TResult>, TResult>();
|
||||
}
|
||||
|
||||
public static Dictionary<ByInt<T_in, T_result>, T_result> GetDirectly => _get;
|
||||
public static FishTable<ByInt<T, TResult>, TResult> GetDirectly
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _get;
|
||||
}
|
||||
|
||||
static ByInt() => Utility.All.Add(_get);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref TResult GetOrAddReference(int key)
|
||||
=> ref Get.GetOrAddReference(Unsafe.As<int, ByInt<T, TResult>>(ref key));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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.NoInlining)]
|
||||
public static ref TResult GetExistingReference(int key)
|
||||
=> ref Get.GetReference(Unsafe.As<int, ByInt<T, TResult>>(ref key));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static ref TResult GetExistingReference(T key) => ref Get.GetReference(new(key));
|
||||
|
||||
public int Key;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ByInt(T_in key) => Key = FunctionPointers.IndexGetter<T_in>.Default(key);
|
||||
public unsafe ByInt(T key) => Key = FunctionPointers.IndexGetter<T>.Default(key);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T_result GetValue(T_in key)
|
||||
{
|
||||
ref var cache = ref Get.TryGetReferenceUnsafe(new(key));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[new(key)] = Reflection.New<T_result>();
|
||||
cache = ref Get.GetReference(new(key))!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[new(key)] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceUnsafe(int key)
|
||||
=> ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_in, T_result>>(ref key))));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceChecked(int key, out bool result)
|
||||
{
|
||||
ref var value = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_in, T_result>>(ref key))));
|
||||
result = !Unsafe.IsNullRef(ref value) && !value.ShouldRefreshNow;
|
||||
return ref value!;
|
||||
}
|
||||
public ByInt(int key) => Key = key;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(T_in key, [MaybeNullWhen(false)] out T_result value)
|
||||
=> Get.TryGetValue(new(key), out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(T_in key)
|
||||
{
|
||||
var cache = Get.TryGetValue(new(key));
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[new(key)] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ByIntRefreshable<T_first, T_second, T_result> : IEquatable<ByIntRefreshable<T_first, T_second, T_result>>
|
||||
where T_first : notnull where T_second : notnull
|
||||
where T_result : IIsRefreshable<ByIntRefreshable<T_first, T_second, T_result>, T_result>
|
||||
{
|
||||
private static EqualityComparer<ByIntRefreshable<T_first, T_second, T_result>> _comparer = new Comparer();
|
||||
private class Comparer : EqualityComparer<ByIntRefreshable<T_first, T_second, T_result>>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(ByIntRefreshable<T_first, T_second, T_result> x, ByIntRefreshable<T_first, T_second, T_result> y)
|
||||
=> x.First == y.First
|
||||
&& x.Second == y.Second;
|
||||
public bool Equals(ByInt<T, TResult> other) => Key == other.Key;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode(ByIntRefreshable<T_first, T_second, T_result> obj)
|
||||
=> HashCode.Combine(obj.First, obj.Second);
|
||||
public override int GetHashCode() => Key;
|
||||
}
|
||||
|
||||
private static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result> _get = new(_comparer);
|
||||
[PublicAPI]
|
||||
public record struct ByInt<T1, T2, TResult>
|
||||
where T1 : notnull where T2 : notnull where TResult : new()
|
||||
{
|
||||
private static FishTable<ByInt<T1, T2, TResult>, TResult> _get
|
||||
= Utility.AddNew<ByInt<T1, T2, TResult>, TResult>();
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result>? _getThreadStatic;
|
||||
private static FishTable<ByInt<T1, T2, TResult>, TResult>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result>>(_comparer);
|
||||
public static FishTable<ByInt<T1, T2, TResult>, TResult> Get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _getThreadStatic ??= Utility.AddNew<ByInt<T1, T2, TResult>, TResult>();
|
||||
}
|
||||
|
||||
public static Dictionary<ByIntRefreshable<T_first, T_second, T_result>, T_result> GetDirectly => _get;
|
||||
private static ref FishTable<ByInt<T1, T2, TResult>, TResult> GetCacheRef
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
_getThreadStatic ??= Utility.AddNew<ByInt<T1, T2, TResult>, TResult>();
|
||||
return ref _getThreadStatic!;
|
||||
}
|
||||
}
|
||||
|
||||
static ByIntRefreshable() => Utility.All.Add(_get);
|
||||
public static FishTable<ByInt<T1, T2, TResult>, TResult> GetDirectly
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T_result GetValue(ref ByIntRefreshable<T_first, T_second, T_result> key)
|
||||
{
|
||||
// CS8347 without this crap
|
||||
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
|
||||
public static ref TResult GetOrAddReference(int first, int second)
|
||||
=> ref Get.GetOrAddReference(new() { First = first, Second = second });
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Reflection.New<T_result>();
|
||||
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref TResult GetOrAddReference(in ByInt<T1, T2, TResult> key)
|
||||
=> ref Get.GetOrAddReference(ref Unsafe.AsRef(in key));
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// public static ref TResult GetOrAddReference(long key) // different, worse, GetHashCode method
|
||||
// => ref Unsafe.As<FishTable<ByInt<T1, T2, TResult>, TResult>, FishTable<long, TResult>>(ref GetCacheRef)
|
||||
// .GetOrAddReference(key);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceUnsafe(int second, int first)
|
||||
=> ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByIntRefreshable<T_first, T_second, T_result>>(ref first))));
|
||||
public static ref TResult GetExistingReference(int first, int second)
|
||||
=> ref Get.GetReference(new() { First = first, Second = second });
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceChecked(int second, int first, out bool result)
|
||||
public static ref TResult GetExistingReference(T1 first, T2 second)
|
||||
=> ref Get.GetReference(new(first, second));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ByInt(T1 first, T2 second)
|
||||
{
|
||||
ref var value = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByIntRefreshable<T_first, T_second, T_result>>(ref first))));
|
||||
result = !Unsafe.IsNullRef(ref value) && !value.ShouldRefreshNow;
|
||||
return ref value!;
|
||||
First = FunctionPointers.IndexGetter<T1>.Default(first);
|
||||
Second = FunctionPointers.IndexGetter<T2>.Default(second);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(ByIntRefreshable<T_first, T_second, T_result> key, [MaybeNullWhen(false)] out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(ByIntRefreshable<T_first, T_second, T_result> key)
|
||||
{
|
||||
var cache = Get.TryGetValue(ref key);
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ByIntRefreshable(T_first first, T_second second)
|
||||
{
|
||||
First = FunctionPointers.IndexGetter<T_first>.Default(first);
|
||||
Second = FunctionPointers.IndexGetter<T_second>.Default(second);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ByIntRefreshable(int first, int second)
|
||||
public ByInt(int first, int second)
|
||||
{
|
||||
First = first;
|
||||
Second = second;
|
||||
}
|
||||
|
||||
public int First;
|
||||
public int Second;
|
||||
public int First, Second;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByIntRefreshable<T_first, T_second, T_result> other)
|
||||
=> First == other.First
|
||||
&& Second == other.Second;
|
||||
public bool Equals(ByInt<T1, T2, TResult> other)
|
||||
=> Unsafe.As<ByInt<T1, T2, TResult>, long>(ref this) == Unsafe.As<ByInt<T1, T2, TResult>, long>(ref other);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(First, Second);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByIntRefreshable<T_first, T_second, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
public static bool operator ==(ByIntRefreshable<T_first, T_second, T_result> left, ByIntRefreshable<T_first, T_second, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(ByIntRefreshable<T_first, T_second, T_result> left, ByIntRefreshable<T_first, T_second, T_result> right)
|
||||
=> !(left == right);
|
||||
public override int GetHashCode() => HashCode.Combine(First, Second);
|
||||
}
|
||||
|
||||
public struct ByInt<T_first, T_second, T_third, T_result> : IEquatable<ByInt<T_first, T_second, T_third, T_result>>
|
||||
where T_first : notnull where T_second : notnull where T_third : notnull
|
||||
where T_result : IIsRefreshable<ByInt<T_first, T_second, T_third, T_result>, T_result>
|
||||
[PublicAPI]
|
||||
public record struct ByInt<T1, T2, T3, TResult>
|
||||
where T1 : notnull where T2 : notnull where T3 : notnull where TResult : new()
|
||||
{
|
||||
private static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result> _get = new();
|
||||
private static FishTable<ByInt<T1, T2, T3, TResult>, TResult> _get
|
||||
= Utility.AddNew<ByInt<T1, T2, T3, TResult>, TResult>();
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result>? _getThreadStatic;
|
||||
private static FishTable<ByInt<T1, T2, T3, TResult>, TResult>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result>>();
|
||||
public static FishTable<ByInt<T1, T2, T3, TResult>, TResult> Get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _getThreadStatic ??= Utility.AddNew<ByInt<T1, T2, T3, TResult>, TResult>();
|
||||
}
|
||||
|
||||
public static Dictionary<ByInt<T_first, T_second, T_third, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByInt() => Utility.All.Add(_get);
|
||||
public static FishTable<ByInt<T1, T2, T3, TResult>, TResult> GetDirectly
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T_result GetValue(ByInt<T_first, T_second, T_third, T_result> key)
|
||||
public static unsafe ref TResult GetOrAddReference(int first, int second, int third)
|
||||
{
|
||||
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Reflection.New<T_result>();
|
||||
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
|
||||
goto RefreshCache;
|
||||
var key = new ByInt<T1, T2, T3, TResult> { First = first, Second = second, Third = third };
|
||||
return ref Get.GetOrAddReference(ref key);
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref TResult GetOrAddReference(in ByInt<T1, T2, T3, TResult> key)
|
||||
=> ref Get.GetOrAddReference(ref Unsafe.AsRef(in key));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceUnsafe(int second, int first, int third)
|
||||
=> ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_first, T_second, T_third, T_result>>(ref first))));
|
||||
public static ref TResult GetExistingReference(int first, int second, int third)
|
||||
=> ref Get.GetReference(new() { First = first, Second = second, Third = third });
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref T_result TryGetReferenceChecked(int second, int first, out bool result)
|
||||
public static ref TResult GetExistingReference(T1 first, T2 second, T3 third)
|
||||
=> ref Get.GetReference(new(first, second, third));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ByInt(T1 first, T2 second, T3 third)
|
||||
{
|
||||
ref var value = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As<int, ByInt<T_first, T_second, T_third, T_result>>(ref first))));
|
||||
result = !Unsafe.IsNullRef(ref value) && !value.ShouldRefreshNow;
|
||||
return ref value!;
|
||||
First = FunctionPointers.IndexGetter<T1>.Default(first);
|
||||
Second = FunctionPointers.IndexGetter<T2>.Default(second);
|
||||
Third = FunctionPointers.IndexGetter<T3>.Default(third);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(ByInt<T_first, T_second, T_third, T_result> key, out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
public int First, Second, Third;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(ByInt<T_first, T_second, T_third, T_result> key)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByInt<T1, T2, T3, TResult> other)
|
||||
=> (Unsafe.As<ByInt<T1, T2, T3, TResult>, long>(ref this)
|
||||
== Unsafe.As<ByInt<T1, T2, T3, TResult>, long>(ref other))
|
||||
& (Third == other.Third);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => HashCode.Combine(First, Second, Third);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public record struct ByInt<T1, T2, T3, T4, TResult>
|
||||
where T1 : notnull where T2 : notnull where T3 : notnull where T4 : notnull
|
||||
where TResult : new()
|
||||
{
|
||||
var cache = Get.TryGetValue(ref key);
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
private static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult> _get
|
||||
= Utility.AddNew<ByInt<T1, T2, T3, T4, TResult>, TResult>();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ByInt(T_first first, T_second second, T_third third)
|
||||
[ThreadStatic]
|
||||
private static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult>? _getThreadStatic;
|
||||
|
||||
public static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult> Get
|
||||
{
|
||||
First = FunctionPointers.IndexGetter<T_first>.Default(first);
|
||||
Second = FunctionPointers.IndexGetter<T_second>.Default(second);
|
||||
Third = FunctionPointers.IndexGetter<T_third>.Default(third);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _getThreadStatic ??= Utility.AddNew<ByInt<T1, T2, T3, T4, TResult>, TResult>();
|
||||
}
|
||||
|
||||
public int First;
|
||||
public int Second;
|
||||
public int Third;
|
||||
|
||||
public static FishTable<ByInt<T1, T2, T3, T4, TResult>, TResult> GetDirectly
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByInt<T_first, T_second, T_third, T_result> other)
|
||||
=> First == other.First
|
||||
&& Second == other.Second
|
||||
&& Third == other.Third;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(First, Second, Third);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByInt<T_first, T_second, T_third, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
public static bool operator ==(ByInt<T_first, T_second, T_third, T_result> left, ByInt<T_first, T_second, T_third, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(ByInt<T_first, T_second, T_third, T_result> left, ByInt<T_first, T_second, T_third, T_result> right)
|
||||
=> !(left == right);
|
||||
get => _get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref TResult GetOrAddReference(int first, int second, int third, int fourth)
|
||||
{
|
||||
var key = new ByInt<T1, T2, T3, T4, TResult>
|
||||
{
|
||||
First = first, Second = second, Third = third, Fourth = fourth
|
||||
};
|
||||
return ref Get.GetOrAddReference(ref key);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref TResult GetOrAddReference(in ByInt<T1, T2, T3, T4, TResult> key)
|
||||
=> ref Get.GetOrAddReference(ref Unsafe.AsRef(in key));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
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 });
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static ref TResult GetExistingReference(T1 first, T2 second, T3 third, T4 fourth)
|
||||
=> ref Get.GetReference(new(first, second, third, fourth));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public unsafe ByInt(T1 first, T2 second, T3 third, T4 fourth)
|
||||
{
|
||||
First = FunctionPointers.IndexGetter<T1>.Default(first);
|
||||
Second = FunctionPointers.IndexGetter<T2>.Default(second);
|
||||
Third = FunctionPointers.IndexGetter<T3>.Default(third);
|
||||
Fourth = FunctionPointers.IndexGetter<T4>.Default(fourth);
|
||||
}
|
||||
|
||||
public int First, Second, Third, Fourth;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByInt<T1, T2, T3, T4, TResult> other)
|
||||
=> (Unsafe.As<ByInt<T1, T2, T3, T4, TResult>, long>(ref this)
|
||||
== Unsafe.As<ByInt<T1, T2, T3, T4, TResult>, long>(ref other))
|
||||
& (Unsafe.As<int, long>(ref Third) == Unsafe.As<int, long>(ref other.Third));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => HashCode.Combine(First, Second, Third, Fourth);
|
||||
}
|
||||
#pragma warning restore CS9091
|
||||
@@ -1,46 +1,90 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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.Cache;
|
||||
|
||||
public static class ByMap<T> where T : new()
|
||||
{
|
||||
private static List<(Map map, T value)> _cache = new();
|
||||
private static List<(Map map, T value)> _cache = Utility.AddNew<List<(Map map, T value)>>();
|
||||
|
||||
[ThreadStatic]
|
||||
private static List<(Map map, T value)>? _cacheThreadStatic;
|
||||
|
||||
public static List<(Map map, T value)> GetCache
|
||||
=> UnityData.IsInMainThread ? _cache
|
||||
: _cacheThreadStatic ??= Utility.AddNew<List<(Map map, T value)>>();
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _cacheThreadStatic ??= Utility.AddNew<List<(Map map, T value)>>();
|
||||
}
|
||||
|
||||
public static List<(Map map, T value)> GetCacheDirectly => _cache;
|
||||
public static List<(Map map, T value)> GetCacheDirectly
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _cache;
|
||||
}
|
||||
|
||||
static ByMap() => Utility.All.Add(_cache);
|
||||
|
||||
private static Func<Map, T> Initialize { get; }
|
||||
= typeof(MapComponent).IsAssignableFrom(typeof(T))
|
||||
? AccessTools.MethodDelegate<Func<Map, T>>(AccessTools.Method(typeof(Map), nameof(Map.GetComponent), Array.Empty<Type>(), new[] { typeof(T) }))
|
||||
? AccessTools.MethodDelegate<Func<Map, T>>(AccessTools.Method(typeof(Map), nameof(Map.GetComponent),
|
||||
Type.EmptyTypes, new[] { typeof(T) }))
|
||||
// missing T constraints cause the compiler to complain when trying to just use map => map.GetComponent<T>
|
||||
: _ => new();
|
||||
: static _ => new();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T GetFor(Map map)
|
||||
{
|
||||
var cache = GetCache;
|
||||
for (var i = 0; i < cache.Count; i++)
|
||||
{
|
||||
var cacheEntry = cache[i];
|
||||
ref var cacheEntry = ref cache.GetReferenceUnverifiable(i);
|
||||
if (cacheEntry.map == map)
|
||||
return cacheEntry.value;
|
||||
|
||||
if (Find.Maps.Count <= i + 1
|
||||
&& !Find.Maps.Contains(cacheEntry.map))
|
||||
{
|
||||
if (EntryValid(i, cacheEntry.map))
|
||||
continue;
|
||||
|
||||
cache.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
|
||||
return AddEntry(map, cache);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T GetReferenceFor(Map map)
|
||||
{
|
||||
var cache = GetCache;
|
||||
for (var i = 0; i < cache.Count; i++)
|
||||
{
|
||||
ref var cacheEntry = ref cache.GetReferenceUnverifiable(i);
|
||||
if (cacheEntry.map == map)
|
||||
return ref cacheEntry.value;
|
||||
|
||||
if (EntryValid(i, cacheEntry.map))
|
||||
continue;
|
||||
|
||||
cache.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
|
||||
return ref AddEntryByRef(map, cache);
|
||||
}
|
||||
|
||||
private static bool EntryValid(int i, Map map)
|
||||
=> Find.Maps.Count > i && Find.Maps.Contains(map);
|
||||
|
||||
private static T AddEntry(Map map, List<(Map map, T value)> cache)
|
||||
{
|
||||
cache.Add((map, Initialize(map)));
|
||||
return cache[^1].value;
|
||||
}
|
||||
|
||||
private static ref T AddEntryByRef(Map map, List<(Map map, T value)> cache)
|
||||
{
|
||||
cache.Add((map, Initialize(map)));
|
||||
return ref cache.GetReferenceUnverifiable(cache.Count - 1).value;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,218 +0,0 @@
|
||||
// Copyright (c) 2022 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.Cache;
|
||||
public static class ByReferenceRefreshable<T_first, T_result>
|
||||
where T_first : notnull
|
||||
where T_result : IIsRefreshable<T_first, T_result>
|
||||
{
|
||||
private static Dictionary<T_first, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<T_first, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<T_first, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<T_first, T_result>>();
|
||||
|
||||
public static Dictionary<T_first, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReferenceRefreshable() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref T_result GetValue(T_first key)
|
||||
{
|
||||
ref var cache = ref Get.TryGetReferenceUnsafe(key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Reflection.New<T_result>();
|
||||
cache = ref Get.GetReference(key)!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(T_first key, out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(T_first key)
|
||||
{
|
||||
var cache = Get.TryGetValue(key);
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
public struct ByReferenceRefreshable<T_first, T_second, T_result> : IEquatable<ByReferenceRefreshable<T_first, T_second, T_result>>
|
||||
where T_first : notnull where T_second : notnull
|
||||
where T_result : IIsRefreshable<ByReferenceRefreshable<T_first, T_second, T_result>, T_result>
|
||||
{
|
||||
internal ByReference<T_first, T_second, T_result> _innerValue;
|
||||
|
||||
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result>>();
|
||||
|
||||
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReferenceRefreshable() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T_result GetValue(ref ByReferenceRefreshable<T_first, T_second, T_result> key)
|
||||
{
|
||||
// CS8347 without this crap
|
||||
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Reflection.New<T_result>();
|
||||
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(ByReferenceRefreshable<T_first, T_second, T_result> key, out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(ByReferenceRefreshable<T_first, T_second, T_result> key)
|
||||
{
|
||||
var cache = Get.TryGetValue(ref key);
|
||||
cache ??= Reflection.New<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
|
||||
public ByReferenceRefreshable(T_first first, T_second second) => _innerValue = new(first, second);
|
||||
|
||||
public T_first First => _innerValue.First;
|
||||
|
||||
public T_second Second => _innerValue.Second;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByReferenceRefreshable<T_first, T_second, T_result> other)
|
||||
=> _innerValue.Equals(other._innerValue);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => _innerValue.GetHashCode();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByReferenceRefreshable<T_first, T_second, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
public static bool operator ==(ByReferenceRefreshable<T_first, T_second, T_result> left, ByReferenceRefreshable<T_first, T_second, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(ByReferenceRefreshable<T_first, T_second, T_result> left, ByReferenceRefreshable<T_first, T_second, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
|
||||
public struct ByReferenceRefreshable<T_first, T_second, T_third, T_result> : IEquatable<ByReferenceRefreshable<T_first, T_second, T_third, T_result>>
|
||||
where T_first : notnull where T_second : notnull where T_third : notnull
|
||||
where T_result : IIsRefreshable<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result>
|
||||
{
|
||||
private ByReference<T_first, T_second, T_third, T_result> _innerValue;
|
||||
|
||||
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result> _get = new();
|
||||
[ThreadStatic]
|
||||
private static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result> Get
|
||||
=> /*UnityData.IsInMainThread ? _get
|
||||
:*/ _getThreadStatic ??= Utility.AddNew<Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result>>();
|
||||
|
||||
public static Dictionary<ByReferenceRefreshable<T_first, T_second, T_third, T_result>, T_result> GetDirectly => _get;
|
||||
|
||||
static ByReferenceRefreshable() => Utility.All.Add(_get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref T_result GetValue(ref ByReferenceRefreshable<T_first, T_second, T_third, T_result> key)
|
||||
{
|
||||
ref var cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache))
|
||||
{
|
||||
Get[key] = Activator.CreateInstance<T_result>();
|
||||
cache = ref Unsafe.AsRef<T_result>(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
|
||||
goto RefreshCache;
|
||||
}
|
||||
|
||||
if (!cache.ShouldRefreshNow)
|
||||
return ref cache!;
|
||||
|
||||
RefreshCache:
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
|
||||
return ref cache!;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool TryGetValue(ByReferenceRefreshable<T_first, T_second, T_third, T_result> key, out T_result value)
|
||||
=> Get.TryGetValue(key, out value)
|
||||
&& !value.ShouldRefreshNow;
|
||||
|
||||
public static T_result GetValueAndRefreshNow(ByReferenceRefreshable<T_first, T_second, T_third, T_result> key)
|
||||
{
|
||||
var cache = Get.TryGetValue(ref key);
|
||||
cache ??= Activator.CreateInstance<T_result>();
|
||||
cache.ShouldRefreshNow = false;
|
||||
Get[key] = cache.SetNewValue(key);
|
||||
return cache;
|
||||
}
|
||||
|
||||
public ByReferenceRefreshable(T_first first, T_second second, T_third third)
|
||||
=> _innerValue = new(first, second, third);
|
||||
|
||||
public T_first First => _innerValue.First;
|
||||
public T_second Second => _innerValue.Second;
|
||||
public T_third Third => _innerValue.Third;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ByReferenceRefreshable<T_first, T_second, T_third, T_result> other)
|
||||
=> _innerValue.Equals(other._innerValue);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => _innerValue.GetHashCode();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object obj)
|
||||
=> obj is ByReferenceRefreshable<T_first, T_second, T_third, T_result> cache
|
||||
&& Equals(cache);
|
||||
|
||||
public static bool operator ==(ByReferenceRefreshable<T_first, T_second, T_third, T_result> left, ByReferenceRefreshable<T_first, T_second, T_third, T_result> right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(ByReferenceRefreshable<T_first, T_second, T_third, T_result> left, ByReferenceRefreshable<T_first, T_second, T_third, T_result> right)
|
||||
=> !(left == right);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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.Cache;
|
||||
|
||||
#pragma warning disable CS9091
|
||||
public static class Database<TCache, TValue> where TCache : ICacheKeyable where TValue : new()
|
||||
{
|
||||
public static object SyncLock = new();
|
||||
|
||||
private static Dictionary<TCache, TValue> _get = Utility.AddNew<Dictionary<TCache, TValue>>();
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<TCache, TValue>? _getThreadStatic;
|
||||
|
||||
public static Dictionary<TCache, TValue> Get
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _getThreadStatic ??= Utility.AddNew<Dictionary<TCache, TValue>>();
|
||||
}
|
||||
|
||||
public static Dictionary<TCache, TValue> GetDirectly
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref TValue GetOrAddReference<VCache, T1, T2>(T1 first, T2 second)
|
||||
where VCache : IMemberCount<T1, T2>, new() // new as generic constraint requires at least function pointers,
|
||||
// and can't be inlined. Makes this slow, unfortunately
|
||||
=> ref Unsafe.As<Dictionary<VCache, TValue>>(Get)
|
||||
.GetOrAddReference(Reflection.New<VCache, T1, T2>(first, second));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ref TValue GetOrAddReference(in TCache key)
|
||||
=> ref Get.GetOrAddReference(ref Unsafe.AsRef(in key));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static unsafe ref TValue GetExistingReference<VCache, T1, T2>(T1 first, T2 second)
|
||||
where VCache : IMemberCount<T1, T2>, new()
|
||||
{
|
||||
var key = Reflection.New<VCache, T1, T2>(first, second);
|
||||
return ref Get.GetReference(ref Unsafe.As<VCache, TCache>(ref key));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static ref TValue GetExistingReference(in TCache key)
|
||||
=> ref Get.GetReference(ref Unsafe.AsRef(in key));
|
||||
}
|
||||
#pragma warning restore CS9091
|
||||
+4
-9
@@ -1,16 +1,11 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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.Cache;
|
||||
public interface IIsRefreshable<T, V> where V : IIsRefreshable<T, V>
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public V SetNewValue(T key);
|
||||
// This is where I'd put extension methods for my cache structs, if I had any
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
|
||||
public interface ICacheKeyable
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int GetHashCode();
|
||||
}
|
||||
|
||||
public interface IMemberCount<T1>
|
||||
{
|
||||
public T1 First
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IMemberCount<T1, T2> : IMemberCount<T1>
|
||||
{
|
||||
public T2 Second
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IMemberCount<T1, T2, T3> : IMemberCount<T1, T2>
|
||||
{
|
||||
public T3 Third
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IMemberCount<T1, T2, T3, T4> : IMemberCount<T1, T2, T3>
|
||||
{
|
||||
public T4 Fourth
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IInitializable<T> : IMemberCount<T>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Initialize(T key);
|
||||
}
|
||||
|
||||
public interface IInitializable<T1, T2> : IMemberCount<T1, T2>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Initialize(T1 first, T2 second);
|
||||
}
|
||||
|
||||
public interface IInitializable<T1, T2, T3> : IMemberCount<T1, T2, T3>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Initialize(T1 first, T2 second, T3 third);
|
||||
}
|
||||
|
||||
public interface IInitializable<T1, T2, T3, T4> : IMemberCount<T1, T2, T3, T4>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Initialize(T1 first, T2 second, T3 third, T4 fourth);
|
||||
}
|
||||
|
||||
public interface IDirtyable
|
||||
{
|
||||
public bool Dirty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ICacheable<TKey> : IDirtyable
|
||||
{
|
||||
public void Update(ref TKey key);
|
||||
}
|
||||
|
||||
public interface ICacheable<TKey, in T2> : IDirtyable
|
||||
{
|
||||
public void Update(ref TKey key, T2 second);
|
||||
}
|
||||
|
||||
public interface ICacheable<TKey, in T2, in T3> : IDirtyable
|
||||
{
|
||||
public void Update(ref TKey key, T2 second, T3 third);
|
||||
}
|
||||
|
||||
public interface IAsyncCacheable<TResult> : IDirtyable
|
||||
{
|
||||
public Task<TResult>? Task
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set;
|
||||
}
|
||||
|
||||
public TResult? Result
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IAsyncCacheable<in TKey, TResult> : IAsyncCacheable<TResult>
|
||||
{
|
||||
public TResult? MakeResultAsync(TKey key);
|
||||
}
|
||||
|
||||
public interface IAsyncCacheable<in TKey, in T2, TResult> : IAsyncCacheable<TResult>
|
||||
{
|
||||
public ValueTask<TResult?> MakeResultAsync(TKey key, T2 second);
|
||||
}
|
||||
|
||||
internal record Countdown(int Value)
|
||||
{
|
||||
public volatile int Value = Value;
|
||||
}
|
||||
@@ -1,43 +1,314 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Cache;
|
||||
|
||||
public static class Utility
|
||||
{
|
||||
public static event Action<Thing>? ThingDestroyed; // TODO
|
||||
|
||||
private static volatile int _destroyedThingCounter, _lastGCTick, _gcRate = 1000;
|
||||
private static int _currentGCProgress;
|
||||
private static volatile bool _gcInProgress;
|
||||
|
||||
private static ICollection[]? _cachesToProcessForGC;
|
||||
|
||||
private static object _gcLock = new();
|
||||
|
||||
public static void NotifyThingDestroyed(Thing thing)
|
||||
{
|
||||
ThingDestroyed?.Invoke(thing);
|
||||
|
||||
if (Interlocked.Increment(ref _destroyedThingCounter) < 100
|
||||
|| Current.programStateInt != ProgramState.Playing
|
||||
|| TickHelper.TicksGame - _lastGCTick < _gcRate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ContinueGC();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ContinueGC()
|
||||
{
|
||||
lock (_gcLock)
|
||||
{
|
||||
if (_gcInProgress)
|
||||
return;
|
||||
|
||||
_gcInProgress = true;
|
||||
|
||||
if (_cachesToProcessForGC is null || _currentGCProgress >= _cachesToProcessForGC.Length)
|
||||
{
|
||||
_cachesToProcessForGC = All.ToArray();
|
||||
_gcRate = GenDate.TicksPerTwelfth / _cachesToProcessForGC.CountNonEmpty();
|
||||
_currentGCProgress = 0;
|
||||
_destroyedThingCounter = 0;
|
||||
}
|
||||
|
||||
while (_cachesToProcessForGC[_currentGCProgress].Count == 0
|
||||
&& _cachesToProcessForGC.Length - _currentGCProgress > 1)
|
||||
{
|
||||
_currentGCProgress++;
|
||||
}
|
||||
|
||||
if (_currentGCProgress < _cachesToProcessForGC.Length)
|
||||
_cachesToProcessForGC[_currentGCProgress++].Clear();
|
||||
|
||||
_lastGCTick = TickHelper.TicksGame;
|
||||
|
||||
_gcInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static int CountNonEmpty(this ICollection[] collection)
|
||||
{
|
||||
var count = 0;
|
||||
for (var i = collection.Length; i-- > 0;)
|
||||
{
|
||||
if (collection.Length > 0)
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public static ConcurrentBag<ICollection> All { get; } = new();
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
foreach (var cache in All)
|
||||
cache.Clear();
|
||||
}
|
||||
|
||||
public static void LogCurrentCacheUtilization()
|
||||
{
|
||||
cache.GetType()
|
||||
.GetMethods()
|
||||
.Where(m
|
||||
=> m.Name == nameof(Array.Clear)
|
||||
&& m.GetParameters().Length == 0)
|
||||
.First()
|
||||
.Invoke(cache, null);
|
||||
var allCaches = All.ToArray();
|
||||
Log.Message($"Total utilized caches: {allCaches.CountNonEmpty()}\n\n"
|
||||
+ $"Individual counts:\n{string.Join("\n",
|
||||
allCaches.Select(static cache => cache.GetType()).Distinct().Select(type
|
||||
=> $"{type.FullDescription()} :: {allCaches.Where(cache => cache.GetType() == type)
|
||||
.Select(static cache => cache.Count).Sum()}{TryCalculateSize(
|
||||
type, allCaches)}"))}\n=============================================================");
|
||||
}
|
||||
|
||||
private static string TryCalculateSize(Type type, ICollection[] allCaches)
|
||||
=> (type.GetProperty(nameof(FishTable<object, object>.SizeEstimate))?.GetMethod
|
||||
?? (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)
|
||||
? AccessTools.Method(typeof(FisheryLib.CollectionExtensions),
|
||||
nameof(FisheryLib.CollectionExtensions.GetSizeEstimate), generics: type.GetGenericArguments())
|
||||
: null)) is { } sizeGetter
|
||||
? $" ({(allCaches.Where(cache => cache.GetType() == type)
|
||||
.Select(cache => (long)(uint)sizeGetter.Invoke(sizeGetter.IsStatic ? null : cache,
|
||||
sizeGetter.IsStatic ? new object[] { cache } : null)).Sum()
|
||||
is var sizeInBytes
|
||||
&& sizeInBytes > 1024L * 1024L ? $"{Math.Round(sizeInBytes / (1024d * 1024d), 2)} MiB"
|
||||
: sizeInBytes > 1024L ? $"{Math.Round(sizeInBytes / 1024d, 2)} KiB"
|
||||
: $"{sizeInBytes} B")})"
|
||||
: "";
|
||||
|
||||
private static void Clear(this ICollection cache)
|
||||
{
|
||||
switch (cache)
|
||||
{
|
||||
case IClearable clearable:
|
||||
clearable.Clear();
|
||||
break;
|
||||
case IDictionary dictionary:
|
||||
dictionary.Clear();
|
||||
break;
|
||||
case IList list:
|
||||
list.Clear();
|
||||
break;
|
||||
default:
|
||||
ref var clearMethod = ref ClearMethods.GetOrAddReference(cache.GetType().TypeHandle.Value);
|
||||
if (clearMethod.Action is null)
|
||||
AssignClearMethod(cache, out clearMethod.Action);
|
||||
clearMethod.Action(cache);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static T AddNew<T>() where T : ICollection, new()
|
||||
private static void AssignClearMethod(ICollection cache, out Action<ICollection> clearMethod)
|
||||
=> clearMethod = Unsafe.As<Action<ICollection>>(Array.Find(cache.GetType().GetMethods(),
|
||||
static m => m.Name == nameof(IList.Clear) && m.GetParameters().Length == 0)
|
||||
.CreateDelegate(typeof(Action<>).MakeGenericType(cache.GetType())));
|
||||
|
||||
private static FishTable<IntPtr, ClearAction> ClearMethods => _clearMethods ??= new();
|
||||
|
||||
[ThreadStatic]
|
||||
private static FishTable<IntPtr, ClearAction>? _clearMethods;
|
||||
|
||||
private record struct ClearAction
|
||||
{
|
||||
var newCollection = new T();
|
||||
public Action<ICollection>? Action;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
||||
public static T AddNew<T>() where T : class, ICollection, new()
|
||||
{
|
||||
var newCollection = Reflection.New<T>();
|
||||
|
||||
All.Add(newCollection);
|
||||
return newCollection;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
||||
public static FishTable<TKey, TValue> AddNew<TKey, TValue>(Action<KeyValuePair<TKey, TValue>>? onEntryAdded = null,
|
||||
Action<KeyValuePair<TKey, TValue>>? onEntryRemoved = null)
|
||||
{
|
||||
var newCollection = new FishTable<TKey, TValue>();
|
||||
|
||||
if (onEntryAdded != null)
|
||||
newCollection.EntryAdded += onEntryAdded;
|
||||
|
||||
if (onEntryRemoved != null)
|
||||
newCollection.EntryRemoved += onEntryRemoved;
|
||||
|
||||
All.Add(newCollection);
|
||||
return newCollection;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
||||
public static T AddNewUnclearable<T>() where T : class, ICollection, new()
|
||||
{
|
||||
var newCollection = Reflection.New<T>();
|
||||
return newCollection;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
||||
public static FishTable<TKey, TValue> AddNewUnclearable<TKey, TValue>(
|
||||
Action<KeyValuePair<TKey, TValue>>? onEntryAdded = null,
|
||||
Action<KeyValuePair<TKey, TValue>>? onEntryRemoved = null)
|
||||
{
|
||||
var newCollection = new FishTable<TKey, TValue>();
|
||||
|
||||
if (onEntryAdded != null)
|
||||
newCollection.EntryAdded += onEntryAdded;
|
||||
|
||||
if (onEntryRemoved != null)
|
||||
newCollection.EntryRemoved += onEntryRemoved;
|
||||
|
||||
return newCollection;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
||||
public static T AddNewWithRef<T>(AccessTools.FieldRef<T?> fieldRef) where T : ICollection, new()
|
||||
{
|
||||
var collectionRef = Reflection.New<CollectionRef<T>, AccessTools.FieldRef<T?>>(fieldRef);
|
||||
All.Add(collectionRef);
|
||||
return Reflection.New<T>();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
|
||||
public static T AddNew<T>(IEqualityComparer comparer) where T : ICollection, new()
|
||||
{
|
||||
var newCollection = (T)Activator.CreateInstance(typeof(T), comparer);
|
||||
All.Add(newCollection);
|
||||
return newCollection;
|
||||
var newCollection = (T)Activator.CreateInstance(typeof(T), comparer); // different comparer subclasses can
|
||||
All.Add(newCollection); // require different constructors.
|
||||
return newCollection; // Reflection.New<T> can't use arguments
|
||||
} // for resolving method calls (yet)
|
||||
}
|
||||
|
||||
public delegate int IndexGetter<in T>(T item);
|
||||
|
||||
public interface IClearable
|
||||
{
|
||||
public void Clear();
|
||||
}
|
||||
|
||||
public interface IFishBool
|
||||
{
|
||||
public bool Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate int IndexGetter<T>(T item);
|
||||
public struct FishBool : IFishBool
|
||||
{
|
||||
private byte _value;
|
||||
|
||||
public bool Value
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _value == 1;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set => _value = value ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
public struct True : IFishBool
|
||||
{
|
||||
public bool Value => true;
|
||||
}
|
||||
|
||||
public struct False : IFishBool
|
||||
{
|
||||
public bool Value => false;
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class CollectionRef<T> : IClearable, ICollection where T : ICollection
|
||||
{
|
||||
private readonly AccessTools.FieldRef<T> _fieldRef;
|
||||
private static readonly ClearInvoker? _clearInvoker = CreateClearDelegate();
|
||||
|
||||
public CollectionRef(AccessTools.FieldRef<T> fieldRef) => _fieldRef = fieldRef;
|
||||
|
||||
public ref T Get => ref _fieldRef();
|
||||
|
||||
public void Create() => Get = Reflection.New<T>();
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (_clearInvoker is null)
|
||||
ThrowForMissingClearMethod();
|
||||
|
||||
_clearInvoker(ref Get);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
private static void ThrowForMissingClearMethod()
|
||||
=> throw new InvalidOperationException(
|
||||
$"Tried to call Clear() on invalid collection of type {typeof(T).FullName}");
|
||||
|
||||
public IEnumerator GetEnumerator() => Get.GetEnumerator();
|
||||
|
||||
public void CopyTo(Array array, int index) => Get.CopyTo(array, index);
|
||||
|
||||
public int Count => Get.Count;
|
||||
|
||||
public object SyncRoot => Get.SyncRoot;
|
||||
|
||||
public bool IsSynchronized => Get.IsSynchronized;
|
||||
|
||||
private static ClearInvoker? CreateClearDelegate()
|
||||
{
|
||||
var clearMethod = AccessTools.Method(typeof(T), nameof(IList.Clear), Type.EmptyTypes);
|
||||
if (clearMethod is null)
|
||||
return null;
|
||||
|
||||
var dm = new DynamicMethod($"ClearInvoker_{typeof(T).FullName}", null,
|
||||
new[] { typeof(T).MakeByRefType() }, typeof(CollectionRef<T>), true);
|
||||
var il = dm.GetILGenerator();
|
||||
|
||||
il.Emit(FishTranspiler.Argument(0));
|
||||
il.Emit(FishTranspiler.Call(clearMethod));
|
||||
il.Emit(FishTranspiler.Return);
|
||||
|
||||
return (ClearInvoker)dm.CreateDelegate(typeof(ClearInvoker));
|
||||
}
|
||||
|
||||
private delegate void ClearInvoker(ref T collectionRef);
|
||||
}
|
||||
@@ -1,645 +0,0 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using RimWorld;
|
||||
using UnityEngine;
|
||||
|
||||
namespace PerformanceFish.Collections;
|
||||
|
||||
#pragma warning disable CS8766, CS8767
|
||||
public class FishTable<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary, IReadOnlyDictionary<TKey, TValue>
|
||||
{
|
||||
//private int[] _buckets;
|
||||
|
||||
internal Entry[] _entries;
|
||||
|
||||
private int _count;
|
||||
|
||||
private int _version;
|
||||
|
||||
//private unsafe delegate*<TKey, int> _keyGetHashCode = FunctionPointers.GetHashCode<TKey>.Default;
|
||||
//private unsafe delegate*<TKey, TKey, bool> _keyEquals = FunctionPointers.Equals<TKey>.Default;
|
||||
|
||||
//private int _freeList;
|
||||
|
||||
//private int _freeCount;
|
||||
|
||||
//private KeyCollection _keys;
|
||||
|
||||
//private ValueCollection _values;
|
||||
|
||||
public ICollection<TKey> Keys => throw new NotImplementedException();
|
||||
|
||||
public ICollection<TValue> Values => throw new NotImplementedException();
|
||||
|
||||
public int Version
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _version;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _count;
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
ICollection IDictionary.Keys => throw new NotImplementedException();
|
||||
|
||||
ICollection IDictionary.Values => throw new NotImplementedException();
|
||||
|
||||
bool IDictionary.IsFixedSize => false;
|
||||
object ICollection.SyncRoot => this; // matching System.Collections.Generic.Dictionary
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => throw new NotImplementedException();
|
||||
|
||||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => throw new NotImplementedException();
|
||||
|
||||
object? IDictionary.this[object key]
|
||||
{
|
||||
get
|
||||
=> key is TKey tkey
|
||||
? this[tkey]
|
||||
: ThrowHelper.ThrowKeyNotFoundException<object, TValue>(key);
|
||||
|
||||
set
|
||||
{
|
||||
if (key is TKey tkey)
|
||||
{
|
||||
if (value is TValue tvalue)
|
||||
this[tkey] = tvalue;
|
||||
else if (value is null)
|
||||
this[tkey] = default;
|
||||
else
|
||||
ThrowHelper.ThrowWrongValueTypeArgumentException(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowHelper.ThrowWrongKeyTypeArgumentException(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TValue? this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
var entry = _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = entry.Next.Entry;
|
||||
else
|
||||
ThrowHelper.ThrowKeyNotFoundException(key);
|
||||
}
|
||||
|
||||
if (entry.Next is null)
|
||||
ThrowHelper.ThrowKeyNotFoundException(key);
|
||||
|
||||
return entry.Value;
|
||||
}
|
||||
set
|
||||
{
|
||||
ref var reference = ref TryGetReferenceUnsafe(key);
|
||||
if (!Unsafe.IsNullRef(ref reference))
|
||||
{
|
||||
reference = value;
|
||||
_version++;
|
||||
return;
|
||||
}
|
||||
|
||||
Add(key, value!);
|
||||
}
|
||||
}
|
||||
|
||||
public FishTable() : this(0) { }
|
||||
|
||||
public FishTable(int minimumCapacity)
|
||||
=> _entries = new Entry[
|
||||
minimumCapacity <= 1 ? 1 // Minimum size to not throw on indexing into _entries. Length is not checked beforehand
|
||||
: Mathf.NextPowerOfTwo(minimumCapacity)]; // System.Collections.Dictionary starts at 3. So wasteful.
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static uint FastModulo(uint first, uint second)
|
||||
=> first & (second - 1);
|
||||
|
||||
public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
var entry = _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
{
|
||||
entry = entry.Next.Entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
value = entry.Value;
|
||||
return entry.Next != null;
|
||||
}
|
||||
|
||||
public TValue? TryGetValue(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
var entry = _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = entry.Next.Entry;
|
||||
else
|
||||
return default;
|
||||
}
|
||||
|
||||
return entry.Value;
|
||||
}
|
||||
|
||||
public ref TValue GetReference(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
ref var entry = ref _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = ref entry.Next.Entry;
|
||||
else
|
||||
ThrowHelper.ThrowKeyNotFoundException(key);
|
||||
}
|
||||
|
||||
if (entry.Next == null)
|
||||
ThrowHelper.ThrowKeyNotFoundException(key);
|
||||
|
||||
return ref entry.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a reference to the value field of an entry if the key exists and an Unsafe.NullRef{TValue} if not.
|
||||
/// Must be checked with Unsafe.IsNullRef before assigning to a regular var or field without ref.
|
||||
/// </summary>
|
||||
/// <param name="key">The key</param>
|
||||
/// <returns>ref TValue or Unsafe.NullRef{TValue}</returns>
|
||||
public ref TValue TryGetReferenceUnsafe(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
ref var entry = ref _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = ref entry.Next.Entry;
|
||||
else
|
||||
return ref Unsafe.NullRef<TValue>()!;
|
||||
}
|
||||
|
||||
return ref entry.Next == null
|
||||
? ref Unsafe.NullRef<TValue>()
|
||||
: ref entry.Value;
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
var entry = _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = entry.Next.Entry;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return entry.Next != null;
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
if (_count == _entries.Length) // throwing DuplicateKeyExceptions happens later, so Expanding occurs based on expected
|
||||
Expand(); // behaviour instead of matching actual results like in System.Collections.Generic.
|
||||
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
ref var entry = ref _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.Next != null)
|
||||
{
|
||||
if (entry.HashCode == keyCode && entry.Key.Equals<TKey>(key))
|
||||
ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(key);
|
||||
|
||||
if (entry.Next == EntryReference.Empty)
|
||||
break;
|
||||
|
||||
entry = ref entry.Next.Entry;
|
||||
}
|
||||
|
||||
if (entry.Next == null) // means the entry is empty
|
||||
entry = new(keyCode, key, value);
|
||||
else // if (entry.Next == _freeSlot) // occupied entry with an empty slot in entry.Next
|
||||
entry.Next = new(keyCode, key, value);
|
||||
// entry.Next != _freeSlot would be a threading issue, as this was just checked in the loop. Unhandled.
|
||||
|
||||
_count++;
|
||||
_version++;
|
||||
}
|
||||
|
||||
private void Expand()
|
||||
{
|
||||
var newLength = Mathf.NextPowerOfTwo(_entries.Length + 1);
|
||||
var newArray = new Entry[newLength];
|
||||
|
||||
for (var i = 0; i < _entries.Length; i++)
|
||||
{
|
||||
var entry = _entries[i];
|
||||
if (entry.Next is null)
|
||||
continue;
|
||||
|
||||
BeforeAdding:
|
||||
ref var newEntry = ref newArray[FastModulo(entry.HashCode, (uint)newLength)];
|
||||
|
||||
while (newEntry.Next != null)
|
||||
{
|
||||
if (newEntry.HashCode == entry.HashCode && newEntry.Key.Equals<TKey>(entry.Key))
|
||||
ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException(entry.Key);
|
||||
|
||||
if (newEntry.Next == EntryReference.Empty)
|
||||
break;
|
||||
|
||||
newEntry = ref newEntry.Next.Entry;
|
||||
}
|
||||
|
||||
var nextEntry = entry.Next;
|
||||
entry.Next = EntryReference.Empty;
|
||||
|
||||
if (newEntry.Next == null)
|
||||
newEntry = entry;
|
||||
else
|
||||
newEntry.Next = new(entry);
|
||||
|
||||
if (nextEntry != EntryReference.Empty)
|
||||
{
|
||||
entry = nextEntry.Entry;
|
||||
goto BeforeAdding;
|
||||
}
|
||||
}
|
||||
|
||||
_entries = newArray;
|
||||
}
|
||||
|
||||
private ref Entry TryFindEntry(TKey key)
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(key);
|
||||
ref var entry = ref _entries[FastModulo(keyCode, (uint)_entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals<TKey>(key))
|
||||
{
|
||||
if (entry.Next != EntryReference.Empty && entry.Next != null)
|
||||
entry = ref entry.Next.Entry;
|
||||
else
|
||||
return ref Unsafe.NullRef<Entry>();
|
||||
}
|
||||
|
||||
return ref entry;
|
||||
}
|
||||
|
||||
private void RemoveEntry(ref Entry entry)
|
||||
{
|
||||
entry = entry.Next != EntryReference.Empty && entry.Next != null
|
||||
? entry.Next.Entry
|
||||
: default;
|
||||
|
||||
_count--;
|
||||
_version++;
|
||||
}
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
ref var entry = ref TryFindEntry(key);
|
||||
if (Unsafe.IsNullRef(ref entry))
|
||||
return false;
|
||||
|
||||
RemoveEntry(ref entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<TKey, TValue> item) => Add(item.Key, item.Value);
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Array.Clear(_entries, 0, _entries.Length);
|
||||
_count = 0;
|
||||
_version++;
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
=> TryGetValue(item.Key, out var value)
|
||||
&& value.Equals<TValue>(item.Value);
|
||||
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
var length = _entries.Length;
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var entry = _entries[i];
|
||||
if (entry.Next == null)
|
||||
continue;
|
||||
|
||||
array[arrayIndex] = new(entry.Key, entry.Value);
|
||||
arrayIndex++;
|
||||
|
||||
while (entry.Next != EntryReference.Empty)
|
||||
{
|
||||
entry = entry.Next.Entry;
|
||||
array[arrayIndex] = new(entry.Key, entry.Value);
|
||||
arrayIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
ref var entry = ref TryFindEntry(item.Key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref entry)
|
||||
|| !entry.Value.Equals<TValue>(item.Value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RemoveEntry(ref entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
=> new(this, Enumerator.KEY_VALUE_PAIR);
|
||||
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
|
||||
=> new Enumerator(this, Enumerator.KEY_VALUE_PAIR);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> new Enumerator(this, Enumerator.KEY_VALUE_PAIR);
|
||||
|
||||
bool IDictionary.Contains(object key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
return key is TKey tkey
|
||||
&& ContainsKey(tkey);
|
||||
}
|
||||
|
||||
void IDictionary.Add(object key, object value) => throw new NotImplementedException();
|
||||
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator()
|
||||
=> new Enumerator(this, Enumerator.DICT_ENTRY);
|
||||
|
||||
void IDictionary.Remove(object key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
|
||||
if (key is TKey tkey)
|
||||
Remove(tkey);
|
||||
}
|
||||
|
||||
void ICollection.CopyTo(Array array, int index) => throw new NotImplementedException();
|
||||
|
||||
internal struct Entry
|
||||
{
|
||||
public uint HashCode;
|
||||
public TKey Key;
|
||||
public EntryReference Next;
|
||||
public TValue Value;
|
||||
|
||||
internal Entry(uint hashCode, TKey key, TValue value)
|
||||
{
|
||||
HashCode = hashCode;
|
||||
Key = key;
|
||||
Value = value;
|
||||
Next = EntryReference.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
internal class EntryReference
|
||||
{
|
||||
public static readonly EntryReference Empty = new();
|
||||
|
||||
public Entry Entry;
|
||||
|
||||
public EntryReference(uint hashCode, TKey key, TValue value) => Entry = new(hashCode, key, value);
|
||||
|
||||
public EntryReference(Entry entry) => Entry = entry;
|
||||
|
||||
private EntryReference() { }
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>, IDictionaryEnumerator
|
||||
{
|
||||
private readonly FishTable<TKey, TValue> _dictionary;
|
||||
private readonly int _version;
|
||||
private int _index;
|
||||
private EntryReference _next;
|
||||
private KeyValuePair<TKey, TValue> _current;
|
||||
private readonly int _getEnumeratorRetType; // What should Enumerator.Current return?
|
||||
|
||||
internal const int DICT_ENTRY = 1;
|
||||
internal const int KEY_VALUE_PAIR = 2;
|
||||
|
||||
internal Enumerator(FishTable<TKey, TValue> dictionary, int getEnumeratorRetType)
|
||||
{
|
||||
_dictionary = dictionary;
|
||||
_version = dictionary._version;
|
||||
_index = 0;
|
||||
_getEnumeratorRetType = getEnumeratorRetType;
|
||||
_current = default;
|
||||
_next = EntryReference.Empty;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_version != _dictionary._version)
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
|
||||
|
||||
if (_next != EntryReference.Empty)
|
||||
{
|
||||
_current = new(_next.Entry.Key, _next.Entry.Value);
|
||||
_next = _next.Entry.Next;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use unsigned comparison since we set index to dictionary.count+1 when the enumeration ends.
|
||||
// dictionary.count+1 could be negative if dictionary.count is int.MaxValue
|
||||
while ((uint)_index < (uint)_dictionary._entries.Length)
|
||||
{
|
||||
ref var entry = ref _dictionary._entries[_index++];
|
||||
|
||||
if (entry.Next != null)
|
||||
{
|
||||
_current = new KeyValuePair<TKey, TValue>(entry.Key, entry.Value);
|
||||
_next = entry.Next;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_index = _dictionary._entries.Length + 1;
|
||||
_current = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public KeyValuePair<TKey, TValue> Current => _current;
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
object? IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index == 0 || (_index == _dictionary._entries.Length + 1))
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
|
||||
|
||||
return _getEnumeratorRetType == DICT_ENTRY
|
||||
? new DictionaryEntry(_current.Key, _current.Value)
|
||||
: new KeyValuePair<TKey, TValue>(_current.Key, _current.Value);
|
||||
}
|
||||
}
|
||||
|
||||
void IEnumerator.Reset()
|
||||
{
|
||||
if (_version != _dictionary._version)
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion();
|
||||
|
||||
_index = 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
DictionaryEntry IDictionaryEnumerator.Entry
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index == 0 || (_index == _dictionary._entries.Length + 1))
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
|
||||
|
||||
return new DictionaryEntry(_current.Key, _current.Value);
|
||||
}
|
||||
}
|
||||
|
||||
object? IDictionaryEnumerator.Key
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index == 0 || (_index == _dictionary._entries.Length + 1))
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
|
||||
|
||||
return _current.Key;
|
||||
}
|
||||
}
|
||||
|
||||
object? IDictionaryEnumerator.Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_index == 0 || (_index == _dictionary._entries.Length + 1))
|
||||
ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen();
|
||||
|
||||
return _current.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ThrowHelper
|
||||
{
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion()
|
||||
=> throw new InvalidOperationException("Collection was modified; enumeration operation may not execute.");
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowInvalidOperationException_InvalidOperation_EnumOpCantHappen()
|
||||
=> throw new InvalidOperationException("Enumeration has either not started or has already finished.");
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowKeyNotFoundException<T>(T key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
ThrowKeyNotFoundException((object)key);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static V ThrowKeyNotFoundException<T, V>(T key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
ThrowKeyNotFoundException((object)key);
|
||||
return default;
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowKeyNotFoundException(object key)
|
||||
=> throw new KeyNotFoundException(
|
||||
string.Format("The given key '{0}' was not present in the dictionary.", key));
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowWrongValueTypeArgumentException(object value)
|
||||
=> throw new ArgumentException(
|
||||
string.Format("The value \"{0}\" is not of type \"{1}\" and cannot be used in this generic collection.",
|
||||
value, typeof(TValue)),
|
||||
nameof(value));
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowWrongKeyTypeArgumentException(object? key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
throw new ArgumentException(
|
||||
string.Format("The value \"{0}\" is not of type \"{1}\" and cannot be used in this generic collection.",
|
||||
key, typeof(TKey)),
|
||||
nameof(key));
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowAddingDuplicateWithKeyArgumentException<T>(T key)
|
||||
{
|
||||
Guard.IsNotNull(key);
|
||||
ThrowAddingDuplicateWithKeyArgumentException((object)key);
|
||||
}
|
||||
|
||||
[DoesNotReturn]
|
||||
internal static void ThrowAddingDuplicateWithKeyArgumentException(object key)
|
||||
=> throw new ArgumentException(
|
||||
string.Format("An item with the same key has already been added. Key: {0}", key));
|
||||
}
|
||||
}
|
||||
public static class Extensions
|
||||
{
|
||||
public static ref TValue TryGetReferenceUnsafe<TKey, TValue>(this FishTable<TKey, TValue> fishTable, ref TKey key)
|
||||
where TKey : struct, IEquatable<TKey>
|
||||
{
|
||||
var keyCode = (uint)HashCode.Get(ref key);
|
||||
ref var entry = ref fishTable._entries[FishTable<TKey, TValue>.FastModulo(keyCode, (uint)fishTable._entries.Length)];
|
||||
|
||||
while (entry.HashCode != keyCode || !entry.Key.Equals(ref key))
|
||||
{
|
||||
if (entry.Next != FishTable<TKey, TValue>.EntryReference.Empty && entry.Next != null)
|
||||
entry = ref entry.Next.Entry;
|
||||
else
|
||||
return ref Unsafe.NullRef<TValue>()!;
|
||||
}
|
||||
|
||||
return ref entry.Next == null
|
||||
? ref Unsafe.NullRef<TValue>()
|
||||
: ref entry.Value;
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS8766, CS8767
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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 PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class ContentFinderCaching : ClassWithFishPrepatches
|
||||
{
|
||||
public class Get_Patch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Caches texture lookups on the ContentFinder. This has a relatively large performance impact when used "
|
||||
+ "with mods like humanoid alien races";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(ContentFinder<Texture2D>.Get);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix<T>(string itemPath, bool reportFailure, ref T? __result, out bool __state)
|
||||
{
|
||||
ref var cache = ref Cache.ByReference<string, CacheValue<T>>.GetOrAddReference(itemPath);
|
||||
|
||||
if (!cache.Cached)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.Value;
|
||||
|
||||
return __state = reportFailure && __result is null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix<T>(string itemPath, T? __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
UpdateCache(itemPath, __result);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void UpdateCache<T>(string itemPath, T? __result)
|
||||
{
|
||||
ref var cache = ref Cache.ByReference<string, CacheValue<T>>.GetExistingReference(itemPath);
|
||||
|
||||
cache.Cached = true;
|
||||
cache.Value = __result;
|
||||
}
|
||||
}
|
||||
|
||||
public record struct CacheValue<T>(bool Cached, T? Value);
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
//DDS importing from // https://github.com/sarbian/DDSLoader/blob/master/DatabaseLoaderTexture_DDS.cs
|
||||
public static class DDSLoader
|
||||
{
|
||||
private const uint
|
||||
DDSD_MIPMAPCOUNT_BIT = 0x00020000,
|
||||
DDPF_ALPHAPIXELS = 0x00000001,
|
||||
DDPF_ALPHA = 0x00000002,
|
||||
DDPF_FOURCC = 0x00000004,
|
||||
DDPF_RGB = 0x00000040,
|
||||
// DDPF_YUV = 0x00000200,
|
||||
DDPF_LUMINANCE = 0x00020000;
|
||||
// DDPF_NORMAL = 0x80000000;
|
||||
|
||||
public static string? error;
|
||||
|
||||
// DDS Texture loader inspired by
|
||||
// http://answers.unity3d.com/questions/555984/can-you-load-dds-textures-during-runtime.html#answer-707772
|
||||
// http://msdn.microsoft.com/en-us/library/bb943992.aspx
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb205578(v=vs.85).aspx
|
||||
// mipmapBias limits the number of mipmap when > 0
|
||||
public static Texture2D? LoadDDS(FileStream fileStream, out bool hasMipMaps)
|
||||
{
|
||||
hasMipMaps = false;
|
||||
using var reader = new BinaryReader(fileStream);
|
||||
|
||||
if (!FourCcEquals(reader.ReadBytes(4), "DDS "))
|
||||
{
|
||||
error = "Invalid DDS file";
|
||||
return null;
|
||||
}
|
||||
|
||||
//this header byte should be 124 for DDS image files
|
||||
if (reader.ReadUInt32() != 124u)
|
||||
{
|
||||
error = "Invalid header size";
|
||||
return null;
|
||||
}
|
||||
|
||||
var dwFlags = reader.ReadUInt32();
|
||||
var dwHeight = (int)reader.ReadUInt32();
|
||||
var dwWidth = (int)reader.ReadUInt32();
|
||||
|
||||
reader.ReadUInt64();
|
||||
var dwMipMapCount = reader.ReadUInt32();
|
||||
|
||||
if ((dwFlags & DDSD_MIPMAPCOUNT_BIT) != 0)
|
||||
hasMipMaps = dwMipMapCount > 1u;
|
||||
|
||||
// dwReserved1
|
||||
for (var i = 5; i-- > 0;)
|
||||
reader.ReadUInt64();
|
||||
|
||||
reader.ReadUInt32();
|
||||
|
||||
// DDS_PIXELFORMAT
|
||||
reader.ReadUInt32();
|
||||
var dds_pxlf_dwFlags = reader.ReadUInt32();
|
||||
var dds_pxlf_dwFourCC = reader.ReadBytes(4);
|
||||
// var fourCC = Encoding.ASCII.GetString(dds_pxlf_dwFourCC);
|
||||
var dds_pxlf_dwRGBBitCount = reader.ReadUInt32();
|
||||
var pixelSize = dds_pxlf_dwRGBBitCount / 8;
|
||||
var dds_pxlf_dwRBitMask = reader.ReadUInt32();
|
||||
var dds_pxlf_dwGBitMask = reader.ReadUInt32();
|
||||
var dds_pxlf_dwBBitMask = reader.ReadUInt32();
|
||||
var dds_pxlf_dwABitMask = reader.ReadUInt32();
|
||||
|
||||
reader.ReadUInt64();
|
||||
reader.ReadUInt64();
|
||||
reader.ReadUInt32();
|
||||
|
||||
var rgb888 = dds_pxlf_dwRBitMask == 0x000000ff
|
||||
&& dds_pxlf_dwGBitMask == 0x0000ff00
|
||||
&& dds_pxlf_dwBBitMask == 0x00ff0000;
|
||||
|
||||
var bgr888 = dds_pxlf_dwRBitMask == 0x00ff0000
|
||||
&& dds_pxlf_dwGBitMask == 0x0000ff00
|
||||
&& dds_pxlf_dwBBitMask == 0x000000ff;
|
||||
|
||||
var rgb565 = dds_pxlf_dwRBitMask == 0x0000F800
|
||||
&& dds_pxlf_dwGBitMask == 0x000007E0
|
||||
&& dds_pxlf_dwBBitMask == 0x0000001F;
|
||||
|
||||
var argb4444 = dds_pxlf_dwABitMask == 0x0000f000
|
||||
&& dds_pxlf_dwRBitMask == 0x00000f00
|
||||
&& dds_pxlf_dwGBitMask == 0x000000f0
|
||||
&& dds_pxlf_dwBBitMask == 0x0000000f;
|
||||
|
||||
var rbga4444 = dds_pxlf_dwABitMask == 0x0000000f
|
||||
&& dds_pxlf_dwRBitMask == 0x0000f000
|
||||
&& dds_pxlf_dwGBitMask == 0x000000f0
|
||||
&& dds_pxlf_dwBBitMask == 0x00000f00;
|
||||
|
||||
var textureFormat = TextureFormat.ARGB32;
|
||||
var isCompressed = false;
|
||||
|
||||
if ((dds_pxlf_dwFlags & DDPF_FOURCC) != 0)
|
||||
{
|
||||
// Texture dos not contain RGB data, check FourCC for format
|
||||
isCompressed = true;
|
||||
|
||||
if (FourCcEquals(dds_pxlf_dwFourCC, "DXT1"))
|
||||
textureFormat = TextureFormat.DXT1;
|
||||
else if (FourCcEquals(dds_pxlf_dwFourCC, "DXT5"))
|
||||
textureFormat = TextureFormat.DXT5;
|
||||
else if (FourCcEquals(dds_pxlf_dwFourCC, "DX10"))
|
||||
textureFormat = TextureFormat.BC7;
|
||||
}
|
||||
else
|
||||
{
|
||||
var alphapixel = (dds_pxlf_dwFlags & DDPF_ALPHAPIXELS) != 0;
|
||||
|
||||
switch ((dds_pxlf_dwFlags & DDPF_RGB) != 0)
|
||||
{
|
||||
case true when rgb888 || bgr888:
|
||||
// RGB or RGBA format
|
||||
textureFormat = alphapixel
|
||||
? TextureFormat.RGBA32
|
||||
: TextureFormat.RGB24;
|
||||
break;
|
||||
case true when rgb565:
|
||||
// Nvidia texconv B5G6R5_UNORM
|
||||
textureFormat = TextureFormat.RGB565;
|
||||
break;
|
||||
case true when alphapixel && argb4444:
|
||||
// Nvidia texconv B4G4R4A4_UNORM
|
||||
textureFormat = TextureFormat.ARGB4444;
|
||||
break;
|
||||
case true when alphapixel && rbga4444:
|
||||
textureFormat = TextureFormat.RGBA4444;
|
||||
break;
|
||||
case false when (dds_pxlf_dwFlags & DDPF_ALPHA) != 0 != ((dds_pxlf_dwFlags & DDPF_LUMINANCE) != 0):
|
||||
// A8 format or Luminance 8
|
||||
textureFormat = TextureFormat.Alpha8;
|
||||
break;
|
||||
default:
|
||||
error = "Only BC7, DXT1, DXT5, A8, RGB24, BGR24, RGBA32, BGBR32, RGB565, ARGB4444 and "
|
||||
+ "RGBA4444 are supported";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var dataBias = (long)(textureFormat != TextureFormat.BC7 ? 128 : 148);
|
||||
var dxtBytesLength = reader.BaseStream.Length - dataBias;
|
||||
reader.BaseStream.Seek(dataBias, SeekOrigin.Begin);
|
||||
var dxtBytes = reader.ReadBytes((int)dxtBytesLength);
|
||||
|
||||
// Swap red and blue.
|
||||
if (!isCompressed && bgr888)
|
||||
{
|
||||
for (uint i = 0; i < dxtBytes.Length; i += pixelSize)
|
||||
{
|
||||
var b = dxtBytes[i + 0];
|
||||
var r = dxtBytes[i + 2];
|
||||
|
||||
dxtBytes[i + 0] = r;
|
||||
dxtBytes[i + 2] = b;
|
||||
}
|
||||
}
|
||||
|
||||
// QualitySettings.masterTextureLimit = 0;
|
||||
// Work around for a >Unity< Bug.
|
||||
// if QualitySettings.masterTextureLimit != 0 (half or quarter texture rez)
|
||||
// and dwWidth and dwHeight divided by 2 (or 4 for quarter rez) are not a multiple of 4
|
||||
// and we are creating a DXT5 or DXT1 texture
|
||||
// Then you get an Unity error on the "new Texture"
|
||||
|
||||
var quality = QualitySettings.masterTextureLimit;
|
||||
|
||||
// If the bug conditions are present then switch to full quality
|
||||
if (isCompressed && quality > 0 && (dwWidth >> quality) % 4 != 0 && (dwHeight >> quality) % 4 != 0)
|
||||
QualitySettings.masterTextureLimit = 0;
|
||||
|
||||
var texture = new Texture2D(dwWidth, dwHeight, textureFormat, hasMipMaps);
|
||||
texture.LoadRawTextureData(dxtBytes);
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static bool FourCcEquals(IList<byte> bytes, string s)
|
||||
=> bytes[0] == s[0] && bytes[1] == s[1] && bytes[2] == s[2] && bytes[3] == s[3];
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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/.
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// 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/.
|
||||
|
||||
// crashes to desktop somehow
|
||||
/*using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class DefDatabasePatches : ClassWithFishPrepatches
|
||||
{
|
||||
public class GetNamedPatch : FishPrepatch
|
||||
{
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(DefDatabase<Def>.GetNamed);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(GetNamed<Def>);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T? GetNamed<T>(string defName, bool errorOnFail = true) where T : Def
|
||||
{
|
||||
var value = DefDatabase<T>.defsByName.TryGetValue(defName);
|
||||
|
||||
if (errorOnFail && value is null)
|
||||
LogMissingDef<T>(defName);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void LogMissingDef<T>(string defName) where T : Def
|
||||
=> Log.Error($"Failed to find {typeof(T)} named {defName}. There are {
|
||||
DefDatabase<T>.defsList.Count} defs of this type loaded.");
|
||||
}
|
||||
|
||||
public class GetByShortHashPatch : FishPrepatch
|
||||
{
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(DefDatabase<Def>.GetByShortHash);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(GetByShortHash<Def>);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T? GetByShortHash<T>(ushort shortHash) where T : Def
|
||||
=> DefDatabase<T>.defsByShortHash.TryGetValue(shortHash);
|
||||
}
|
||||
}*/
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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/.
|
||||
@@ -7,8 +7,12 @@
|
||||
//mostly integrated into vanilla with 1.4
|
||||
|
||||
using System.Linq;
|
||||
using AllDesignationsOnCache = PerformanceFish.Cache.ByIntRefreshable<Verse.DesignationManager, Verse.Thing, PerformanceFish.DesignationManagerCaching.DesignationCache<Verse.Thing>>;
|
||||
using DesignationsOfDefCache = PerformanceFish.Cache.ByIntRefreshable<Verse.DesignationManager, Verse.DesignationDef, PerformanceFish.DesignationManagerCaching.DesignationCache<Verse.DesignationDef>>;
|
||||
using AllDesignationsOnCache =
|
||||
PerformanceFish.Cache.ByIntRefreshable<Verse.DesignationManager, Verse.Thing,
|
||||
PerformanceFish.DesignationManagerCaching.DesignationCache<Verse.Thing>>;
|
||||
using DesignationsOfDefCache =
|
||||
PerformanceFish.Cache.ByIntRefreshable<Verse.DesignationManager, Verse.DesignationDef,
|
||||
PerformanceFish.DesignationManagerCaching.DesignationCache<Verse.DesignationDef>>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
@@ -17,8 +21,12 @@ public class DesignationManagerCaching : ClassWithFishPatches
|
||||
public class SpawnedDesignationsOfDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.SpawnedDesignationsOfDef(null);
|
||||
public static bool Prefix(DesignationManager __instance, DesignationDef def, ref IEnumerable<Designation> __result, out bool __state)
|
||||
|
||||
public override Expression<Action> TargetMethod
|
||||
=> () => default(DesignationManager)!.SpawnedDesignationsOfDef(null);
|
||||
|
||||
public static bool Prefix(DesignationManager __instance, DesignationDef def,
|
||||
ref IEnumerable<Designation> __result, out bool __state)
|
||||
{
|
||||
if (def.GetType() != typeof(DesignationDef)) // different types have different indexing. Can't use those
|
||||
{
|
||||
@@ -32,7 +40,9 @@ public class DesignationManagerCaching : ClassWithFishPatches
|
||||
__result = value.designations;
|
||||
return __state = false;
|
||||
}
|
||||
public static void Postfix(DesignationManager __instance, DesignationDef def, bool __state, IEnumerable<Designation> __result)
|
||||
|
||||
public static void Postfix(DesignationManager __instance, DesignationDef def, bool __state,
|
||||
IEnumerable<Designation> __result)
|
||||
{
|
||||
if (__state)
|
||||
RefreshDesignationCache(DesignationsOfDefCache.Get, __instance, def, __result);
|
||||
@@ -40,7 +50,8 @@ public class DesignationManagerCaching : ClassWithFishPatches
|
||||
}
|
||||
|
||||
public static void RefreshDesignationCache<T>( /*Cache.ByIndex<DesignationManager, T, DesignationCache<T>>*/
|
||||
Dictionary<Cache.ByIntRefreshable<DesignationManager, T, DesignationCache<T>>, DesignationCache<T>> dict, DesignationManager instance, T key, IEnumerable<Designation> result)
|
||||
Dictionary<Cache.ByIntRefreshable<DesignationManager, T, DesignationCache<T>>, DesignationCache<T>> dict,
|
||||
DesignationManager instance, T key, IEnumerable<Designation> result)
|
||||
where T : notnull
|
||||
{
|
||||
if (dict.TryGetValue(new(instance, key), out var cache))
|
||||
@@ -54,16 +65,24 @@ public class DesignationManagerCaching : ClassWithFishPatches
|
||||
public class AnySpawnedDesignationOfDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.AnySpawnedDesignationOfDef(null);
|
||||
public static bool AnySpawnedDesignationOfDef(DesignationManager __instance, DesignationDef def) => __instance.SpawnedDesignationsOfDef(def).Any();
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions) => Reflection.GetCodeInstructions(AnySpawnedDesignationOfDef);
|
||||
|
||||
public override Expression<Action> TargetMethod
|
||||
=> () => default(DesignationManager)!.AnySpawnedDesignationOfDef(null);
|
||||
|
||||
public static bool AnySpawnedDesignationOfDef(DesignationManager __instance, DesignationDef def)
|
||||
=> __instance.SpawnedDesignationsOfDef(def).Any();
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.GetCodeInstructions(AnySpawnedDesignationOfDef);
|
||||
}
|
||||
|
||||
public class AllDesignationsOn_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.AllDesignationsOn(null);
|
||||
public static bool Prefix(DesignationManager __instance, Thing t, ref IEnumerable<Designation> __result, out bool __state)
|
||||
|
||||
public static bool Prefix(DesignationManager __instance, Thing t, ref IEnumerable<Designation> __result,
|
||||
out bool __state)
|
||||
{
|
||||
if (!AllDesignationsOnCache.TryGetValue(new(__instance, t), out var value))
|
||||
return __state = true;
|
||||
@@ -71,7 +90,9 @@ public class DesignationManagerCaching : ClassWithFishPatches
|
||||
__result = value.designations;
|
||||
return __state = false;
|
||||
}
|
||||
public static void Postfix(DesignationManager __instance, Thing t, bool __state, IEnumerable<Designation> __result)
|
||||
|
||||
public static void Postfix(DesignationManager __instance, Thing t, bool __state,
|
||||
IEnumerable<Designation> __result)
|
||||
{
|
||||
if (__state)
|
||||
RefreshDesignationCache(AllDesignationsOnCache.Get, __instance, t, __result);
|
||||
@@ -82,15 +103,22 @@ public class DesignationManagerCaching : ClassWithFishPatches
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.DesignationOn(null);
|
||||
public static Designation DesignationOn(DesignationManager __instance, Thing t) => __instance.AllDesignationsOn(t).FirstOrDefault();
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions) => Reflection.GetCodeInstructions(DesignationOn);
|
||||
|
||||
public static Designation DesignationOn(DesignationManager __instance, Thing t)
|
||||
=> __instance.AllDesignationsOn(t).FirstOrDefault();
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.GetCodeInstructions(DesignationOn);
|
||||
}
|
||||
|
||||
public class DesignationOn_ByDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.DesignationOn(null, null);
|
||||
public static Designation? DesignationOn(DesignationManager __instance, Thing t, DesignationDef def) => FirstDesignationOfDef(__instance.AllDesignationsOn(t), def);
|
||||
|
||||
public static Designation? DesignationOn(DesignationManager __instance, Thing t, DesignationDef def)
|
||||
=> FirstDesignationOfDef(__instance.AllDesignationsOn(t), def);
|
||||
|
||||
public static Designation? FirstDesignationOfDef(IEnumerable<Designation> designations, DesignationDef def)
|
||||
{
|
||||
if (designations is not List<Designation> designationList)
|
||||
@@ -102,8 +130,10 @@ public class DesignationManagerCaching : ClassWithFishPatches
|
||||
if (designation.def == def)
|
||||
return designation;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Designation? Fallback(IEnumerable<Designation> designations, DesignationDef def)
|
||||
{
|
||||
foreach (var designation in designations)
|
||||
@@ -111,20 +141,28 @@ public class DesignationManagerCaching : ClassWithFishPatches
|
||||
if (designation.def == def)
|
||||
return designation;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions) => Reflection.GetCodeInstructions(DesignationOn);
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.GetCodeInstructions(DesignationOn);
|
||||
}
|
||||
|
||||
public class HasMapDesignationOn_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Designation caching";
|
||||
public override Expression<Action> TargetMethod => () => default(DesignationManager)!.HasMapDesignationOn(null);
|
||||
public static bool HasMapDesignationOn(DesignationManager __instance, Thing t) => __instance.AllDesignationsOn(t).Any();
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions) => Reflection.GetCodeInstructions(HasMapDesignationOn);
|
||||
|
||||
public static bool HasMapDesignationOn(DesignationManager __instance, Thing t)
|
||||
=> __instance.AllDesignationsOn(t).Any();
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.GetCodeInstructions(HasMapDesignationOn);
|
||||
}
|
||||
|
||||
public struct DesignationCache<T> : Cache.IIsRefreshable<Cache.ByIntRefreshable<DesignationManager, T, DesignationCache<T>>, DesignationCache<T>>
|
||||
public record struct DesignationCache<T> : Cache.IIsRefreshable<
|
||||
Cache.ByIntRefreshable<DesignationManager, T, DesignationCache<T>>, DesignationCache<T>>
|
||||
where T : notnull
|
||||
{
|
||||
public IEnumerable<Designation> designations;
|
||||
@@ -139,6 +177,7 @@ public class DesignationManagerCaching : ClassWithFishPatches
|
||||
_manager = manager;
|
||||
_managerListState = manager.allDesignations.Version();
|
||||
}
|
||||
|
||||
public void UpdateList(IEnumerable<Designation> designations)
|
||||
{
|
||||
var cachedList = (List<Designation>)this.designations;
|
||||
@@ -147,12 +186,17 @@ public class DesignationManagerCaching : ClassWithFishPatches
|
||||
ShouldRefreshNow = false;
|
||||
_managerListState = _manager.allDesignations.Version();
|
||||
}
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame || _managerListState != _manager.allDesignations.Version();
|
||||
get
|
||||
=> _nextRefreshTick < Current.Game.tickManager.TicksGame
|
||||
|| _managerListState != _manager.allDesignations.Version();
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public DesignationCache<T> SetNewValue(Cache.ByIntRefreshable<DesignationManager, T, DesignationCache<T>> key) => throw new NotImplementedException();
|
||||
|
||||
public DesignationCache<T> SetNewValue(Cache.ByIntRefreshable<DesignationManager, T, DesignationCache<T>> key)
|
||||
=> throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public unsafe struct UnalignedPointer
|
||||
{
|
||||
private fixed byte _data[6]; // 48bit
|
||||
|
||||
// [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] // 48bit
|
||||
// private byte[] _data;
|
||||
|
||||
public void* Value
|
||||
{
|
||||
get
|
||||
{
|
||||
fixed (void* value = _data)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public UnalignedPointer(void* pointer)
|
||||
{
|
||||
fixed (UnalignedPointer* instance = &this)
|
||||
{
|
||||
instance->_data = (byte*)pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe struct TaggedPointer
|
||||
{
|
||||
private void* _data;
|
||||
|
||||
public void* Value
|
||||
{
|
||||
get
|
||||
{
|
||||
var value = _data;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public ushort UpperBits => (ushort)_data;
|
||||
|
||||
public byte LowerBits => (byte)_data;
|
||||
|
||||
public TaggedPointer(void* pointer) => _data = pointer;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,300 @@
|
||||
// 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;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
[StaticConstructorOnStartup]
|
||||
public class JITPatch
|
||||
{
|
||||
static JITPatch()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Handling System.Type objects:
|
||||
*
|
||||
* Fields defined as System.Type in managed code should be defined as MonoObject*
|
||||
* in unmanaged structures, and the monotype_cast () function should be used for
|
||||
* casting them to MonoReflectionType* to avoid crashes/security issues when
|
||||
* encountering instances of user defined subclasses of System.Type.
|
||||
*/
|
||||
|
||||
/* This corresponds to System.Type */
|
||||
[MONO_RT_MANAGED_ATTR]
|
||||
struct MonoReflectionType
|
||||
{
|
||||
MonoObject @object;
|
||||
MonoType* type;
|
||||
}
|
||||
|
||||
/* This corresponds to System.RuntimeType */
|
||||
unsafe struct MonoReflectionMonoType
|
||||
{
|
||||
MonoReflectionType type;
|
||||
MonoObject* type_info;
|
||||
}
|
||||
|
||||
/*
|
||||
* The following structure must match the C# implementation in our corlib.
|
||||
*/
|
||||
|
||||
[MONO_RT_MANAGED_ATTR]
|
||||
unsafe struct MonoReflectionMethod
|
||||
{
|
||||
MonoObject @object;
|
||||
MonoMethod* method;
|
||||
MonoString* name;
|
||||
MonoReflectionType* reftype;
|
||||
}
|
||||
/* Safely access System.Reflection.MonoMethod from native code */
|
||||
|
||||
[MONO_RT_MANAGED_ATTR]
|
||||
unsafe struct MonoReflectionMethodBody
|
||||
{
|
||||
MonoObject @object;
|
||||
MonoArray* clauses;
|
||||
MonoArray* locals;
|
||||
MonoArray* il;
|
||||
MonoBoolean init_locals;
|
||||
uint local_var_sig_token;
|
||||
uint max_stack;
|
||||
}
|
||||
|
||||
unsafe struct MonoMethodInfo
|
||||
{
|
||||
MonoReflectionType* parent;
|
||||
MonoReflectionType* ret;
|
||||
uint attrs;
|
||||
uint implattrs;
|
||||
uint callconv;
|
||||
}
|
||||
|
||||
[MONO_RT_MANAGED_ATTR]
|
||||
struct MonoString
|
||||
{
|
||||
MonoObject @object;
|
||||
int length;
|
||||
ushort chars [MONO_ZERO_LEN_ARRAY];
|
||||
}
|
||||
|
||||
[MONO_RT_MANAGED_ATTR]
|
||||
unsafe struct MonoArray
|
||||
{
|
||||
MonoObject obj;
|
||||
/* bounds is NULL for szarrays */
|
||||
MonoArrayBounds* bounds;
|
||||
/* total number of elements of the array */
|
||||
uint max_length;
|
||||
/* we use double to ensure proper alignment on platforms that need it */
|
||||
double vector [MONO_ZERO_LEN_ARRAY];
|
||||
}
|
||||
|
||||
struct MonoArrayBounds
|
||||
{
|
||||
uint length;
|
||||
int lower_bound;
|
||||
}
|
||||
|
||||
struct MonoBoolean
|
||||
{
|
||||
byte Value;
|
||||
}
|
||||
|
||||
[MONO_RT_MANAGED_ATTR]
|
||||
unsafe struct MonoObject
|
||||
{
|
||||
MonoVTable* vtable;
|
||||
MonoThreadsSync* synchronisation;
|
||||
}
|
||||
|
||||
unsafe struct MonoMethod {
|
||||
ushort flags; /* method flags */
|
||||
ushort iflags; /* method implementation flags */
|
||||
uint token;
|
||||
MonoClass* klass; /* To what class does this method belong */
|
||||
MonoMethodSignature* signature;
|
||||
/* name is useful mostly for debugging */
|
||||
readonly char* name;
|
||||
/* this is used by the inlining algorithm */
|
||||
uint inline_info:1;
|
||||
uint inline_failure:1;
|
||||
uint wrapper_type:5;
|
||||
uint string_ctor:1;
|
||||
uint save_lmf:1;
|
||||
uint dynamic:1; /* created & destroyed during runtime */
|
||||
uint sre_method:1; /* created at runtime using Reflection.Emit */
|
||||
uint is_generic:1; /* whenever this is a generic method definition */
|
||||
uint is_inflated:1; /* whether we're a MonoMethodInflated */
|
||||
uint skip_visibility:1; /* whenever to skip JIT visibility checks */
|
||||
uint verification_success:1; /* whether this method has been verified successfully.*/
|
||||
int slot : 16;
|
||||
|
||||
/*
|
||||
* If is_generic is TRUE, the generic_container is stored in image->property_hash,
|
||||
* using the key MONO_METHOD_PROP_GENERIC_CONTAINER.
|
||||
*/
|
||||
}
|
||||
|
||||
unsafe struct MonoClass
|
||||
{
|
||||
/* element class for arrays and enum basetype for enums */
|
||||
MonoClass* element_class;
|
||||
/* used for subtype checks */
|
||||
MonoClass* cast_class;
|
||||
|
||||
/* for fast subtype checks */
|
||||
MonoClass** supertypes;
|
||||
ushort idepth;
|
||||
|
||||
/* array dimension */
|
||||
byte rank;
|
||||
|
||||
int instance_size; /* object instance size */
|
||||
|
||||
uint inited : 1;
|
||||
|
||||
/* A class contains static and non static data. Static data can be
|
||||
* of the same type as the class itself, but it does not influence
|
||||
* the instance size of the class. To avoid cyclic calls to
|
||||
* mono_class_init (from mono_class_instance_size ()) we first
|
||||
* initialise all non static fields. After that we set size_inited
|
||||
* to 1, because we know the instance size now. After that we
|
||||
* initialise all static fields.
|
||||
*/
|
||||
|
||||
/* ALL BITFIELDS SHOULD BE WRITTEN WHILE HOLDING THE LOADER LOCK */
|
||||
uint size_inited : 1;
|
||||
uint valuetype : 1; /* derives from System.ValueType */
|
||||
uint enumtype : 1; /* derives from System.Enum */
|
||||
uint blittable : 1; /* class is blittable */
|
||||
uint unicode : 1; /* class uses unicode char when marshalled */
|
||||
uint wastypebuilder : 1; /* class was created at runtime from a TypeBuilder */
|
||||
uint is_array_special_interface : 1; /* gtd or ginst of once of the magic interfaces that arrays implement */
|
||||
|
||||
/* next byte */
|
||||
byte min_align;
|
||||
|
||||
/* next byte */
|
||||
uint packing_size : 4;
|
||||
uint ghcimpl : 1; /* class has its own GetHashCode impl */
|
||||
uint has_finalize : 1; /* class has its own Finalize impl */
|
||||
uint marshalbyref : 1; /* class is a MarshalByRefObject */
|
||||
uint contextbound : 1; /* class is a ContextBoundObject */
|
||||
/* next byte */
|
||||
uint @delegate : 1; /* class is a Delegate */
|
||||
uint gc_descr_inited : 1; /* gc_descr is initialized */
|
||||
uint has_cctor : 1; /* class has a cctor */
|
||||
uint has_references : 1; /* it has GC-tracked references in the instance */
|
||||
uint has_static_refs : 1; /* it has static fields that are GC-tracked */
|
||||
uint no_special_static_fields : 1; /* has no thread/context static fields */
|
||||
/* directly or indirectly derives from ComImport attributed class.
|
||||
* this means we need to create a proxy for instances of this class
|
||||
* for COM Interop. set this flag on loading so all we need is a quick check
|
||||
* during object creation rather than having to traverse supertypes
|
||||
*/
|
||||
uint is_com_object : 1;
|
||||
uint nested_classes_inited : 1; /* Whenever nested_class is initialized */
|
||||
|
||||
/* next byte*/
|
||||
uint class_kind : 3; /* One of the values from MonoTypeKind */
|
||||
uint interfaces_inited : 1; /* interfaces is initialized */
|
||||
uint simd_type : 1; /* class is a simd intrinsic type */
|
||||
uint has_finalize_inited : 1; /* has_finalize is initialized */
|
||||
uint fields_inited : 1; /* setup_fields () has finished */
|
||||
uint has_failure : 1; /* See mono_class_get_exception_data () for a MonoErrorBoxed with the details */
|
||||
uint has_weak_fields : 1; /* class has weak reference fields */
|
||||
|
||||
MonoClass* parent;
|
||||
MonoClass* nested_in;
|
||||
|
||||
MonoImage* image;
|
||||
const char* name;
|
||||
const char* name_space;
|
||||
|
||||
uint type_token;
|
||||
int vtable_size; /* number of slots */
|
||||
|
||||
ushort interface_count;
|
||||
uint interface_id; /* unique inderface id (for interfaces) */
|
||||
uint max_interface_id;
|
||||
|
||||
ushort interface_offsets_count;
|
||||
MonoClass** interfaces_packed;
|
||||
ushort* interface_offsets_packed;
|
||||
/* enabled only with small config for now: we might want to do it unconditionally */
|
||||
byte* interface_bitmap;
|
||||
|
||||
MonoClass** interfaces;
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)] // union
|
||||
struct sizes
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
int class_size; /* size of area for static fields */
|
||||
[FieldOffset(0)]
|
||||
int element_size; /* for array types */
|
||||
[FieldOffset(0)]
|
||||
int generic_param_token; /* for generic param types, both var and mvar */
|
||||
}
|
||||
|
||||
/*
|
||||
* Field information: Type and location from object base
|
||||
*/
|
||||
MonoClassField* fields;
|
||||
|
||||
MonoMethod** methods;
|
||||
|
||||
/* used as the type of the this argument and when passing the arg by value */
|
||||
MonoType this_arg;
|
||||
MonoType byval_arg;
|
||||
|
||||
MonoGCDescriptor gc_descr;
|
||||
|
||||
MonoClassRuntimeInfo *runtime_info;
|
||||
|
||||
/* Generic vtable. Initialized by a call to mono_class_setup_vtable () */
|
||||
MonoMethod** vtable;
|
||||
|
||||
/* Infrequently used items. See class-accessors.c: InfrequentDataKind for what goes into here. */
|
||||
MonoPropertyBag infrequent_data;
|
||||
|
||||
void* unity_user_data;
|
||||
}
|
||||
|
||||
/* the interface_offsets array is stored in memory before this struct */
|
||||
unsafe struct MonoVTable
|
||||
{
|
||||
MonoClass* klass;
|
||||
/*
|
||||
* According to comments in gc_gcj.h, this should be the second word in
|
||||
* the vtable.
|
||||
*/
|
||||
MonoGCDescriptor gc_descr;
|
||||
MonoDomain* domain; /* each object/vtable belongs to exactly one domain */
|
||||
nint type; /* System.Type type for klass */
|
||||
byte* interface_bitmap;
|
||||
uint max_interface_id;
|
||||
byte rank;
|
||||
/* Keep this a guint8, the jit depends on it */
|
||||
byte initialized; /* cctor has been run */
|
||||
uint remote : 1; /* class is remotely activated */
|
||||
uint init_failed : 1; /* cctor execution failed */
|
||||
uint has_static_fields : 1; /* pointer to the data stored at the end of the vtable array */
|
||||
uint gc_bits : MONO_VTABLE_AVAILABLE_GC_BITS; /* Those bits are reserved for the usaged of the GC */
|
||||
|
||||
uint imt_collisions_bitmap;
|
||||
MonoRuntimeGenericContext* runtime_generic_context;
|
||||
/* do not add any fields after vtable, the structure is dynamically extended */
|
||||
/* vtable contains function pointers to methods or their trampolines, at the
|
||||
end there may be a slot containing the pointer to the static fields */
|
||||
nint vtable [MONO_ZERO_LEN_ARRAY];
|
||||
}
|
||||
#endif
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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/.
|
||||
|
||||
@@ -1,399 +0,0 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasDescription
|
||||
{
|
||||
public virtual bool ShouldBenchmark => false;
|
||||
|
||||
public virtual bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
_enabled = value;
|
||||
if (value)
|
||||
{
|
||||
if (ShouldBePatched)
|
||||
TryPatch();
|
||||
}
|
||||
else
|
||||
{
|
||||
var shouldBePatched = ShouldBePatched;
|
||||
TryUnpatch();
|
||||
ShouldBePatched = shouldBePatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
private bool _enabled = true;
|
||||
public virtual bool DefaultState => true;
|
||||
public virtual int PrefixMethodPriority => TryGetPriority(PrefixMethodInfo);
|
||||
public virtual int PostfixMethodPriority => TryGetPriority(PostfixMethodInfo);
|
||||
public virtual int TranspilerMethodPriority => TryGetPriority(TranspilerMethodInfo);
|
||||
public virtual int FinalizerMethodPriority => TryGetPriority(FinalizerMethodInfo);
|
||||
public MethodInfo? HarmonyMethodInfo => HarmonyMethodInfos.FirstOrDefault();
|
||||
public List<MethodInfo> HarmonyMethodInfos { get; } = new();
|
||||
public MethodInfo? BenchmarkHarmonyMethodInfo => BenchmarkHarmonyMethodInfos.FirstOrDefault();
|
||||
public List<MethodInfo> BenchmarkHarmonyMethodInfos { get; } = new();
|
||||
private bool Patched { get; set; }
|
||||
private bool ShouldBePatched { get; set; }
|
||||
|
||||
public virtual string? Name => null;
|
||||
public virtual string? Description => null;
|
||||
|
||||
public virtual Delegate? TargetMethodGroup => null;
|
||||
public virtual IEnumerable<Delegate>? TargetMethodGroups => null;
|
||||
public virtual Expression<Action>? TargetMethod => null;
|
||||
public virtual IEnumerable<Expression<Action>>? TargetMethods => null;
|
||||
public virtual MethodBase TargetMethodInfo
|
||||
=> TargetMethod != null
|
||||
? SymbolExtensions.GetMethodInfo(TargetMethod)
|
||||
: TargetMethodGroup?.Method
|
||||
?? throw new MissingMethodException(GetType().ToString());
|
||||
|
||||
public virtual IEnumerable<MethodBase> TargetMethodInfos
|
||||
=> _targetMethodInfos
|
||||
??= (TargetMethods?.Select(m
|
||||
=> SymbolExtensions.GetMethodInfo(m))
|
||||
?? TargetMethodGroups?.Select(m => m.Method)
|
||||
?? (TargetMethodInfo != null
|
||||
? new MethodBase[] { TargetMethodInfo }.AsEnumerable()
|
||||
: throw new MissingMethodException(GetType().ToString()))).ToArray();
|
||||
|
||||
private MethodBase[]? _targetMethodInfos;
|
||||
|
||||
public virtual MethodInfo ReversePatchMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "REVERSEPATCH" || (m.HasAttribute<HarmonyReversePatch>() && !m.HasAttribute<HarmonyTranspiler>()));
|
||||
public virtual MethodInfo ReversePatchTranspilerMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "REVERSEPATCHTRANSPILER" || (m.HasAttribute<HarmonyTranspiler>() && m.HasAttribute<HarmonyReversePatch>()));
|
||||
public virtual MethodInfo PrefixMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "PREFIX" || m.HasAttribute<HarmonyPrefix>());
|
||||
public virtual MethodInfo PostfixMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "POSTFIX" || m.HasAttribute<HarmonyPostfix>());
|
||||
public virtual MethodInfo TranspilerMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "TRANSPILER" || (m.HasAttribute<HarmonyTranspiler>() && !m.HasAttribute<HarmonyReversePatch>()));
|
||||
public virtual MethodInfo FinalizerMethodInfo => GetType().GetMethods(AccessTools.all)
|
||||
.FirstOrDefault(m => m.Name.ToUpperInvariant() == "FINALIZER" || m.HasAttribute<HarmonyFinalizer>());
|
||||
|
||||
internal bool IsBenchmarking => ShouldBenchmark && !FinishedBenchmarking;
|
||||
internal bool FinishedBenchmarking = false;
|
||||
|
||||
protected FishPatch() => _enabled = DefaultState;
|
||||
|
||||
public virtual MethodInfo? TryPatch()
|
||||
{
|
||||
if (!TargetMethodInfos?.Any() ?? true)
|
||||
{
|
||||
Log.Error($"Tried to apply patches for {GetType().Name}, but it lacks a target method for patching.");
|
||||
return null;
|
||||
}
|
||||
if (PrefixMethodInfo is null && PostfixMethodInfo is null && TranspilerMethodInfo is null && FinalizerMethodInfo is null && ReversePatchMethodInfo is null)
|
||||
{
|
||||
Log.Error($"Tried to apply patches for {GetType().Name}, but there are none. This is likely not intended.");
|
||||
return null;
|
||||
}
|
||||
|
||||
ShouldBePatched = true;
|
||||
if (Enabled && !Patched)
|
||||
ApplyPatch();
|
||||
|
||||
return HarmonyMethodInfo;
|
||||
}
|
||||
|
||||
protected virtual void ApplyPatch()
|
||||
{
|
||||
Patched = true;
|
||||
foreach (var method in TargetMethodInfos!)
|
||||
{
|
||||
DebugLog.Message($"Performance Fish is applying {GetType().Name} on {method.FullDescription()}");
|
||||
|
||||
try
|
||||
{
|
||||
if (IsBenchmarking)
|
||||
{
|
||||
Patched = false;
|
||||
|
||||
if (AccessTools.EnumeratorMoveNext(method) is { } moveNext)
|
||||
{
|
||||
BenchmarkHarmonyMethodInfos.Add(PerformanceFishMod.Harmony.Patch(moveNext,
|
||||
prefix: new(methodof(Benchmarking.Prefix), Priority.First + 1),
|
||||
postfix: new(methodof(Benchmarking.Postfix), Priority.Last - 1)));
|
||||
}
|
||||
|
||||
BenchmarkHarmonyMethodInfos.Add(PerformanceFishMod.Harmony.Patch(method,
|
||||
prefix: new(methodof(Benchmarking.Prefix), Priority.First + 1),
|
||||
postfix: new(methodof(Benchmarking.Postfix), Priority.Last - 1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ReversePatchMethodInfo != null)
|
||||
HarmonyMethodInfos.Add(Harmony.ReversePatch(method, new(ReversePatchMethodInfo), ReversePatchTranspilerMethodInfo));
|
||||
|
||||
if (PrefixMethodInfo != null || PostfixMethodInfo != null || TranspilerMethodInfo != null || FinalizerMethodInfo != null)
|
||||
{
|
||||
HarmonyMethodInfos.Add(PerformanceFishMod.Harmony.Patch(method,
|
||||
prefix: PrefixMethodInfo != null ? new(PrefixMethodInfo, PrefixMethodPriority) : null,
|
||||
postfix: PostfixMethodInfo != null ? new(PostfixMethodInfo, PostfixMethodPriority) : null,
|
||||
transpiler: TranspilerMethodInfo != null ? new(TranspilerMethodInfo, TranspilerMethodPriority) : null,
|
||||
finalizer: FinalizerMethodInfo != null ? new(FinalizerMethodInfo, FinalizerMethodPriority) : null));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to patch {method.FullDescription()} with {PrefixMethodInfo.FullDescription()}" +
|
||||
$", {PostfixMethodInfo.FullDescription()}, {TranspilerMethodInfo.FullDescription()}, {FinalizerMethodInfo.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void TryUnpatch()
|
||||
{
|
||||
ShouldBePatched = false;
|
||||
if (/*!Enabled &&*/ Patched)
|
||||
ApplyUnpatching();
|
||||
}
|
||||
|
||||
protected virtual void ApplyUnpatching()
|
||||
{
|
||||
Patched = false;
|
||||
foreach (var method in TargetMethodInfos)
|
||||
{
|
||||
if (PrefixMethodInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(method, PrefixMethodInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception when unpatching {PrefixMethodInfo.FullDescription()} from {method.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
if (PostfixMethodInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(method, PostfixMethodInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception when unpatching {PostfixMethodInfo.FullDescription()} from {method.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
if (TranspilerMethodInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(method, TranspilerMethodInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception when unpatching {TranspilerMethodInfo.FullDescription()} from {method.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
if (FinalizerMethodInfo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(method, FinalizerMethodInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception when unpatching {FinalizerMethodInfo.FullDescription()} from {method.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int TryGetPriority(MethodInfo info) => info?.TryGetAttribute<HarmonyPriority>()?.info.priority ?? Priority.Normal;
|
||||
|
||||
public virtual void ExposeData()
|
||||
=> Scribe_Values.Look(ref _enabled, "enabled", DefaultState);
|
||||
}
|
||||
|
||||
internal static class Benchmarking
|
||||
{
|
||||
internal static int SwitchFrame = int.MaxValue;
|
||||
internal static int EndFrame = int.MaxValue;
|
||||
|
||||
public static void Prefix(MethodBase __originalMethod, out Results __state)
|
||||
{
|
||||
if (!Cache.ByReference<MethodBase, Results>.Get.TryGetValue(__originalMethod, out __state))
|
||||
Cache.ByReference<MethodBase, Results>.Get[__originalMethod] = __state = new(__originalMethod);
|
||||
|
||||
__state.Stopwatch.Start();
|
||||
}
|
||||
|
||||
public static void Postfix(Results __state)
|
||||
{
|
||||
__state.Stopwatch.Stop();
|
||||
__state.Count++;
|
||||
|
||||
//Log.Message($"SwitchFrame is {SwitchFrame}, EndFrame is {EndFrame}, CurrentFrame is {Time.frameCount}");
|
||||
|
||||
//if (__state.Stopwatch.ElapsedMilliseconds > 100 || __state.Count > 100000)
|
||||
if (__state.Switched ? GenTicks.TicksGame > EndFrame : GenTicks.TicksGame > SwitchFrame)
|
||||
PrintResultsAndStop(__state);
|
||||
}
|
||||
|
||||
public static void PrintResultsAndStop(Results results)
|
||||
{
|
||||
var ticks = results.Stopwatch.ElapsedTicks;
|
||||
Log.Message($"{results.Method.FullDescription()} took {ticks} ticks for {results.Count} calls. This equals {ticks * 1000L / results.Count} ticks per 1000 calls on average.");
|
||||
|
||||
results.Count = 0;
|
||||
results.Switched = true;
|
||||
results.Stopwatch.Reset();
|
||||
|
||||
var fishPatch = PerformanceFishMod.AllPatchClasses
|
||||
.SelectMany(patchClass
|
||||
=> patchClass.Patches)
|
||||
.First(patch
|
||||
=> patch.TargetMethodInfos.Contains(results.Method));
|
||||
|
||||
if (fishPatch.FinishedBenchmarking)
|
||||
{
|
||||
foreach (var target in fishPatch.TargetMethodInfos)
|
||||
{
|
||||
if (AccessTools.EnumeratorMoveNext(target) is { } moveNext)
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(moveNext, methodof(Prefix));
|
||||
PerformanceFishMod.Harmony.Unpatch(moveNext, methodof(Postfix));
|
||||
}
|
||||
|
||||
PerformanceFishMod.Harmony.Unpatch(target, methodof(Prefix));
|
||||
PerformanceFishMod.Harmony.Unpatch(target, methodof(Postfix));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fishPatch.FinishedBenchmarking = true;
|
||||
fishPatch.TryPatch();
|
||||
}
|
||||
}
|
||||
|
||||
public class Results
|
||||
{
|
||||
public int Count;
|
||||
public Stopwatch Stopwatch;
|
||||
public MethodBase Method;
|
||||
public bool Switched;
|
||||
|
||||
public Results(MethodBase method)
|
||||
{
|
||||
Stopwatch = new();
|
||||
Method = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class FirstPriorityFishPatch : FishPatch
|
||||
{
|
||||
public override int PrefixMethodPriority => Priority.First;
|
||||
public override int PostfixMethodPriority => Priority.First;
|
||||
public override int TranspilerMethodPriority => Priority.First;
|
||||
public override int FinalizerMethodPriority => Priority.First;
|
||||
}
|
||||
|
||||
public static class FishPatchExtensions
|
||||
{
|
||||
public static void PatchAll(this IEnumerable<FishPatch> patches)
|
||||
{
|
||||
foreach (var patch in patches)
|
||||
patch.TryPatch();
|
||||
}
|
||||
public static void UnpatchAll(this IEnumerable<FishPatch> patches)
|
||||
{
|
||||
foreach (var patch in patches)
|
||||
patch.TryUnpatch();
|
||||
}
|
||||
}
|
||||
|
||||
public class FishPatchHolder : IExposable, IEnumerable<FishPatch>
|
||||
{
|
||||
public ConcurrentDictionary<Type, FishPatch> All => _all;
|
||||
public T Get<T>() where T : FishPatch => (T)All[typeof(T)];
|
||||
public void Add(FishPatch patch) => All[patch.GetType()] = patch;
|
||||
public void PatchAll() => All.Values.PatchAll();
|
||||
public void UnpatchAll() => All.Values.UnpatchAll();
|
||||
public FishPatch this[Type type] => All[type];
|
||||
|
||||
public FishPatchHolder(Type type)
|
||||
{
|
||||
_type = type;
|
||||
AddPatchesRecursively(type);
|
||||
}
|
||||
|
||||
public void Scribe() => ExposeData(); // Exposable.Scribe(this, _type.FullName); // directly calling ExposeData prevents creating nested nodes in the config file. Looks cleaner imo.
|
||||
|
||||
public void ExposeData()
|
||||
{
|
||||
foreach (var (type, patch) in All)
|
||||
Exposable.Scribe(patch, type.FullName);
|
||||
}
|
||||
|
||||
private void AddPatchesRecursively(Type type)
|
||||
{
|
||||
if (typeof(FishPatch).IsAssignableFrom(type) && !All.ContainsKey(type))
|
||||
{
|
||||
if (PerformanceFishMod.AllPatchClasses is { } allPatches)
|
||||
RemoveDupes(allPatches, type);
|
||||
|
||||
All.TryAdd(type, FishPatch.Get(type));
|
||||
}
|
||||
|
||||
foreach (var nestedType in type.GetNestedTypes(AccessTools.all))
|
||||
AddPatchesRecursively(nestedType);
|
||||
}
|
||||
|
||||
private void RemoveDupes(IHasFishPatch[] patches, Type type)
|
||||
{
|
||||
var dupeClasses = patches
|
||||
.Where(patchClass
|
||||
=> patchClass.GetType() != _type
|
||||
&& patchClass.Patches.All.ContainsKey(type));
|
||||
|
||||
if (!dupeClasses.Any())
|
||||
return;
|
||||
|
||||
foreach (var patchClass in dupeClasses)
|
||||
{
|
||||
patchClass.Patches[type].Enabled = false;
|
||||
patchClass.Patches.All.TryRemove(type, out _);
|
||||
Log.Warning($"Performance Fish removed a duplicate patch from {patchClass.GetType().FullName}. This is likely caused by no longer valid mod configs");
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<FishPatch> GetEnumerator() => All.Values.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => All.Values.GetEnumerator();
|
||||
|
||||
private ConcurrentDictionary<Type, FishPatch> _all = new();
|
||||
private Type _type;
|
||||
}
|
||||
|
||||
public interface IHasFishPatch
|
||||
{
|
||||
public FishPatchHolder Patches { get; }
|
||||
public bool RequiresLoadedGameForPatching { get; }
|
||||
}
|
||||
|
||||
public interface IHasDescription
|
||||
{
|
||||
public string? Description { get; }
|
||||
}
|
||||
|
||||
public abstract class ClassWithFishPatches : SingletonFactory<ClassWithFishPatches>, IHasFishPatch
|
||||
{
|
||||
public virtual FishPatchHolder Patches { get; }
|
||||
public virtual bool RequiresLoadedGameForPatching => false;
|
||||
|
||||
protected ClassWithFishPatches() => Patches = new(GetType());
|
||||
}
|
||||
@@ -1,44 +1,78 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using PerformanceFish.ModCompatibility;
|
||||
using PerformanceFish.Planet;
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
[PublicAPI]
|
||||
public class FishSettings : ModSettings
|
||||
{
|
||||
public static bool SettingsLoaded { get; private set; }
|
||||
|
||||
public override void ExposeData()
|
||||
{
|
||||
base.ExposeData();
|
||||
|
||||
Scribe_Values.Look(ref _threadingEnabled, "ThreadingEnabled", false);
|
||||
Scribe_Values.Look(ref _threadingEnabled, "ThreadingEnabled");
|
||||
Scribe_Values.Look(ref MothballEverything, "MothballEverything");
|
||||
|
||||
foreach (var patchClass in PerformanceFishMod.AllPatchClasses)
|
||||
if (PerformanceFishMod.AllPatchClasses is { } allPatchClasses)
|
||||
{
|
||||
foreach (var patchClass in allPatchClasses)
|
||||
patchClass.Patches.Scribe();
|
||||
}
|
||||
|
||||
if (PerformanceFishMod.AllPrepatchClasses is { } allPrepatchClasses)
|
||||
{
|
||||
foreach (var patchClass in allPrepatchClasses)
|
||||
patchClass.Patches.Scribe();
|
||||
}
|
||||
|
||||
SettingsLoaded = true;
|
||||
}
|
||||
|
||||
public static void DoSettingsWindowContents(Rect inRect)
|
||||
{
|
||||
var windowRect = Find.WindowStack.currentlyDrawnWindow.windowRect;
|
||||
if (Widgets.ButtonText(new(windowRect.width - Window.CloseButSize.x - (Window.StandardMargin * 2f),
|
||||
windowRect.height - Window.FooterRowHeight - Window.StandardMargin, Window.CloseButSize.x,
|
||||
Window.CloseButSize.y),
|
||||
"Reset all"))
|
||||
{
|
||||
ResetAll();
|
||||
}
|
||||
|
||||
Widgets.BeginScrollView(inRect, ref _scrollPosition, _scrollRect);
|
||||
var ls = new Listing_Standard();
|
||||
ls.Begin(_scrollRect);
|
||||
|
||||
var checkModEnabled = ModEnabled;
|
||||
ls.CheckboxLabeled("Toggle all patches", ref checkModEnabled, "This enables or disables all patches of the mod with one click. For testing purposes");
|
||||
ls.CheckboxLabeled("Toggle all patches", ref checkModEnabled,
|
||||
"This enables or disables all patches of the mod with one click. For testing purposes");
|
||||
if (checkModEnabled != ModEnabled)
|
||||
{
|
||||
ModEnabled = checkModEnabled;
|
||||
foreach (var patchClass in PerformanceFishMod.AllPatchClasses)
|
||||
{
|
||||
foreach (var patch in patchClass.Patches)
|
||||
patch.Enabled = checkModEnabled;
|
||||
|
||||
if (checkModEnabled && (!patchClass.RequiresLoadedGameForPatching || Current.ProgramState == ProgramState.Playing))
|
||||
patchClass.Patches.PatchAll();
|
||||
}
|
||||
ToggleAllPatches(checkModEnabled);
|
||||
}
|
||||
|
||||
foreach (var patchClass in PerformanceFishMod.AllPatchClasses)
|
||||
ls.Gap();
|
||||
var curFontStyle = Text.CurFontStyle;
|
||||
var curFontSize = curFontStyle.fontSize;
|
||||
curFontStyle.fontSize = 20;
|
||||
ls.Label("Harmony Patches");
|
||||
curFontStyle.fontSize = curFontSize;
|
||||
ls.GapLine(2f);
|
||||
ls.Gap();
|
||||
|
||||
foreach (var patchClass in PerformanceFishMod.AllPatchClasses!)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -62,10 +96,13 @@ public class FishSettings : ModSettings
|
||||
var label = patch.Name ?? patch.GetType().Name;
|
||||
if (label.EndsWith("_Patch"))
|
||||
label = label.Remove(label.Length - 6);
|
||||
else if (label.EndsWith("Patch"))
|
||||
label = label.Remove(label.Length - 5);
|
||||
ls.CheckboxLabeled(label, ref check, patch.Description);
|
||||
if (check != patch.Enabled)
|
||||
patch.Enabled = check;
|
||||
}
|
||||
|
||||
ls.Gap();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -74,11 +111,82 @@ public class FishSettings : ModSettings
|
||||
}
|
||||
}
|
||||
|
||||
//ls.Gap();
|
||||
//var threadingEnabled = ThreadingEnabled;
|
||||
//ls.CheckboxLabeled("Allow threading", ref threadingEnabled, "Currently affects the ListerHaulables tick, ingredient lookups in WorkGiver_DoBill and refreshing of the shared altar thoughtworker cache. " +
|
||||
// "Experimental and disabled by default.");
|
||||
//ThreadingEnabled = threadingEnabled;
|
||||
ls.Gap();
|
||||
curFontStyle.fontSize = 20;
|
||||
ls.Label("Prepatches");
|
||||
curFontStyle.fontSize = curFontSize;
|
||||
ls.GapLine();
|
||||
ls.Gap();
|
||||
|
||||
foreach (var patchClass in PerformanceFishMod.AllPrepatchClasses!)
|
||||
{
|
||||
try
|
||||
{
|
||||
ls.Label(patchClass.GetType().Name,
|
||||
// ReSharper disable once SuspiciousTypeConversion.Global
|
||||
tooltip: patchClass is IHasDescription classWithDescription
|
||||
? classWithDescription.Description
|
||||
: null);
|
||||
|
||||
ls.GapLine(2f);
|
||||
foreach (var patch in patchClass.Patches)
|
||||
{
|
||||
if (ShouldSkipForScrollView(inRect.height, Text.LineHeight, ls.curY, _scrollPosition.y))
|
||||
{
|
||||
ls.curY += Text.LineHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
var check = patch.Enabled;
|
||||
if (patch.Description == null)
|
||||
Widgets.DrawHighlightIfMouseover(_scrollRect with { y = ls.curY, height = Text.LineHeight });
|
||||
var label = patch.Name ?? patch.GetType().Name;
|
||||
if (label.EndsWith("_Patch"))
|
||||
label = label.Remove(label.Length - 6);
|
||||
else if (label.EndsWith("Patch"))
|
||||
label = label.Remove(label.Length - 5);
|
||||
ls.CheckboxLabeled(label, ref check, patch.Description);
|
||||
if (check != patch.Enabled)
|
||||
patch.Enabled = check;
|
||||
}
|
||||
|
||||
ls.Gap();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
ls.Gap();
|
||||
ls.CheckboxLabeled("Mothball everything", ref MothballEverything, MothballEverythingDescription);
|
||||
|
||||
ls.Gap();
|
||||
|
||||
if (ls.ButtonText("Log hediffs affected by mothball optimization"))
|
||||
{
|
||||
Log.Message($"Allowed defs for mothballing: {MothballOptimization.AllowedDefs.ToStringSafeEnumerable()}");
|
||||
Log.Message($"Blocking defs for mothballing: {DefDatabase<HediffDef>.AllDefsListForReading.Where(static def
|
||||
=> !MothballOptimization.AllowedDefs.Contains(def)).ToStringSafeEnumerable()}");
|
||||
|
||||
Verse.Log.TryOpenLogWindow();
|
||||
}
|
||||
|
||||
ls.Gap();
|
||||
|
||||
if (ls.ButtonText("Log patch count"))
|
||||
{
|
||||
PerformanceFishMod.LogPatchCount();
|
||||
Verse.Log.TryOpenLogWindow();
|
||||
}
|
||||
|
||||
ls.Gap();
|
||||
|
||||
if (ls.ButtonText("Log cache utilization"))
|
||||
{
|
||||
Cache.Utility.LogCurrentCacheUtilization();
|
||||
Verse.Log.TryOpenLogWindow();
|
||||
}
|
||||
|
||||
ls.Gap();
|
||||
if (ls.ButtonText("Clear Cache"))
|
||||
@@ -87,14 +195,72 @@ public class FishSettings : ModSettings
|
||||
ls.End();
|
||||
|
||||
Widgets.EndScrollView();
|
||||
_scrollRect = _scrollRect with { height = ls.curY + 50f, width = inRect.width - GUI.skin.verticalScrollbar.fixedWidth - 5f };
|
||||
_scrollRect = _scrollRect with
|
||||
{
|
||||
height = ls.curY + 50f, width = inRect.width - GUI.skin.verticalScrollbar.fixedWidth - 5f
|
||||
};
|
||||
}
|
||||
|
||||
public static bool ShouldSkipForScrollView(float scrollViewSize, float entrySize, float entryPosition, float scrollPosition)
|
||||
public static string MothballEverythingDescription
|
||||
= MothballOptimization.BASIC_DESCRIPTION + " This setting removes all conditions, enabling every world "
|
||||
+ "pawn for mothballing. Note, health conditions do not progress on mothballed pawns. Requires the "
|
||||
+ "WorldPawnsDefPreventingMothball patch to apply";
|
||||
|
||||
private static void ToggleAllPatches(bool newState) => ToggleAllPatches(_ => newState, _ => newState);
|
||||
|
||||
private static void ToggleAllPatches(Func<FishPatch, bool> fishPatchFunc,
|
||||
Func<FishPrepatchBase, bool> fishPrepatchFunc)
|
||||
{
|
||||
foreach (var patchClass in PerformanceFishMod.AllPatchClasses!)
|
||||
{
|
||||
foreach (var patch in patchClass.Patches)
|
||||
patch.Enabled = fishPatchFunc(patch);
|
||||
}
|
||||
|
||||
foreach (var patchClass in PerformanceFishMod.AllPrepatchClasses!)
|
||||
{
|
||||
foreach (var patch in patchClass.Patches)
|
||||
patch.Enabled = fishPrepatchFunc(patch);
|
||||
}
|
||||
|
||||
Cache.Utility.Clear();
|
||||
}
|
||||
|
||||
private static void ResetAll()
|
||||
{
|
||||
ToggleAllPatches(static patch => patch.DefaultState, static prepatch => prepatch.DefaultState);
|
||||
TryDeleteModSettingsFile();
|
||||
}
|
||||
|
||||
private static void TryDeleteModSettingsFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settingsFile = new FileInfo(LoadedModManager.GetSettingsFilename(
|
||||
PerformanceFishMod.Mod!.Content.FolderName, nameof(PerformanceFishMod)));
|
||||
if (settingsFile.Exists)
|
||||
settingsFile.Delete();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Exception thrown while trying to delete mod settings:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ShouldSkipForScrollView(float scrollViewSize, float entrySize, float entryPosition,
|
||||
float scrollPosition)
|
||||
=> entryPosition + entrySize < scrollPosition || entryPosition > scrollPosition + scrollViewSize;
|
||||
|
||||
public static bool ModEnabled { get => _modEnabled; set => _modEnabled = value; }
|
||||
public static bool ModEnabled
|
||||
{
|
||||
get => _modEnabled;
|
||||
set => _modEnabled = value;
|
||||
}
|
||||
|
||||
private static bool _modEnabled = true;
|
||||
|
||||
public static bool MothballEverything;
|
||||
|
||||
public static bool ThreadingEnabled
|
||||
{
|
||||
get => false; //_threadingEnabled;
|
||||
@@ -107,8 +273,28 @@ public class FishSettings : ModSettings
|
||||
_threadingEnabled = value;
|
||||
}
|
||||
}
|
||||
private static bool _threadingEnabled = false;
|
||||
|
||||
private static bool _threadingEnabled;
|
||||
public static List<FishPatch> ThreadingPatches { get; } = new();
|
||||
private static Rect _scrollRect = new(0f, 0f, 500f, 9001f);
|
||||
private static Vector2 _scrollPosition;
|
||||
|
||||
private static FishSettings TryLoadModSettings(bool fallBackAttempt = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
return PerformanceFishMod.Settings = LoadedModManager.ReadModSettings<FishSettings>(
|
||||
ActiveMods.TryGetModContentPack(PerformanceFishMod.PACKAGE_ID)!.FolderName, nameof(PerformanceFishMod));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"{PerformanceFishMod.NAME} encountered an exception while trying to load its settings. "
|
||||
+ $"Resetting now to avoid further issues.\n{ex}");
|
||||
TryDeleteModSettingsFile();
|
||||
|
||||
return !fallBackAttempt ? TryLoadModSettings(true) : null!;
|
||||
}
|
||||
}
|
||||
|
||||
public static FishSettings Instance { get; private set; } = TryLoadModSettings();
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using RimWorld.Planet;
|
||||
|
||||
namespace PerformanceFish.FunctionPointers;
|
||||
|
||||
@@ -14,20 +16,24 @@ public static class IndexGetter<T> where T : notnull
|
||||
|
||||
public static class IndexGetter
|
||||
{
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
|
||||
public static class Methods
|
||||
{
|
||||
public static int HediffSet(HediffSet set) => set.pawn.thingIDNumber;
|
||||
public static int HediffDef(HediffDef def) => def.index;
|
||||
public static int HediffDef(HediffDef def) => def.shortHash;
|
||||
public static int Bill(Bill bill) => bill.loadID;
|
||||
public static int Pawn(Pawn pawn) => pawn.thingIDNumber;
|
||||
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 DesignationDef(DesignationDef def) => def.index;
|
||||
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.index;
|
||||
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;
|
||||
}
|
||||
|
||||
internal static Cache.IndexGetter<T> GetDefaultIndexGetter<T>() where T : notnull
|
||||
@@ -36,22 +42,22 @@ public static class IndexGetter
|
||||
: Fallback<T>();
|
||||
|
||||
internal static unsafe delegate*<T, int> GetDefaultIndexFunctionPointer<T>() where T : notnull
|
||||
=> (delegate*<T, int>)(GetGetterMethods().FirstOrDefault(m => m.GetParameters()[0].ParameterType == typeof(T))
|
||||
=> (delegate*<T, int>)(_getterMethods.TryGetValue(typeof(T))
|
||||
?? Fallback<T>().Method).MethodHandle.GetFunctionPointer();
|
||||
|
||||
private static IEnumerable<MethodInfo> GetGetterMethods()
|
||||
=> typeof(Methods)
|
||||
.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly)
|
||||
.Where(m
|
||||
=> m.ReturnType == typeof(int)
|
||||
&& m.GetParameters().Length == 1);
|
||||
=> typeof(Methods).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly)
|
||||
.Where(static m => m.ReturnType == typeof(int) && m.GetParameters().Length == 1);
|
||||
|
||||
private static Cache.IndexGetter<T> Fallback<T>() where T : notnull
|
||||
{
|
||||
var isDef = typeof(Def).IsAssignableFrom(typeof(T));
|
||||
var isThing = false; //typeof(Thing).IsAssignableFrom(typeof(T)) && !typeof(Mote).IsAssignableFrom(typeof(T)); // generally always safe in vanilla, but logging mods won't hurt either
|
||||
var isThing = false; //typeof(Thing).IsAssignableFrom(typeof(T)) && !typeof(Mote).IsAssignableFrom(typeof(T));
|
||||
//// generally always safe in vanilla, but logging mods won't hurt either
|
||||
|
||||
var message = $"Performance Fish failed to find type {typeof(T).FullDescription()} in indexFuncs dictionary. Using fallback instead";
|
||||
var message
|
||||
= $"Performance Fish failed to find type {
|
||||
typeof(T).FullName} in indexFuncs dictionary. Using fallback instead";
|
||||
|
||||
if (isDef)
|
||||
Log.Error($"{message} to prevent further issues, but this likely won't work correctly.");
|
||||
@@ -60,18 +66,26 @@ public static class IndexGetter
|
||||
|
||||
var type = typeof(T).BaseType;
|
||||
Delegate func;
|
||||
while (!_indexFuncs.TryGetValue(type, out func))
|
||||
type = type.BaseType ?? throw new Exception($"Performance Fish failed to find a fallback method to handle type {typeof(T).FullDescription()} from mod {typeof(T).Assembly.GetName().Name}");
|
||||
while (!_indexFuncs.TryGetValue(type, out func!))
|
||||
{
|
||||
type = type.BaseType
|
||||
?? throw new($"Performance Fish failed to find a fallback method to handle type {
|
||||
typeof(T).FullName} from mod {typeof(T).Assembly.GetName().Name}");
|
||||
}
|
||||
|
||||
if (!isThing)
|
||||
Log.Message($"Found fallback of type {type.FullDescription()}.{(!isDef ? " This likely means we can safely continue from here." : "")}");
|
||||
{
|
||||
Log.Message($"Found fallback of type {type.FullName}.{
|
||||
(!isDef ? " This likely means we can safely continue from here." : "")}");
|
||||
}
|
||||
|
||||
return (Cache.IndexGetter<T>)func;
|
||||
}
|
||||
|
||||
private static Dictionary<Type, MethodInfo> _getterMethods
|
||||
= GetGetterMethods().ToDictionary(static m => m.GetParameters()[0].ParameterType, static m => m);
|
||||
|
||||
private static Dictionary<Type, Delegate> _indexFuncs
|
||||
= GetGetterMethods()
|
||||
.ToDictionary(
|
||||
m => m.GetParameters()[0].ParameterType,
|
||||
m => m.CreateDelegate(typeof(Cache.IndexGetter<>).MakeGenericType(m.GetParameters()[0].ParameterType)));
|
||||
= _getterMethods.ToDictionary(static m => m.Key,
|
||||
static m => m.Value.CreateDelegate(typeof(Cache.IndexGetter<>).MakeGenericType(m.Key)));
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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/.
|
||||
@@ -6,14 +6,19 @@
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
// ReSharper disable PossibleMultipleEnumeration
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class GenLocalDateCaching : ClassWithFishPatches
|
||||
{
|
||||
public class DayTickByThing_Patch : FishPatch
|
||||
{
|
||||
public override string? Description => "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.";
|
||||
public override Delegate? TargetMethodGroup => (Func<Thing, int>)GenLocalDate.DayTick;
|
||||
public override string Description { get; }
|
||||
= "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.";
|
||||
|
||||
public override Delegate TargetMethodGroup { get; } = (Func<Thing, int>)GenLocalDate.DayTick;
|
||||
public override int TranspilerMethodPriority => Priority.First;
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes, MethodBase method, ILGenerator generator)
|
||||
@@ -37,7 +42,6 @@ public class GenLocalDateCaching : ClassWithFishPatches
|
||||
|
||||
foreach (var code in codes)
|
||||
yield return code;
|
||||
|
||||
}
|
||||
|
||||
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -58,21 +62,24 @@ public class GenLocalDateCaching : ClassWithFishPatches
|
||||
|
||||
public class DayTickByMap_Patch : FishPatch
|
||||
{
|
||||
public override string? Description => "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.";
|
||||
public override Delegate? TargetMethodGroup => (Func<Map, int>)GenLocalDate.DayTick;
|
||||
public override string Description { get; }
|
||||
= "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.";
|
||||
|
||||
public override Delegate TargetMethodGroup { get; } = (Func<Map, int>)GenLocalDate.DayTick;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Map map, ref int __result, out bool __state)
|
||||
{
|
||||
if (map != SavedMap)
|
||||
{
|
||||
if (SavedMap is null && Find.Maps?.Count > 0 && Find.Maps[0] == map)
|
||||
if (SavedMap is null && Find.Maps is { Count: > 0 } maps && maps[0] == map)
|
||||
SavedMap = map;
|
||||
|
||||
return __state = true;
|
||||
}
|
||||
|
||||
if (SavedTick != GenTicks.TicksGame)
|
||||
if (SavedTick != TickHelper.TicksGame)
|
||||
return __state = true;
|
||||
|
||||
__result = DayTick;
|
||||
@@ -85,7 +92,7 @@ public class GenLocalDateCaching : ClassWithFishPatches
|
||||
if (!__state || SavedMap != map)
|
||||
return;
|
||||
|
||||
SavedTick = GenTicks.TicksGame;
|
||||
SavedTick = TickHelper.TicksGame;
|
||||
DayTick = __result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class GenTypesPatches : ClassWithFishPrepatches
|
||||
{
|
||||
public class AllTypes : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Fix for thread safety. The vanilla method was rarely breaking and preventing the game from loading";
|
||||
|
||||
public static object Lock = new();
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.PropertyGetter(typeof(GenTypes), nameof(GenTypes.AllTypes));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Prefix() => Monitor.Enter(Lock);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix() => Monitor.Exit(Lock); // TODO: replace with finalizer
|
||||
}
|
||||
|
||||
public class AllTypesWithAttribute : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Fix for thread safety. The vanilla method was rarely breaking and preventing the game from loading";
|
||||
|
||||
public static object Lock = new();
|
||||
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(GenTypes.AllTypesWithAttribute<Attribute>);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody<Attribute>);
|
||||
|
||||
public static List<Type> ReplacementBody<TAttr>() where TAttr : Attribute
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
var typesWithAttribute = GenTypes.cachedTypesWithAttribute.TryGetValue(typeof(TAttr));
|
||||
|
||||
if (typesWithAttribute is null)
|
||||
{
|
||||
GenTypes.cachedTypesWithAttribute.Add(typeof(TAttr),
|
||||
typesWithAttribute = GenTypes.AllTypes.AsParallel()
|
||||
.Where(Predicate<TAttr>()).ToList());
|
||||
}
|
||||
|
||||
return typesWithAttribute;
|
||||
}
|
||||
}
|
||||
|
||||
public static Func<Type, bool> Predicate<TAttr>() where TAttr : Attribute
|
||||
=> static type => type.HasAttribute<TAttr>();
|
||||
}
|
||||
|
||||
public class AllSubclasses : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Fix for thread safety. The vanilla method was rarely breaking and preventing the game from loading";
|
||||
|
||||
public static object Lock = new();
|
||||
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(GenTypes.AllSubclasses);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static List<Type> ReplacementBody(Type baseType)
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
var subclasses = GenTypes.cachedSubclasses.TryGetValue(baseType);
|
||||
|
||||
if (subclasses is null)
|
||||
{
|
||||
((PredicateClass)Predicate.Target).BaseType = baseType;
|
||||
GenTypes.cachedSubclasses.Add(baseType,
|
||||
subclasses = GenTypes.AllTypes.AsParallel()
|
||||
.Where(Predicate).ToList());
|
||||
}
|
||||
|
||||
return subclasses;
|
||||
}
|
||||
}
|
||||
|
||||
public static Func<Type, bool> Predicate = new PredicateClass().Invoke;
|
||||
|
||||
public class PredicateClass
|
||||
{
|
||||
public Type? BaseType;
|
||||
public bool Invoke(Type type) => type.IsSubclassOf(BaseType!);
|
||||
}
|
||||
}
|
||||
|
||||
public class AllSubclassesNonAbstract : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Fix for thread safety, paired with a small optimization for faster loading. The vanilla method was "
|
||||
+ "rarely breaking and preventing the game from loading";
|
||||
|
||||
public static object Lock = new();
|
||||
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(GenTypes.AllSubclassesNonAbstract);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static List<Type> ReplacementBody(Type baseType)
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
var subclassesNonAbstract = GenTypes.cachedSubclassesNonAbstract.TryGetValue(baseType);
|
||||
|
||||
if (subclassesNonAbstract is null)
|
||||
{
|
||||
GenTypes.cachedSubclassesNonAbstract.Add(baseType,
|
||||
subclassesNonAbstract = baseType.AllSubclasses().AsParallel()
|
||||
.Where(Predicate).ToList());
|
||||
}
|
||||
|
||||
return subclassesNonAbstract;
|
||||
}
|
||||
}
|
||||
|
||||
public static Func<Type, bool> Predicate = static subClass => !subClass.IsAbstract;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class GeneTrackerOptimization : ClassWithFishPrepatches
|
||||
{
|
||||
public class GeneTrackerTickPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Every pawn has a gene tracker, which is responsible for ticking each of their genes. Normally it ticks "
|
||||
+ "all of them equally, including those don't change or affect anything through ticking, like skin colors "
|
||||
+ "or basic stat modifiers. This patch improves the gene tracker to determine genes that need ticking in "
|
||||
+ "advance, cache the list of them, and only tick those, skipping all the others.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.Method(typeof(Pawn_GeneTracker), nameof(Pawn_GeneTracker.GeneTrackerTick));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(GeneTrackerTick);
|
||||
|
||||
public static void GeneTrackerTick(Pawn_GeneTracker instance)
|
||||
{
|
||||
if (!ModLister.BiotechInstalled)
|
||||
return;
|
||||
|
||||
var genesToTick = instance.GenesToTick();
|
||||
if (Dirty(instance, genesToTick))
|
||||
Update(instance, genesToTick);
|
||||
|
||||
for (var i = genesToTick.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (genesToTick[i].Active)
|
||||
genesToTick[i].Tick();
|
||||
}
|
||||
|
||||
if (instance.pawn.Spawned
|
||||
&& instance.Xenotype != XenotypeDefOf.Baseliner
|
||||
&& instance.pawn.IsHashIntervalTick(300))
|
||||
{
|
||||
LessonAutoActivator.TeachOpportunity(ConceptDefOf.GenesAndXenotypes, OpportunityType.Important);
|
||||
}
|
||||
}
|
||||
|
||||
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
// public static bool Dirty(List<Gene> genes, List<Gene> genesToTick) => genes._version != genesToTick._version;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Dirty(Pawn_GeneTracker instance, List<Gene> genesToTick)
|
||||
=> GetListVersion(instance) != genesToTick._version;
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void Update(Pawn_GeneTracker instance, List<Gene> genesToTick)
|
||||
{
|
||||
genesToTick.Clear();
|
||||
|
||||
AddTickableGenes(genesToTick, instance.xenogenes);
|
||||
AddTickableGenes(genesToTick, instance.endogenes);
|
||||
|
||||
genesToTick._version = GetListVersion(instance);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
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);
|
||||
|
||||
private static void AddTickableGenes(List<Gene> genesToTick, List<Gene> genes)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// [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
|
||||
= typeof(Gene).SubclassesWithNoMethodOverrideAndSelf(nameof(Gene.Tick)).ToHashSet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
using RimWorld.Planet;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class GetCompCaching : ClassWithFishPrepatches
|
||||
{
|
||||
public class ThingCompPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Optimizes GetComp 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.Method(typeof(ThingWithComps), nameof(ThingWithComps.GetComp));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(GetComp<ThingComp>);
|
||||
|
||||
public static T? GetComp<T>(ThingWithComps thing) where T : ThingComp
|
||||
{
|
||||
if (thing.comps != null)
|
||||
{
|
||||
ref var cache = ref Cache.ByInt<ThingWithComps, CacheValue<T>>.GetOrAddReference(thing.thingIDNumber);
|
||||
return !cache.IsDirty(thing.comps) ? cache.Comp : UpdateCache(thing, ref cache);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static T? UpdateCache<T>(ThingWithComps thing, ref CacheValue<T> cache)
|
||||
where T : ThingComp
|
||||
{
|
||||
for (var i = 0; i < thing.comps.Count; i++)
|
||||
{
|
||||
if (thing.comps[i] is not T comp)
|
||||
continue;
|
||||
|
||||
cache.Update(thing, comp);
|
||||
return comp;
|
||||
}
|
||||
|
||||
cache.Update(thing, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
public record struct CacheValue<T> where T : ThingComp
|
||||
{
|
||||
private int _listVersion = -1;
|
||||
public T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<ThingComp> comps) => comps._version != _listVersion;
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public void Update(ThingWithComps thing, T? comp)
|
||||
{
|
||||
if (thing.thingIDNumber < 0)
|
||||
return;
|
||||
|
||||
_listVersion = thing.comps._version;
|
||||
Comp = comp;
|
||||
}
|
||||
|
||||
public CacheValue() {}
|
||||
}
|
||||
}
|
||||
|
||||
public class HediffCompPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Optimizes GetComp 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; }
|
||||
= methodof(HediffUtility.TryGetComp<HediffComp>);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(TryGetComp<HediffComp>);
|
||||
|
||||
public static T? TryGetComp<T>(Hediff hediff) where T : HediffComp
|
||||
{
|
||||
if (hediff is HediffWithComps { comps: { } } hediffWithComps)
|
||||
{
|
||||
ref var cache = ref Cache.ByInt<HediffWithComps, CacheValue<T>>.GetOrAddReference(hediffWithComps.loadID);
|
||||
return !cache.IsDirty(hediffWithComps.comps) ? cache.Comp : UpdateCache(hediffWithComps, ref cache);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static T? UpdateCache<T>(HediffWithComps hediffWithComps, ref CacheValue<T> cache)
|
||||
where T : HediffComp
|
||||
{
|
||||
for (var i = 0; i < hediffWithComps.comps.Count; i++)
|
||||
{
|
||||
if (hediffWithComps.comps[i] is not T comp)
|
||||
continue;
|
||||
|
||||
cache.Update(hediffWithComps, comp);
|
||||
return comp;
|
||||
}
|
||||
|
||||
cache.Update(hediffWithComps, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
public record struct CacheValue<T> where T : HediffComp
|
||||
{
|
||||
private int _listVersion = -1;
|
||||
public T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<HediffComp> comps) => comps._version != _listVersion;
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public void Update(HediffWithComps hediff, T? comp)
|
||||
{
|
||||
if (hediff.loadID < 0)
|
||||
return;
|
||||
|
||||
_listVersion = hediff.comps._version;
|
||||
Comp = comp;
|
||||
}
|
||||
|
||||
public CacheValue() {}
|
||||
}
|
||||
}
|
||||
|
||||
public class HediffCompPropsPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Optimizes GetComp 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.Method(typeof(HediffDef), nameof(HediffDef.CompProps));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(CompProps<HediffCompProperties>);
|
||||
|
||||
public static T? CompProps<T>(HediffDef instance) where T : HediffCompProperties
|
||||
{
|
||||
if (instance.comps != null)
|
||||
{
|
||||
ref var cache = ref Cache.ByInt<HediffDef, CacheValue<T>>.GetOrAddReference(instance.shortHash);
|
||||
return !cache.IsDirty(instance.comps) ? cache.Comp : UpdateCache(instance, ref cache);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static T? UpdateCache<T>(HediffDef hediffDef, ref CacheValue<T> cache)
|
||||
where T : HediffCompProperties
|
||||
{
|
||||
for (var i = 0; i < hediffDef.comps.Count; i++)
|
||||
{
|
||||
if (hediffDef.comps[i] is not T comp)
|
||||
continue;
|
||||
|
||||
cache.Update(hediffDef, comp);
|
||||
return comp;
|
||||
}
|
||||
|
||||
cache.Update(hediffDef, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
public record struct CacheValue<T> where T : HediffCompProperties
|
||||
{
|
||||
private int _listVersion = -1;
|
||||
public T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<HediffCompProperties> comps) => comps._version != _listVersion;
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public void Update(HediffDef hediffDef, T? comp)
|
||||
{
|
||||
if (hediffDef.shortHash == 0)
|
||||
return;
|
||||
|
||||
_listVersion = hediffDef.comps._version;
|
||||
Comp = comp;
|
||||
}
|
||||
|
||||
public CacheValue() {}
|
||||
}
|
||||
}
|
||||
|
||||
public class AbilityCompPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Optimizes GetComp 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.Method(typeof(Ability), nameof(Ability.CompOfType));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(CompOfType<AbilityComp>);
|
||||
|
||||
public static T? CompOfType<T>(Ability ability) where T : AbilityComp
|
||||
{
|
||||
if (ability.comps != null)
|
||||
{
|
||||
ref var cache = ref Cache.ByInt<Ability, CacheValue<T>>.GetOrAddReference(ability.Id);
|
||||
return !cache.IsDirty(ability.comps) ? cache.Comp : UpdateCache(ability, ref cache);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static T? UpdateCache<T>(Ability ability, ref CacheValue<T> cache)
|
||||
where T : AbilityComp
|
||||
{
|
||||
for (var i = 0; i < ability.comps.Count; i++)
|
||||
{
|
||||
if (ability.comps[i] is not T comp)
|
||||
continue;
|
||||
|
||||
cache.Update(ability, comp);
|
||||
return comp;
|
||||
}
|
||||
|
||||
cache.Update(ability, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
public record struct CacheValue<T> where T : AbilityComp
|
||||
{
|
||||
private int _listVersion = -1;
|
||||
public T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<AbilityComp> comps) => comps._version != _listVersion;
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public void Update(Ability ability, T? comp)
|
||||
{
|
||||
if (ability.Id < 0)
|
||||
return;
|
||||
|
||||
_listVersion = ability.comps._version;
|
||||
Comp = comp;
|
||||
}
|
||||
|
||||
public CacheValue() {}
|
||||
}
|
||||
}
|
||||
|
||||
public class WorldObjectCompPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Optimizes GetComp 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.Method(typeof(WorldObject), nameof(WorldObject.GetComponent), Type.EmptyTypes);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(GetComponent<WorldObjectComp>);
|
||||
|
||||
public static T? GetComponent<T>(WorldObject worldObject) where T : WorldObjectComp
|
||||
{
|
||||
ref var cache = ref Cache.ByInt<WorldObject, CacheValue<T>>.GetOrAddReference(worldObject.ID);
|
||||
return !cache.IsDirty(worldObject.comps) ? cache.Comp : UpdateCache(worldObject, ref cache);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static T? UpdateCache<T>(WorldObject worldObject, ref CacheValue<T> cache) where T : WorldObjectComp
|
||||
{
|
||||
for (var i = 0; i < worldObject.comps.Count; i++)
|
||||
{
|
||||
if (worldObject.comps[i] is not T comp)
|
||||
continue;
|
||||
|
||||
cache.Update(worldObject, comp);
|
||||
return comp;
|
||||
}
|
||||
|
||||
cache.Update(worldObject, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
public record struct CacheValue<T> where T : WorldObjectComp
|
||||
{
|
||||
private int _listVersion = -1;
|
||||
public T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<WorldObjectComp> comps) => comps._version != _listVersion;
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public void Update(WorldObject worldObject, T? comp)
|
||||
{
|
||||
if (worldObject.ID < 0)
|
||||
return;
|
||||
|
||||
_listVersion = worldObject.comps._version;
|
||||
Comp = comp;
|
||||
}
|
||||
|
||||
public CacheValue() {}
|
||||
}
|
||||
}
|
||||
|
||||
public class MapComponentPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Optimizes GetComp 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.Method(typeof(Map), nameof(Map.GetComponent), Type.EmptyTypes);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(GetComponent<MapComponent>);
|
||||
|
||||
public static T? GetComponent<T>(Map map) where T : MapComponent
|
||||
{
|
||||
ref var cache = ref Cache.ByMap<CacheValue<T>>.GetReferenceFor(map);
|
||||
return !cache.IsDirty(map.components) ? cache.Comp : UpdateCache(map, ref cache);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static T? UpdateCache<T>(Map map, ref CacheValue<T> cache) where T : MapComponent
|
||||
{
|
||||
for (var i = 0; i < map.components.Count; i++)
|
||||
{
|
||||
if (map.components[i] is not T comp)
|
||||
continue;
|
||||
|
||||
cache.Update(map, comp);
|
||||
return comp;
|
||||
}
|
||||
|
||||
cache.Update(map, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
public record struct CacheValue<T> where T : MapComponent
|
||||
{
|
||||
private int _listVersion = -1;
|
||||
public T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(List<MapComponent> comps) => comps._version != _listVersion;
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public void Update(Map map, T? comp)
|
||||
{
|
||||
if (map.uniqueID < 0)
|
||||
return;
|
||||
|
||||
_listVersion = map.components._version;
|
||||
Comp = comp;
|
||||
}
|
||||
|
||||
public CacheValue() {}
|
||||
}
|
||||
}
|
||||
|
||||
public class WorldComponentPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Optimizes GetComp 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.Method(typeof(World), nameof(World.GetComponent), Type.EmptyTypes);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(GetComponent<WorldComponent>);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T? GetComponent<T>(World world) where T : WorldComponent
|
||||
=> !Cache<T>.IsDirty(world) ? Cache<T>.Comp : UpdateCache<T>(world);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static T? UpdateCache<T>(World world) where T : WorldComponent
|
||||
{
|
||||
for (var i = 0; i < world.components.Count; ++i)
|
||||
{
|
||||
if (world.components[i] is not T comp)
|
||||
continue;
|
||||
|
||||
Cache<T>.Update(world, comp);
|
||||
return comp;
|
||||
}
|
||||
|
||||
Cache<T>.Update(world, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class Cache<T> where T : WorldComponent
|
||||
{
|
||||
private static int _listVersion = -1;
|
||||
private static World? _world;
|
||||
public static T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsDirty(World world) => world.components._version != _listVersion || _world != world;
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void Update(World world, T? comp)
|
||||
{
|
||||
_listVersion = world.components._version;
|
||||
_world = world;
|
||||
Comp = comp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class GameComponentPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Optimizes GetComp 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.Method(typeof(Game), nameof(Game.GetComponent), Type.EmptyTypes);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(GetComponent<GameComponent>);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T? GetComponent<T>(Game game) where T : GameComponent
|
||||
=> !Cache<T>.IsDirty(game) ? Cache<T>.Comp : UpdateCache<T>(game);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static T? UpdateCache<T>(Game game) where T : GameComponent
|
||||
{
|
||||
for (var i = 0; i < game.components.Count; ++i)
|
||||
{
|
||||
if (game.components[i] is not T comp)
|
||||
continue;
|
||||
|
||||
Cache<T>.Update(game, comp);
|
||||
return comp;
|
||||
}
|
||||
|
||||
Cache<T>.Update(game, null);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class Cache<T> where T : GameComponent
|
||||
{
|
||||
private static int _listVersion = -1;
|
||||
private static Game? _game;
|
||||
public static T? Comp;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsDirty(Game game) => game.components._version != _listVersion || _game != game;
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void Update(Game game, T? comp)
|
||||
{
|
||||
_listVersion = game.components._version;
|
||||
_game = game;
|
||||
Comp = comp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class HediffComp_PsychicHarmonizer_Bugfix : ClassWithFishPatches
|
||||
{
|
||||
public class CompPostTick_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Fixes a bug in the psychic harmonizer tick method that was causing it to recalculate results on 149 out of 150 ticks instead of once every 150 ticks. Large impact.";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(HediffComp_PsychicHarmonizer), nameof(HediffComp_PsychicHarmonizer.CompPostTick));
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
{
|
||||
var codes = CodeInstructions.ToList();
|
||||
var pawn_int_IsHashIntervalTick = FishTranspiler.Call<Func<Thing, int, bool>>(Gen.IsHashIntervalTick);
|
||||
var success = false;
|
||||
for (var i = 0; i < codes.Count; i++)
|
||||
{
|
||||
if (i + 2 < codes.Count && codes[i].opcode == Ldc_I4 && codes[i + 1] == pawn_int_IsHashIntervalTick && codes[i + 2].opcode == Brfalse_S)
|
||||
{
|
||||
//codes[i].operand = 128;
|
||||
yield return codes[i];
|
||||
yield return codes[i + 1];
|
||||
codes[i + 2].opcode = Brtrue_S;
|
||||
yield return codes[i + 2];
|
||||
success = true;
|
||||
i += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return codes[i];
|
||||
}
|
||||
}
|
||||
if (!success)
|
||||
Log.Warning("Performance Fish failed to insert its Psychic Harmonizer Tick bugfix. This is probably harmless and likely just means someone else fixed it already");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
// Copyright (c) 2022 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 PerformanceFish.Collections;
|
||||
using HediffCache = PerformanceFish.Cache.ByIntRefreshable<Verse.HediffSet, Verse.HediffDef, PerformanceFish.HediffSetCaching.HediffCacheValue>;
|
||||
using VisibleHediffCache = PerformanceFish.Cache.ByIntRefreshable<Verse.HediffSet, Verse.HediffDef, PerformanceFish.HediffSetCaching.VisibleHediffCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class HediffSetCaching : ClassWithFishPatches
|
||||
{
|
||||
public class HasHediff_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches results of the HediffSet.HasHediff method. This scales with the number of hediffs pawns have and can make a decent difference in transhumanist playthroughs.";
|
||||
public override Expression<Action> TargetMethod => () => default(HediffSet)!.HasHediff(null, default);
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> Reflection.GetCodeInstructions(Replacement);
|
||||
public static bool Replacement(HediffSet __instance, HediffDef def, bool mustBeVisible) => __instance.GetFirstHediffOfDef(def, mustBeVisible) != null;
|
||||
}
|
||||
|
||||
public class GetFirstHediffOfDef_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches results of the HediffSet.GetFirstHediffOfDef method. This scales with the number of hediffs pawns have and can make a decent difference in transhumanist playthroughs.";
|
||||
public override Expression<Action> TargetMethod => () => default(HediffSet)!.GetFirstHediffOfDef(null, default);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(HediffSet __instance, HediffDef def, bool mustBeVisible, ref Hediff? __result, out bool __state)
|
||||
{
|
||||
if (__instance.hediffs.Count < 5 || def is null || def.GetType() != typeof(HediffDef)) // different types have different indexing. Can't use those
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var key = new HediffCache() { First = __instance.pawn.thingIDNumber, Second = def.index };
|
||||
ref var cache = ref mustBeVisible ? ref Unsafe.As<VisibleHediffCacheValue, HediffCacheValue>(ref VisibleHediffCache.Get.TryGetReferenceUnsafe(ref Unsafe.As<HediffCache, VisibleHediffCache>(ref key)))
|
||||
: ref HediffCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache) || cache.ShouldRefreshNow)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.Hediff;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(HediffSet __instance, HediffDef def, bool mustBeVisible, Hediff? __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
if (mustBeVisible)
|
||||
VisibleHediffCache.Get[new(__instance, def)] = new(__instance, __result);
|
||||
else
|
||||
HediffCache.Get[new(__instance, def)] = new(__instance, __result);
|
||||
}
|
||||
}
|
||||
|
||||
public class HediffSet_DirtyCache_Patch : FishPatch
|
||||
{
|
||||
public override string? Description => "Patched to trigger Psylink cache clearing for the psychic entropy optimization.";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(HediffSet), nameof(HediffSet.DirtyCache));
|
||||
public static void Postfix(HediffSet __instance)
|
||||
{
|
||||
if (__instance.pawn.psychicEntropy is { } entropy)
|
||||
entropy.psylinkCachedForTick = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public struct HediffCacheValue : Cache.IIsRefreshable<HediffCache, HediffCacheValue>
|
||||
{
|
||||
private int _nextRefreshTick;
|
||||
private int _version;
|
||||
private HediffSet _set;
|
||||
public Hediff? Hediff;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public HediffCacheValue(HediffSet set, Hediff? hediff)
|
||||
{
|
||||
_set = set;
|
||||
Hediff = hediff;
|
||||
_version = set.hediffs._version;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _nextRefreshTick < Current.Game.tickManager.ticksGameInt || _version != _set.hediffs._version;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
|
||||
public HediffCacheValue SetNewValue(HediffCache key) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public struct VisibleHediffCacheValue : Cache.IIsRefreshable<VisibleHediffCache, VisibleHediffCacheValue>
|
||||
{
|
||||
public HediffCacheValue Value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public VisibleHediffCacheValue(HediffSet set, Hediff? hediff) => Value = new(set, hediff);
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => Value.ShouldRefreshNow;
|
||||
set => Value.ShouldRefreshNow = value;
|
||||
}
|
||||
|
||||
public VisibleHediffCacheValue SetNewValue(VisibleHediffCache key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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 PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish.Hediffs;
|
||||
|
||||
public class HealthTrackerOptimization : ClassWithFishPrepatches
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
var allHediffDefs = DefDatabase<HediffDef>.AllDefsListForReading;
|
||||
for (var i = allHediffDefs.Count; i-- > 0;)
|
||||
{
|
||||
var hediffDef = allHediffDefs[i];
|
||||
|
||||
if (hediffDef.stages is { Count: > 1})
|
||||
continue;
|
||||
|
||||
SkippableHediffs.Add(hediffDef);
|
||||
}
|
||||
}
|
||||
|
||||
public class HealthTickPatch : FishPrepatch
|
||||
{
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.Method(typeof(Pawn_HealthTracker), nameof(Pawn_HealthTracker.HealthTick));
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static HashSet<HediffDef> SkippableHediffs = new();
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
|
||||
namespace PerformanceFish.Hediffs;
|
||||
|
||||
public class HediffComp_PsychicHarmonizer_Bugfix : ClassWithFishPatches
|
||||
{
|
||||
public class CompPostTick_Patch : FishPatch
|
||||
{
|
||||
public override string Description { get; }
|
||||
= "Fixes a bug in the psychic harmonizer tick method that was causing it to recalculate results on 149 "
|
||||
+ "out of 150 ticks instead of once every 150 ticks. Large impact.";
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.Method(typeof(HediffComp_PsychicHarmonizer),
|
||||
nameof(HediffComp_PsychicHarmonizer.CompPostTick));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codeInstructions)
|
||||
{
|
||||
var codes = codeInstructions.ToList();
|
||||
var pawn_int_IsHashIntervalTick = FishTranspiler.Call<Func<Thing, int, bool>>(Gen.IsHashIntervalTick);
|
||||
var success = false;
|
||||
for (var i = 0; i < codes.Count; i++)
|
||||
{
|
||||
if (i + 2 < codes.Count
|
||||
&& codes[i].opcode == Ldc_I4
|
||||
&& codes[i + 1] == pawn_int_IsHashIntervalTick
|
||||
&& codes[i + 2].opcode == Brfalse_S)
|
||||
{
|
||||
//codes[i].operand = 128;
|
||||
yield return codes[i];
|
||||
yield return codes[i + 1];
|
||||
|
||||
codes[i + 2].opcode = Brtrue_S;
|
||||
yield return codes[i + 2];
|
||||
|
||||
success = true;
|
||||
i += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return codes[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
Log.Warning("Performance Fish failed to insert its Psychic Harmonizer Tick bugfix. This is "
|
||||
+ "probably harmless and can happen when toggling patches from mod settings. It could indicate "
|
||||
+ "an actual failure to patch if settings were not toggled however, which would mean some other "
|
||||
+ "patch or a vanilla change either messes with this or implements the same thing");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
using HediffCache
|
||||
= PerformanceFish.Cache.ByInt<Verse.HediffSet, Verse.HediffDef,
|
||||
PerformanceFish.Hediffs.HediffSetCaching.HediffCacheValue>;
|
||||
using VisibleHediffCache
|
||||
= PerformanceFish.Cache.ByInt<Verse.HediffSet, Verse.HediffDef,
|
||||
PerformanceFish.Hediffs.HediffSetCaching.VisibleHediffCacheValue>;
|
||||
using NotMissingPartsCache
|
||||
= PerformanceFish.Cache.ByInt<Verse.HediffSet,
|
||||
PerformanceFish.Hediffs.HediffSetCaching.NotMissingPartsCacheValue>;
|
||||
|
||||
namespace PerformanceFish.Hediffs;
|
||||
|
||||
public class HediffSetPrecaching : ClassWithFishPrepatches
|
||||
{
|
||||
public class GetFirstHediffOfDef : FishPrepatch
|
||||
{
|
||||
public override string Description { get; }
|
||||
= "Caches results of the HediffSet.GetFirstHediffOfDef method. Impact scales with hediff count.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= SymbolExtensions.GetMethodInfo(static ()
|
||||
=> default(HediffSet)!.GetFirstHediffOfDef(null, default));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(Replacement);
|
||||
|
||||
public static Hediff? Replacement(HediffSet instance, HediffDef? def, bool mustBeVisible)
|
||||
{
|
||||
var canCache = true;
|
||||
|
||||
if (instance.hediffs.Count >= 5
|
||||
&& 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);
|
||||
if (!cache.Dirty)
|
||||
return cache.Hediff;
|
||||
}
|
||||
else
|
||||
{
|
||||
canCache = false;
|
||||
}
|
||||
|
||||
// original method, mostly left untouched in case of other mods patching in
|
||||
Hediff? result;
|
||||
var hediffs = instance.hediffs;
|
||||
for (var i = 0; i < hediffs.Count; i++)
|
||||
{
|
||||
if (hediffs[i].def != def || (mustBeVisible && !hediffs[i].Visible))
|
||||
continue;
|
||||
|
||||
result = hediffs[i];
|
||||
goto Result;
|
||||
}
|
||||
|
||||
result = null;
|
||||
|
||||
Result:
|
||||
if (canCache)
|
||||
UpdateCache(instance, def!, mustBeVisible, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe ref HediffSetCaching.HediffCacheValue GetCacheValue(HediffSet instance, HediffDef def,
|
||||
bool mustBeVisible)
|
||||
{
|
||||
var key = new HediffCache { First = instance.pawn.thingIDNumber, Second = def.shortHash };
|
||||
ref var cache = ref mustBeVisible
|
||||
? ref Unsafe.As<HediffSetCaching.VisibleHediffCacheValue, HediffSetCaching.HediffCacheValue>(
|
||||
ref VisibleHediffCache.GetOrAddReference(Unsafe.As<HediffCache, VisibleHediffCache>(ref key)))
|
||||
: ref HediffCache.GetOrAddReference(key);
|
||||
|
||||
return ref cache;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void UpdateCache(HediffSet __instance, HediffDef def, bool mustBeVisible, Hediff? __result)
|
||||
{
|
||||
if (mustBeVisible)
|
||||
VisibleHediffCache.GetExistingReference(__instance, def).Update(__instance, __result);
|
||||
else
|
||||
HediffCache.GetExistingReference(__instance, def).Update(__instance, __result);
|
||||
}
|
||||
}
|
||||
|
||||
public class HasHediff : FishPrepatch
|
||||
{
|
||||
public override string Description { get; }
|
||||
= "Caches results of the HediffSet.HasHediff method. Impact scales with hediff count.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= SymbolExtensions.GetMethodInfo(static ()
|
||||
=> default(HediffSet)!.HasHediff(null, default));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(Replacement);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Replacement(HediffSet __instance, HediffDef def, bool mustBeVisible)
|
||||
=> __instance.GetFirstHediffOfDef(def, mustBeVisible) != null;
|
||||
}
|
||||
|
||||
public class GetNotMissingParts : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Caches results of the HediffSet.GetNotMissingParts method. Impact scales with hediff count.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.Method(typeof(HediffSet), nameof(HediffSet.GetNotMissingParts));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(HediffSet __instance, BodyPartHeight height, BodyPartDepth depth, BodyPartTagDef? tag,
|
||||
BodyPartRecord? partParent, ref IEnumerable<BodyPartRecord> __result, out bool __state)
|
||||
{
|
||||
if (height != BodyPartHeight.Undefined
|
||||
|| depth != BodyPartDepth.Undefined
|
||||
|| tag != null
|
||||
|| partParent != null)
|
||||
{
|
||||
__state = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
ref var cache = ref NotMissingPartsCache.GetOrAddReference(__instance.pawn.thingIDNumber);
|
||||
|
||||
if (cache.Dirty)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.Parts;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(HediffSet __instance, bool __state, ref IEnumerable<BodyPartRecord> __result)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
UpdateCache(__instance, ref __result);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void UpdateCache(HediffSet __instance, ref IEnumerable<BodyPartRecord> __result)
|
||||
=> NotMissingPartsCache.GetExistingReference(__instance).Update(__instance, ref __result);
|
||||
}
|
||||
}
|
||||
|
||||
public class HediffSetCaching : ClassWithFishPatches
|
||||
{
|
||||
public class DirtyCache : FishPatch
|
||||
{
|
||||
public override string Description { get; }
|
||||
= "Patched to trigger psylink cache clearing for the psychic entropy optimization.";
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.Method(typeof(HediffSet), nameof(HediffSet.DirtyCache));
|
||||
|
||||
public static void Postfix(HediffSet __instance)
|
||||
{
|
||||
if (__instance.pawn.psychicEntropy is { } entropy)
|
||||
entropy.psylinkCachedForTick = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public record struct NotMissingPartsCacheValue
|
||||
{
|
||||
private int _version = -1;
|
||||
private List<Hediff> _hediffsInSet = new();
|
||||
private int _nextRefreshTick;
|
||||
public List<BodyPartRecord> Parts = new();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(HediffSet set, ref IEnumerable<BodyPartRecord> result)
|
||||
{
|
||||
_hediffsInSet = set.hediffs;
|
||||
_version = set.hediffs._version;
|
||||
_nextRefreshTick = TickHelper.Add(GenTicks.TickLongInterval, set.pawn.thingIDNumber);
|
||||
|
||||
var previousPartsVersion = Parts._version;
|
||||
Parts.Clear();
|
||||
Parts.AddRange(result);
|
||||
Parts._version = previousPartsVersion;
|
||||
|
||||
result = Parts;
|
||||
}
|
||||
|
||||
public bool Dirty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _version != _hediffsInSet._version || TickHelper.Past(_nextRefreshTick);
|
||||
}
|
||||
|
||||
public NotMissingPartsCacheValue()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public record struct HediffCacheValue
|
||||
{
|
||||
private int _version = -1;
|
||||
private List<Hediff> _hediffsInSet = new();
|
||||
private int _nextRefreshTick;
|
||||
public Hediff? Hediff;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(HediffSet set, Hediff? hediff)
|
||||
{
|
||||
_hediffsInSet = set.hediffs;
|
||||
Hediff = hediff;
|
||||
_version = _hediffsInSet._version;
|
||||
_nextRefreshTick = TickHelper.Add(GenTicks.TickLongInterval, set.pawn.thingIDNumber);
|
||||
}
|
||||
|
||||
public bool Dirty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _version != _hediffsInSet._version || TickHelper.Past(_nextRefreshTick);
|
||||
}
|
||||
|
||||
public HediffCacheValue()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public record struct VisibleHediffCacheValue
|
||||
{
|
||||
public HediffCacheValue Value = new();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(HediffSet set, Hediff? hediff) => Value.Update(set, hediff);
|
||||
|
||||
public VisibleHediffCacheValue()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
// 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 ImmunityInfoCache
|
||||
= PerformanceFish.Cache.ByInt<Verse.Pawn,
|
||||
PerformanceFish.Hediffs.ImmunityHandlerCaching.ImmunityInfoCacheValue>;
|
||||
using ImmunityRecordExistsCache
|
||||
= PerformanceFish.Cache.ByInt<Verse.Pawn, Verse.HediffDef,
|
||||
PerformanceFish.Hediffs.ImmunityHandlerCaching.ImmunityRecordExistsCacheValue>;
|
||||
|
||||
namespace PerformanceFish.Hediffs;
|
||||
|
||||
public class ImmunityHandlerCaching : ClassWithFishPatches
|
||||
{
|
||||
public class NeededImmunitiesNow : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description { get; }
|
||||
= "Caches results of the ImmunityHandler.NeededImmunitiesNow method. Impact scales with hediff quantity.";
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.Method(typeof(ImmunityHandler), nameof(ImmunityHandler.NeededImmunitiesNow));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(ImmunityHandler __instance, ref List<ImmunityHandler.ImmunityInfo> __result,
|
||||
out bool __state)
|
||||
{
|
||||
ref var cache = ref ImmunityInfoCache.GetOrAddReference(__instance.pawn.thingIDNumber);
|
||||
|
||||
if (cache.Dirty)
|
||||
return __state = true;
|
||||
|
||||
(__result = ImmunityHandler.tmpNeededImmunitiesNow).Clear();
|
||||
__result.AddRangeFast(cache.Infos);
|
||||
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
// ImmunityHandler.tmpNeededImmunitiesNow, which is normally used for the result, is static
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(ImmunityHandler __instance, List<ImmunityHandler.ImmunityInfo> __result,
|
||||
bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
UpdateCache(__instance, __result);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void UpdateCache(ImmunityHandler __instance, List<ImmunityHandler.ImmunityInfo> __result)
|
||||
=> ImmunityInfoCache.GetExistingReference(__instance.pawn)
|
||||
.Update(__instance.pawn.health.hediffSet, __result);
|
||||
}
|
||||
|
||||
public class ImmunityRecordExists : FishPatch
|
||||
{
|
||||
public override string? Description { get; } = "Required by the TryAddImmunityRecord optimization.";
|
||||
|
||||
public override bool Enabled
|
||||
{
|
||||
set
|
||||
{
|
||||
if (base.Enabled == value)
|
||||
return;
|
||||
|
||||
Get<TryAddImmunityRecord>().Enabled = base.Enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.Method(typeof(ImmunityHandler), nameof(ImmunityHandler.ImmunityRecordExists));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(ImmunityHandler __instance, HediffDef def, bool __result)
|
||||
{
|
||||
ref var cache
|
||||
= ref ImmunityRecordExistsCache.GetOrAddReference(__instance.pawn.thingIDNumber, def.shortHash);
|
||||
cache.Value = __result;
|
||||
cache.ImmunityListVersion = __instance.immunityList._version;
|
||||
}
|
||||
}
|
||||
|
||||
public class TryAddImmunityRecord : FishPatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Optimization for this specific method to not run more checks than necessary.";
|
||||
|
||||
public override bool Enabled
|
||||
{
|
||||
set
|
||||
{
|
||||
if (base.Enabled == value)
|
||||
return;
|
||||
|
||||
Get<ImmunityRecordExists>().Enabled = base.Enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.Method(typeof(ImmunityHandler), nameof(ImmunityHandler.TryAddImmunityRecord));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(ImmunityHandler __instance, HediffDef def)
|
||||
{
|
||||
ref var cache
|
||||
= ref ImmunityRecordExistsCache.GetOrAddReference(__instance.pawn.thingIDNumber, def.shortHash);
|
||||
return !cache.Value || cache.ImmunityListVersion != __instance.immunityList._version;
|
||||
}
|
||||
}
|
||||
|
||||
public record struct ImmunityInfoCacheValue
|
||||
{
|
||||
private int _version = -1;
|
||||
private List<Hediff> _hediffsInSet = new();
|
||||
private int _nextRefreshTick;
|
||||
public List<ImmunityHandler.ImmunityInfo> Infos = new();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(HediffSet set, List<ImmunityHandler.ImmunityInfo> infos)
|
||||
{
|
||||
Infos.ReplaceContentsWith(infos);
|
||||
|
||||
_hediffsInSet = set.hediffs;
|
||||
_version = _hediffsInSet._version;
|
||||
_nextRefreshTick = TickHelper.Add(GenTicks.TickLongInterval, set.pawn.thingIDNumber);
|
||||
}
|
||||
|
||||
public bool Dirty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _version != _hediffsInSet._version || TickHelper.Past(_nextRefreshTick);
|
||||
}
|
||||
|
||||
public ImmunityInfoCacheValue()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public record struct ImmunityRecordExistsCacheValue
|
||||
{
|
||||
public bool Value;
|
||||
public int ImmunityListVersion;
|
||||
}
|
||||
}
|
||||
+11
-8
@@ -1,27 +1,30 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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;
|
||||
namespace PerformanceFish.Hediffs;
|
||||
|
||||
public class Pawn_PsychicEntropyTrackerOptimization : ClassWithFishPatches
|
||||
{
|
||||
public class Psylink_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string? Description => "Caches/throttles the psylink method to only update every 32 ticks.";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(Pawn_PsychicEntropyTracker), nameof(Pawn_PsychicEntropyTracker.Psylink));
|
||||
public override string Description { get; } = "Caches/throttles the psylink method to only update every 32 ticks.";
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> Reflection.GetCodeInstructions(Psylink);
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.PropertyGetter(typeof(Pawn_PsychicEntropyTracker),
|
||||
nameof(Pawn_PsychicEntropyTracker.Psylink));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes) => Reflection.GetCodeInstructions(Psylink);
|
||||
|
||||
public static Hediff_Psylink Psylink(Pawn_PsychicEntropyTracker __instance)
|
||||
{
|
||||
if (__instance.psylinkCachedForTick <= Find.TickManager.TicksGame)
|
||||
if (__instance.psylinkCachedForTick <= TickHelper.TicksGame)
|
||||
{
|
||||
__instance.psylinkCached = __instance.pawn.GetMainPsylinkSource();
|
||||
__instance.psylinkCachedForTick = Find.TickManager.TicksGame + 32;
|
||||
__instance.psylinkCachedForTick = TickHelper.TicksGame + 32;
|
||||
}
|
||||
|
||||
return __instance.psylinkCached;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,26 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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 PerformanceFish.Cache;
|
||||
using StyleDominanceCache = PerformanceFish.Cache.ByIntRefreshable<Verse.Thing, RimWorld.Ideo, PerformanceFish.IdeoUtilityCaching.StyleDominanceCacheValue>;
|
||||
using PerformanceFish.Prepatching;
|
||||
using StyleDominanceCache
|
||||
= PerformanceFish.Cache.ByInt<Verse.Thing, RimWorld.Ideo,
|
||||
PerformanceFish.IdeoUtilityCaching.StyleDominanceCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
public class IdeoUtilityCaching : ClassWithFishPatches
|
||||
|
||||
public class IdeoUtilityCaching : ClassWithFishPrepatches
|
||||
{
|
||||
public class GetStyleDominance_Patch : FishPatch
|
||||
public class GetStyleDominance_Patch : FishPrepatch
|
||||
{
|
||||
public override string? Description => "Caches IdeoUtility.GetStyleDominance to only update about once every 600 ticks or so. Only active when not using Performance Optimizer, as that mod includes a similar version too.";
|
||||
public override bool Enabled => base.Enabled && !ModsConfig.IsActive("Taranchuk.PerformanceOptimizer");
|
||||
public override Delegate? TargetMethodGroup => IdeoUtility.GetStyleDominance;
|
||||
public override string Description { get; }
|
||||
= "Caches IdeoUtility.GetStyleDominance to only update about once every 900 ticks or so.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(IdeoUtility.GetStyleDominance);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Thing t, Ideo ideo, ref float __result, out bool __state)
|
||||
public static bool Prefix(Thing? t, Ideo? ideo, ref float __result, out bool __state)
|
||||
{
|
||||
if (t is null || ideo is null || t.thingIDNumber < 0)
|
||||
{
|
||||
@@ -24,10 +28,9 @@ public class IdeoUtilityCaching : ClassWithFishPatches
|
||||
return true;
|
||||
}
|
||||
|
||||
var key = new StyleDominanceCache(t.thingIDNumber, ideo.id);
|
||||
ref var cache = ref StyleDominanceCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
ref var cache = ref StyleDominanceCache.GetOrAddReference(new(t.thingIDNumber, ideo.id));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache) || cache.ShouldRefreshNow)
|
||||
if (cache.Dirty)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.Value;
|
||||
@@ -40,28 +43,30 @@ public class IdeoUtilityCaching : ClassWithFishPatches
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
StyleDominanceCache.Get[new(t, ideo)] = new(__result);
|
||||
}
|
||||
UpdateCache(t, ideo, __result);
|
||||
}
|
||||
|
||||
public struct StyleDominanceCacheValue : IIsRefreshable<StyleDominanceCache, StyleDominanceCacheValue>
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void UpdateCache(Thing t, Ideo ideo, float __result)
|
||||
=> StyleDominanceCache.GetExistingReference(t, ideo).Update(__result, t);
|
||||
}
|
||||
|
||||
public record struct StyleDominanceCacheValue
|
||||
{
|
||||
public float Value;
|
||||
private int _nextRefreshTick;
|
||||
|
||||
public StyleDominanceCacheValue(float value)
|
||||
public void Update(float value, Thing thing)
|
||||
{
|
||||
Value = value;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 512 + Math.Abs(Rand.Int % 256); // Pawn_StyleObserverTracker.StyleObservableTick runs every 900 ticks
|
||||
_nextRefreshTick = TickHelper.Add(900 - 128, thing.thingIDNumber, 256);
|
||||
// Pawn_StyleObserverTracker.StyleObservableTick runs every 900 ticks
|
||||
}
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
public bool Dirty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 512 + Math.Abs(Rand.Int % 256);
|
||||
}
|
||||
|
||||
StyleDominanceCacheValue IIsRefreshable<StyleDominanceCache, StyleDominanceCacheValue>.SetNewValue(StyleDominanceCache key) => throw new NotImplementedException();
|
||||
get => TickHelper.Past(_nextRefreshTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// 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.JobSystem;
|
||||
|
||||
public class DisabledWorkTypesOptimization : ClassWithFishPatches
|
||||
{
|
||||
public class PawnGetDisabledWorkTypes : FishPatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Essentially fixes a bug in Pawn.GetDisabledWorkTypes that causes the method to discard its cache and "
|
||||
+ "constantly keep recalculating. With the patch applied it'll return early when a valid cache exists.";
|
||||
|
||||
public override MethodBase? TargetMethodInfo { get; }
|
||||
= AccessTools.Method(typeof(Pawn), nameof(Pawn.GetDisabledWorkTypes));
|
||||
|
||||
[HarmonyPriority(Priority.VeryLow)]
|
||||
public static bool Prefix(Pawn __instance, bool permanentOnly, out List<WorkTypeDef> __result)
|
||||
=> (__result = permanentOnly
|
||||
? __instance.cachedDisabledWorkTypesPermanent
|
||||
: __instance.cachedDisabledWorkTypes) is null;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,28 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
/*using System.Threading.Tasks;
|
||||
#if disabledForNow
|
||||
using System.Threading.Tasks;
|
||||
using Verse.AI;
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class GenClosestPatches : ClassWithFishPatches
|
||||
{*/
|
||||
#if disabledForNow
|
||||
{
|
||||
public class ClosestThing_Global_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Threading for job scanning. Disabled and unstable.";
|
||||
public override string Description { get; } = "Threading for job scanning. Disabled and unstable.";
|
||||
public override bool DefaultState => false;
|
||||
public static bool ThreadingEnabled { get; set; }
|
||||
public override Expression<Action> TargetMethod => () => GenClosest.ClosestThing_Global(default, null, default, null, null);
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= 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;
|
||||
@@ -27,18 +31,32 @@ public class GenClosestPatches : ClassWithFishPatches
|
||||
public float closestDistSquared;
|
||||
public Predicate<Thing>? validator;
|
||||
}
|
||||
public static Thing? ClosestThing_Global_Replacement(IntVec3 center, IEnumerable searchSet, float maxDistance = 99999f, Predicate<Thing>? validator = null, Func<Thing, float>? priorityGetter = null)
|
||||
|
||||
public static Thing? ClosestThing_Global_Replacement(IntVec3 center, IEnumerable? searchSet,
|
||||
float maxDistance = 99999f, Predicate<Thing>? validator = null, Func<Thing, float>? priorityGetter = null)
|
||||
{
|
||||
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 };
|
||||
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 (ThreadingEnabled
|
||||
&& validator != null
|
||||
&& JobGiver_Work_Predicate.GetScanner(predicate) is WorkGiver_DoBill
|
||||
/*or WorkGiver_ConstructDeliverResources or WorkGiver_ConstructFinishFrames*/
|
||||
)
|
||||
{
|
||||
if (searchSet is IList<Thing> thingList)
|
||||
{
|
||||
@@ -48,14 +66,15 @@ public class GenClosestPatches : ClassWithFishPatches
|
||||
{
|
||||
Parallel.For<(IList<Thing> list, Variables vars)>(0, thingList.Count,
|
||||
() => (thingList, variables),
|
||||
(i, loop, listAndVars) => (listAndVars.list, ProcessThreaded(listAndVars.list[i], listAndVars.vars)),
|
||||
_ => { });
|
||||
(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");
|
||||
|
||||
@@ -98,10 +117,12 @@ public class GenClosestPatches : ClassWithFishPatches
|
||||
}
|
||||
|
||||
return chosen;
|
||||
|
||||
void Process<T>(T t, Variables variables) where T : Thing
|
||||
{
|
||||
if (t.Spawned)
|
||||
{
|
||||
if (!t.Spawned)
|
||||
return;
|
||||
|
||||
float num = (variables.center - t.Position).LengthHorizontalSquared;
|
||||
if (!(num > variables.maxDistanceSquared)
|
||||
&& (variables.priorityGetter != null || num < closestDistSquared)
|
||||
@@ -111,31 +132,38 @@ public class GenClosestPatches : ClassWithFishPatches
|
||||
if (variables.priorityGetter != null)
|
||||
{
|
||||
num2 = variables.priorityGetter(t);
|
||||
if (num2 < bestPrio || (num2 == bestPrio && num >= closestDistSquared))
|
||||
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)
|
||||
{
|
||||
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)))
|
||||
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 || (num2 == bestPrio && num >= closestDistSquared))
|
||||
if (num2 < bestPrio || (Math.Abs(num2 - bestPrio) < 0.001f && num >= closestDistSquared))
|
||||
return variables;
|
||||
}
|
||||
else
|
||||
@@ -143,41 +171,58 @@ public class GenClosestPatches : ClassWithFishPatches
|
||||
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);
|
||||
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));
|
||||
= (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);
|
||||
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));
|
||||
= (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");
|
||||
|
||||
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");
|
||||
|
||||
public static AccessTools.FieldRef<T, Predicate<Thing>> Validator { get; }
|
||||
= AccessTools.FieldRefAccess<T, Predicate<Thing>>("validator");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -187,8 +232,10 @@ public class GenClosestPatches : ClassWithFishPatches
|
||||
{
|
||||
protected CanReach_Patch() => FishSettings.ThreadingPatches.Add(this);
|
||||
public override bool DefaultState => false;
|
||||
public override string Description => "Lock for threading. Only needed with threading enabled and off by default";
|
||||
public override Expression<Action> TargetMethod => () => default(Reachability)!.CanReach(default, null, default, default);
|
||||
public override string Description { get; }
|
||||
= "Lock for threading. Only needed with threading enabled and off by default";
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => default(Reachability)!.CanReach(default, null, default, default);
|
||||
public static void Prefix()
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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 PerformanceFish.ModCompatibility;
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class JobGiver_HaulOptimization : ClassWithFishPatches
|
||||
{
|
||||
public class PotentialWorkThingsGlobal_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Sorts haulables by distance before running expensive hauling calculations on them to avoid checks on far away items";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(JobGiver_Haul), nameof(JobGiver_Haul.TryGiveJob));
|
||||
public override string Description { get; }
|
||||
= "Sorts haulables by distance before running expensive hauling calculations on them to avoid checks on "
|
||||
+ "far away items";
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.Method(typeof(JobGiver_Haul), nameof(JobGiver_Haul.TryGiveJob));
|
||||
|
||||
public static CodeInstructions? Transpiler(CodeInstructions codes, MethodBase method)
|
||||
{
|
||||
var pawn_Map_listerHaulables_ThingsPotentiallyNeedingHauling = FishTranspiler.Call(() => default(ListerHaulables)!.ThingsPotentiallyNeedingHauling());
|
||||
if (ActiveMods.Multiplayer) // PUAH has known issues with sorting and multiplayer
|
||||
return codes;
|
||||
|
||||
var pawn_Map_listerHaulables_ThingsPotentiallyNeedingHauling
|
||||
= FishTranspiler.Call(static () => default(ListerHaulables)!.ThingsPotentiallyNeedingHauling());
|
||||
|
||||
try
|
||||
{
|
||||
return codes.InsertAfter(
|
||||
c => c == pawn_Map_listerHaulables_ThingsPotentiallyNeedingHauling,
|
||||
return codes.InsertAfter(c => c == pawn_Map_listerHaulables_ThingsPotentiallyNeedingHauling,
|
||||
new CodeInstruction[]
|
||||
{
|
||||
FishTranspiler.Argument(method, "pawn"),
|
||||
@@ -28,16 +38,17 @@ public class JobGiver_HaulOptimization : ClassWithFishPatches
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"{ex}");
|
||||
//Log.Error("Performance Fish failed to apply its JobGiver_Haul transpiler. This particular optimization will be doing nothing now");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Thing> SortedThingsPotentiallyNeedingHauling(List<Thing> things, Pawn pawn)
|
||||
{
|
||||
_comparer.rootCell = pawn.Position;
|
||||
things.Sort(_comparer);
|
||||
return things;
|
||||
}
|
||||
|
||||
private static WorkGiver_DoBillOptimization.ThingPositionComparer _comparer = new();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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/.
|
||||
|
||||
@@ -1,40 +1,61 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using Verse.AI;
|
||||
using IngredientCacheList = PerformanceFish.Utility.KeyedList<Verse.IngredientCount, (System.Collections.Generic.List<Verse.ThingDef> Defs, bool Found)>;
|
||||
using RecipeIngredientCache = PerformanceFish.Cache.ByIndex<RimWorld.Bill, PerformanceFish.JobSystem.WorkGiver_DoBillOptimization.RecipeIngredientCacheValue>;
|
||||
using IngredientCacheList
|
||||
= PerformanceFish.Utility.KeyedList<Verse.IngredientCount, (System.Collections.Generic.List<Verse.ThingDef> Defs,
|
||||
bool Found)>;
|
||||
using RecipeIngredientCache
|
||||
= PerformanceFish.Cache.ByIndex<RimWorld.Bill,
|
||||
PerformanceFish.JobSystem.WorkGiver_DoBillOptimization.RecipeIngredientCacheValue>;
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
{
|
||||
public static List<Type> BlackListedModExtensions => _blackListedModExtensions ??= new() { Reflection.Type("VFEAncients", "VFEAncients.RecipeExtension_Mend") };
|
||||
private static List<Type>? _blackListedModExtensions;
|
||||
public static List<Type?> BlackListedModExtensions
|
||||
=> _blackListedModExtensions ??= new() { Reflection.Type("VFEAncients", "VFEAncients.RecipeExtension_Mend") };
|
||||
|
||||
public class TryFindBestIngredientsHelper_InnerDelegate_Patch : FishPatch
|
||||
private static List<Type?>? _blackListedModExtensions;
|
||||
|
||||
public class TryFindBestIngredientsHelper_InnerDelegate_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Optimizes WorkGiver_DoBill to only check things that get accepted by a bill's filter for its ingredient lookup, instead of looping over everything and then checking filters afterwards. " +
|
||||
"Also caches these filter acceptances. Large performance impact.";
|
||||
public override string Description { get; }
|
||||
= "Optimizes WorkGiver_DoBill to only check things that get accepted by a bill's filter for its "
|
||||
+ "ingredient lookup, instead of looping over everything and then checking filters afterwards. "
|
||||
+ "Also caches these filter acceptances. Large performance impact.";
|
||||
|
||||
public override MethodBase TargetMethodInfo
|
||||
=> AccessTools.FirstMethod(DelegateInstanceType, m
|
||||
=> m.GetParameters() is { Length: 1 } parms
|
||||
&& parms[0].ParameterType == typeof(Region)
|
||||
public override bool Enabled
|
||||
{
|
||||
set
|
||||
{
|
||||
if (base.Enabled == value)
|
||||
return;
|
||||
|
||||
Get<TryFindBestIngredientsHelper_Patch>().Enabled
|
||||
= Get<TryFindBestIngredientsInSet_NoMixHelper_Patch>().Enabled
|
||||
= base.Enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.FirstMethod(DelegateInstanceType, static m
|
||||
=> m.GetParameters() is { Length: 1 } parameterInfos
|
||||
&& parameterInfos[0].ParameterType == typeof(Region)
|
||||
&& m.ReturnType == typeof(bool)
|
||||
&& PatchProcessor.ReadMethodBody(m).Any(pair
|
||||
=> (pair.Key == OpCodes.Call || pair.Key == OpCodes.Callvirt)
|
||||
&& pair.Value is MethodInfo info
|
||||
&& MethodBodyReader.GetInstructions(null, m).Any(static instruction
|
||||
=> (instruction.opcode == OpCodes.Call || instruction.opcode == OpCodes.Callvirt)
|
||||
&& instruction.operand is MethodInfo info
|
||||
&& info == AccessTools.Method(typeof(ListerThings), nameof(ListerThings.ThingsMatching))));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions, MethodBase method)
|
||||
=> Reflection.MakeReplacementCall(
|
||||
AccessTools.Method(typeof(TryFindBestIngredientsHelper_InnerDelegate_Patch), nameof(Replacement), generics: new[] { DelegateInstanceType }));
|
||||
public static CodeInstructions Transpiler(CodeInstructions codeInstructions, MethodBase method)
|
||||
=> Reflection.MakeReplacementCall(AccessTools.Method(
|
||||
typeof(TryFindBestIngredientsHelper_InnerDelegate_Patch), nameof(Replacement),
|
||||
generics: new[] { DelegateInstanceType }));
|
||||
|
||||
public static bool Replacement<T>(T __instance, Region r) where T : class
|
||||
{
|
||||
@@ -54,41 +75,42 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
#if V1_4
|
||||
|| bill.billStack?.billGiver is Building_MechGestator
|
||||
#endif
|
||||
|| (BlackListedModExtensions.Exists(b => b != null) && IsBlackListed(bill))) // <-- Fuck these too
|
||||
|| (BlackListedModExtensions.Exists(static b => b != null)
|
||||
&& IsBlackListed(bill))) // <-- Fuck these too
|
||||
{
|
||||
list = GetDefaultList(r);
|
||||
}
|
||||
else
|
||||
{
|
||||
ref var cache = ref RecipeIngredientCache.Get.GetReference(bill);
|
||||
if (cache.thingDefs is null || cache.ShouldRefreshNow)
|
||||
if (cache.thingDefs is null || cache.Dirty)
|
||||
{
|
||||
cache.thingDefs = UpdateCache(bill, r, cache.thingDefs);
|
||||
cache.ShouldRefreshNow = false;
|
||||
RecipeIngredientCache.Get[bill] = cache;
|
||||
cache.Update(bill);
|
||||
}
|
||||
|
||||
list = GetList(r.listerThings.listsByDef, cache.thingDefs);
|
||||
}
|
||||
}
|
||||
|
||||
ActualLoop(list, r, instance.Pawn, instance.BaseValidator, instance.BillGiverIsPawn);
|
||||
|
||||
//list.Clear();
|
||||
|
||||
instance.RegionsProcessed++;
|
||||
if (WorkGiver_DoBill.newRelevantThings.Count > 0 && instance.RegionsProcessed > instance.AdjacentRegionsAvailable)
|
||||
{
|
||||
if (WorkGiver_DoBill.newRelevantThings.Count <= 0
|
||||
|| instance.RegionsProcessed <= instance.AdjacentRegionsAvailable)
|
||||
return false;
|
||||
|
||||
//WorkGiver_DoBill.relevantThings.AddRange(WorkGiver_DoBill.newRelevantThings);
|
||||
InsertAtCorrectPosition(WorkGiver_DoBill.relevantThings, WorkGiver_DoBill.newRelevantThings, instance.Pawn);
|
||||
InsertAtCorrectPosition(WorkGiver_DoBill.relevantThings, WorkGiver_DoBill.newRelevantThings,
|
||||
instance.Pawn);
|
||||
|
||||
WorkGiver_DoBill.newRelevantThings.Clear();
|
||||
if (instance.FoundAllIngredientsAndChoose(WorkGiver_DoBill.relevantThings))
|
||||
{
|
||||
if (!instance.FoundAllIngredientsAndChoose(WorkGiver_DoBill.relevantThings))
|
||||
return false;
|
||||
|
||||
instance.FoundAll = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
private static bool HasNoFilters(Bill bill)
|
||||
@@ -113,13 +135,13 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
return false;
|
||||
}*/
|
||||
|
||||
private static bool IsBlackListed(Bill bill)
|
||||
private static bool IsBlackListed(Bill? bill)
|
||||
=> bill is null
|
||||
|| (bill.recipe.modExtensions?.Exists(e
|
||||
=> BlackListedModExtensions.Contains(e.GetType()))
|
||||
?? false);
|
||||
|| bill.recipe.modExtensions.ExistsAndNotNull(static e
|
||||
=> BlackListedModExtensions.Contains(e.GetType()));
|
||||
|
||||
private static List<Thing> GetDefaultList(Region r) => r.ListerThings.ThingsMatching(ThingRequest.ForGroup(ThingRequestGroup.HaulableEver));
|
||||
private static List<Thing> GetDefaultList(Region r)
|
||||
=> r.ListerThings.ThingsMatching(ThingRequest.ForGroup(ThingRequestGroup.HaulableEver));
|
||||
|
||||
public static void InsertAtCorrectPosition(List<Thing> list, List<Thing> thingsToInsert, Pawn pawn)
|
||||
{
|
||||
@@ -139,20 +161,26 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
|
||||
private static readonly ThingPositionComparer _insertAtCorrectPositionComparer = new();
|
||||
|
||||
public static void ActualLoop(List<Thing> list, Region r, Pawn pawn, Predicate<Thing> baseValidator, bool billGiverIsPawn)
|
||||
public static void ActualLoop(List<Thing> list, Region r, Pawn pawn, Predicate<Thing> baseValidator,
|
||||
bool billGiverIsPawn)
|
||||
{
|
||||
var count = list.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var thing = list[i];
|
||||
if (!WorkGiver_DoBill.processedThings.Contains(thing)
|
||||
&& ReachabilityWithinRegion.ThingFromRegionListerReachable(thing, r, PathEndMode.ClosestTouch, pawn) && !(billGiverIsPawn && thing.def.IsMedicine) && baseValidator(thing))
|
||||
if (WorkGiver_DoBill.processedThings.Contains(thing)
|
||||
|| !ReachabilityWithinRegion.ThingFromRegionListerReachable(thing, r, PathEndMode.ClosestTouch,
|
||||
pawn)
|
||||
|| (billGiverIsPawn && thing.def.IsMedicine)
|
||||
|| !baseValidator(thing))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
WorkGiver_DoBill.newRelevantThings.Add(thing);
|
||||
WorkGiver_DoBill.processedThings.Add(thing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Thing> GetList(Dictionary<ThingDef, List<Thing>> listsByDef, IngredientCacheList cacheList)
|
||||
{
|
||||
@@ -170,15 +198,16 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
var count = defs.Count;
|
||||
for (var j = 0; j < count; j++)
|
||||
{
|
||||
if (listsByDef.TryGetValue(defs[j], out var listByDef) && listByDef.Any())
|
||||
if (!listsByDef.TryGetValue(defs[j], out var listByDef)
|
||||
|| !listByDef.Any()
|
||||
|| (i > 0 && !set.Add(defs[j])))
|
||||
{
|
||||
if (i > 0 && !set.Add(defs[j]))
|
||||
continue;
|
||||
}
|
||||
|
||||
list.AddRange(listByDef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
@@ -190,7 +219,8 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
var listsByDef = r.Map.listerThings.listsByDef;
|
||||
var result = cacheList ?? new(3);
|
||||
|
||||
for (var i = 0; i < ingredients.Count; i++) // generally not larger than 3 in vanilla and anything that tries to be
|
||||
for (var i = 0; i < ingredients.Count;
|
||||
i++) // generally not larger than 3 in vanilla and anything that tries to be
|
||||
{
|
||||
if (result.TryGetValue(ingredients[i], out var ingredientList))
|
||||
{
|
||||
@@ -203,13 +233,12 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
}
|
||||
|
||||
var filter = ingredients[i].filter;
|
||||
var allowedDefCount = filter.allowedDefs.Count;
|
||||
|
||||
if (allowedDefCount < 1)
|
||||
continue;
|
||||
|
||||
if (allowedDefCount == 1)
|
||||
switch (filter.allowedDefs.Count)
|
||||
{
|
||||
case < 1:
|
||||
continue;
|
||||
case 1:
|
||||
ingredientList.Defs.Add(filter.allowedDefs.First());
|
||||
continue;
|
||||
}
|
||||
@@ -220,19 +249,25 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
|
||||
OrderFiltersBySize(ref smallestFilter, ref mediumFilter, ref largestFilter);
|
||||
|
||||
var cachedSmallestList = AllowedDefsListCache.GetValue(smallestFilter).defs;
|
||||
var cachedSmallestList = AllowedDefsListCache
|
||||
.GetAndCheck<ThingFilterPatches.AllowedDefsListCacheValue>(smallestFilter).Defs;
|
||||
|
||||
if (cachedSmallestList.Count != smallestFilter.allowedDefs.Count)
|
||||
{
|
||||
// Log.Warning($"Cached list count: {cachedSmallestList.Count}, actual filter list count: {smallestFilter.allowedDefs.Count}. Forcing a refresh now to fix.");
|
||||
// Log.Warning($"Cached list count: {cachedSmallestList.Count}, actual filter list count: {
|
||||
// smallestFilter.allowedDefs.Count}. Forcing a refresh now to fix.");
|
||||
ThingFilterPatches.ForceSynchronizeCache(smallestFilter);
|
||||
// Log.Warning($"New cached list count: {cachedSmallestList.Count}, actual filter list count: {smallestFilter.allowedDefs.Count}.");
|
||||
// Log.Warning($"New cached list count: {cachedSmallestList.Count}, actual filter list count: {
|
||||
// smallestFilter.allowedDefs.Count}.");
|
||||
// didn't trigger in 1.3, but now does. Fuck
|
||||
}
|
||||
|
||||
var smallestListCount = cachedSmallestList.Count;
|
||||
for (var k = 0; k < smallestListCount; k++)
|
||||
{
|
||||
var def = cachedSmallestList[k];
|
||||
if (listsByDef.TryGetValue(def, out var listByDef) && listByDef.Any()
|
||||
if (listsByDef.TryGetValue(def, out var listByDef)
|
||||
&& listByDef.Any()
|
||||
&& mediumFilter.Allows(def)
|
||||
&& largestFilter.Allows(def))
|
||||
{
|
||||
@@ -244,7 +279,8 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void OrderFiltersBySize(ref ThingFilter smallestFilter, ref ThingFilter mediumFilter, ref ThingFilter largestFilter)
|
||||
private static void OrderFiltersBySize(ref ThingFilter smallestFilter, ref ThingFilter mediumFilter,
|
||||
ref ThingFilter largestFilter)
|
||||
{
|
||||
if (smallestFilter.allowedDefs.Count > mediumFilter.allowedDefs.Count)
|
||||
(smallestFilter, mediumFilter) = (mediumFilter, smallestFilter);
|
||||
@@ -261,9 +297,10 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
}
|
||||
|
||||
private static Type DelegateInstanceType { get; }
|
||||
= AccessTools.FirstInner(typeof(WorkGiver_DoBill), t
|
||||
=> AccessTools.FirstMethod(t, m
|
||||
=> m.Name.Contains($"<{nameof(WorkGiver_DoBill.TryFindBestIngredientsHelper)}>")) != null
|
||||
= AccessTools.FirstInner(typeof(WorkGiver_DoBill), static t
|
||||
=> AccessTools.FirstMethod(t, static m
|
||||
=> m.Name.Contains($"<{nameof(WorkGiver_DoBill.TryFindBestIngredientsHelper)}>"))
|
||||
!= null
|
||||
&& AccessTools.Field(t, "pawn") != null
|
||||
&& AccessTools.Field(t, "baseValidator") != null
|
||||
&& AccessTools.Field(t, "billGiverIsPawn") != null
|
||||
@@ -272,25 +309,30 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
&& AccessTools.Field(t, "foundAllIngredientsAndChoose") != null
|
||||
&& AccessTools.Field(t, "foundAll") != null
|
||||
&& AccessTools.Field(t, "thingValidator") != null)
|
||||
?? throw new("Performance Fish failed to find the target type for its WorkGiver_DoBill patch. This means that specific patch won't apply.");
|
||||
?? throw new("Performance Fish failed to find the target type for its WorkGiver_DoBill patch. "
|
||||
+ "This means that specific patch won't apply.");
|
||||
|
||||
private static Type ThingValidatorInstanceType { get; }
|
||||
= AccessTools.FirstInner(typeof(WorkGiver_DoBill), t
|
||||
=> AccessTools.FirstMethod(t, m
|
||||
=> m.Name.Contains($"<{nameof(WorkGiver_DoBill.TryFindBestBillIngredients)}>")) != null
|
||||
= AccessTools.FirstInner(typeof(WorkGiver_DoBill), static t
|
||||
=> AccessTools.FirstMethod(t, static m
|
||||
=> m.Name.Contains($"<{nameof(WorkGiver_DoBill.TryFindBestBillIngredients)}>"))
|
||||
!= null
|
||||
&& AccessTools.Field(t, "bill") != null
|
||||
//&& AccessTools.Field(t, "chosen") != null
|
||||
//&& AccessTools.Field(t, "billGiver") != null
|
||||
/*&& AccessTools.Field(t, "pawn") != null*/)
|
||||
?? throw new("Performance Fish failed to find the thingValidator type for its WorkGiver_DoBill patch. This likely flat out breaks the entire patch.");
|
||||
?? throw new("Performance Fish failed to find the thingValidator type for its WorkGiver_DoBill patch. "
|
||||
+ "This likely flat out breaks the entire patch.");
|
||||
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles")]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static DelegateInstance _delegateInstance { get; }
|
||||
= (DelegateInstance)Activator.CreateInstance(typeof(DelegateInstanceImplementation<>).MakeGenericType(DelegateInstanceType));
|
||||
= (DelegateInstance)Activator.CreateInstance(
|
||||
typeof(DelegateInstanceImplementation<>).MakeGenericType(DelegateInstanceType));
|
||||
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles")]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static ThingValidatorInstance _thingValidatorInstance { get; }
|
||||
= (ThingValidatorInstance)Activator.CreateInstance(typeof(ThingValidatorInstanceImplementation<>).MakeGenericType(ThingValidatorInstanceType));
|
||||
= (ThingValidatorInstance)Activator.CreateInstance(
|
||||
typeof(ThingValidatorInstanceImplementation<>).MakeGenericType(ThingValidatorInstanceType));
|
||||
|
||||
private abstract class DelegateInstance
|
||||
{
|
||||
@@ -318,23 +360,75 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
return this;
|
||||
}
|
||||
|
||||
public override Pawn Pawn { get => _pawn(_instance); set => _pawn(_instance) = value; }
|
||||
public override Predicate<Thing> BaseValidator { get => _baseValidator(_instance); set => _baseValidator(_instance) = value; }
|
||||
public override bool BillGiverIsPawn { get => _billGiverIsPawn(_instance); set => _billGiverIsPawn(_instance) = value; }
|
||||
public override int RegionsProcessed { get => _regionsProcessed(_instance); set => _regionsProcessed(_instance) = value; }
|
||||
public override int AdjacentRegionsAvailable { get => _adjacentRegionsAvailable(_instance); set => _adjacentRegionsAvailable(_instance) = value; }
|
||||
public override Predicate<List<Thing>> FoundAllIngredientsAndChoose { get => _foundAllIngredientsAndChoose(_instance); set => _foundAllIngredientsAndChoose(_instance) = value; }
|
||||
public override bool FoundAll { get => _foundAll(_instance); set => _foundAll(_instance) = value; }
|
||||
public override Predicate<Thing> ThingValidator { get => _thingValidator(_instance); set => _thingValidator(_instance) = value; }
|
||||
public override Pawn Pawn
|
||||
{
|
||||
get => _pawn(_instance);
|
||||
set => _pawn(_instance) = value;
|
||||
}
|
||||
|
||||
public override Predicate<Thing> BaseValidator
|
||||
{
|
||||
get => _baseValidator(_instance);
|
||||
set => _baseValidator(_instance) = value;
|
||||
}
|
||||
|
||||
public override bool BillGiverIsPawn
|
||||
{
|
||||
get => _billGiverIsPawn(_instance);
|
||||
set => _billGiverIsPawn(_instance) = value;
|
||||
}
|
||||
|
||||
public override int RegionsProcessed
|
||||
{
|
||||
get => _regionsProcessed(_instance);
|
||||
set => _regionsProcessed(_instance) = value;
|
||||
}
|
||||
|
||||
public override int AdjacentRegionsAvailable
|
||||
{
|
||||
get => _adjacentRegionsAvailable(_instance);
|
||||
set => _adjacentRegionsAvailable(_instance) = value;
|
||||
}
|
||||
|
||||
public override Predicate<List<Thing>> FoundAllIngredientsAndChoose
|
||||
{
|
||||
get => _foundAllIngredientsAndChoose(_instance);
|
||||
set => _foundAllIngredientsAndChoose(_instance) = value;
|
||||
}
|
||||
|
||||
public override bool FoundAll
|
||||
{
|
||||
get => _foundAll(_instance);
|
||||
set => _foundAll(_instance) = value;
|
||||
}
|
||||
|
||||
public override Predicate<Thing> ThingValidator
|
||||
{
|
||||
get => _thingValidator(_instance);
|
||||
set => _thingValidator(_instance) = value;
|
||||
}
|
||||
|
||||
private static AccessTools.FieldRef<T, Pawn> _pawn = AccessTools.FieldRefAccess<T, Pawn>("pawn");
|
||||
private static AccessTools.FieldRef<T, Predicate<Thing>> _baseValidator = AccessTools.FieldRefAccess<T, Predicate<Thing>>("baseValidator");
|
||||
private static AccessTools.FieldRef<T, bool> _billGiverIsPawn = AccessTools.FieldRefAccess<T, bool>("billGiverIsPawn");
|
||||
private static AccessTools.FieldRef<T, int> _regionsProcessed = AccessTools.FieldRefAccess<T, int>("regionsProcessed");
|
||||
private static AccessTools.FieldRef<T, int> _adjacentRegionsAvailable = AccessTools.FieldRefAccess<T, int>("adjacentRegionsAvailable");
|
||||
private static AccessTools.FieldRef<T, Predicate<List<Thing>>> _foundAllIngredientsAndChoose = AccessTools.FieldRefAccess<T, Predicate<List<Thing>>>("foundAllIngredientsAndChoose");
|
||||
|
||||
private static AccessTools.FieldRef<T, Predicate<Thing>> _baseValidator
|
||||
= AccessTools.FieldRefAccess<T, Predicate<Thing>>("baseValidator");
|
||||
|
||||
private static AccessTools.FieldRef<T, bool> _billGiverIsPawn
|
||||
= AccessTools.FieldRefAccess<T, bool>("billGiverIsPawn");
|
||||
|
||||
private static AccessTools.FieldRef<T, int> _regionsProcessed
|
||||
= AccessTools.FieldRefAccess<T, int>("regionsProcessed");
|
||||
|
||||
private static AccessTools.FieldRef<T, int> _adjacentRegionsAvailable
|
||||
= AccessTools.FieldRefAccess<T, int>("adjacentRegionsAvailable");
|
||||
|
||||
private static AccessTools.FieldRef<T, Predicate<List<Thing>>> _foundAllIngredientsAndChoose
|
||||
= AccessTools.FieldRefAccess<T, Predicate<List<Thing>>>("foundAllIngredientsAndChoose");
|
||||
|
||||
private static AccessTools.FieldRef<T, bool> _foundAll = AccessTools.FieldRefAccess<T, bool>("foundAll");
|
||||
private static AccessTools.FieldRef<T, Predicate<Thing>> _thingValidator = AccessTools.FieldRefAccess<T, Predicate<Thing>>("thingValidator");
|
||||
|
||||
private static AccessTools.FieldRef<T, Predicate<Thing>> _thingValidator
|
||||
= AccessTools.FieldRefAccess<T, Predicate<Thing>>("thingValidator");
|
||||
}
|
||||
|
||||
private abstract class ThingValidatorInstance
|
||||
@@ -364,7 +458,11 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
}
|
||||
}
|
||||
|
||||
public override Bill Bill { get => _bill(_instance); set => _bill(_instance) = value; }
|
||||
public override Bill Bill
|
||||
{
|
||||
get => _bill(_instance);
|
||||
set => _bill(_instance) = value;
|
||||
}
|
||||
//public override Thing BillGiver { get => _billGiver(_instance); set => _billGiver(_instance) = value; }
|
||||
|
||||
private static AccessTools.FieldRef<T, Bill> _bill = AccessTools.FieldRefAccess<T, Bill>("bill");
|
||||
@@ -373,9 +471,20 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
|
||||
public class TryFindBestIngredientsHelper_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the DoBill optimization";
|
||||
public override string Description { get; } = "Part of the DoBill optimization";
|
||||
|
||||
public override Delegate TargetMethodGroup => WorkGiver_DoBill.TryFindBestIngredientsHelper;
|
||||
public override bool Enabled
|
||||
{
|
||||
set
|
||||
{
|
||||
if (base.Enabled == value)
|
||||
return;
|
||||
|
||||
Get<TryFindBestIngredientsInSet_NoMixHelper_Patch>().Enabled = base.Enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Delegate TargetMethodGroup { get; } = WorkGiver_DoBill.TryFindBestIngredientsHelper;
|
||||
|
||||
public static void Postfix(Predicate<Thing> thingValidator)
|
||||
{
|
||||
@@ -399,24 +508,43 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
|
||||
public class TryFindBestIngredientsInSet_NoMixHelper_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the DoBill optimization";
|
||||
public override string Description { get; }
|
||||
= "Part of the DoBill optimization. Bills that need multiple ingredients, like for example steel and "
|
||||
+ "components for guns, normally scan their surroundings for all those ingredients and count them, until "
|
||||
+ "enough of both is found. This patch removes ingredients from the lookup when enough of them is found, "
|
||||
+ "allowing the patched method to skip counting them and go straight to the other missing ingredient. In "
|
||||
+ "case of steel and components that could mean not having to go over 100s of steel stacks at times. "
|
||||
+ "Prevents large spikes from these situations, does nothing for all other bills.";
|
||||
|
||||
public override Delegate TargetMethodGroup => WorkGiver_DoBill.TryFindBestIngredientsInSet_NoMixHelper;
|
||||
public override bool Enabled
|
||||
{
|
||||
set
|
||||
{
|
||||
if (base.Enabled == value)
|
||||
return;
|
||||
|
||||
Get<TryFindBestIngredientsHelper_Patch>().Enabled = base.Enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Delegate TargetMethodGroup { get; } = WorkGiver_DoBill.TryFindBestIngredientsInSet_NoMixHelper;
|
||||
|
||||
public static void Prefix(ref bool alreadySorted, Bill? bill)
|
||||
=> alreadySorted = alreadySorted
|
||||
|| (bill?.billStack is { } stack
|
||||
#if V1_4
|
||||
// ReSharper disable once MergeIntoPattern
|
||||
&& stack.billGiver is not Building_MechGestator
|
||||
#endif
|
||||
);
|
||||
|
||||
public static CodeInstructions? Transpiler(CodeInstructions codes, MethodBase method)
|
||||
public static CodeInstructions Transpiler(CodeInstructions instructions, MethodBase method,
|
||||
ILGenerator generator)
|
||||
{
|
||||
var i_variable = FishTranspiler.FirstLocalVariable(method, typeof(int));
|
||||
var new_i_label = generator.DefineLabel();
|
||||
|
||||
return codes.ReplaceAt(
|
||||
(codes, i)
|
||||
return instructions.ReplaceAt((codes, i)
|
||||
=> i + 3 < codes.Count
|
||||
&& codes[i] == i_variable.Load()
|
||||
&& codes[i + 1] == FishTranspiler.Constant(1) // i++;
|
||||
@@ -425,16 +553,27 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
code
|
||||
=> new[]
|
||||
{
|
||||
new(OpCodes.Br_S, new_i_label), // else
|
||||
FishTranspiler.FirstLocalVariable(method, typeof(IngredientCount))
|
||||
.WithLabels(code.ExtractLabels()),
|
||||
FishTranspiler.FirstArgument(method, typeof(Bill)),
|
||||
FishTranspiler.Call(MarkIngredientCountAsFound),
|
||||
code
|
||||
}
|
||||
);
|
||||
code.WithLabels(new_i_label)
|
||||
});
|
||||
|
||||
// if (!flag)
|
||||
// {
|
||||
// if (missingIngredients == null)
|
||||
// return false;
|
||||
// missingIngredients.Add(ingredientCount);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// MarkIngredientCountAsFound(ingredientCount, bill);
|
||||
// }
|
||||
}
|
||||
|
||||
public static void MarkIngredientCountAsFound(IngredientCount ingredientCount, Bill bill)
|
||||
public static void MarkIngredientCountAsFound(IngredientCount ingredientCount, Bill? bill)
|
||||
{
|
||||
if (bill?.billStack is not { } billStack
|
||||
#if V1_4
|
||||
@@ -451,7 +590,7 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
|
||||
ref var cacheItem = ref cache._items[cache.IndexOfKey(ingredientCount)];
|
||||
|
||||
if ((cache.Count == 1 && cacheItem.Value.Defs.Count == 1)
|
||||
if ((cache.Count == 1 /*&& cacheItem.Value.Defs.Count == 1*/)
|
||||
|| bill.recipe?.workerCounterClass != typeof(RecipeWorkerCounter))
|
||||
{
|
||||
return;
|
||||
@@ -465,25 +604,24 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
|
||||
{
|
||||
public IntVec3 rootCell;
|
||||
|
||||
public int Compare(Thing x, Thing y) => (x.Position - rootCell).LengthHorizontalSquared.CompareTo((y.Position - rootCell).LengthHorizontalSquared);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int Compare(Thing x, Thing y)
|
||||
=> (x.Position - rootCell).LengthHorizontalSquared.CompareTo(
|
||||
(y.Position - rootCell).LengthHorizontalSquared);
|
||||
}
|
||||
|
||||
public struct RecipeIngredientCacheValue
|
||||
public record struct RecipeIngredientCacheValue
|
||||
{
|
||||
public IngredientCacheList thingDefs;
|
||||
|
||||
public IngredientCacheList? thingDefs;
|
||||
private int _nextRefreshTick;
|
||||
|
||||
public RecipeIngredientCacheValue(IngredientCacheList thingDefs)
|
||||
{
|
||||
this.thingDefs = thingDefs;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
|
||||
}
|
||||
public void Update(Bill bill)
|
||||
=> _nextRefreshTick = TickHelper.Add(GenTicks.TickRareInterval * 2, bill.loadID);
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
public bool Dirty
|
||||
{
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => TickHelper.Past(_nextRefreshTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,38 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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 PerformanceFish.ModCompatibility;
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class WorkGiver_HaulOptimization : ClassWithFishPatches
|
||||
{
|
||||
public class PotentialWorkThingsGlobal_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Sorts haulables by distance before running expensive hauling calculations on them to avoid checks on far away items";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(WorkGiver_Haul), nameof(WorkGiver_Haul.PotentialWorkThingsGlobal));
|
||||
public static IEnumerable<Thing> Postfix(IEnumerable<Thing> __result, Pawn pawn)
|
||||
public override bool Enabled => base.Enabled && !ActiveMods.Multiplayer;
|
||||
|
||||
public override string Description { get; }
|
||||
= "Sorts haulables by distance before running expensive hauling calculations on them to avoid checks on "
|
||||
+ "far away items";
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.Method(typeof(WorkGiver_Haul), nameof(WorkGiver_Haul.PotentialWorkThingsGlobal));
|
||||
|
||||
public static IEnumerable<Thing> Postfix(IEnumerable<Thing> __result, Pawn? pawn)
|
||||
{
|
||||
if (pawn != null && __result is List<Thing> list)
|
||||
Sort(list, pawn);
|
||||
return __result;
|
||||
}
|
||||
|
||||
private static void Sort(List<Thing> list, Pawn pawn)
|
||||
{
|
||||
_comparer.rootCell = pawn.Position;
|
||||
list.Sort(_comparer);
|
||||
|
||||
//Log.ErrorOnce(list.Select(t => t.Position.DistanceToSquared(pawn.Position)).ToStringSafeEnumerable(), 84736285);
|
||||
}
|
||||
|
||||
private static WorkGiver_DoBillOptimization.ThingPositionComparer _comparer = new();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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/.
|
||||
@@ -11,19 +11,22 @@ public class WorkGiver_MergeOptimization : ClassWithFishPatches
|
||||
{
|
||||
public class JobOnThing_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Removes mergeables from ListerMergeables if they no longer qualify as mergeable when trying to check for a merging job on them. Vanilla RimWorld just fails and keeps them in";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(WorkGiver_Merge), nameof(WorkGiver_Merge.JobOnThing));
|
||||
public override string Description { get; }
|
||||
= "Removes mergeables from ListerMergeables if they no longer qualify as mergeable when trying to check "
|
||||
+ "for a merging job on them. Vanilla RimWorld just fails and keeps them in";
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.Method(typeof(WorkGiver_Merge), nameof(WorkGiver_Merge.JobOnThing));
|
||||
|
||||
public static bool Prefix(Thing t, ref Job? __result)
|
||||
{
|
||||
if (t.GetSlotGroup() is null)
|
||||
{
|
||||
if (t.GetSlotGroup() is not null)
|
||||
return true;
|
||||
|
||||
RemoveFromListerMergeables(t);
|
||||
__result = null;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void RemoveFromListerMergeables(Thing t)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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/.
|
||||
@@ -13,12 +13,18 @@ public class Buildings : ClassWithFishPatches
|
||||
/// Get Things from the ListerThings dictionary with a rather quick lookup instead of looping over every single element in the lists of listerBuildings
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static List<Thing> ThingsOfDefFast(ListerThings listerThings, ThingDef def) => listerThings.listsByDef.TryGetValue(def) ?? ListerThings.EmptyList;
|
||||
public static List<Thing> ThingsOfDefFast(ListerThings listerThings, ThingDef def)
|
||||
=> listerThings.listsByDef.TryGetValue(def) ?? ListerThings.EmptyList;
|
||||
|
||||
public class ColonistsHaveBuilding_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.ColonistsHaveBuilding(default(ThingDef));
|
||||
public override string Description { get; }
|
||||
= "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much "
|
||||
+ "outside of a few prevented spikes here and there either";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => default(ListerBuildings)!.ColonistsHaveBuilding(default(ThingDef));
|
||||
|
||||
public static bool Prefix(ListerBuildings __instance, ref bool __result, ThingDef def)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
@@ -32,35 +38,53 @@ public class Buildings : ClassWithFishPatches
|
||||
|
||||
public class ColonistsHaveResearchBench_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.ColonistsHaveResearchBench();
|
||||
public override string Description { get; }
|
||||
= "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much "
|
||||
+ "outside of a few prevented spikes here and there either";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => default(ListerBuildings)!.ColonistsHaveResearchBench();
|
||||
|
||||
public static bool Prefix(ListerBuildings __instance, ref bool __result)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
if (lister is null)
|
||||
return true;
|
||||
|
||||
foreach (var def in LazyInit.ResearchBenches)
|
||||
{
|
||||
if (ContainsDefForColonists(lister, def))
|
||||
var researchBenches = LazyInit.ResearchBenches;
|
||||
for (var i = 0; i < researchBenches.Length; i++)
|
||||
{
|
||||
if (!ContainsDefForColonists(lister, researchBenches[i]))
|
||||
continue;
|
||||
|
||||
__result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class LazyInit
|
||||
{
|
||||
public static ThingDef[] ResearchBenches = DefDatabase<ThingDef>.AllDefsListForReading.Where(def => def.thingClass == typeof(Building_ResearchBench)).ToArray();
|
||||
static LazyInit() { }
|
||||
public static ThingDef[] ResearchBenches = DefDatabase<ThingDef>.AllDefsListForReading
|
||||
.Where(static def => def.thingClass == typeof(Building_ResearchBench)).ToArray();
|
||||
|
||||
static LazyInit()
|
||||
{
|
||||
// beforefieldinit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ColonistsHaveBuildingWithPowerOn_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.ColonistsHaveBuildingWithPowerOn(null);
|
||||
public override string Description { get; }
|
||||
= "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much "
|
||||
+ "outside of a few prevented spikes here and there either";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => default(ListerBuildings)!.ColonistsHaveBuildingWithPowerOn(null);
|
||||
|
||||
public static bool Prefix(ListerBuildings __instance, ref bool __result, ThingDef def)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
@@ -71,21 +95,29 @@ public class Buildings : ClassWithFishPatches
|
||||
var playerFaction = Faction.OfPlayerSilentFail;
|
||||
for (var i = 0; i < things.Count; i++)
|
||||
{
|
||||
if (things[i].Faction == playerFaction
|
||||
&& things[i].TryGetComp<CompPowerTrader>() is not { PowerOn: false })
|
||||
if (things[i].Faction != playerFaction
|
||||
|| things[i].TryGetComp<CompPowerTrader>() is { PowerOn: false })
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
__result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public class AllBuildingsColonistOfDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.AllBuildingsColonistOfDef(null);
|
||||
public override string Description { get; }
|
||||
= "ListerBuildings optimization. Relatively large performance impact when having water power plants on the "
|
||||
+ "map.";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => default(ListerBuildings)!.AllBuildingsColonistOfDef(null);
|
||||
|
||||
public static bool Prefix(ListerBuildings __instance, ref IEnumerable<Building> __result, ThingDef def)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
@@ -95,6 +127,7 @@ public class Buildings : ClassWithFishPatches
|
||||
__result = AllBuildingsColonistOfDefReplacement(lister, def);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IEnumerable<Building> AllBuildingsColonistOfDefReplacement(ListerThings lister, ThingDef def)
|
||||
{
|
||||
var things = ThingsOfDefFast(lister, def);
|
||||
@@ -115,10 +148,20 @@ public class Buildings : ClassWithFishPatches
|
||||
}
|
||||
}
|
||||
|
||||
public record struct AllBuildingsColonistOfDefCacheValue
|
||||
{
|
||||
public List<Building> Buildings;
|
||||
}
|
||||
|
||||
public class AllBuildingsNonColonistOfDef_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerBuildings)!.AllBuildingsNonColonistOfDef(null);
|
||||
public override string Description { get; }
|
||||
= "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much "
|
||||
+ "outside of a few prevented spikes here and there either";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => default(ListerBuildings)!.AllBuildingsNonColonistOfDef(null);
|
||||
|
||||
public static bool Prefix(ListerBuildings __instance, ref IEnumerable<Building> __result, ThingDef def)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
@@ -128,6 +171,7 @@ public class Buildings : ClassWithFishPatches
|
||||
__result = AllBuildingsNonColonistOfDefReplacement(lister, def);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IEnumerable<Building> AllBuildingsNonColonistOfDefReplacement(ListerThings lister, ThingDef def)
|
||||
{
|
||||
var things = ThingsOfDefFast(lister, def);
|
||||
@@ -151,33 +195,47 @@ public class Buildings : ClassWithFishPatches
|
||||
#if harmonyIsFailingOnThisOne
|
||||
public class AllBuildingsColonistOfClass_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much outside of a few prevented spikes here and there either";
|
||||
public override Expression<Action> TargetMethodExpression => () => default(ListerBuildings).AllBuildingsColonistOfClass<Building>();
|
||||
public override string Description { get; }
|
||||
= "ListerBuildings optimization. Should be relatively riskfree, but doesn't impact performance much "
|
||||
+ "outside of a few prevented spikes here and there either";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => default(ListerBuildings)!.AllBuildingsColonistOfClass<Building>();
|
||||
|
||||
public static bool Prefix(ListerBuildings __instance, MethodBase __originalMethod, ref IEnumerable __result)
|
||||
{
|
||||
var lister = GetListerThings(__instance);
|
||||
if (lister is null)
|
||||
return true;
|
||||
|
||||
var type = __originalMethod.GetGenericArguments()[0]; //<-- This always returns Building instead of the type it's actually meant to look for
|
||||
var type = __originalMethod.GetGenericArguments()[0]; //<-- This always returns Building instead of the
|
||||
// type it's actually meant to look for
|
||||
DebugLog.Message($"AllBuildingsColonistOfClass running for type {type.Name}");
|
||||
if (!Replacements.TryGetValue(type, out var replacement))
|
||||
Replacements.Add(type, (GenericReplacementInterface)Activator.CreateInstance(typeof(GenericReplacement<>).MakeGenericType(type)));
|
||||
{
|
||||
Replacements.Add(type,
|
||||
replacement = (GenericReplacementBase)Activator.CreateInstance(
|
||||
typeof(GenericReplacement<>).MakeGenericType(type)));
|
||||
}
|
||||
|
||||
__result = replacement.Replacement(lister);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Dictionary<Type, GenericReplacementInterface> Replacements { get; } = new();
|
||||
public interface GenericReplacementInterface
|
||||
public static Dictionary<Type, GenericReplacementBase> Replacements { get; } = new();
|
||||
|
||||
public abstract class GenericReplacementBase
|
||||
{
|
||||
public IEnumerable Replacement(ListerThings lister);
|
||||
public abstract IEnumerable Replacement(ListerThings lister);
|
||||
}
|
||||
public class GenericReplacement<T> : GenericReplacementInterface where T : Building
|
||||
|
||||
public class GenericReplacement<T> : GenericReplacementBase where T : Building
|
||||
{
|
||||
public IEnumerable Replacement(ListerThings lister)
|
||||
public override IEnumerable Replacement(ListerThings lister)
|
||||
{
|
||||
buildingsOfClass ??= DefDatabase<ThingDef>.AllDefsListForReading.Where(def => def.thingClass == typeof(T)).ToArray();
|
||||
buildingsOfClass ??= DefDatabase<ThingDef>.AllDefsListForReading
|
||||
.Where(static def => def.thingClass == typeof(T)).ToArray();
|
||||
|
||||
foreach (var def in buildingsOfClass)
|
||||
{
|
||||
var things = ThingsOfDefFast(lister, def);
|
||||
@@ -189,44 +247,56 @@ public class Buildings : ClassWithFishPatches
|
||||
}
|
||||
}
|
||||
|
||||
public ThingDef[] buildingsOfClass;
|
||||
public ThingDef[]? buildingsOfClass;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public class Map_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerBuildings optimizations. Without this patch mods like Colony Manager throw errors during loading";
|
||||
public override MethodBase TargetMethodInfo => AccessTools.Constructor(typeof(Map));
|
||||
public override string Description { get; }
|
||||
= "Part of the ListerBuildings optimizations. Without this patch mods like Colony Manager throw errors "
|
||||
+ "during loading";
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; } = AccessTools.Constructor(typeof(Map));
|
||||
public static void Postfix(Map __instance) => _tempMap = __instance;
|
||||
}
|
||||
|
||||
private static Map? _tempMap; //mapComps get constructed before maps are added to Game.Maps, so this makes the patches work there too
|
||||
private static Map? _tempMap;
|
||||
// mapComps get constructed before maps are added to Game.Maps, so this makes the patches work there too
|
||||
|
||||
public static void ThrowWarning(ThingDef def, Thing thing) => Log.Warning($"Tried to get building of def {def}, but its type is {thing.GetType()} instead of Building");
|
||||
public static void ThrowWarning(ThingDef def, Thing thing)
|
||||
=> Log.Warning($"Tried to get building of def {def}, but its type is {thing.GetType()} instead of Building");
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ListerThings? GetListerThings(ListerBuildings listerBuildings)
|
||||
{
|
||||
var maps = Current.Game.Maps;
|
||||
var maps = Current.gameInt.Maps;
|
||||
for (var i = 0; i < maps.Count; i++)
|
||||
{
|
||||
if (maps[i].listerBuildings == listerBuildings)
|
||||
return maps[i].listerThings;
|
||||
}
|
||||
|
||||
return _tempMap!.listerBuildings == listerBuildings ? _tempMap!.listerThings : null;
|
||||
return GetListerThingsUsingTempMapFallback(listerBuildings);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static ListerThings? GetListerThingsUsingTempMapFallback(ListerBuildings listerBuildings)
|
||||
=> _tempMap!.listerBuildings == listerBuildings ? _tempMap.listerThings : null;
|
||||
|
||||
public static bool ContainsDefForColonists(ListerThings listerThings, ThingDef def)
|
||||
{
|
||||
if (!listerThings.listsByDef.TryGetValue(def, out var things))
|
||||
return false;
|
||||
|
||||
var playerFaction = Faction.OfPlayerSilentFail;
|
||||
for (var i = 0; i < things.Count; i++)
|
||||
{
|
||||
if (things[i].Faction == playerFaction)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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/.
|
||||
|
||||
global using HaulablesCache = PerformanceFish.Cache.ByReferenceRefreshable<Verse.Thing, RimWorld.ListerHaulables, PerformanceFish.Listers.Haulables.FishHaulableInfo>;
|
||||
global using HaulablesCache
|
||||
= PerformanceFish.Cache.ByReference<Verse.Thing, RimWorld.ListerHaulables,
|
||||
PerformanceFish.Listers.Haulables.HaulablesCacheValue>;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using PerformanceFish.Cache;
|
||||
using PerformanceFish.ModCompatibility;
|
||||
|
||||
namespace PerformanceFish.Listers;
|
||||
|
||||
@@ -15,13 +20,18 @@ public class Haulables : ClassWithFishPatches
|
||||
{
|
||||
public class MapPreTick_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Throttling of the ListerHaulablesTick. This marks whether items should be moved from one stockpile to another. " +
|
||||
"When throttled these checks get spread out more evenly, helping against spikes that can happen with large deep storage containers.";
|
||||
public override bool Enabled => base.Enabled && !ActiveMods.Multiplayer;
|
||||
|
||||
public override Expression<Action> TargetMethod => () => default(Map)!.MapPreTick();
|
||||
public override string Description { get; }
|
||||
= "Throttling of the ListerHaulablesTick. This marks whether items should be moved from one stockpile to "
|
||||
+ "another. When throttled these checks get spread out more evenly, helping against spikes that can "
|
||||
+ "happen with large deep storage containers.";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; } = static () => default(Map)!.MapPreTick();
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> codes.MethodReplacer(new ListerHaulables(null).ListerHaulablesTick, ThreadedOrThrottled_ListerHaulablesTick);
|
||||
=> codes.MethodReplacer(new ListerHaulables(null).ListerHaulablesTick,
|
||||
ThreadedOrThrottled_ListerHaulablesTick);
|
||||
|
||||
public static void ThreadedOrThrottled_ListerHaulablesTick(ListerHaulables instance)
|
||||
{
|
||||
@@ -42,7 +52,7 @@ public class Haulables : ClassWithFishPatches
|
||||
if (currentCellCheckCount is null)
|
||||
return;
|
||||
|
||||
if (_currentTask != null && !_currentTask.IsCompleted)
|
||||
if (_currentTask is { IsCompleted: false })
|
||||
{
|
||||
if (currentCellCheckCount.value > 1)
|
||||
currentCellCheckCount.value--;
|
||||
@@ -51,8 +61,8 @@ public class Haulables : ClassWithFishPatches
|
||||
Interlocked.Increment(ref _queue);
|
||||
_currentTask = _currentTask.ContinueWith(t =>
|
||||
{
|
||||
if (t.Exception is { } exception)
|
||||
Log.Error(exception.ToString());
|
||||
if (t.Exception is { } taskException)
|
||||
Log.Error(taskException.ToString());
|
||||
|
||||
lock (Lock)
|
||||
{
|
||||
@@ -67,7 +77,8 @@ public class Haulables : ClassWithFishPatches
|
||||
{
|
||||
if (_skippedTicks >= 9)
|
||||
{
|
||||
Log.Warning("Skipped 10 or more ListerHaulables ticks. This often means there's some kind of hauling error happening");
|
||||
Log.Warning("Skipped 10 or more ListerHaulables ticks. This often means there's some "
|
||||
+ "kind of hauling error happening");
|
||||
_skippedTicks = 0;
|
||||
}
|
||||
else
|
||||
@@ -75,6 +86,7 @@ public class Haulables : ClassWithFishPatches
|
||||
_skippedTicks++;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
@@ -122,6 +134,7 @@ public class Haulables : ClassWithFishPatches
|
||||
{
|
||||
Log.Error(ex.ToString());
|
||||
}
|
||||
|
||||
Stopwatch.Stop();
|
||||
if (Stopwatch.ElapsedTicks > 4000L)
|
||||
{
|
||||
@@ -133,6 +146,7 @@ public class Haulables : ClassWithFishPatches
|
||||
if (cellCheckCount.value < 4)
|
||||
cellCheckCount.value++;
|
||||
}
|
||||
|
||||
Stopwatch.Reset();
|
||||
}
|
||||
|
||||
@@ -147,6 +161,7 @@ public class Haulables : ClassWithFishPatches
|
||||
if (groupCycleIndex >= int.MaxValue)
|
||||
groupCycleIndex = 0;
|
||||
}
|
||||
|
||||
_lastGroupCycleIndex = groupCycleIndex;
|
||||
|
||||
var allGroupsListForReading = instance.map.haulDestinationManager.AllGroupsListForReading;
|
||||
@@ -155,9 +170,11 @@ public class Haulables : ClassWithFishPatches
|
||||
|
||||
var num = groupCycleIndex % allGroupsListForReading.Count;
|
||||
var slotGroup = allGroupsListForReading[num];
|
||||
return slotGroup.CellsList.Count == 0 ? null
|
||||
return slotGroup.CellsList.Count == 0
|
||||
? null
|
||||
: ListerHaulablesTick_Patch.CellCheckCountsBySlotGroup.GetOrCreateValue(slotGroup);
|
||||
}
|
||||
|
||||
public static int MainThread { get; private set; }
|
||||
public static ConcurrentQueue<Thing> ThingsToRemove { get; } = new();
|
||||
public static ConcurrentQueue<Thing> ThingsToAdd { get; } = new();
|
||||
@@ -173,15 +190,21 @@ public class Haulables : ClassWithFishPatches
|
||||
|
||||
public class ListerHaulablesTick_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerHaulables throttling optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerHaulables)!.ListerHaulablesTick();
|
||||
public override bool Enabled => base.Enabled && !ActiveMods.Multiplayer;
|
||||
|
||||
public override string Description { get; } = "Part of the ListerHaulables throttling optimization";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => default(ListerHaulables)!.ListerHaulablesTick();
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> codes //.MethodReplacer(GridsUtility.GetThingList, GetCopyOfThingList)
|
||||
.Manipulator(c => c.LoadsConstant(4), c =>
|
||||
.Manipulator(static c => c.LoadsConstant(4), static c =>
|
||||
{
|
||||
c.opcode = OpCodes.Call;
|
||||
c.operand = typeof(ListerHaulablesTick_Patch).GetProperty(nameof(CurrentCellCheckCount)).GetMethod;
|
||||
c.operand = typeof(ListerHaulablesTick_Patch).GetProperty(nameof(CurrentCellCheckCount))!.GetMethod;
|
||||
});
|
||||
|
||||
/*public static List<Thing> GetCopyOfThingList(IntVec3 c, Map map)
|
||||
{
|
||||
if (FishSettings.ThreadingEnabled)
|
||||
@@ -196,6 +219,7 @@ public class Haulables : ClassWithFishPatches
|
||||
}*/
|
||||
public static int CurrentCellCheckCount { get; set; } = ListerHaulables.CellsPerTick;
|
||||
public static ConditionalWeakTable<SlotGroup, CellCheckCount> CellCheckCountsBySlotGroup { get; } = new();
|
||||
|
||||
public class CellCheckCount
|
||||
{
|
||||
public int value = ListerHaulables.CellsPerTick;
|
||||
@@ -205,15 +229,14 @@ public class Haulables : ClassWithFishPatches
|
||||
|
||||
public static void TryAdd(ListerHaulables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckHaulablesCache(instance, t);
|
||||
if (!cache.contains)
|
||||
{
|
||||
ref var cache = ref HaulablesCache.GetAndCheck<HaulablesCacheValue>(t, instance);
|
||||
if (cache.Contains)
|
||||
return;
|
||||
|
||||
if (!instance.haulables.Contains(t))
|
||||
instance.haulables.Add(t);
|
||||
cache.contains = true;
|
||||
cache.ShouldRefreshNow = false;
|
||||
HaulablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
|
||||
cache.SetValue(true, t);
|
||||
}
|
||||
|
||||
public static void ThreadSafeTryAdd(ListerHaulables instance, Thing t)
|
||||
@@ -224,21 +247,19 @@ public class Haulables : ClassWithFishPatches
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!GetAndCheckHaulablesCache(instance, t).contains)
|
||||
if (!HaulablesCache.GetAndCheck<HaulablesCacheValue>(t, instance).Contains)
|
||||
MapPreTick_Patch.ThingsToAdd.Enqueue(t);
|
||||
}
|
||||
}
|
||||
|
||||
public static void TryRemove(ListerHaulables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckHaulablesCache(instance, t);
|
||||
if (cache.contains)
|
||||
{
|
||||
ref var cache = ref HaulablesCache.GetAndCheck<HaulablesCacheValue>(t, instance);
|
||||
if (!cache.Contains)
|
||||
return;
|
||||
|
||||
instance.haulables.Remove(t);
|
||||
cache.contains = false;
|
||||
cache.ShouldRefreshNow = false;
|
||||
HaulablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
cache.SetValue(false, t);
|
||||
}
|
||||
|
||||
public static void ThreadSafeTryRemove(ListerHaulables instance, Thing t)
|
||||
@@ -249,37 +270,45 @@ public class Haulables : ClassWithFishPatches
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetAndCheckHaulablesCache(instance, t).contains)
|
||||
if (HaulablesCache.GetAndCheck<HaulablesCacheValue>(t, instance).Contains)
|
||||
MapPreTick_Patch.ThingsToRemove.Enqueue(t);
|
||||
}
|
||||
}
|
||||
|
||||
public class Check_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimization for haulables checking";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerHaulables)!.Check(null);
|
||||
public override string Description { get; } = "Optimization for haulables checking";
|
||||
public override Expression<Action> TargetMethod { get; } = static () => default(ListerHaulables)!.Check(null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Check(ListerHaulables instance, Thing t)
|
||||
{
|
||||
//So the situation here is this: Vanilla RimWorld normally checks 4 storage cells per tick for valid haul destinations, usually with the intent of reaching higher priority storage.
|
||||
//This check itself can get quite expensive, but 4 of them aren't at all enough to be worth bothering with.
|
||||
//Now problems arise when LWM's deep storage comes into play: You put 5 stacks of items in one cell and suddenly 4 checks turn into 20.
|
||||
//20 hauling checks in turn is a number large enough to cause performance issues in colonies with high wealth.
|
||||
// So the situation here is this: Vanilla RimWorld normally checks 4 storage cells per tick for valid haul
|
||||
// destinations, usually with the intent of reaching higher priority storage. This check itself can get
|
||||
// quite expensive, but 4 of them aren't at all enough to be worth bothering with. Now problems arise when
|
||||
// LWM's deep storage comes into play: You put 5 stacks of items in one cell and suddenly 4 checks turn
|
||||
// into 20. 20 hauling checks in turn is a number large enough to cause performance issues in colonies with
|
||||
// high wealth.
|
||||
|
||||
if (!t.def.alwaysHaulable && !t.def.designateHaulable)
|
||||
return;
|
||||
|
||||
if (instance.ShouldBeHaulable(t))
|
||||
ThreadSafeTryAdd(instance, t);
|
||||
else
|
||||
ThreadSafeTryRemove(instance, t);
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(Check);
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codeInstructions, ILGenerator generator)
|
||||
=> Reflection.GetCodeInstructions(Check, generator);
|
||||
}
|
||||
|
||||
public class TryRemove_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerHaulables optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerHaulables)!.TryRemove(null);
|
||||
public override string Description { get; } = "Part of the ListerHaulables optimization";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => default(ListerHaulables)!.TryRemove(null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void TryRemove(ListerHaulables instance, Thing t)
|
||||
@@ -287,52 +316,62 @@ public class Haulables : ClassWithFishPatches
|
||||
if (t.def.category == ThingCategory.Item)
|
||||
ThreadSafeTryRemove(instance, t);
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(TryRemove);
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codeInstructions, ILGenerator generator)
|
||||
=> Reflection.GetCodeInstructions(TryRemove, generator);
|
||||
}
|
||||
|
||||
public class CheckAdd_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerHaulables optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerHaulables)!.CheckAdd(null);
|
||||
public override string Description { get; } = "Part of the ListerHaulables optimization";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => default(ListerHaulables)!.CheckAdd(null);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void CheckAdd(ListerHaulables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckHaulablesCache(instance, t);
|
||||
|
||||
if (cache.contains || !instance.ShouldBeHaulable(t))
|
||||
if (!t.def.alwaysHaulable && !t.def.designateHaulable)
|
||||
return;
|
||||
|
||||
if (HaulablesCache.GetAndCheck<HaulablesCacheValue>(t, instance).Contains
|
||||
|| !instance.ShouldBeHaulable(t))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadSafeTryAdd(instance, t);
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
=> Reflection.MakeReplacementCall(CheckAdd);
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codeInstructions, ILGenerator generator)
|
||||
=> Reflection.GetCodeInstructions(CheckAdd, generator);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FishHaulableInfo GetAndCheckHaulablesCache(ListerHaulables lister, Thing thing)
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public record struct HaulablesCacheValue : ICacheable<HaulablesCache>
|
||||
{
|
||||
var key = new HaulablesCache(thing, lister);
|
||||
return HaulablesCache.GetValue(ref key);
|
||||
private int _nextRefreshTick;
|
||||
public bool Contains;
|
||||
|
||||
public void Update(ref HaulablesCache key)
|
||||
{
|
||||
Contains = key.Second.haulables.Contains(key.First);
|
||||
SetDirty(false, key.First.thingIDNumber);
|
||||
}
|
||||
|
||||
public struct FishHaulableInfo : Cache.IIsRefreshable<HaulablesCache, FishHaulableInfo>
|
||||
public void SetValue(bool contains, Thing thing)
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public bool contains;
|
||||
Contains = contains;
|
||||
SetDirty(false, thing.thingIDNumber);
|
||||
}
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
public void SetDirty(bool value, int offset)
|
||||
=> _nextRefreshTick = value ? 0 : TickHelper.Add(3072, offset, 2048);
|
||||
|
||||
public bool Dirty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
public FishHaulableInfo SetNewValue(HaulablesCache key)
|
||||
{
|
||||
contains = key.Second.haulables.Contains(key.First);
|
||||
ShouldRefreshNow = false;
|
||||
return this;
|
||||
get => TickHelper.Past(_nextRefreshTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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/.
|
||||
|
||||
global using MergeablesCache = PerformanceFish.Cache.ByReferenceRefreshable<Verse.Thing, RimWorld.ListerMergeables, PerformanceFish.Listers.Mergeables.FishMergeableInfo>;
|
||||
global using MergeablesCache
|
||||
= PerformanceFish.Cache.ByReference<Verse.Thing, RimWorld.ListerMergeables,
|
||||
PerformanceFish.Listers.Mergeables.MergeablesCacheValue>;
|
||||
using PerformanceFish.Cache;
|
||||
|
||||
namespace PerformanceFish.Listers;
|
||||
|
||||
@@ -11,108 +14,111 @@ public class Mergeables : ClassWithFishPatches
|
||||
{
|
||||
public class Check_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Optimization for mergeables checking";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerMergeables)!.Check(null);
|
||||
public override string Description { get; } = "Optimization for mergeables checking";
|
||||
public override Expression<Action> TargetMethod { get; } = static () => default(ListerMergeables)!.Check(null);
|
||||
|
||||
public static void Check(ListerMergeables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckMergeablesCache(instance, t);
|
||||
ref var cache = ref MergeablesCache.GetAndCheck<MergeablesCacheValue>(t, instance);
|
||||
|
||||
if (cache.contains)
|
||||
{
|
||||
if (!instance.ShouldBeMergeable(t))
|
||||
if (cache.Contains)
|
||||
{
|
||||
if (instance.ShouldBeMergeable(t))
|
||||
return;
|
||||
|
||||
instance.mergeables.Remove(t);
|
||||
cache.contains = false;
|
||||
cache.ShouldRefreshNow = false;
|
||||
MergeablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
|
||||
cache.SetValue(false, t);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (instance.ShouldBeMergeable(t))
|
||||
{
|
||||
if (!instance.ShouldBeMergeable(t))
|
||||
return;
|
||||
|
||||
if (!instance.mergeables.Contains(t))
|
||||
instance.mergeables.Add(t);
|
||||
cache.contains = true;
|
||||
cache.ShouldRefreshNow = false;
|
||||
MergeablesCache.Get[new(t, instance)] = cache;
|
||||
|
||||
cache.SetValue(true, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codeInstructions)
|
||||
=> Reflection.MakeReplacementCall(Check);
|
||||
}
|
||||
|
||||
public class CheckAdd_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerMergeables optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerMergeables)!.CheckAdd(null);
|
||||
public override string Description { get; } = "Part of the ListerMergeables optimization";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => default(ListerMergeables)!.CheckAdd(null);
|
||||
|
||||
public static void CheckAdd(ListerMergeables instance, Thing t)
|
||||
{
|
||||
var cache = GetAndCheckMergeablesCache(instance, t);
|
||||
ref var cache = ref MergeablesCache.GetAndCheck<MergeablesCacheValue>(t, instance);
|
||||
|
||||
if (cache.contains || !instance.ShouldBeMergeable(t))
|
||||
if (cache.Contains || !instance.ShouldBeMergeable(t))
|
||||
return;
|
||||
|
||||
if (!instance.mergeables.Contains(t))
|
||||
instance.mergeables.Add(t);
|
||||
cache.contains = true;
|
||||
cache.ShouldRefreshNow = false;
|
||||
|
||||
MergeablesCache.Get[new(t, instance)] = cache;
|
||||
cache.SetValue(true, t);
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codeInstructions)
|
||||
=> Reflection.MakeReplacementCall(CheckAdd);
|
||||
}
|
||||
|
||||
public class TryRemove_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the ListerMergeables optimization";
|
||||
public override Expression<Action> TargetMethod => () => default(ListerMergeables)!.TryRemove(null);
|
||||
public override string Description { get; } = "Part of the ListerMergeables optimization";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => default(ListerMergeables)!.TryRemove(null);
|
||||
|
||||
public static void TryRemove(ListerMergeables instance, Thing t)
|
||||
{
|
||||
if (t.def.category == ThingCategory.Item)
|
||||
{
|
||||
var cache = GetAndCheckMergeablesCache(instance, t);
|
||||
ref var cache = ref MergeablesCache.GetAndCheck<MergeablesCacheValue>(t, instance);
|
||||
|
||||
if (!cache.contains)
|
||||
if (!cache.Contains)
|
||||
return;
|
||||
|
||||
instance.mergeables.Remove(t);
|
||||
cache.contains = false;
|
||||
cache.ShouldRefreshNow = false;
|
||||
cache.SetValue(false, t);
|
||||
}
|
||||
}
|
||||
|
||||
MergeablesCache.Get[new(t, instance)] = cache;
|
||||
}
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
public static CodeInstructions Transpiler(CodeInstructions codeInstructions)
|
||||
=> Reflection.MakeReplacementCall(TryRemove);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FishMergeableInfo GetAndCheckMergeablesCache(ListerMergeables lister, Thing thing)
|
||||
public record struct MergeablesCacheValue : ICacheable<MergeablesCache>
|
||||
{
|
||||
var key = new MergeablesCache(thing, lister);
|
||||
return MergeablesCache.GetValue(ref key);
|
||||
private int _nextRefreshTick;
|
||||
public bool Contains;
|
||||
|
||||
public void Update(ref MergeablesCache key)
|
||||
{
|
||||
Contains = key.Second.mergeables.Contains(key.First);
|
||||
SetDirty(false, key.First.thingIDNumber);
|
||||
}
|
||||
|
||||
public struct FishMergeableInfo : Cache.IIsRefreshable<MergeablesCache, FishMergeableInfo>
|
||||
public void SetValue(bool contains, Thing thing)
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public bool contains;
|
||||
Contains = contains;
|
||||
SetDirty(false, thing.thingIDNumber);
|
||||
}
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
public void SetDirty(bool value, int offset)
|
||||
=> _nextRefreshTick = value ? 0 : TickHelper.Add(3072, offset, 2048);
|
||||
|
||||
public bool Dirty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
|
||||
public FishMergeableInfo SetNewValue(MergeablesCache key)
|
||||
{
|
||||
contains = key.Second.mergeables.Contains(key.First);
|
||||
ShouldRefreshNow = false;
|
||||
return this;
|
||||
get => TickHelper.Past(_nextRefreshTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,25 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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.Listers;
|
||||
|
||||
public class Misc : ClassWithFishPatches
|
||||
{
|
||||
public class Building_Destroy_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Used for haulables caching. Items don't become haulable after deconstructing stockpiles without this patch";
|
||||
public override Expression<Action> TargetMethod => () => default(Building)!.Destroy(default);
|
||||
public override string Description { get; }
|
||||
= "Used for haulables caching. Items don't become haulable after deconstructing stockpiles without this "
|
||||
+ "patch";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; } = static () => default(Building)!.Destroy(default);
|
||||
|
||||
public static void Prefix(Building __instance)
|
||||
{
|
||||
if (__instance is not IHaulDestination)
|
||||
return;
|
||||
|
||||
HaulablesCache.Get.Clear();
|
||||
MergeablesCache.Get.Clear();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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 StorageSettingsCache = PerformanceFish.Cache.ByReferenceRefreshable<Verse.Thing, RimWorld.StorageSettings, PerformanceFish.Listers.StorageSettingsPatches.FishStorageSettingsInfo>;
|
||||
using System.Runtime.InteropServices;
|
||||
using PerformanceFish.Cache;
|
||||
using StorageSettingsCache
|
||||
= PerformanceFish.Cache.ByReference<Verse.Thing, RimWorld.StorageSettings,
|
||||
PerformanceFish.Listers.StorageSettingsPatches.StorageSettingsCacheValue>;
|
||||
|
||||
namespace PerformanceFish.Listers;
|
||||
|
||||
@@ -11,19 +15,20 @@ public class StorageSettingsPatches : ClassWithFishPatches
|
||||
{
|
||||
public class AllowedToAccept_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "StorageSettings caching";
|
||||
public override Expression<Action> TargetMethod => () => default(StorageSettings)!.AllowedToAccept(default(Thing));
|
||||
public override string Description { get; } = "StorageSettings caching";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => default(StorageSettings)!.AllowedToAccept(default(Thing));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static unsafe bool Prefix(StorageSettings __instance, Thing t, ref bool __result, out bool __state)
|
||||
public static bool Prefix(StorageSettings __instance, Thing t, ref bool __result, out bool __state)
|
||||
{
|
||||
var key = new StorageSettingsCache(t, __instance);
|
||||
ref var cache = ref StorageSettingsCache.Get.TryGetReferenceUnsafe(ref key);
|
||||
ref var cache = ref StorageSettingsCache.GetOrAddReference(new(t, __instance));
|
||||
|
||||
if (Unsafe.IsNullRef(ref cache) || cache.ShouldRefreshNow)
|
||||
if (cache.Dirty)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.allowedToAccept;
|
||||
__result = cache.AllowedToAccept;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
@@ -33,14 +38,15 @@ public class StorageSettingsPatches : ClassWithFishPatches
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
StorageSettingsCache.Get[new(t, __instance)] = new() { ShouldRefreshNow = false, allowedToAccept = __result };
|
||||
StorageSettingsCache.Update<StorageSettingsCacheValue, bool>(t, __instance, __result);
|
||||
}
|
||||
}
|
||||
|
||||
public class TryNotifyChanged_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "StorageSettings cache resetting";
|
||||
public override Expression<Action> TargetMethod => () => default(StorageSettings)!.TryNotifyChanged();
|
||||
public override string Description { get; } = "StorageSettings cache resetting";
|
||||
public override Expression<Action> TargetMethod { get; } = static () => default(StorageSettings)!.TryNotifyChanged();
|
||||
|
||||
public static void Postfix(StorageSettings __instance)
|
||||
{
|
||||
StorageSettingsCache.Get.Clear();
|
||||
@@ -49,17 +55,22 @@ public class StorageSettingsPatches : ClassWithFishPatches
|
||||
}
|
||||
}
|
||||
|
||||
public struct FishStorageSettingsInfo : Cache.IIsRefreshable<StorageSettingsCache, FishStorageSettingsInfo>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public record struct StorageSettingsCacheValue : ICacheable<StorageSettingsCache, bool>
|
||||
{
|
||||
public int nextRefreshTick;
|
||||
public bool allowedToAccept;
|
||||
private int _nextRefreshTick;
|
||||
public bool AllowedToAccept;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
public bool Dirty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
get => TickHelper.Past(_nextRefreshTick);
|
||||
}
|
||||
|
||||
public void Update(ref StorageSettingsCache key, bool allowedToAccept)
|
||||
{
|
||||
AllowedToAccept = allowedToAccept;
|
||||
_nextRefreshTick = TickHelper.Add(3072, key.First.thingIDNumber, 2048);
|
||||
}
|
||||
public FishStorageSettingsInfo SetNewValue(StorageSettingsCache key) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,71 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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 disabled
|
||||
namespace PerformanceFish.Listers;
|
||||
|
||||
public class Things : ClassWithFishPatches
|
||||
{
|
||||
public class ThingsMatching_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description { get; }
|
||||
= "Part of the ThingFilter.BestThingRequest optimization. Required by that particular patch and gets "
|
||||
+ "toggled alongside it";
|
||||
|
||||
public override bool Enabled
|
||||
{
|
||||
set
|
||||
{
|
||||
if (base.Enabled == value)
|
||||
return;
|
||||
|
||||
Get<ThingFilterPatches.BestThingRequest_Patch>().Enabled = base.Enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => new ListerThings(default).ThingsMatching(default);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(ListerThings __instance, ThingRequest req, ref List<Thing> __result)
|
||||
{
|
||||
if ((object)req.singleDef is not HashSet<ThingDef> hashSet)
|
||||
return true;
|
||||
|
||||
__result = ThingsOfDefsFor(__instance, hashSet);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Thing> ThingsOfDefsFor(ListerThings lister, HashSet<ThingDef> set)
|
||||
{
|
||||
var list = GetThingsOfDefsListFor(set);
|
||||
list.Clear();
|
||||
|
||||
foreach (var def in set)
|
||||
{
|
||||
if (lister.listsByDef.TryGetValue(def) is { } thingsOfSingleDef)
|
||||
list.AddRangeFast(thingsOfSingleDef);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static List<Thing> GetThingsOfDefsListFor(IEnumerable<ThingDef> thingDefs)
|
||||
=> (_thingsOfDefsDictionary ??= Cache.Utility.AddNew<Dictionary<IEnumerable<ThingDef>, List<Thing>>>())
|
||||
.GetOrAdd(thingDefs);
|
||||
|
||||
[ThreadStatic]
|
||||
private static Dictionary<IEnumerable<ThingDef>, List<Thing>>? _thingsOfDefsDictionary;
|
||||
|
||||
#if disabled
|
||||
public class ThingRequest_IsUndefined_Patch : FishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.IsUndefined));
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.IsUndefined));
|
||||
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
public static void Postfix(ThingRequest __instance, ref bool __result)
|
||||
{
|
||||
@@ -19,13 +74,16 @@ public class Things : ClassWithFishPatches
|
||||
|
||||
__result = NewResult(__instance);
|
||||
}
|
||||
|
||||
public static bool NewResult(ThingRequest __instance)
|
||||
=> __instance.group == ThingRequestGroup.Undefined && !((FishCache)__instance.singleDef).IsSingleDef;
|
||||
}
|
||||
|
||||
public class ThingRequest_CanBeFoundInRegion_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override MethodBase TargetMethodInfo => AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.CanBeFoundInRegion));
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.CanBeFoundInRegion));
|
||||
|
||||
public static bool Prefix(ThingRequest __instance, ref bool __result)
|
||||
{
|
||||
if (__instance.singleDef is not FishCache)
|
||||
@@ -34,48 +92,59 @@ public class Things : ClassWithFishPatches
|
||||
__result = NewResult(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool NewResult(ThingRequest __instance)
|
||||
=> ((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 class Add_Patch : FishPatch
|
||||
{
|
||||
public override Expression<Action> TargetMethod => () => new ListerThings(default).Add(null);
|
||||
public override Expression<Action> TargetMethod { get; } = static () => new ListerThings(default).Add(null);
|
||||
|
||||
public static void Postfix(ListerThings __instance, Thing t)
|
||||
{
|
||||
if (t.TryGetComp<CompStyleable>() is not { } style || style.SourcePrecept is null)
|
||||
return;
|
||||
Cache<ListerThings, CompStyleablesInfo>.GetValue(__instance).things.Add(t);
|
||||
|
||||
Cache.ByReference<ListerThings, CompStyleablesInfo>.GetOrAddReference(__instance).Things.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
public class Remove_Patch : FishPatch
|
||||
{
|
||||
public override Expression<Action> TargetMethod => () => new ListerThings(default).Remove(null);
|
||||
public override Expression<Action> TargetMethod { get; } = static () => new ListerThings(default).Remove(null);
|
||||
|
||||
public static void Postfix(ListerThings __instance, Thing t)
|
||||
{
|
||||
if (t.TryGetComp<CompStyleable>() is not { } style || style.SourcePrecept is null)
|
||||
return;
|
||||
Cache<ListerThings, CompStyleablesInfo>.GetValue(__instance).things.Remove(t);
|
||||
|
||||
Cache.ByReference<ListerThings, CompStyleablesInfo>.GetOrAddReference(__instance).Things.Remove(t);
|
||||
}
|
||||
}
|
||||
|
||||
public class Clear_Patch : FishPatch
|
||||
{
|
||||
public override Expression<Action> TargetMethod => () => new ListerThings(default).Clear();
|
||||
public override Expression<Action> TargetMethod { get; } = static () => new ListerThings(default).Clear();
|
||||
|
||||
public static void Postfix(ListerThings __instance, Thing t)
|
||||
{
|
||||
if (t.TryGetComp<CompStyleable>() is not { } style || style.SourcePrecept is null)
|
||||
return;
|
||||
Cache<ListerThings, CompStyleablesInfo>.GetValue(__instance).things.Clear();
|
||||
|
||||
Cache.ByReference<ListerThings, CompStyleablesInfo>.GetOrAddReference(__instance).Things.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public class ThingsMatching_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Part of the work scanning optimization";
|
||||
public override Expression<Action> TargetMethod => () => new ListerThings(default).ThingsMatching(default);
|
||||
public override string Description { get; } = "Part of the work scanning optimization";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => new ListerThings(default).ThingsMatching(default);
|
||||
|
||||
public static bool Prefix(ListerThings __instance, ThingRequest req, ref List<Thing> __result)
|
||||
{
|
||||
if (req.singleDef is not FishCache)
|
||||
@@ -84,6 +153,7 @@ public class Things : ClassWithFishPatches
|
||||
__result = GetCache(__instance, req);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<Thing> GetCache(ListerThings __instance, ThingRequest req)
|
||||
{
|
||||
var fishCache = (FishCache)req.singleDef;
|
||||
@@ -106,31 +176,34 @@ public class Things : ClassWithFishPatches
|
||||
: __instance.listsByGroup.TryGetItem((int)req.group, out var item) ? item : null) ?? ListerThings.EmptyList, CurrentPawn));*/
|
||||
}
|
||||
|
||||
public static CompStyleablesInfo GetCompStyleablesCache(ListerThings lister) => Cache<ListerThings, CompStyleablesInfo>.GetValue(lister);
|
||||
public static CompStyleablesInfo GetCompStyleablesCache(ListerThings lister)
|
||||
=> Cache.ByReference<ListerThings, CompStyleablesInfo>.GetOrAddReference(lister);
|
||||
|
||||
public struct CompStyleablesInfo : ICanRefresh<ListerThings, CompStyleablesInfo>
|
||||
public record struct CompStyleablesInfo
|
||||
{
|
||||
public List<Thing> things;
|
||||
public List<Thing> Things;
|
||||
private int _nextRefreshTick;
|
||||
|
||||
public bool ShouldRefreshNow
|
||||
public bool Dirty
|
||||
{
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
}
|
||||
|
||||
public CompStyleablesInfo SetNewValue(ListerThings key)
|
||||
{
|
||||
if (things is null)
|
||||
things = new();
|
||||
if (Things is null)
|
||||
Things = new();
|
||||
else
|
||||
things.Clear();
|
||||
Things.Clear();
|
||||
var allBuildings = key.listsByGroup[(int)ThingRequestGroup.BuildingArtificial];
|
||||
var count = allBuildings.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (allBuildings[i].TryGetComp<CompStyleable>() is { SourcePrecept: not null })
|
||||
things.Add(allBuildings[i]);
|
||||
Things.Add(allBuildings[i]);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -140,6 +213,7 @@ public class Things : ClassWithFishPatches
|
||||
public bool IsSingleDef => Def != null;
|
||||
public ThingDef Def { get; private set; }
|
||||
public List<Thing> FilteredThings { get; set; }
|
||||
|
||||
public static FishCache ForDef(ThingDef def)
|
||||
{
|
||||
if (_dictOfDefs.TryGetValue(def, out var value))
|
||||
@@ -148,6 +222,7 @@ public class Things : ClassWithFishPatches
|
||||
_dictOfDefs[def] = value = new(def.defName) { Def = def };
|
||||
return value;
|
||||
}
|
||||
|
||||
public static FishCache ForGroup(ThingRequestGroup group)
|
||||
{
|
||||
if (_dictOfGroups.TryGetValue(group, out var value))
|
||||
@@ -156,14 +231,16 @@ public class Things : ClassWithFishPatches
|
||||
_dictOfGroups[group] = value = new(group.ToString());
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void ClearAll()
|
||||
{
|
||||
_dictOfGroups.Clear();
|
||||
_dictOfDefs.Clear();
|
||||
}
|
||||
|
||||
private FishCache(string name) => defName = name;
|
||||
private static Dictionary<ThingRequestGroup, FishCache> _dictOfGroups { get; } = new();
|
||||
private static Dictionary<ThingDef, FishCache> _dictOfDefs { get; } = new();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
+191
-10
@@ -1,24 +1,205 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using JetBrains.Annotations;
|
||||
using PerformanceFish.Pools;
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public static class Log
|
||||
{
|
||||
public static void Message(string message) => _message(message);
|
||||
public static void Warning(string message) => _warning(message);
|
||||
public static void Error(string message) => _error(message);
|
||||
public static void Message(string message) => Config.Message(message);
|
||||
public static void Message(PooledStringHandler message) => Config.Message(message.GetFormattedText());
|
||||
|
||||
public static void Warning(string message) => Config.Warning(message);
|
||||
public static void Warning(PooledStringHandler message) => Config.Warning(message.GetFormattedText());
|
||||
|
||||
public static void Error(string message) => Config.Error(message);
|
||||
public static void Error(PooledStringHandler message) => Config.Error(message.GetFormattedText());
|
||||
|
||||
[PublicAPI]
|
||||
public static class Config
|
||||
{
|
||||
public static Action<string> Message { set => _message = value; }
|
||||
public static Action<string> Warning { set => _warning = value; }
|
||||
public static Action<string> Error { set => _error = value; }
|
||||
public static Action<string> Message { get; set; } = static text =>
|
||||
{
|
||||
Verse.Log.logLock ??= new(); // in case this runs from the Log cctor, and in turn before it finishes setup
|
||||
Verse.Log.Message(text);
|
||||
HandleEarlyMessage(text, LogMessageType.Message);
|
||||
};
|
||||
|
||||
public static Action<string> Warning { get; set; } = static text =>
|
||||
{
|
||||
Verse.Log.logLock ??= new();
|
||||
Verse.Log.Warning(text);
|
||||
HandleEarlyMessage(text, LogMessageType.Warning);
|
||||
};
|
||||
|
||||
public static Action<string> Error { get; set; } = static text =>
|
||||
{
|
||||
Verse.Log.logLock ??= new();
|
||||
Verse.Log.Error(text);
|
||||
HandleEarlyMessage(text, LogMessageType.Error);
|
||||
};
|
||||
}
|
||||
|
||||
private static Action<string> _message = Verse.Log.Message;
|
||||
private static Action<string> _warning = Verse.Log.Warning;
|
||||
private static Action<string> _error = Verse.Log.Error;
|
||||
private static void HandleEarlyMessage(string text, LogMessageType messageType)
|
||||
{
|
||||
if (Ready)
|
||||
return;
|
||||
|
||||
FindAndEnqueueUnhandledMessage(text, messageType);
|
||||
}
|
||||
|
||||
private static void FindAndEnqueueUnhandledMessage(string text, LogMessageType messageType)
|
||||
{
|
||||
var foundMessage = false;
|
||||
|
||||
var lastMessage = Verse.Log.messageQueue.lastMessage;
|
||||
if (!TryEnqueueUnhandledMessage(text, messageType, lastMessage, ref foundMessage))
|
||||
{
|
||||
foreach (var message in Verse.Log.messageQueue.messages)
|
||||
{
|
||||
if (TryEnqueueUnhandledMessage(text, messageType, message, ref foundMessage))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundMessage)
|
||||
{
|
||||
var message = Verse.Log.messageQueue.messages.FirstOrDefault(message
|
||||
=> message.text.Contains(text) || text.Contains(message.text));
|
||||
if (message != null)
|
||||
UnhandledMessages!.Enqueue(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryEnqueueUnhandledMessage(string text, LogMessageType messageType, LogMessage message,
|
||||
ref bool foundMessage)
|
||||
{
|
||||
if (message.text != text || message.type != messageType)
|
||||
return false;
|
||||
|
||||
UnhandledMessages!.Enqueue(message);
|
||||
foundMessage = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static UnhandledMessageQueue? UnhandledMessages { get; }
|
||||
= SetupUnhandledMessageQueue();
|
||||
|
||||
private static UnhandledMessageQueue? SetupUnhandledMessageQueue()
|
||||
{
|
||||
if (Ready)
|
||||
return null;
|
||||
|
||||
ref var stash = ref FishStash.Get;
|
||||
if (stash.UnhandledMessages is { } stashedMessages)
|
||||
{
|
||||
stash.MessageQueueGCHandle.Free();
|
||||
return stashedMessages;
|
||||
}
|
||||
|
||||
stash.UnhandledMessages = stashedMessages = new();
|
||||
stash.MessageQueueGCHandle = GCHandle.Alloc(stashedMessages, GCHandleType.Pinned);
|
||||
return stash.UnhandledMessages;
|
||||
}
|
||||
|
||||
public static bool Ready { get; set; } = PerformanceFishMod.Mod != null;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct LogMessageData : IDisposable
|
||||
{
|
||||
public IntPtr Text,
|
||||
StackTrace;
|
||||
public int Type,
|
||||
Repeats;
|
||||
|
||||
public LogMessage ToLogMessage()
|
||||
=> new((LogMessageType)Type, Marshal.PtrToStringUni(Text), Marshal.PtrToStringUni(StackTrace))
|
||||
{
|
||||
repeats = Repeats
|
||||
};
|
||||
|
||||
public LogMessageData(LogMessage logMessage)
|
||||
{
|
||||
Text = Marshal.StringToHGlobalUni(logMessage.text);
|
||||
Type = (int)logMessage.type;
|
||||
Repeats = logMessage.repeats;
|
||||
StackTrace = Marshal.StringToHGlobalUni(logMessage.stackTrace);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Marshal.FreeHGlobal(Text);
|
||||
Marshal.FreeHGlobal(StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe class UnhandledMessageQueue : IDisposable
|
||||
{
|
||||
public int Count { get; private set; }
|
||||
private int _arrayLength;
|
||||
private LogMessageData* _messages;
|
||||
|
||||
public void Enqueue(LogMessageData message)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (Count >= _arrayLength)
|
||||
Expand();
|
||||
|
||||
_messages[Count++] = message;
|
||||
}
|
||||
}
|
||||
|
||||
public void Enqueue(LogMessage message) => Enqueue(new LogMessageData(message));
|
||||
|
||||
public bool TryDequeue(out LogMessageData message)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (--Count >= 0)
|
||||
{
|
||||
message = _messages[Count];
|
||||
_messages[Count] = default;
|
||||
return true;
|
||||
}
|
||||
|
||||
message = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Expand()
|
||||
{
|
||||
if (_arrayLength == 0)
|
||||
{
|
||||
_arrayLength = 1;
|
||||
_messages = (LogMessageData*)Marshal.AllocHGlobal(sizeof(LogMessageData));
|
||||
}
|
||||
else
|
||||
{
|
||||
_arrayLength <<= 1;
|
||||
_messages = (LogMessageData*)Marshal.ReAllocHGlobal((IntPtr)_messages, (IntPtr)_arrayLength);
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once InconsistentlySynchronizedField
|
||||
private void ReleaseUnmanagedResources() => Marshal.FreeHGlobal((IntPtr)_messages);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ReleaseUnmanagedResources();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~UnhandledMessageQueue() => ReleaseUnmanagedResources();
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
using PerformanceFish.Prepatching;
|
||||
using FieldAttributes = Mono.Cecil.FieldAttributes;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public class LogPatches : ClassWithFishPrepatches
|
||||
{
|
||||
public const string PERFORMANCE_FISH_WELCOME_MESSAGE = "Performance Fish!!!";
|
||||
public const string PERFORMANCE_FISH_WELCOME_STACKTRACE = "UwU";
|
||||
|
||||
// This breaks my FishStash somehow // TODO: FIX
|
||||
// public class LogVersionNumberPatch : FishPrepatch
|
||||
// {
|
||||
// public override MethodBase MethodBase { get; } = methodof(Root.CheckGlobalInit);
|
||||
//
|
||||
// public static void Prefix() => Log.Message("Performance Fish???");
|
||||
// }
|
||||
|
||||
public class ClassPatch : FishClassPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Important logging patch";
|
||||
|
||||
public override bool Enabled => true;
|
||||
|
||||
public override Type Type => typeof(Verse.Log);
|
||||
|
||||
public override void FreePatch(TypeDefinition typeDefinition)
|
||||
{
|
||||
var module = typeDefinition.Module;
|
||||
|
||||
var fishStashHandleField = new FieldDefinition(nameof(FishStashHandle),
|
||||
FieldAttributes.Private | FieldAttributes.Static,
|
||||
typeDefinition.Module.ImportReference(typeof(FishStashHandle)));
|
||||
|
||||
typeDefinition.Fields.Add(fishStashHandleField);
|
||||
|
||||
var staticConstructorBody = typeDefinition.GetStaticConstructor().Body;
|
||||
|
||||
staticConstructorBody.GetILProcessor().InsertRange(staticConstructorBody.Instructions.Count - 2,
|
||||
(OpCodes.Ldc_I8, FishStash.StaticHandle),
|
||||
(OpCodes.Newobj,
|
||||
module.ImportReference(typeof(FishStashHandle).GetConstructor(new[] { typeof(long) }))),
|
||||
(OpCodes.Stsfld, fishStashHandleField),
|
||||
(OpCodes.Call, module.ImportReference(InsertEarlyMessages)));
|
||||
}
|
||||
|
||||
public static void InsertEarlyMessages()
|
||||
{
|
||||
var unhandledMessages = Log.UnhandledMessages;
|
||||
if (unhandledMessages is null)
|
||||
Debug.LogError("Message queue is null");
|
||||
|
||||
Log.Ready = true;
|
||||
|
||||
Verse.Log.messageQueue.Enqueue(new(LogMessageType.Message, PERFORMANCE_FISH_WELCOME_MESSAGE,
|
||||
PERFORMANCE_FISH_WELCOME_STACKTRACE));
|
||||
|
||||
while (unhandledMessages!.TryDequeue(out var message))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Marshal.PtrToStringUni(message.Text) == PERFORMANCE_FISH_WELCOME_MESSAGE)
|
||||
continue;
|
||||
|
||||
Verse.Log.messageQueue.Enqueue(message.ToLogMessage());
|
||||
}
|
||||
finally
|
||||
{
|
||||
message.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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/.
|
||||
@@ -12,6 +12,7 @@ public static class MapPawnsPatches
|
||||
List<Pawn> allPawnsUnspawned = instance.AllPawnsUnspawned;
|
||||
if (allPawnsUnspawned.Count == 0)
|
||||
return instance.pawnsSpawned;
|
||||
|
||||
instance.allPawnsResult.Clear();
|
||||
instance.allPawnsResult.AddRange(instance.pawnsSpawned);
|
||||
instance.allPawnsResult.AddRange(allPawnsUnspawned);
|
||||
@@ -21,12 +22,14 @@ public static class MapPawnsPatches
|
||||
public static List<Pawn> AllPawnsUnspawned(MapPawns instance)
|
||||
{
|
||||
instance.allPawnsUnspawnedResult.Clear();
|
||||
ThingOwnerUtility.GetAllThingsRecursively(instance.map, ThingRequest.ForGroup(ThingRequestGroup.Pawn), instance.allPawnsUnspawnedResult, allowUnreal: true, null, alsoGetSpawnedThings: false);
|
||||
ThingOwnerUtility.GetAllThingsRecursively(instance.map, ThingRequest.ForGroup(ThingRequestGroup.Pawn),
|
||||
instance.allPawnsUnspawnedResult, allowUnreal: true, null, alsoGetSpawnedThings: false);
|
||||
for (int num = instance.allPawnsUnspawnedResult.Count - 1; num >= 0; num--)
|
||||
{
|
||||
if (instance.allPawnsUnspawnedResult[num].Dead)
|
||||
instance.allPawnsUnspawnedResult.RemoveAt(num);
|
||||
}
|
||||
|
||||
return instance.allPawnsUnspawnedResult;
|
||||
}
|
||||
|
||||
@@ -37,11 +40,13 @@ public static class MapPawnsPatches
|
||||
Log.Error("Called PawnsInFaction with null faction.");
|
||||
return new List<Pawn>();
|
||||
}
|
||||
|
||||
if (!instance.pawnsInFactionResult.TryGetValue(faction, out var value))
|
||||
{
|
||||
value = new List<Pawn>();
|
||||
instance.pawnsInFactionResult.Add(faction, value);
|
||||
}
|
||||
|
||||
value.Clear();
|
||||
var allPawns = instance.AllPawns;
|
||||
for (var i = 0; i < allPawns.Count; i++)
|
||||
@@ -49,6 +54,7 @@ public static class MapPawnsPatches
|
||||
if (allPawns[i].Faction == faction)
|
||||
value.Add(allPawns[i]);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -59,13 +65,17 @@ public static class MapPawnsPatches
|
||||
value = new List<Pawn>();
|
||||
instance.freeHumanlikesOfFactionResult.Add(faction, value);
|
||||
}
|
||||
|
||||
value.Clear();
|
||||
List<Pawn> allPawns = instance.AllPawns;
|
||||
for (int i = 0; i < allPawns.Count; i++)
|
||||
{
|
||||
if (allPawns[i].Faction == faction && (allPawns[i].HostFaction == null || allPawns[i].IsSlave) && allPawns[i].RaceProps.Humanlike)
|
||||
if (allPawns[i].Faction == faction
|
||||
&& (allPawns[i].HostFaction == null || allPawns[i].IsSlave)
|
||||
&& allPawns[i].RaceProps.Humanlike)
|
||||
value.Add(allPawns[i]);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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 GearMassCache = PerformanceFish.Cache.ByIndex<Verse.Pawn, PerformanceFish.MassUtilityCaching.GearMassCacheValue>;
|
||||
using InventoryMassCache = PerformanceFish.Cache.ByIndex<Verse.Pawn, PerformanceFish.MassUtilityCaching.InventoryMassCacheValue>;
|
||||
using GearMassCache = PerformanceFish.Cache.ByInt<Verse.Pawn, PerformanceFish.MassUtilityCaching.GearMassCacheValue>;
|
||||
using InventoryMassCache
|
||||
= PerformanceFish.Cache.ByInt<Verse.Pawn, PerformanceFish.MassUtilityCaching.InventoryMassCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
@@ -12,17 +13,17 @@ public class MassUtilityCaching : ClassWithFishPatches
|
||||
{
|
||||
public class GearMass_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches pawn gear mass. Hauling jobs check this quite a lot";
|
||||
public override Delegate TargetMethodGroup => MassUtility.GearMass;
|
||||
public override string Description { get; } = "Caches pawn gear mass. Hauling jobs check this quite a lot";
|
||||
public override Delegate TargetMethodGroup { get; } = MassUtility.GearMass;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Pawn p, ref float __result, out bool __state)
|
||||
{
|
||||
ref var cache = ref GearMassCache.Get.GetReference(p);
|
||||
if (cache.ShouldRefreshNow(p))
|
||||
ref var cache = ref GearMassCache.GetOrAddReference(p.thingIDNumber);
|
||||
if (cache.IsDirty(p))
|
||||
return __state = true;
|
||||
|
||||
__result = cache.mass;
|
||||
__result = cache.Mass;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
@@ -32,23 +33,27 @@ public class MassUtilityCaching : ClassWithFishPatches
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
GearMassCache.Get[p] = new(__result, p);
|
||||
UpdateCache(p, __result);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void UpdateCache(Pawn p, float __result)
|
||||
=> GearMassCache.GetExistingReference(p).Update(__result, p);
|
||||
}
|
||||
|
||||
public class InventoryMass_Patch : FirstPriorityFishPatch
|
||||
{
|
||||
public override string Description => "Caches pawn inventory mass. Hauling jobs check this quite a lot";
|
||||
public override Delegate TargetMethodGroup => MassUtility.InventoryMass;
|
||||
public override string Description { get; } = "Caches pawn inventory mass. Hauling jobs check this quite a lot";
|
||||
public override Delegate TargetMethodGroup { get; } = MassUtility.InventoryMass;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Pawn p, ref float __result, out bool __state)
|
||||
{
|
||||
ref var cache = ref InventoryMassCache.Get.GetReference(p);
|
||||
if (cache.ShouldRefreshNow(p))
|
||||
ref var cache = ref InventoryMassCache.GetOrAddReference(p.thingIDNumber);
|
||||
if (cache.IsDirty(p))
|
||||
return __state = true;
|
||||
|
||||
__result = cache.mass;
|
||||
__result = cache.Mass;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
@@ -58,43 +63,64 @@ public class MassUtilityCaching : ClassWithFishPatches
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
InventoryMassCache.Get[p] = new(__result, p);
|
||||
}
|
||||
UpdateCache(p, __result);
|
||||
}
|
||||
|
||||
public struct GearMassCacheValue
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void UpdateCache(Pawn p, float __result)
|
||||
=> InventoryMassCache.GetExistingReference(p).Update(__result, p);
|
||||
}
|
||||
|
||||
public record struct GearMassCacheValue
|
||||
{
|
||||
public float mass;
|
||||
public float Mass;
|
||||
private int _nextRefreshTick;
|
||||
private int _equipmentListsState;
|
||||
public GearMassCacheValue(float mass, Pawn p)
|
||||
|
||||
public void Update(float mass, Pawn p)
|
||||
{
|
||||
this.mass = mass;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 3072 + Math.Abs(Rand.Int % 2048);
|
||||
Mass = mass;
|
||||
_nextRefreshTick = TickHelper.Add(3072, p.thingIDNumber, 2048);
|
||||
_equipmentListsState = GetEquipmentListsState(p);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetEquipmentListsState(Pawn p)
|
||||
{
|
||||
var result = p.apparel != null ? p.apparel.WornApparel.Version() : 0;
|
||||
var result = p.apparel != null ? p.apparel.WornApparel._version : 0;
|
||||
if (p.equipment != null)
|
||||
result += p.equipment.AllEquipmentListForReading.Version();
|
||||
result += p.equipment.AllEquipmentListForReading._version;
|
||||
return result;
|
||||
}
|
||||
public bool ShouldRefreshNow(Pawn p) => _nextRefreshTick < Current.Game.tickManager.TicksGame || _equipmentListsState != GetEquipmentListsState(p);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(Pawn p)
|
||||
=> TickHelper.Past(_nextRefreshTick)
|
||||
|| _equipmentListsState != GetEquipmentListsState(p);
|
||||
}
|
||||
|
||||
public struct InventoryMassCacheValue
|
||||
public record struct InventoryMassCacheValue
|
||||
{
|
||||
public float mass;
|
||||
public float Mass;
|
||||
private int _nextRefreshTick;
|
||||
private int _inventoryListsState;
|
||||
public InventoryMassCacheValue(float mass, Pawn p)
|
||||
|
||||
public void Update(float mass, Pawn p)
|
||||
{
|
||||
this.mass = mass;
|
||||
_nextRefreshTick = Current.Game.tickManager.TicksGame + 256 + Math.Abs(Rand.Int % 256);
|
||||
_inventoryListsState = GetInventoryListsState(p); //shorter refresh interval because of stack sizes not being considered by the listVersion check
|
||||
Mass = mass;
|
||||
_nextRefreshTick = TickHelper.Add(GenTicks.TickRareInterval, p.thingIDNumber);
|
||||
_inventoryListsState
|
||||
= GetInventoryListsState(
|
||||
p); //shorter refresh interval because of stack sizes not being considered by the listVersion check
|
||||
}
|
||||
private static int GetInventoryListsState(Pawn p) => p.inventory?.innerContainer is { } container ? container.innerList.Version() : 0;
|
||||
public bool ShouldRefreshNow(Pawn p) => _nextRefreshTick < Current.Game.tickManager.TicksGame || _inventoryListsState != GetInventoryListsState(p);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetInventoryListsState(Pawn p)
|
||||
=> p.inventory?.innerContainer is { } container ? container.innerList._version : 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsDirty(Pawn p)
|
||||
=> TickHelper.Past(_nextRefreshTick)
|
||||
|| _inventoryListsState != GetInventoryListsState(p);
|
||||
}
|
||||
}
|
||||
@@ -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 PsyfocusGainCache
|
||||
= PerformanceFish.Cache.ByInt<Verse.Pawn, Verse.Thing,
|
||||
PerformanceFish.MeditationUtilityCaching.PsyfocusCacheValue>;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
// Suggested by https://github.com/notfood
|
||||
public class MeditationUtilityCaching : ClassWithFishPatches
|
||||
{
|
||||
public class PsyfocusGainPerTick : FirstPriorityFishPatch
|
||||
{
|
||||
public override string? Description { get; } = "Caches psyfocus gain per tick while the target remains "
|
||||
+ "constant. Impactful in psycasting focused playthroughs";
|
||||
|
||||
public override Delegate TargetMethodGroup { get; } = MeditationUtility.PsyfocusGainPerTick;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(Pawn pawn, Thing? focus, ref float __result, out bool __state)
|
||||
{
|
||||
ref var cache = ref PsyfocusGainCache.GetOrAddReference(pawn.thingIDNumber, focus?.thingIDNumber ?? 0);
|
||||
|
||||
if (cache.Dirty)
|
||||
return __state = true;
|
||||
|
||||
__result = cache.PsyfocusGain;
|
||||
return __state = false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Postfix(Pawn pawn, Thing? focus, float __result, bool __state)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
UpdateCache(pawn, focus, __result);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void UpdateCache(Pawn pawn, Thing? focus, float __result)
|
||||
=> PsyfocusGainCache.GetExistingReference(pawn.thingIDNumber, focus?.thingIDNumber ?? 0)
|
||||
.Update(__result, pawn);
|
||||
}
|
||||
|
||||
public record struct PsyfocusCacheValue
|
||||
{
|
||||
private int _nextRefreshTick;
|
||||
public float PsyfocusGain;
|
||||
|
||||
public void Update(float psyfocusGain, Pawn pawn)
|
||||
{
|
||||
PsyfocusGain = psyfocusGain;
|
||||
_nextRefreshTick = TickHelper.Add(GenTicks.TickRareInterval, pawn.thingIDNumber);
|
||||
}
|
||||
|
||||
public bool Dirty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => TickHelper.Past(_nextRefreshTick);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,79 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
|
||||
namespace PerformanceFish;
|
||||
public class MiscOptimizations : ClassWithFishPatches
|
||||
{
|
||||
public class GenList_ListFullCopy_Patch : FishPatch
|
||||
{
|
||||
public override void TryUnpatch() { } // prevent unpatching
|
||||
|
||||
public override string Description => "Minor optimization. Requires a full restart to toggle off due to a bug in harmony";
|
||||
public override Delegate TargetMethodGroup => GenList.ListFullCopy<object>; // harmony patches for all T types
|
||||
public class MiscPrepatchedOptimizations : ClassWithFishPrepatches
|
||||
{
|
||||
public class GenList_ListFullCopy : FishPrepatch
|
||||
{
|
||||
public override string Description { get; }
|
||||
= "Optimization for a basic and frequently used list copying method. Replaces a for loop with an "
|
||||
+ "optimized IL instruction.";/*{(ActiveMods.GradientHair
|
||||
? " 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 void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ListFullCopy<object>/*Utility.CollectionExtensions.Copy<object>*/);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Prefix(IList source, ref IList __result)
|
||||
=> source is null || source.Count == 0
|
||||
|| (__result = FisheryLib.CollectionExtensions.TryNew(source, source.GetType().GetGenericArguments()[0])) is null;
|
||||
public static List<T> ListFullCopy<T>(List<T> source) => source.Copy();
|
||||
}
|
||||
|
||||
public class WindManager_WindManagerTick_Patch : FishPatch
|
||||
public class GenCollection_FirstOrDefault : FishPrepatch
|
||||
{
|
||||
public override string Description => "Throttles plant sway to update at most once per frame instead of every tick. Also fixes the plant sway setting to actually disable plant sway calculations when it should. " +
|
||||
"By default it simply sets values to 0 but then still goes through everything, sacrificing tps for no reason";
|
||||
public override Expression<Action> TargetMethod => () => default(WindManager)!.WindManagerTick();
|
||||
public override string? Description { get; }
|
||||
= "Minor optimization for a RimWorld method used for list lookups, by looping in a more efficient manner "
|
||||
+ "with fewer bounds checks";
|
||||
|
||||
public static CodeInstructions? Transpiler(CodeInstructions codes)
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(GenCollection.FirstOrDefault<object>);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(FirstOrDefault<object>);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T? FirstOrDefault<T>(List<T> list, Predicate<T> predicate) => list.FirstOrDefaultFast(predicate);
|
||||
}
|
||||
}
|
||||
|
||||
public class MiscOptimizations : ClassWithFishPatches
|
||||
{
|
||||
var find_CurrentMap = FishTranspiler.CallPropertyGetter(typeof(Find), nameof(Find.CurrentMap));
|
||||
public class WindManager_WindManagerTick : FishPatch
|
||||
{
|
||||
public override string Description { get; }
|
||||
= "Throttles plant sway to update at most once per frame instead of every tick. Also fixes the plant sway "
|
||||
+ "setting to actually disable plant sway calculations when it should. By default it simply sets values to "
|
||||
+ "0 but then still goes through everything, sacrificing tps for no reason";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; } = static () => default(WindManager)!.WindManagerTick();
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions instructions)
|
||||
{
|
||||
var find_CurrentMap = FishTranspiler.PropertyGetter(typeof(Find), nameof(Find.CurrentMap));
|
||||
var this_map = FishTranspiler.Field(typeof(WindManager), nameof(WindManager.map));
|
||||
|
||||
return codes.ReplaceAt(
|
||||
(codes, i)
|
||||
return instructions.ReplaceAt((codes, i)
|
||||
=> i - 3 >= 0
|
||||
&& codes[i - 3] == find_CurrentMap
|
||||
&& codes[i - 1] == this_map
|
||||
&& codes[i].operand is Label, // goto return;
|
||||
|
||||
code
|
||||
static code
|
||||
=> new[]
|
||||
{
|
||||
code,
|
||||
FishTranspiler.Call(ShouldSway),
|
||||
FishTranspiler.IfFalse_Short((Label)code.operand)
|
||||
}
|
||||
);
|
||||
code, FishTranspiler.Call(ShouldSway), FishTranspiler.IfFalse_Short((Label)code.operand)
|
||||
});
|
||||
}
|
||||
|
||||
public static bool ShouldSway()
|
||||
@@ -71,21 +95,92 @@ public class MiscOptimizations : ClassWithFishPatches
|
||||
private static bool _lastPrefValue;
|
||||
}
|
||||
|
||||
public class DrawBatch_Flush_Patch : FishPatch
|
||||
public class CompRottable : FishPatch
|
||||
{
|
||||
public override string Description => "Optimization of a fleck rendering method. Low - medium performance impact";
|
||||
public override Expression<Action> TargetMethod => () => default(DrawBatch)!.Flush(default);
|
||||
public override string? Description { get; }
|
||||
= "Throttles rotting on misconfigured defs to only recalculate every 256 ticks, instead of constantly.";
|
||||
|
||||
public static CodeInstructions? Transpiler(CodeInstructions CodeInstructions, ILGenerator generator)
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.Method(typeof(RimWorld.CompRottable), nameof(RimWorld.CompRottable.CompTick));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes, ILGenerator generator)
|
||||
=> Reflection.GetCodeInstructions(Replacement, generator);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Replacement(RimWorld.CompRottable instance)
|
||||
{
|
||||
var codes = CodeInstructions.ToList();
|
||||
if ((TickHelper.TicksGame & 255) == (instance.parent.thingIDNumber & 255))
|
||||
instance.Tick(256);
|
||||
}
|
||||
}
|
||||
|
||||
var instance_tmpPropertyBlocks = FishTranspiler.Field(typeof(DrawBatch), nameof(DrawBatch.tmpPropertyBlocks));
|
||||
var instance_tmpPropertyBlocks_instance_propertyBlockCache_AddRange = FishTranspiler.Call<Action<HashSet<DrawBatchPropertyBlock>, List<DrawBatchPropertyBlock>>>(GenCollection.AddRange);
|
||||
var instance_tmpPropertyBlocks_key_propertyBlock_Add = FishTranspiler.Call(new HashSet<DrawBatchPropertyBlock>().Add);
|
||||
public class RitualObligationTrigger_Date : FirstPriorityFishPatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Literally just reorders instructions in this method. They were nonsensical, discarding 90% of the "
|
||||
+ "computation.";
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.Method(typeof(RimWorld.RitualObligationTrigger_Date),
|
||||
nameof(RimWorld.RitualObligationTrigger_Date.Tick));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes, ILGenerator generator)
|
||||
=> Reflection.GetCodeInstructions(Replacement, generator);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Replacement(RimWorld.RitualObligationTrigger_Date instance)
|
||||
{
|
||||
if (instance.ritual.isAnytime)
|
||||
return;
|
||||
|
||||
if ((!instance.mustBePlayerIdeo || Faction.OfPlayerSilentFail.ideos.Has(instance.ritual.ideo))
|
||||
&& instance.CurrentTickRelative() == instance.OccursOnTick())
|
||||
{
|
||||
instance.ritual.AddObligation(new(instance.ritual));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ReportProbablyMissingAttributesFix : FishPatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= """Fixes the "probably needs a StaticConstructorOnStartup attribute" log warning to display full """
|
||||
+ "type names to help identify what mod it's actually from.";
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.FindIncludingInnerTypes(typeof(StaticConstructorOnStartupUtility),
|
||||
static t => AccessTools.FirstMethod(t, static m
|
||||
=> PatchProcessor.ReadMethodBody(m).Any(static pair
|
||||
=> pair.Value is string text
|
||||
&& text.Contains("probably needs a StaticConstructorOnStartup attribute"))));
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> codes.MethodReplacer(AccessTools.PropertyGetter(typeof(MemberInfo), nameof(MemberInfo.Name)),
|
||||
methodof(Reflection.FullDescription));
|
||||
}
|
||||
|
||||
public class DrawBatch_Flush : FishPatch
|
||||
{
|
||||
public override string Description { get; }
|
||||
= "Optimization of a fleck rendering method. Low - medium performance impact";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; } = static () => default(DrawBatch)!.Flush(default);
|
||||
|
||||
public static CodeInstructions? Transpiler(CodeInstructions codeInstructions, ILGenerator generator)
|
||||
{
|
||||
var codes = codeInstructions.ToList();
|
||||
|
||||
var instance_tmpPropertyBlocks
|
||||
= FishTranspiler.Field(typeof(DrawBatch), nameof(DrawBatch.tmpPropertyBlocks));
|
||||
var instance_tmpPropertyBlocks_instance_propertyBlockCache_AddRange
|
||||
= FishTranspiler.Call<Action<HashSet<DrawBatchPropertyBlock>, List<DrawBatchPropertyBlock>>>(
|
||||
GenCollection.AddRange);
|
||||
var instance_tmpPropertyBlocks_key_propertyBlock_Add
|
||||
= FishTranspiler.Call(new HashSet<DrawBatchPropertyBlock>().Add);
|
||||
|
||||
var label_inFrontOf_instance_batches_Clear = generator.DefineLabel();
|
||||
var instance_batches_Clear = FishTranspiler.Call(new Dictionary<DrawBatch.BatchKey, List<DrawBatch.BatchData>>().Clear);
|
||||
var instance_batches_Clear
|
||||
= FishTranspiler.Call(new Dictionary<DrawBatch.BatchKey, List<DrawBatch.BatchData>>().Clear);
|
||||
|
||||
codes[
|
||||
codes.FindIndex(c => c == instance_batches_Clear)
|
||||
@@ -93,7 +188,8 @@ public class MiscOptimizations : ClassWithFishPatches
|
||||
].labels.Add(label_inFrontOf_instance_batches_Clear);
|
||||
|
||||
var instance_myPropertyBlocks = FishTranspiler.Field(typeof(DrawBatch), nameof(DrawBatch.myPropertyBlocks));
|
||||
var instance_myPropertyBlocks_GetEnumerator = FishTranspiler.Call(new HashSet<DrawBatchPropertyBlock>().GetEnumerator);
|
||||
var instance_myPropertyBlocks_GetEnumerator
|
||||
= FishTranspiler.Call(new HashSet<DrawBatchPropertyBlock>().GetEnumerator);
|
||||
|
||||
var addRangeRemoved = false;
|
||||
var addRemoved = false;
|
||||
@@ -124,6 +220,7 @@ public class MiscOptimizations : ClassWithFishPatches
|
||||
yield return FishTranspiler.Call(CheckFleckLeaks);
|
||||
yield return FishTranspiler.IfFalse(label_inFrontOf_instance_batches_Clear);
|
||||
yield return FishTranspiler.This;
|
||||
|
||||
checkFleckLeaksInserted = true;
|
||||
}
|
||||
else
|
||||
@@ -133,47 +230,49 @@ public class MiscOptimizations : ClassWithFishPatches
|
||||
}
|
||||
|
||||
if (!addRangeRemoved || !addRemoved || !checkFleckLeaksInserted)
|
||||
Log.Error($"Performance Fish failed to apply its DrawBatch.Flush() patch. Mod Settings has an option to disable this");
|
||||
{
|
||||
Log.Error("Performance Fish failed to apply its DrawBatch.Flush() patch. Mod Settings has an "
|
||||
+ "option to disable this");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckFleckLeaks(DrawBatch instance)
|
||||
{
|
||||
if (instance.myPropertyBlocks.Count != instance.propertyBlockCache.Count)
|
||||
{
|
||||
if (instance.myPropertyBlocks.Count == instance.propertyBlockCache.Count)
|
||||
return false;
|
||||
|
||||
instance.tmpPropertyBlocks.AddRange(instance.propertyBlockCache);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
#if whatItsGonnaLookLikeAfterTranspiling
|
||||
public static void Flush(DrawBatch __instance, bool draw = true)
|
||||
{
|
||||
if (__instance.tmpPropertyBlock == null)
|
||||
__instance.tmpPropertyBlock = new MaterialPropertyBlock();
|
||||
__instance.tmpPropertyBlock ??= new();
|
||||
__instance.tmpPropertyBlocks.Clear();
|
||||
// __instance.tmpPropertyBlocks.AddRange(__instance.propertyBlockCache); // <--------------------------------------------------------- removed
|
||||
// __instance.tmpPropertyBlocks.AddRange(__instance.propertyBlockCache); // <------------------------------- removed
|
||||
try
|
||||
{
|
||||
foreach (var batch in __instance.batches)
|
||||
foreach (var (batchKey, batchDatas) in __instance.batches)
|
||||
{
|
||||
var key = batch.Key;
|
||||
try
|
||||
{
|
||||
foreach (var item in batch.Value)
|
||||
foreach (var batchData in batchDatas)
|
||||
{
|
||||
var batchData = item;
|
||||
if (draw)
|
||||
{
|
||||
__instance.tmpPropertyBlock.Clear();
|
||||
if (key.propertyBlock != null)
|
||||
key.propertyBlock.Write(__instance.tmpPropertyBlock);
|
||||
if (key.renderInstanced)
|
||||
batchKey.propertyBlock?.Write(__instance.tmpPropertyBlock);
|
||||
if (batchKey.renderInstanced)
|
||||
{
|
||||
key.material.enableInstancing = true;
|
||||
batchKey.material.enableInstancing = true;
|
||||
if (batchData.hasAnyColors)
|
||||
__instance.tmpPropertyBlock.SetVectorArray("_Color", batchData.colors);
|
||||
Graphics.DrawMeshInstanced(key.mesh, 0, key.material, item.matrices, item.ptr, __instance.tmpPropertyBlock, UnityEngine.Rendering.ShadowCastingMode.On, receiveShadows: true, key.layer);
|
||||
Graphics.DrawMeshInstanced(batchKey.mesh, 0, batchKey.material, batchData.matrices,
|
||||
batchData.ptr,
|
||||
__instance.tmpPropertyBlock, UnityEngine.Rendering.ShadowCastingMode.On,
|
||||
receiveShadows: true, batchKey.layer);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -183,39 +282,46 @@ public class MiscOptimizations : ClassWithFishPatches
|
||||
var vector = batchData.colors[i];
|
||||
if (batchData.hasAnyColors)
|
||||
__instance.tmpPropertyBlock.SetColor("_Color", vector);
|
||||
Graphics.DrawMesh(key.mesh, matrix, key.material, key.layer, null, 0, __instance.tmpPropertyBlock);
|
||||
Graphics.DrawMesh(batchKey.mesh, matrix, batchKey.material, batchKey.layer,
|
||||
null, 0,
|
||||
__instance.tmpPropertyBlock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
batchData.Clear();
|
||||
__instance.batchDataListCache.Add(batchData);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (key.propertyBlock != null && __instance.myPropertyBlocks.Contains(key.propertyBlock))
|
||||
if (batchKey.propertyBlock != null && __instance.myPropertyBlocks.Contains(batchKey.propertyBlock))
|
||||
{
|
||||
// __instance.tmpPropertyBlocks.Add(key.propertyBlock); // <---------------------------------------------------------- removed
|
||||
key.propertyBlock.Clear();
|
||||
__instance.propertyBlockCache.Add(key.propertyBlock);
|
||||
// __instance.tmpPropertyBlocks.Add(key.propertyBlock); // <-------------------------------- removed
|
||||
batchKey.propertyBlock.Clear();
|
||||
__instance.propertyBlockCache.Add(batchKey.propertyBlock);
|
||||
}
|
||||
__instance.batchListCache.Add(batch.Value);
|
||||
batch.Value.Clear();
|
||||
|
||||
__instance.batchListCache.Add(batchDatas);
|
||||
batchDatas.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (CheckFleckLeaks(__instance)) // <------------------------------------------------------------------------------------------ added
|
||||
if (CheckFleckLeaks(__instance)) // <------------------------------------------------------------------- added
|
||||
{
|
||||
foreach (var myPropertyBlock in __instance.myPropertyBlocks)
|
||||
{
|
||||
if (!__instance.tmpPropertyBlocks.Contains(myPropertyBlock))
|
||||
Log.Warning("Property block from FleckDrawBatch leaked!" + ((myPropertyBlock.leakDebugString == null) ? null : ("Leak debug information: \n" + myPropertyBlock.leakDebugString)));
|
||||
Log.Warning("Property block from FleckDrawBatch leaked!"
|
||||
+ ((myPropertyBlock.leakDebugString == null)
|
||||
? null
|
||||
: ("Leak debug information: \n" + myPropertyBlock.leakDebugString)));
|
||||
}
|
||||
var hashSet = __instance.myPropertyBlocks;
|
||||
__instance.myPropertyBlocks = __instance.tmpPropertyBlocks;
|
||||
__instance.tmpPropertyBlocks = hashSet;
|
||||
|
||||
(__instance.myPropertyBlocks, __instance.tmpPropertyBlocks)
|
||||
= (__instance.tmpPropertyBlocks, __instance.myPropertyBlocks);
|
||||
}
|
||||
|
||||
__instance.batches.Clear();
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.ModCompatibility;
|
||||
|
||||
public static class ActiveMods
|
||||
{
|
||||
public static readonly bool
|
||||
Fishery = Contains(PackageIDs.FISHERY),
|
||||
// GradientHair = Contains(PackageIDs.GRADIENT_HAIR),
|
||||
GraphicsSetter = Contains(PackageIDs.GRAPHICS_SETTINGS),
|
||||
Harmony = Contains(PackageIDs.HARMONY),
|
||||
Multiplayer = Contains(PackageIDs.MULTIPLAYER),
|
||||
PerformanceOptimizer = Contains(PackageIDs.PERFORMANCE_OPTIMIZER),
|
||||
Prepatcher = Contains(PackageIDs.PREPATCHER),
|
||||
VanillaExpandedFramework = Contains(PackageIDs.VANILLA_EXPANDED_FRAMEWORK);
|
||||
|
||||
// ModsConfig.IsActive does not ignore _steam postfixes, causing it to fail when local copies were made
|
||||
public static bool Contains(string packageID) => TryGetModMetaData(packageID) != null;
|
||||
|
||||
public static ModMetaData? TryGetModMetaData(string packageID)
|
||||
=> ModLister.GetActiveModWithIdentifier(packageID, true);
|
||||
|
||||
public static ModContentPack? TryGetModContentPack(string packageID)
|
||||
{
|
||||
var allMods = LoadedModManager.RunningModsListForReading;
|
||||
var count = allMods.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (allMods[i].PackageIdPlayerFacing.Equals(packageID, StringComparison.OrdinalIgnoreCase))
|
||||
return allMods[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Assembly? TryGetAssembly(this ModContentPack modContentPack, string name)
|
||||
{
|
||||
var assemblies = modContentPack.assemblies.loadedAssemblies;
|
||||
|
||||
for (var i = 0; i < assemblies.Count; i++)
|
||||
{
|
||||
if (assemblies[i].GetName().Name.Equals(name, StringComparison.OrdinalIgnoreCase))
|
||||
return assemblies[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void CheckRequirements()
|
||||
{
|
||||
if (!Fishery)
|
||||
ThrowForMissingModDependency(nameof(Fishery));
|
||||
|
||||
if (!Harmony)
|
||||
ThrowForMissingModDependency(nameof(Harmony));
|
||||
|
||||
VerifyLoadOrder(nameof(Fishery), PackageIDs.FISHERY, PackageIDs.PERFORMANCE_FISH, PackageIDs.BETTER_LOG);
|
||||
VerifyLoadOrder(nameof(Harmony), PackageIDs.HARMONY, PackageIDs.PERFORMANCE_FISH);
|
||||
|
||||
CheckHarmonyVersion();
|
||||
CheckFisheryVersion();
|
||||
}
|
||||
|
||||
private static void VerifyLoadOrder(string dependencyName, string dependencyID, params string[] dependingModIDs)
|
||||
{
|
||||
var activeModsInLoadOrder = (List<ModMetaData>)ModsConfig.ActiveModsInLoadOrder;
|
||||
if (activeModsInLoadOrder.FindIndex(mod => mod.PackageId == dependencyID)
|
||||
> activeModsInLoadOrder.FindIndex(mod => dependingModIDs.Contains(mod.PackageId)))
|
||||
{
|
||||
ThrowForIncorrectLoadOrder(dependencyName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckFisheryVersion()
|
||||
{
|
||||
if (FisheryLib.FisheryLib.VERSION > FisheryLib.FisheryLib.CurrentlyLoadedVersion)
|
||||
ThrowForOutdatedModDependency(nameof(Fishery));
|
||||
}
|
||||
|
||||
private static void CheckHarmonyVersion()
|
||||
=> CheckDependencyVersion(nameof(Harmony), typeof(Harmony), "0Harmony");
|
||||
|
||||
private static void CheckDependencyVersion(string dependencyName, Type dependencyType,
|
||||
string dependencyAssemblyName)
|
||||
{
|
||||
if (typeof(PerformanceFishMod).Assembly.GetReferencedAssemblies().First(assembly
|
||||
=> assembly.FullName.StartsWith($"{dependencyAssemblyName}, Version", StringComparison.Ordinal)).Version
|
||||
> dependencyType.Assembly.GetName().Version)
|
||||
{
|
||||
ThrowForOutdatedModDependency(dependencyName);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ThrowForIncorrectLoadOrder(string dependencyName)
|
||||
=> ThrowHelper.ThrowOperationCanceledException($"Incorrect load order detected. {
|
||||
dependencyName} must be loaded before any mod depending on it.");
|
||||
|
||||
private static void ThrowForOutdatedModDependency(string modName)
|
||||
=> ThrowHelper.ThrowOperationCanceledException($"Outdated {modName} mod detected. {
|
||||
modName} must be updated for Performance Fish to work correctly");
|
||||
|
||||
private static void ThrowForMissingModDependency(string modName)
|
||||
=> ThrowHelper.ThrowOperationCanceledException(
|
||||
$"{modName} mod is missing. Performance Fish requires this mod in order to function.");
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.ModCompatibility;
|
||||
|
||||
public static class GraphicsSetter
|
||||
{
|
||||
public static MethodBase? TargetMethod { get; }
|
||||
= AccessTools.Method("GraphicSetter.Patches.TextureLoadingPatch+LoadPNGPatch:Prefix")
|
||||
?? AccessTools.Method("GraphicSetter.GraphicsPatches+LoadPNGPatch:Prefix");
|
||||
|
||||
public static ModMetaData? Mod { get; } = ActiveMods.TryGetModMetaData(PackageIDs.GRAPHICS_SETTINGS);
|
||||
|
||||
public static MethodBase? TryPatch()
|
||||
=> TargetMethod != null
|
||||
? PerformanceFishMod.Harmony.Patch(TargetMethod, transpiler: new(methodof(Transpiler)))
|
||||
: null;
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
{
|
||||
yield return FishTranspiler.Constant(1);
|
||||
yield return FishTranspiler.Return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.ModCompatibility;
|
||||
|
||||
public static class PackageIDs
|
||||
{
|
||||
public const string
|
||||
BETTER_LOG = "bs.betterlog",
|
||||
FISHERY = "bs.fishery",
|
||||
// GRADIENT_HAIR = "automatic.gradienthair",
|
||||
GRAPHICS_SETTINGS = "Telefonmast.GraphicsSettings",
|
||||
HARMONY = "brrainz.harmony",
|
||||
MULTIPLAYER = "rwmt.Multiplayer",
|
||||
PERFORMANCE_FISH = "bs.performance",
|
||||
PERFORMANCE_OPTIMIZER = "Taranchuk.PerformanceOptimizer",
|
||||
PREPATCHER = "zetrith.prepatcher",
|
||||
VANILLA_EXPANDED_FRAMEWORK = "OskarPotocki.VanillaFactionsExpanded.Core";
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.ModCompatibility;
|
||||
|
||||
public static class PerformanceOptimizer
|
||||
{
|
||||
private const string ASSEMBLY_NAME = "PerformanceOptimizer";
|
||||
|
||||
public static bool Active => ActiveMods.PerformanceOptimizer;
|
||||
|
||||
public static ModContentPack? ModContentPack { get; }
|
||||
= ActiveMods.TryGetModContentPack(PackageIDs.PERFORMANCE_OPTIMIZER);
|
||||
|
||||
public static Type? OptimizationBaseType { get; }
|
||||
|
||||
public static Type[]? BlockedPatches { get; }
|
||||
|
||||
public static string[] BlockedPatchNames { get; }
|
||||
=
|
||||
{
|
||||
"PerformanceOptimizer.Optimization_FasterGetCompReplacement",
|
||||
"PerformanceOptimizer.Optimization_IdeoUtility_GetStyleDominance"
|
||||
};
|
||||
|
||||
static PerformanceOptimizer()
|
||||
{
|
||||
if (ModContentPack is null)
|
||||
return;
|
||||
|
||||
var assembly = ModContentPack.TryGetAssembly(ASSEMBLY_NAME);
|
||||
|
||||
if (assembly is null)
|
||||
{
|
||||
Log.Error("Performance Fish failed to find Performance Optimizer's assembly. "
|
||||
+ "This is likely to cause issues.");
|
||||
return;
|
||||
}
|
||||
|
||||
OptimizationBaseType = assembly.GetType("PerformanceOptimizer.Optimization");
|
||||
|
||||
BlockedPatches = Array.ConvertAll(BlockedPatchNames, name => assembly.GetType(name));
|
||||
}
|
||||
|
||||
public static bool TryPatch()
|
||||
{
|
||||
if (BlockedPatches.NullOrEmpty())
|
||||
return false;
|
||||
|
||||
foreach (var patch in BlockedPatches.Concat(OptimizationBaseType))
|
||||
{
|
||||
if (AccessTools.DeclaredPropertyGetter(patch, "IsEnabled") is { } isEnabledGetter)
|
||||
PerformanceFishMod.Harmony.Patch(isEnabledGetter, new(methodof(IsEnabledPatch)));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsEnabledPatch(object __instance, ref bool __result)
|
||||
=> !BlockedPatches!.Contains(__instance.GetType())
|
||||
|| (__result = false);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.ModCompatibility;
|
||||
|
||||
public class VanillaExpandedFixes : ClassWithFishPatches
|
||||
{
|
||||
public class ExpandableGraphicData : FishPatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Fixes the LoadAllFiles method in Vanilla Expanded Framework's ExpandableGraphicData to not scan through "
|
||||
+ "all texture folders of all mods for every graphic and instead just use cached files the game already "
|
||||
+ "loaded. Improves load times and prevents dds related errors.";
|
||||
|
||||
public override bool Enabled => ActiveMods.VanillaExpandedFramework;
|
||||
|
||||
public override MethodBase TargetMethodInfo { get; }
|
||||
= AccessTools.Method("VFECore.ExpandableGraphicData:LoadAllFiles");
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions codes)
|
||||
=> Reflection.MakeReplacementCall(LoadAllFiles);
|
||||
|
||||
public static List<string> LoadAllFiles(object __instance, string? folderPath)
|
||||
{
|
||||
var paths = new List<string>();
|
||||
var mods = LoadedModManager.RunningModsListForReading;
|
||||
var count = mods.Count;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var contentListTrie = mods[i].GetContentHolder<Texture2D>().contentListTrie;
|
||||
|
||||
var prefix = !folderPath.NullOrEmpty() && folderPath![^1] == '/'
|
||||
? folderPath
|
||||
: folderPath + '/';
|
||||
|
||||
foreach (var path in contentListTrie.GetByPrefix(prefix))
|
||||
paths.Add(path);
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using PerformanceFish.ModCompatibility;
|
||||
using PerformanceFish.Prepatching;
|
||||
using PerformanceFish.System;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
/// <summary>
|
||||
/// Gets called before CreateModClasses(), after rimworld finishes loading assemblies
|
||||
/// </summary>
|
||||
public static class OnAssembliesLoaded
|
||||
{
|
||||
public static bool Loaded { get; private set; }
|
||||
|
||||
public static void Start()
|
||||
{
|
||||
Loaded = true;
|
||||
|
||||
TryPatchGraphicsSetter();
|
||||
TryPatchPerformanceOptimizer();
|
||||
|
||||
TryInitialize(EqualityComparerOptimization.Optimization.Initialize);
|
||||
|
||||
PerformanceFishMod.AllPatchClasses = PerformanceFishMod.InitializeAllPatchClasses<IHasFishPatch>();
|
||||
PerformanceFishMod.AllPrepatchClasses ??= PerformanceFishMod.InitializeAllPatchClasses<ClassWithFishPrepatches>();
|
||||
|
||||
TryInitialize(AnalyzerFixes.Patch, true);
|
||||
|
||||
_ = FishSettings.Instance;
|
||||
|
||||
Parallel.ForEach(PerformanceFishMod.AllPatchClasses, static patchClass =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!patchClass.RequiresLoadedGameForPatching)
|
||||
patchClass.Patches.PatchAll();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"{PerformanceFishMod.NAME} encountered an exception while trying to initialize {
|
||||
patchClass.GetType().Name}:\n{ex}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void TryPatchGraphicsSetter()
|
||||
{
|
||||
if (ActiveMods.GraphicsSetter && GraphicsSetter.TryPatch() is null)
|
||||
{
|
||||
Log.Error($"Failed to apply compatibility patch for {
|
||||
GraphicsSetter.Mod?.Name ?? "Graphics Settings+"}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryPatchPerformanceOptimizer()
|
||||
{
|
||||
if (ActiveMods.PerformanceOptimizer && !PerformanceOptimizer.TryPatch())
|
||||
{
|
||||
Log.Error($"Failed to apply compatibility patch for {
|
||||
PerformanceOptimizer.ModContentPack?.Name ?? "Performance Optimizer"}");
|
||||
}
|
||||
}
|
||||
|
||||
private static async void TryInitialize(Action action, bool async = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (async)
|
||||
await Task.Run(action).ConfigureAwait(false);
|
||||
else
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"{PerformanceFishMod.NAME} encountered an exception while trying to initialize {
|
||||
action.Method.FullDescription()}:\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Prepatcher's FreePatch invoke, the Log patch and if neither of the two happened the Mod ctor invoke call this.
|
||||
/// Generally runs twice with Prepatcher active as a result and doesn't really enable running anything earlier than
|
||||
/// without this initializer.
|
||||
/// </summary>
|
||||
#pragma warning disable CA2255
|
||||
public static class OnModuleLoaded
|
||||
{
|
||||
[ModuleInitializer]
|
||||
public static void Start()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA2255
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
[StaticConstructorOnStartup]
|
||||
[UsedImplicitly]
|
||||
public static class OnStartup
|
||||
{
|
||||
static OnStartup()
|
||||
{
|
||||
// Log.Message($"LoadItem took {TextureLoadingPatches.Stopwatch.ElapsedMilliSecondsAccurate()} ms");
|
||||
|
||||
// Log.Message($"Allowed defs for mothballing: {MothballOptimization.AllowedDefs.ToStringSafeEnumerable()}");
|
||||
// Log.Message($"Blocking defs for mothballing: {DefDatabase<HediffDef>.AllDefsListForReading.Where(static def
|
||||
// => !MothballOptimization.AllowedDefs.Contains(def)).ToStringSafeEnumerable()}");
|
||||
|
||||
Fields.Initialized = true;
|
||||
}
|
||||
|
||||
public static class State
|
||||
{
|
||||
public static bool Initialized => Fields.Initialized;
|
||||
}
|
||||
|
||||
private static class Fields
|
||||
{
|
||||
internal static bool Initialized;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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/.
|
||||
|
||||
extern alias nuget;
|
||||
using nuget::JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Patching;
|
||||
|
||||
[PublicAPI]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors | ImplicitUseTargetFlags.WithMembers)]
|
||||
public abstract class ClassWithFishPatches : SingletonFactory<ClassWithFishPatches>, IHasFishPatch
|
||||
{
|
||||
public virtual FishPatchHolder Patches { get; }
|
||||
public virtual bool RequiresLoadedGameForPatching => false;
|
||||
|
||||
protected ClassWithFishPatches() => Patches = new(GetType());
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// 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.Patching;
|
||||
|
||||
public abstract class FirstPriorityFishPatch : FishPatch
|
||||
{
|
||||
public override int PrefixMethodPriority => Priority.First;
|
||||
public override int PostfixMethodPriority => Priority.First;
|
||||
public override int TranspilerMethodPriority => Priority.First;
|
||||
public override int FinalizerMethodPriority => Priority.First;
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
// 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/.
|
||||
|
||||
extern alias nuget;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using nuget::JetBrains.Annotations;
|
||||
using PerformanceFish.Cache;
|
||||
|
||||
namespace PerformanceFish.Patching;
|
||||
|
||||
[PublicAPI]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors | ImplicitUseTargetFlags.WithMembers)]
|
||||
public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasDescription
|
||||
{
|
||||
public virtual bool ShouldBenchmark => false;
|
||||
|
||||
public virtual bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (value == _enabled)
|
||||
return;
|
||||
|
||||
_enabled = value;
|
||||
if (value)
|
||||
{
|
||||
if (ShouldBePatched)
|
||||
TryPatch();
|
||||
}
|
||||
else
|
||||
{
|
||||
var shouldBePatched = ShouldBePatched;
|
||||
TryUnpatch();
|
||||
ShouldBePatched = shouldBePatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
|
||||
public virtual bool DefaultState
|
||||
{
|
||||
[Pure] get => true;
|
||||
}
|
||||
|
||||
public virtual int PrefixMethodPriority => TryGetPriority(PrefixMethodInfo);
|
||||
public virtual int PostfixMethodPriority => TryGetPriority(PostfixMethodInfo);
|
||||
public virtual int TranspilerMethodPriority => TryGetPriority(TranspilerMethodInfo);
|
||||
public virtual int FinalizerMethodPriority => TryGetPriority(FinalizerMethodInfo);
|
||||
public MethodInfo? HarmonyMethodInfo => HarmonyMethodInfos.FirstOrDefault();
|
||||
public List<MethodInfo> HarmonyMethodInfos { get; } = new();
|
||||
public MethodInfo? BenchmarkHarmonyMethodInfo => BenchmarkHarmonyMethodInfos.FirstOrDefault();
|
||||
public List<MethodInfo> BenchmarkHarmonyMethodInfos { get; } = new();
|
||||
private bool Patched { get; set; }
|
||||
private bool ShouldBePatched { get; set; }
|
||||
|
||||
public virtual string? Name => null;
|
||||
public virtual string? Description => null;
|
||||
|
||||
public virtual Delegate? TargetMethodGroup => null;
|
||||
public virtual IEnumerable<Delegate>? TargetMethodGroups => null;
|
||||
public virtual Expression<Action>? TargetMethod => null;
|
||||
public virtual IEnumerable<Expression<Action>>? TargetMethods => null;
|
||||
|
||||
public virtual MethodBase? TargetMethodInfo
|
||||
=> TargetMethod != null
|
||||
? SymbolExtensions.GetMethodInfo(TargetMethod)
|
||||
: TargetMethodGroup?.Method;
|
||||
|
||||
public virtual IEnumerable<MethodBase> TargetMethodInfos
|
||||
=> _targetMethodInfos
|
||||
??= (TargetMethods?.Select(SymbolExtensions.GetMethodInfo)
|
||||
?? TargetMethodGroups?.Select(static m => m.Method))?.Cast<MethodBase>().ToArray()
|
||||
?? (TargetMethodInfo != null!
|
||||
? new[] { TargetMethodInfo }
|
||||
: Array.Empty<MethodBase>());
|
||||
|
||||
private MethodBase[]? _targetMethodInfos;
|
||||
|
||||
public virtual MethodInfo? ReversePatchMethodInfo
|
||||
=> TryGetMethod("REVERSEPATCH",
|
||||
static m => m.HasAttribute<HarmonyReversePatch>() && !m.HasAttribute<HarmonyTranspiler>());
|
||||
|
||||
public virtual MethodInfo? ReversePatchTranspilerMethodInfo
|
||||
=> TryGetMethod("REVERSEPATCHTRANSPILER",
|
||||
static m => m.HasAttribute<HarmonyTranspiler>() && m.HasAttribute<HarmonyReversePatch>());
|
||||
|
||||
public virtual MethodInfo? PrefixMethodInfo
|
||||
=> TryGetMethod("PREFIX", static m => m.HasAttribute<HarmonyPrefix>());
|
||||
|
||||
public virtual MethodInfo? PostfixMethodInfo
|
||||
=> TryGetMethod("POSTFIX", static m => m.HasAttribute<HarmonyPostfix>());
|
||||
|
||||
public virtual MethodInfo? TranspilerMethodInfo
|
||||
=> TryGetMethod("TRANSPILER",
|
||||
static m => m.HasAttribute<HarmonyTranspiler>() && !m.HasAttribute<HarmonyReversePatch>());
|
||||
|
||||
public virtual MethodInfo? FinalizerMethodInfo
|
||||
=> TryGetMethod("FINALIZER", static m => m.HasAttribute<HarmonyFinalizer>());
|
||||
|
||||
internal Type ParentType => GetType().DeclaringType;
|
||||
|
||||
internal IHasFishPatch? ParentClass
|
||||
=> PerformanceFishMod.AllPatchClasses?.FirstOrDefault(patchClass => patchClass.GetType() == ParentType);
|
||||
|
||||
internal bool IsBenchmarking => ShouldBenchmark && !FinishedBenchmarking;
|
||||
internal bool FinishedBenchmarking;
|
||||
|
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
||||
protected FishPatch() => _enabled = DefaultState;
|
||||
|
||||
public virtual MethodInfo? TryPatch()
|
||||
{
|
||||
if ((ParentClass?.RequiresLoadedGameForPatching ?? false) && Current.ProgramState != ProgramState.Playing)
|
||||
return HarmonyMethodInfo;
|
||||
|
||||
ShouldBePatched = true;
|
||||
if (!Enabled || Patched)
|
||||
return HarmonyMethodInfo;
|
||||
|
||||
if (!TargetMethodInfos.Any())
|
||||
{
|
||||
Log.Error($"Tried to apply patches for {GetType().Name}, but it lacks a target method for patching.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (PrefixMethodInfo is null
|
||||
&& PostfixMethodInfo is null
|
||||
&& TranspilerMethodInfo is null
|
||||
&& FinalizerMethodInfo is null
|
||||
&& ReversePatchMethodInfo is null)
|
||||
{
|
||||
Log.Error($"Tried to apply patches for {
|
||||
GetType().Name}, but there are none. This is likely not intended.");
|
||||
return null;
|
||||
}
|
||||
|
||||
ApplyPatch();
|
||||
|
||||
return HarmonyMethodInfo;
|
||||
}
|
||||
|
||||
protected virtual void ApplyPatch()
|
||||
{
|
||||
Patched = true;
|
||||
foreach (var method in TargetMethodInfos)
|
||||
{
|
||||
DebugLog.Message($"Performance Fish is applying {GetType().Name} on {method.FullDescription()}");
|
||||
|
||||
try
|
||||
{
|
||||
if (IsBenchmarking)
|
||||
{
|
||||
Patched = false;
|
||||
|
||||
if (AccessTools.EnumeratorMoveNext(method) is { } moveNext)
|
||||
{
|
||||
BenchmarkHarmonyMethodInfos.Add(PerformanceFishMod.Harmony!.Patch(moveNext,
|
||||
prefix: new(methodof(Benchmarking.Prefix), Priority.First + 1),
|
||||
postfix: new(methodof(Benchmarking.Postfix), Priority.Last - 1)));
|
||||
}
|
||||
|
||||
BenchmarkHarmonyMethodInfos.Add(PerformanceFishMod.Harmony!.Patch(method,
|
||||
prefix: new(methodof(Benchmarking.Prefix), Priority.First + 1),
|
||||
postfix: new(methodof(Benchmarking.Postfix), Priority.Last - 1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ReversePatchMethodInfo != null)
|
||||
{
|
||||
HarmonyMethodInfos.Add(Harmony.ReversePatch(method, new(ReversePatchMethodInfo),
|
||||
ReversePatchTranspilerMethodInfo));
|
||||
}
|
||||
|
||||
if (PrefixMethodInfo != null
|
||||
|| PostfixMethodInfo != null
|
||||
|| TranspilerMethodInfo != null
|
||||
|| FinalizerMethodInfo != null)
|
||||
{
|
||||
HarmonyMethodInfos.Add(PerformanceFishMod.Harmony!.Patch(method,
|
||||
prefix: TryMakeHarmonyMethod(PrefixMethodInfo, PrefixMethodPriority),
|
||||
postfix: TryMakeHarmonyMethod(PostfixMethodInfo, PostfixMethodPriority),
|
||||
transpiler: TryMakeHarmonyMethod(TranspilerMethodInfo, TranspilerMethodPriority),
|
||||
finalizer: TryMakeHarmonyMethod(FinalizerMethodInfo, FinalizerMethodPriority)));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to patch {
|
||||
method.FullDescription()} with {PrefixMethodInfo.FullDescription()}, {
|
||||
PostfixMethodInfo.FullDescription()}, {TranspilerMethodInfo.FullDescription()}, {
|
||||
FinalizerMethodInfo.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static HarmonyMethod? TryMakeHarmonyMethod(MethodInfo? method, int priority = -1, string[]? before = null,
|
||||
string[]? after = null, bool? debug = null)
|
||||
=> method != null ? new(method, priority, before, after, debug) : null;
|
||||
|
||||
public virtual void TryUnpatch()
|
||||
{
|
||||
ShouldBePatched = false;
|
||||
if ( /*!Enabled &&*/ Patched)
|
||||
ApplyUnpatching();
|
||||
}
|
||||
|
||||
protected virtual void ApplyUnpatching()
|
||||
{
|
||||
Patched = false;
|
||||
foreach (var method in TargetMethodInfos)
|
||||
{
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
TryUnpatchMethod(method, i switch
|
||||
{
|
||||
0 => PrefixMethodInfo,
|
||||
1 => PostfixMethodInfo,
|
||||
2 => TranspilerMethodInfo,
|
||||
_ => FinalizerMethodInfo
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryUnpatchMethod(MethodBase targetMethod, MethodInfo? patchMethod)
|
||||
{
|
||||
if (patchMethod is null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(targetMethod, patchMethod);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception when unpatching {
|
||||
patchMethod.FullDescription()} from {targetMethod.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
private MethodInfo? TryGetMethod(string nameCaseInsensitive, Predicate<MethodInfo>? predicate = null)
|
||||
=> GetType().TryGetMethod(nameCaseInsensitive, predicate);
|
||||
|
||||
private static int TryGetPriority(MethodInfo? info)
|
||||
=> info?.TryGetAttribute<HarmonyPriority>()?.info.priority ?? Priority.Normal;
|
||||
|
||||
public virtual void ExposeData() => Scribe_Values.Look(ref _enabled, "enabled", DefaultState);
|
||||
|
||||
public override string ToString() => GetType().FullName;
|
||||
}
|
||||
|
||||
internal static class Benchmarking
|
||||
{
|
||||
internal static int SwitchFrame = int.MaxValue;
|
||||
internal static int EndFrame = int.MaxValue;
|
||||
|
||||
public static void Prefix(MethodBase __originalMethod, out Results __state)
|
||||
{
|
||||
__state = ByReference<MethodBase, Results>.Get.GetOrAdd(__originalMethod);
|
||||
if (__state.Stopwatch is null)
|
||||
ByReference<MethodBase, Results>.Get.GetReference(__originalMethod).Setup(__originalMethod);
|
||||
|
||||
__state.Stopwatch!.Start();
|
||||
}
|
||||
|
||||
public static void Postfix(Results __state)
|
||||
{
|
||||
__state.Stopwatch!.Stop();
|
||||
__state.Count++;
|
||||
|
||||
//Log.Message($"SwitchFrame is {SwitchFrame}, EndFrame is {EndFrame}, CurrentFrame is {Time.frameCount}");
|
||||
|
||||
//if (__state.Stopwatch.ElapsedMilliseconds > 100 || __state.Count > 100000)
|
||||
if (__state.Switched ? GenTicks.TicksGame > EndFrame : GenTicks.TicksGame > SwitchFrame)
|
||||
PrintResultsAndStop(__state);
|
||||
}
|
||||
|
||||
public static void PrintResultsAndStop(Results results)
|
||||
{
|
||||
var stopwatch = results.Stopwatch!;
|
||||
Log.Message($"{results.Method.FullDescription()} took {stopwatch.ElapsedMilliSecondsAccurate()} ms for {
|
||||
results.Count} calls. This equals {
|
||||
stopwatch.ElapsedNanoSeconds() / results.Count} ns per call on average.");
|
||||
|
||||
results.Count = 0;
|
||||
results.Switched = true;
|
||||
stopwatch.Reset();
|
||||
|
||||
var fishPatch = PerformanceFishMod.AllPatchClasses!
|
||||
.SelectMany(static patchClass => patchClass.Patches)
|
||||
.First(patch => patch.TargetMethodInfos.Contains(results.Method));
|
||||
|
||||
if (fishPatch.FinishedBenchmarking)
|
||||
{
|
||||
foreach (var target in fishPatch.TargetMethodInfos)
|
||||
{
|
||||
if (AccessTools.EnumeratorMoveNext(target) is { } moveNext)
|
||||
{
|
||||
PerformanceFishMod.Harmony!.Unpatch(moveNext, methodof(Prefix));
|
||||
PerformanceFishMod.Harmony.Unpatch(moveNext, methodof(Postfix));
|
||||
}
|
||||
|
||||
PerformanceFishMod.Harmony!.Unpatch(target, methodof(Prefix));
|
||||
PerformanceFishMod.Harmony.Unpatch(target, methodof(Postfix));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fishPatch.FinishedBenchmarking = true;
|
||||
fishPatch.TryPatch();
|
||||
}
|
||||
}
|
||||
|
||||
public class Results
|
||||
{
|
||||
public int Count;
|
||||
public Stopwatch? Stopwatch;
|
||||
public MethodBase? Method;
|
||||
public bool Switched;
|
||||
|
||||
public void Setup(MethodBase method)
|
||||
{
|
||||
Stopwatch = new();
|
||||
Method = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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.Patching;
|
||||
|
||||
public static class FishPatchExtensions
|
||||
{
|
||||
public static void PatchAll(this IEnumerable<FishPatch> patches)
|
||||
{
|
||||
foreach (var patch in patches)
|
||||
patch.TryPatch();
|
||||
}
|
||||
|
||||
public static void UnpatchAll(this IEnumerable<FishPatch> patches)
|
||||
{
|
||||
foreach (var patch in patches)
|
||||
patch.TryUnpatch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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/.
|
||||
|
||||
extern alias nuget;
|
||||
using System.Collections.Concurrent;
|
||||
using nuget::JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Patching;
|
||||
|
||||
[PublicAPI]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
||||
public class FishPatchHolder : IExposable, IEnumerable<FishPatch>
|
||||
{
|
||||
public ConcurrentDictionary<Type, FishPatch> All { get; } = new();
|
||||
|
||||
public T Get<T>() where T : FishPatch => (T)All[typeof(T)];
|
||||
public void Add(FishPatch patch) => All[patch.GetType()] = patch;
|
||||
public void PatchAll() => All.Values.PatchAll();
|
||||
public void UnpatchAll() => All.Values.UnpatchAll();
|
||||
public FishPatch this[Type type] => All[type];
|
||||
|
||||
public FishPatchHolder(Type type)
|
||||
{
|
||||
_type = type;
|
||||
AddPatchesRecursively(type);
|
||||
}
|
||||
|
||||
public void Scribe() => ExposeData(); // Exposable.Scribe(this, _type.FullName);
|
||||
// directly calling ExposeData prevents creating nested nodes in the config file. Looks cleaner imo.
|
||||
|
||||
public void ExposeData()
|
||||
{
|
||||
foreach (var (type, patch) in All)
|
||||
Exposable.Scribe(patch, type.FullName ?? type.Name);
|
||||
}
|
||||
|
||||
private void AddPatchesRecursively(Type type)
|
||||
{
|
||||
if (typeof(FishPatch).IsAssignableFrom(type) && !All.ContainsKey(type))
|
||||
{
|
||||
if (PerformanceFishMod.AllPatchClasses is { } allPatches)
|
||||
RemoveDupes(allPatches, type);
|
||||
|
||||
All.TryAdd(type, FishPatch.Get(type));
|
||||
}
|
||||
|
||||
foreach (var nestedType in type.GetNestedTypes(AccessTools.all))
|
||||
AddPatchesRecursively(nestedType);
|
||||
}
|
||||
|
||||
private void RemoveDupes(IHasFishPatch[] patches, Type type)
|
||||
{
|
||||
foreach (var patchClass in patches)
|
||||
{
|
||||
if (patchClass.GetType() == _type
|
||||
|| !patchClass.Patches.All.ContainsKey(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
patchClass.Patches[type].Enabled = false;
|
||||
patchClass.Patches.All.TryRemove(type, out _);
|
||||
Log.Warning($"Performance Fish removed a duplicate patch from {patchClass.GetType().FullName
|
||||
}. This is likely caused by no longer valid mod configs");
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<FishPatch> GetEnumerator() => All.Values.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => All.Values.GetEnumerator();
|
||||
|
||||
private Type _type;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// 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/.
|
||||
|
||||
extern alias nuget;
|
||||
using nuget::JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Patching;
|
||||
|
||||
[PublicAPI]
|
||||
public interface IHasDescription
|
||||
{
|
||||
public string? Description { get; }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// 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/.
|
||||
|
||||
extern alias nuget;
|
||||
using nuget::JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Patching;
|
||||
|
||||
[PublicAPI]
|
||||
public interface IHasFishPatch
|
||||
{
|
||||
public FishPatchHolder Patches { get; }
|
||||
public bool RequiresLoadedGameForPatching { get; }
|
||||
}
|
||||
@@ -5,23 +5,36 @@
|
||||
<LangVersion>preview</LangVersion>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<Optimize>true</Optimize>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<Authors>bradson</Authors>
|
||||
<Copyright>Copyright (c) 2021 bradson</Copyright>
|
||||
<Copyright>Copyright (c) 2023 bradson</Copyright>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<EnableNETAnalyzers>True</EnableNETAnalyzers>
|
||||
<AnalysisLevel>preview-all</AnalysisLevel>
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>portable</DebugType>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>5</WarningLevel>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<Configurations>v1.3;v1.4</Configurations>
|
||||
<Configurations>v1.4;v1.3</Configurations>
|
||||
<Version>0.3.800.85</Version>
|
||||
<NoWarn>1701;1702;IL2121;CS8500;CS9082</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>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<When Condition="'$(Configuration)' == 'v1.3'">
|
||||
<PropertyGroup>
|
||||
<OutputPath>..\..\1.3\Assemblies\</OutputPath>
|
||||
<OutDir>..\..\1.3\Assemblies\</OutDir>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.3.*" />
|
||||
@@ -30,25 +43,21 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<When Condition="'$(Configuration)' == 'v1.4'">
|
||||
<PropertyGroup>
|
||||
<OutputPath>..\..\1.4\Assemblies\</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.4.*-*" />
|
||||
<Reference Include="1Fishery">
|
||||
<HintPath>..\..\..\Fishery\1.4\Assemblies\1Fishery.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</When>
|
||||
</Choose>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lib.Harmony" Version="2.*" ExcludeAssets="runtime" />
|
||||
<PackageReference Include="Zetrith.Prepatcher" Version="1.*" />
|
||||
<PackageReference Include="Krafs.Publicizer" Version="2.*" />
|
||||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.*" /><!-- Version included in Unity is missing IsNullRef -->
|
||||
<PackageReference Include="Nullable" Version="1.*" />
|
||||
<PackageReference Include="IndexRange" Version="1.*" ExcludeAssets="runtime" />
|
||||
<Publicize Include="Assembly-CSharp" />
|
||||
<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" />
|
||||
@@ -56,12 +65,23 @@
|
||||
<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.Runtime.CompilerServices.JitHelpers.UnsafeEnumCast" />
|
||||
<Publicize Include="mscorlib:System.Runtime.CompilerServices.JitHelpers.UnsafeEnumCastLong" />
|
||||
<Publicize Include="mscorlib:System.Runtime.CompilerServices.JitHelpers.UnsafeCast" />
|
||||
<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></PackagePath>
|
||||
<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>
|
||||
@@ -1,8 +1,10 @@
|
||||
// Copyright (c) 2022 bradson
|
||||
// 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/.
|
||||
|
||||
// #define DEBUG
|
||||
|
||||
global using System;
|
||||
global using System.Collections;
|
||||
global using System.Collections.Generic;
|
||||
@@ -11,117 +13,164 @@ global using System.Reflection;
|
||||
global using System.Runtime.CompilerServices;
|
||||
global using System.Threading;
|
||||
global using FisheryLib;
|
||||
global using FisheryLib.Cecil;
|
||||
global using FisheryLib.Collections;
|
||||
global using FisheryLib.Utility.Diagnostics;
|
||||
global using HarmonyLib;
|
||||
global using PerformanceFish.Utility;
|
||||
global using PerformanceFish.Patching;
|
||||
global using RimWorld;
|
||||
global using UnityEngine;
|
||||
global using Verse;
|
||||
global using static FisheryLib.Aliases;
|
||||
global using CodeInstructions = System.Collections.Generic.IEnumerable<HarmonyLib.CodeInstruction>;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using PerformanceFish.System;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using PerformanceFish.ModCompatibility;
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
[assembly: AllowPartiallyTrustedCallers]
|
||||
[assembly: SecurityTransparent]
|
||||
[assembly: SecurityRules(SecurityRuleSet.Level2, SkipVerificationInFullTrust = true)]
|
||||
[assembly: Debuggable(false, false)]
|
||||
|
||||
[module: SkipLocalsInit]
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
[PublicAPI]
|
||||
public class PerformanceFishMod : Mod
|
||||
{
|
||||
public const string NAME = "Performance Fish";
|
||||
public const decimal VERSION = 0.3M;
|
||||
public const string PACKAGE_ID = PackageIDs.PERFORMANCE_FISH;
|
||||
public const decimal VERSION = 0.4M;
|
||||
|
||||
#pragma warning disable IDE0079 // Visual Studio freaking out a bit over here
|
||||
#pragma warning disable CS8618
|
||||
public static FishSettings Settings { get; private set; }
|
||||
public static PerformanceFishMod Mod { get; private set; }
|
||||
public static Harmony Harmony { get; private set; }
|
||||
public static IHasFishPatch[] AllPatchClasses { get; private set; }
|
||||
#pragma warning restore CS8618, IDE0079
|
||||
public static FishSettings? Settings { get; internal set; }
|
||||
public static PerformanceFishMod? Mod { get; private set; }
|
||||
public static Harmony Harmony { get; private set; } = new(PACKAGE_ID);
|
||||
public static IHasFishPatch[]? AllPatchClasses { get; internal set; }
|
||||
public static ClassWithFishPrepatches[]? AllPrepatchClasses { get; internal set; }
|
||||
|
||||
public PerformanceFishMod(ModContentPack content) : base(content)
|
||||
{
|
||||
ActiveMods.CheckRequirements();
|
||||
InitializeMod();
|
||||
}
|
||||
|
||||
private void InitializeMod()
|
||||
{
|
||||
DebugLog.Message($"Initializing {NAME} v{VERSION}");
|
||||
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (Mod != null)
|
||||
ThrowHelper.ThrowInvalidOperationException($"Initialized a second instance of the {NAME} mod. This should never happen. Cancelling now to avoid further issues.");
|
||||
{
|
||||
Verse.Log.TryOpenLogWindow();
|
||||
ThrowHelper.ThrowInvalidOperationException($"Initialized a second instance of the {
|
||||
NAME} mod. This should never happen. Cancelling now to avoid further issues.");
|
||||
}
|
||||
|
||||
Mod = this;
|
||||
|
||||
TryInitialize(EqualityComparerOptimization.Initialize);
|
||||
|
||||
Harmony = new(content.PackageIdPlayerFacing);
|
||||
|
||||
AllPatchClasses = Assembly.GetExecutingAssembly().GetTypes()
|
||||
.Where(t => typeof(IHasFishPatch).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract)
|
||||
.Select(type
|
||||
=> {
|
||||
try
|
||||
if (!ActiveMods.Prepatcher)
|
||||
{
|
||||
return SingletonFactory<IHasFishPatch>.Get(type);
|
||||
Verse.Log.TryOpenLogWindow();
|
||||
Log.Error("Prepatcher mod is missing. Performance Fish will not be working correctly in this state.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {type.Name}:\n{ex}");
|
||||
return null!;
|
||||
}
|
||||
})
|
||||
.Where(p => p != null)
|
||||
.ToArray();
|
||||
|
||||
ReflectionCaching.Initialize();
|
||||
modSettings = Settings;
|
||||
modSettings!.Mod = this;
|
||||
|
||||
if (!OnAssembliesLoaded.Loaded)
|
||||
{
|
||||
Verse.Log.TryOpenLogWindow();
|
||||
Log.Error("Performance Fish failed to initialize entirely. There are either critical dependencies "
|
||||
+ "missing or mods with hard incompatibilities present.");
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
LogPatchCount();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void LogPatchCount()
|
||||
{
|
||||
var allFishPatches = AllPatchClasses!.SelectMany(static patchClass => patchClass.Patches.All.Values).ToList();
|
||||
var allFishPrepatches
|
||||
= AllPrepatchClasses!.SelectMany(static patchClass => patchClass.Patches.All.Values).ToList();
|
||||
|
||||
var prefixCount = SumFor(allFishPatches, static fishPatch => fishPatch.PrefixMethodInfo);
|
||||
var postfixCount = SumFor(allFishPatches, static fishPatch => fishPatch.PostfixMethodInfo);
|
||||
var transpilerCount = SumFor(allFishPatches, static fishPatch => fishPatch.TranspilerMethodInfo);
|
||||
var prepatcherPrefixCount = SumFor(allFishPrepatches, static fishPrepatch
|
||||
=> (fishPrepatch as FishPrepatch)?.PrefixMethodInfo);
|
||||
var prepatcherPostfixCount = SumFor(
|
||||
allFishPrepatches, static fishPrepatch => (fishPrepatch as FishPrepatch)?.PostfixMethodInfo);
|
||||
var prepatcherTranspilerCount = SumFor(allFishPrepatches, static fishPrepatch
|
||||
=> fishPrepatch is FishPrepatch { PrefixMethodInfo: null, PostfixMethodInfo: null }
|
||||
? methodof(SumFor<object>) : null);
|
||||
var prepatcherClassPatchCount = SumFor(allFishPrepatches, static fishPrepatch
|
||||
=> fishPrepatch is FishClassPrepatch ? methodof(SumFor<object>) : null);
|
||||
|
||||
Log.Message($"Performance Fish applied {prefixCount} harmony prefixes, {postfixCount} harmony postfixes, {
|
||||
transpilerCount} harmony transpilers, {prepatcherPrefixCount} prepatcher prefixes, {
|
||||
prepatcherPostfixCount} prepatcher postfixes, {prepatcherTranspilerCount} prepatcher transpilers and {
|
||||
prepatcherClassPatchCount} prepatcher class patch{
|
||||
(prepatcherClassPatchCount == 1 ? "" : "es")}. That's a total of {prefixCount + postfixCount
|
||||
+ transpilerCount + prepatcherPrefixCount + prepatcherPostfixCount
|
||||
+ prepatcherTranspilerCount + prepatcherClassPatchCount} patches. Amazing!\n\n"
|
||||
+ $"The following methods were patched:\n{string.Join("\n", allFishPatches.SelectMany(static fishPatch
|
||||
=> fishPatch.TargetMethodInfos).Concat(allFishPrepatches.Select(static fishPrepatch
|
||||
=> (fishPrepatch as FishPrepatch)?.TargetMethodBase)).Where(static method
|
||||
=> method != null).Select(static method => method.FullDescription()))}\n"
|
||||
+ $"===================================================================================");
|
||||
}
|
||||
|
||||
private static int SumFor<T>(List<T> allFishPatches, Func<T, MethodInfo?> methodInfoGetter)
|
||||
=> allFishPatches.Sum(fishPatch => methodInfoGetter(fishPatch) != null ? 1 : 0);
|
||||
|
||||
internal static T[] InitializeAllPatchClasses<T>() where T : notnull
|
||||
{
|
||||
var types = Assembly.GetExecutingAssembly().GetTypes();
|
||||
var list = new List<T>(types.Length);
|
||||
|
||||
Parallel.ForEach(types, type =>
|
||||
{
|
||||
if (!type.IsAssignableTo(typeof(T))
|
||||
|| type.IsInterface
|
||||
|| type.IsAbstract)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Settings = GetSettings<FishSettings>();
|
||||
var patchClass = SingletonFactory<T>.Get(type);
|
||||
|
||||
lock (((ICollection)list).SyncRoot)
|
||||
list.Add(patchClass);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an expection while trying to load its settings\n{ex}");
|
||||
WriteSettings();
|
||||
Log.Error($"{NAME} encountered an exception while trying to initialize {type.Name}:\n{ex}");
|
||||
}
|
||||
});
|
||||
|
||||
TryInitialize(AnalyzerFixes.Patch);
|
||||
|
||||
foreach (var patch in AllPatchClasses)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!patch.RequiresLoadedGameForPatching)
|
||||
patch.Patches.PatchAll();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {patch.GetType().Name}:\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryInitialize(Action action)
|
||||
{
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to initialize {action.Method.FullDescription()}:\n{ex}");
|
||||
}
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
public static T? Get<T>()
|
||||
{
|
||||
foreach (var patch in AllPatchClasses)
|
||||
for (var i = AllPatchClasses!.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (patch is T patchOfT)
|
||||
if (AllPatchClasses[i] is T patchOfT)
|
||||
return patchOfT;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public override string SettingsCategory() => "Performance Fish";
|
||||
public override string SettingsCategory() => Mod!.Content.Name;
|
||||
public override void DoSettingsWindowContents(Rect inRect) => FishSettings.DoSettingsWindowContents(inRect);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
using RimWorld.Planet;
|
||||
|
||||
namespace PerformanceFish.Planet;
|
||||
|
||||
public class MothballOptimization : ClassWithFishPrepatches
|
||||
{
|
||||
public const string BASIC_DESCRIPTION
|
||||
= "Mothballing means to \"take something out of operation but maintain it so that it can be used in the "
|
||||
+ "future.\" RimWorld has this system for world pawns along with a number of conditions which can block it "
|
||||
+ "from taking effect.";
|
||||
|
||||
public class WorldPawnsDefPreventingMothball : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= BASIC_DESCRIPTION + " This patch essentially serves as a whitelist to allow mothballing a lot more "
|
||||
+ "pawns. Large performance impact, but not entirely side-effect free. Certain slowly progressing and not "
|
||||
+ "lethal conditions like addictions or pregnancy do not progress on mothballed pawns.";
|
||||
|
||||
public static bool MothballEverything => FishSettings.MothballEverything;
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.Method(typeof(WorldPawns), nameof(WorldPawns.DefPreventingMothball));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
{
|
||||
var instructions = ilProcessor.instructions;
|
||||
for (var i = 0; i < instructions.Count; i++)
|
||||
{
|
||||
if (instructions[i].Operand is not MethodReference { Name: "IsPermanent" })
|
||||
continue;
|
||||
|
||||
ilProcessor.InsertAt(i++, OpCodes.Dup);
|
||||
i++; // bool IsPermanent(hediff)
|
||||
|
||||
ilProcessor.InsertRange(i,
|
||||
OpCodes.Ldarg_0,
|
||||
OpCodes.Ldarg_1,
|
||||
(OpCodes.Call, methodof(AllowMothball)));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool AllowMothball(Hediff hediff, bool isPermanent, WorldPawns worldPawns, Pawn pawn)
|
||||
=> MothballEverything
|
||||
|| isPermanent
|
||||
|| !hediff.Visible
|
||||
|| (/*IsLowPriority(worldPawns, pawn)
|
||||
&&*/ AllowedDefs.Contains(hediff.def)
|
||||
&& hediff is
|
||||
{
|
||||
CurStage.lifeThreatening: false, SummaryHealthPercentImpact: < 0.01f, Bleeding: false
|
||||
}
|
||||
&& (!HasBlockingComp(hediff) || !HasLifeThreateningStage(hediff.def)));
|
||||
|
||||
private static bool HasBlockingComp(Hediff hediff)
|
||||
=> hediff.def.minSeverity <= 0f
|
||||
&& hediff is HediffWithComps hediffWithComps
|
||||
&& hediffWithComps.comps.ExistsAndNotNull(static comp
|
||||
=> comp is HediffComp_TendDuration
|
||||
&& comp.props is not HediffCompProperties_TendDuration { showTendQuality: false });
|
||||
|
||||
private static bool IsLowPriority(WorldPawns worldPawns, Pawn pawn)
|
||||
=> WorldPawns.lowPrioritySituations.Contains(worldPawns.GetSituation(pawn));
|
||||
}
|
||||
|
||||
public static HashSet<HediffDef> AllowedDefs => _allowedDefs ??= PrepareAllowedDefs();
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static HashSet<HediffDef> PrepareAllowedDefs()
|
||||
=> DefDatabase<HediffDef>.AllDefsListForReading.Where(static def
|
||||
=> def.lethalSeverity < 0f
|
||||
// && !def.displayWound
|
||||
&& !CausesBleeding(def)
|
||||
&& !LosesSeverityPerDay(def)
|
||||
&& (!HasLifeThreateningStage(def) || !HasBlockingCompProperties(def)))
|
||||
.ToHashSet();
|
||||
|
||||
private static bool HasBlockingCompProperties(HediffDef def)
|
||||
=> def.minSeverity <= 0f
|
||||
&& def.comps.ExistsAndNotNull(static props
|
||||
=> (props.compClass?.IsAssignableTo(typeof(HediffComp_TendDuration)) ?? false)
|
||||
&& props is not HediffCompProperties_TendDuration { showTendQuality: false });
|
||||
|
||||
private static bool LosesSeverityPerDay(HediffDef def)
|
||||
=> def.minSeverity <= 0f
|
||||
&& def.comps.ExistsAndNotNull(static props
|
||||
=> props is HediffCompProperties_SeverityPerDay severityProps
|
||||
&& (severityProps.severityPerDay <= -0.35f || severityProps.severityPerDayRange.max <= -0.35f));
|
||||
|
||||
private static bool HasLifeThreateningStage(HediffDef def)
|
||||
=> def.stages.ExistsAndNotNull(static stage => stage.lifeThreatening);
|
||||
|
||||
private static bool CausesBleeding(HediffDef def) => def.injuryProps is { bleedRate: > 0.001f };
|
||||
|
||||
private static HashSet<HediffDef>? _allowedDefs;
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
using RimWorld.Planet;
|
||||
|
||||
namespace PerformanceFish.Planet;
|
||||
|
||||
public class WorldObjectsOptimization : ClassWithFishPrepatches
|
||||
{
|
||||
public class WorldObjectsHolderTickPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "The world objects holder is responsible for ticking every world object. This includes settlements, "
|
||||
+ "caravans, outposts and any other object placed in the world, instead of maps. Normally it ticks "
|
||||
+ "everything equally, including many static objects that cannot possibly affect anything from a tick. "
|
||||
+ "This patch improves the world objects holder to determine objects that need ticking in advance, cache "
|
||||
+ "the list of them, and only tick those, skipping all the others.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.Method(typeof(WorldObjectsHolder), nameof(WorldObjectsHolder.WorldObjectsHolderTick));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(WorldObjectsHolderTick);
|
||||
|
||||
public static void WorldObjectsHolderTick(WorldObjectsHolder instance)
|
||||
{
|
||||
if (CacheDirty(instance))
|
||||
UpdateCache(instance);
|
||||
|
||||
var worldObjects = WorldObjectsHolder.tmpWorldObjects;
|
||||
for (var i = worldObjects.Count; i-- > 0;)
|
||||
worldObjects[i].Tick();
|
||||
}
|
||||
|
||||
public static void UpdateCache(WorldObjectsHolder instance)
|
||||
{
|
||||
var staticWorldObjects = WorldObjectsHolder.tmpWorldObjects;
|
||||
staticWorldObjects.Clear();
|
||||
|
||||
var instanceWorldObjects = instance.worldObjects;
|
||||
|
||||
for (var i = instanceWorldObjects.Count; i-- > 0;)
|
||||
{
|
||||
var worldObject = instanceWorldObjects[i];
|
||||
|
||||
if (worldObject is not MapParent { HasMap: true }
|
||||
&& SkippableWorldObjects.Contains(worldObject.GetType())
|
||||
&& (worldObject is not Settlement settlement || settlement.trader?.stock is null)
|
||||
&& CanSkipCompTick(worldObject))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
staticWorldObjects.Add(worldObject);
|
||||
}
|
||||
|
||||
CachedWorldObjectsVersion = instanceWorldObjects._version;
|
||||
CachedMapsVersion = Current.gameInt.maps._version;
|
||||
}
|
||||
|
||||
private static bool CanSkipCompTick(WorldObject worldObject)
|
||||
{
|
||||
var comps = worldObject.comps;
|
||||
|
||||
for (var i = comps.Count; i-- > 0;)
|
||||
{
|
||||
var comp = comps[i];
|
||||
if (comp is EnterCooldownComp { Active: true })
|
||||
return false;
|
||||
|
||||
if (!SkippableComps.Contains(comp.GetType()))
|
||||
return false;
|
||||
|
||||
comp.CompTick();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static int
|
||||
CachedWorldObjectsVersion,
|
||||
CachedMapsVersion;
|
||||
|
||||
private static Type[]
|
||||
_whitelistedTickingCompTypes
|
||||
=
|
||||
{
|
||||
typeof(WorldObjectComp),
|
||||
typeof(FormCaravanComp),
|
||||
typeof(TimedDetectionRaids),
|
||||
typeof(EnterCooldownComp)
|
||||
},
|
||||
_whitelistedWorldObjectTypes = { typeof(WorldObject), typeof(MapParent), typeof(Settlement) };
|
||||
|
||||
public static HashSet<Type>
|
||||
SkippableComps = MakeSubclassHashSet(typeof(WorldObjectComp), nameof(WorldObjectComp.CompTick),
|
||||
_whitelistedTickingCompTypes),
|
||||
SkippableWorldObjects = MakeSubclassHashSet(typeof(WorldObject), nameof(WorldObject.Tick),
|
||||
_whitelistedWorldObjectTypes);
|
||||
|
||||
private static HashSet<Type> MakeSubclassHashSet(Type type, string name, Type[] allowedDeclaringTypes)
|
||||
=> type.SubclassesWithNoMethodOverrideAndSelf(allowedDeclaringTypes, name).ToHashSet();
|
||||
|
||||
public static bool CacheDirty(WorldObjectsHolder instance)
|
||||
=> CachedWorldObjectsVersion != instance.worldObjects._version
|
||||
|| CachedMapsVersion != Current.gameInt.maps._version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
using RimWorld.Planet;
|
||||
|
||||
namespace PerformanceFish.Planet;
|
||||
|
||||
public class WorldPawnGCOptimization : ClassWithFishPrepatches
|
||||
{
|
||||
public class AddRelationshipsPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "The world pawn GC is responsible for deleting no longer relevant pawns. AddAllRelationships here tries "
|
||||
+ "to find every pawn another has any kind of relation to, no matter how distant and through how many "
|
||||
+ "other relatives, to block these pawns from getting deleted. This normally runs for every pawn the game "
|
||||
+ "considers critical, which includes colonists, faction leaders, spawned pawns and many other less "
|
||||
+ "important pawns, like someone who happened to have come up in someone's battle log, in a tale for art, "
|
||||
+ "in a quest etc. This patch prevents this relationship calculation for many of the less important pawns.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.Method(typeof(WorldPawnGC), nameof(WorldPawnGC.AddAllRelationships));
|
||||
|
||||
public static bool Prefix(WorldPawnGC __instance, Pawn pawn, Dictionary<Pawn, string> keptPawns)
|
||||
=> AcceptedCriticalPawnReasons.Contains(keptPawns[pawn]);
|
||||
}
|
||||
|
||||
public class WorldPawnGCTickPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Normally the world pawn GC processes 1 pawn per tick, and speeds up if it fails to complete its "
|
||||
+ "processing before the game had to add or remove world pawns through other means. This patch changes the "
|
||||
+ "baseline to one pawn every 4 ticks to make the slowdown less noticeable.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.Method(typeof(WorldPawnGC), nameof(WorldPawnGC.WorldPawnGCTick));
|
||||
|
||||
public static bool Prefix() => TickHelper.MatchesModulo(4);
|
||||
}
|
||||
|
||||
public class CancelGCPassPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "When the game determines that it has to speed up its GC process because of world pawn additions or "
|
||||
+ "removals it normally aborts the process and entirely starts over. This patch makes it not do that, and "
|
||||
+ "instead just lets it continue at the faster rate.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.Method(typeof(WorldPawnGC), nameof(WorldPawnGC.CancelGCPass));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static void ReplacementBody(WorldPawnGC instance)
|
||||
{
|
||||
if (instance.activeGCProcess == null)
|
||||
return;
|
||||
|
||||
// instance.activeGCProcess = null;
|
||||
instance.currentGCRate = Mathf.Min(instance.currentGCRate * 2, 16777216);
|
||||
if (DebugViewSettings.logWorldPawnGC)
|
||||
Log.Message("World pawn GC cancelled");
|
||||
}
|
||||
}
|
||||
|
||||
public class GetCriticalPawnReasonPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Critical pawns are pawns the game's world pawn GC does not delete. Relationships other than parents of "
|
||||
+ "colonists are normally not included here, and instead get calculated within the AddRelationships "
|
||||
+ "method. The AddRelationships patch Performance Fish includes prevents many of the relationship "
|
||||
+ "calculations in that method however, so this here adds every direct relation to player-visible pawns "
|
||||
+ "as critical pawn reason, preventing these relatives from getting deleted.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.Method(typeof(WorldPawnGC), nameof(WorldPawnGC.GetCriticalPawnReason));
|
||||
|
||||
public static void Postfix(ref string? __result, Pawn pawn)
|
||||
{
|
||||
if (__result != null || pawn.relations is not { } relations)
|
||||
return;
|
||||
|
||||
foreach (var pawnWithDirectRelation in relations.pawnsWithDirectRelationsWithMe)
|
||||
{
|
||||
if (pawnWithDirectRelation is (not { RaceProps.Humanlike: true }) or
|
||||
{
|
||||
Faction: not { IsPlayer: true }, Spawned: false,
|
||||
HostFaction: not { IsPlayer: true }
|
||||
})
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
__result = "Relationship";
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static HashSet<string> AcceptedCriticalPawnReasons
|
||||
= new(new[] { "Colonist", "Generating", "Kidnapped", "CaravanMember", "TransportPod", "Spawned" });
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Prepatching;
|
||||
using RimWorld.Planet;
|
||||
|
||||
namespace PerformanceFish.Planet;
|
||||
|
||||
public class WorldPawnsOptimization : ClassWithFishPrepatches
|
||||
{
|
||||
public class AllPawnsAlivePatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Minor optimization to reduce the amount of copying happening in the WorldPawns.AllPawnsAlive method";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.PropertyGetter(typeof(WorldPawns), nameof(WorldPawns.AllPawnsAlive));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static List<Pawn> ReplacementBody(WorldPawns instance)
|
||||
{
|
||||
ref var cache = ref instance.Cache();
|
||||
|
||||
if (CacheDirty(ref cache, instance))
|
||||
{
|
||||
instance.allPawnsAliveResult.Clear();
|
||||
instance.allPawnsAliveResult.AddRange(instance.pawnsAlive);
|
||||
instance.allPawnsAliveResult.AddRange(instance.pawnsMothballed);
|
||||
|
||||
UpdateCache(ref cache, instance);
|
||||
}
|
||||
|
||||
return instance.allPawnsAliveResult;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void UpdateCache(ref Cache cache, WorldPawns instance)
|
||||
{
|
||||
cache.PawnsAliveVersion = instance.pawnsAlive._version;
|
||||
cache.PawnsMothballedVersion = instance.pawnsMothballed._version;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool CacheDirty(ref Cache cache, WorldPawns instance)
|
||||
=> instance.pawnsAlive._version != cache.PawnsAliveVersion
|
||||
|| instance.pawnsMothballed._version != cache.PawnsMothballedVersion;
|
||||
}
|
||||
|
||||
public class AllPawnsAliveOrDeadPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Minor optimization to reduce the amount of copying happening in the WorldPawns.AllPawnsAliveOrDead "
|
||||
+ "method";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.PropertyGetter(typeof(WorldPawns), nameof(WorldPawns.AllPawnsAliveOrDead));
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static List<Pawn> ReplacementBody(WorldPawns instance)
|
||||
{
|
||||
ref var cache = ref instance.Cache();
|
||||
|
||||
var allPawnsAlive = instance.AllPawnsAlive;
|
||||
var allPawnsDead = instance.AllPawnsDead;
|
||||
|
||||
if (CacheDirty(ref cache, allPawnsAlive, allPawnsDead))
|
||||
{
|
||||
instance.allPawnsAliveOrDeadResult.Clear();
|
||||
instance.allPawnsAliveOrDeadResult.AddRange(allPawnsAlive);
|
||||
instance.allPawnsAliveOrDeadResult.AddRange(allPawnsDead);
|
||||
|
||||
UpdateCache(ref cache, allPawnsAlive, allPawnsDead);
|
||||
}
|
||||
|
||||
return instance.allPawnsAliveOrDeadResult;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void UpdateCache(ref Cache cache, List<Pawn> allPawnsAlive, HashSet<Pawn> allPawnsDead)
|
||||
{
|
||||
cache.AllPawnsAliveVersion = allPawnsAlive._version;
|
||||
cache.AllPawnsDeadVersion = allPawnsDead._version;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool CacheDirty(ref Cache cache, List<Pawn> allPawnsAlive, HashSet<Pawn> allPawnsDead)
|
||||
=> allPawnsAlive._version != cache.AllPawnsAliveVersion
|
||||
|| allPawnsDead._version != cache.AllPawnsDeadVersion;
|
||||
}
|
||||
|
||||
public record struct Cache
|
||||
{
|
||||
public int
|
||||
PawnsAliveVersion = -1,
|
||||
PawnsMothballedVersion = -1,
|
||||
AllPawnsAliveVersion = -1,
|
||||
AllPawnsDeadVersion = -1;
|
||||
|
||||
public Cache()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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.Pools;
|
||||
|
||||
public static class FishPool<T> where T : IFishPoolable
|
||||
{
|
||||
private static T?[] _freeItems = new T[4];
|
||||
private static int _freeItemCount;
|
||||
|
||||
public static T Get()
|
||||
{
|
||||
if (_freeItemCount == 0)
|
||||
return Reflection.New<T>();
|
||||
|
||||
var freeItem = _freeItems[--_freeItemCount];
|
||||
_freeItems[_freeItemCount] = default;
|
||||
return freeItem!;
|
||||
}
|
||||
|
||||
public static void Return(T item)
|
||||
{
|
||||
item.Reset();
|
||||
|
||||
if (++_freeItemCount >= _freeItems.Length)
|
||||
ExpandFreeItems();
|
||||
|
||||
_freeItems[_freeItemCount - 1] = item;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ExpandFreeItems() => Array.Resize(ref _freeItems, _freeItems.Length << 1);
|
||||
}
|
||||
|
||||
public interface IFishPoolable
|
||||
{
|
||||
public void Reset();
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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.Pools;
|
||||
|
||||
public struct PooledIList<T> : IDisposable where T : IList, new()
|
||||
{
|
||||
public T List;
|
||||
|
||||
private static T?[] _freeItems = new T[4];
|
||||
private static int _freeItemCount;
|
||||
|
||||
public PooledIList() => List = Get();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (List == null)
|
||||
return;
|
||||
|
||||
Return(List);
|
||||
List = default!;
|
||||
}
|
||||
|
||||
public static T Get()
|
||||
{
|
||||
if (_freeItemCount == 0)
|
||||
return Reflection.New<T>();
|
||||
|
||||
var freeItem = _freeItems[--_freeItemCount];
|
||||
_freeItems[_freeItemCount] = default;
|
||||
return freeItem!;
|
||||
}
|
||||
|
||||
public static void Return(T item)
|
||||
{
|
||||
item.Clear();
|
||||
|
||||
if (++_freeItemCount >= _freeItems.Length)
|
||||
ExpandFreeItems();
|
||||
|
||||
_freeItems[_freeItemCount - 1] = item;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ExpandFreeItems() => Array.Resize(ref _freeItems, _freeItems.Length << 1);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Pools;
|
||||
|
||||
public struct PooledStringBuilder : IDisposable
|
||||
{
|
||||
private FishPoolable _fishPoolable;
|
||||
|
||||
public StringBuilder Builder => _fishPoolable.Builder;
|
||||
|
||||
public StringBuilder Append(string? value) => Builder.Append(value);
|
||||
public StringBuilder Append(char value) => Builder.Append(value);
|
||||
public StringBuilder Append(char[] value) => Builder.Append(value);
|
||||
public StringBuilder Append(PooledStringBuilderHandler value) => value.AppendToStringBuilder(Builder);
|
||||
|
||||
public StringBuilder AppendLine(string? value) => Builder.AppendLine(value);
|
||||
|
||||
public StringBuilder Insert(int index, string? value) => Builder.Insert(index, value);
|
||||
public StringBuilder Insert(int index, char value) => Builder.Insert(index, value);
|
||||
|
||||
public StringBuilder Remove(int startIndex, int length) => Builder.Remove(startIndex, length);
|
||||
|
||||
public void Clear() => Builder.Clear();
|
||||
|
||||
public override string ToString() => Builder.ToString();
|
||||
|
||||
[SecuritySafeCritical]
|
||||
public PooledStringBuilder() => _fishPoolable = FishPool<FishPoolable>.Get();
|
||||
|
||||
public PooledStringBuilder(int minimumSize) : this()
|
||||
{
|
||||
if (_fishPoolable.Builder.Capacity < minimumSize)
|
||||
_fishPoolable.Builder.Capacity = minimumSize;
|
||||
}
|
||||
|
||||
[SecuritySafeCritical]
|
||||
public void Dispose()
|
||||
{
|
||||
if (_fishPoolable.Builder == null!)
|
||||
return;
|
||||
|
||||
FishPool<FishPoolable>.Return(_fishPoolable);
|
||||
_fishPoolable.Builder = null!;
|
||||
}
|
||||
|
||||
private struct FishPoolable : IFishPoolable
|
||||
{
|
||||
public StringBuilder Builder = new();
|
||||
|
||||
public void Reset() => Builder.Clear();
|
||||
|
||||
[UsedImplicitly]
|
||||
public FishPoolable() {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Pools;
|
||||
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
|
||||
[InterpolatedStringHandler]
|
||||
public struct PooledStringBuilderHandler : IDisposable
|
||||
{
|
||||
private PooledIList<List<char>> _chars;
|
||||
|
||||
public PooledStringBuilderHandler(int literalLength, int formattedCount)
|
||||
{
|
||||
_chars = new();
|
||||
_chars.List.EnsureCapacity(literalLength + (formattedCount * 8));
|
||||
}
|
||||
|
||||
public void AppendLiteral(string s) => _chars.List.Add(s);
|
||||
|
||||
public void AppendFormatted<T>(T t)
|
||||
{
|
||||
if (t != null)
|
||||
_chars.List.Add(t.ToString());
|
||||
}
|
||||
|
||||
public StringBuilder AppendToStringBuilder(StringBuilder destination)
|
||||
{
|
||||
destination.Append(_chars.List);
|
||||
Dispose();
|
||||
return destination;
|
||||
}
|
||||
|
||||
public void Dispose() => _chars.Dispose();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Pools;
|
||||
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
|
||||
[InterpolatedStringHandler]
|
||||
public struct PooledStringHandler : IDisposable
|
||||
{
|
||||
private PooledStringBuilder _builder;
|
||||
|
||||
public PooledStringHandler(int literalLength, int formattedCount)
|
||||
=> _builder = new(literalLength + (formattedCount * 8));
|
||||
|
||||
public void AppendLiteral(string s) => _builder.Append(s);
|
||||
|
||||
public void AppendFormatted<T>(T t)
|
||||
{
|
||||
if (typeof(T) == typeof(PooledStringBuilderHandler))
|
||||
_builder.Append(Unsafe.As<T, PooledStringBuilderHandler>(ref t));
|
||||
else
|
||||
_builder.Append(t?.ToString());
|
||||
}
|
||||
|
||||
public string GetFormattedText()
|
||||
{
|
||||
var result = _builder.ToString();
|
||||
Dispose();
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Dispose() => _builder.Dispose();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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 PerformanceFish.Planet;
|
||||
using Prepatcher;
|
||||
using RimWorld.Planet;
|
||||
|
||||
namespace PerformanceFish;
|
||||
|
||||
public static class PrepatcherFields
|
||||
{
|
||||
[PrepatcherField]
|
||||
[ValueInitializer(nameof(CreateGeneList))]
|
||||
public static extern List<Gene> GenesToTick(this Pawn_GeneTracker geneTracker);
|
||||
|
||||
// [PrepatcherField]
|
||||
// [ValueInitializer(nameof(CreateGeneList))]
|
||||
// public static extern List<Gene> EndogenesToTick(this Pawn_GeneTracker geneTracker);
|
||||
|
||||
[PrepatcherField]
|
||||
[ValueInitializer(nameof(CreateWorldPawnsCache))]
|
||||
public static extern ref WorldPawnsOptimization.Cache Cache(this WorldPawns worldPawns);
|
||||
|
||||
#if false
|
||||
[PrepatcherField]
|
||||
[ValueInitializer(nameof(CreateHediffList))]
|
||||
public static extern List<Hediff> HediffsToTick(this Pawn_HealthTracker healthTracker);
|
||||
|
||||
public static List<Hediff> CreateHediffList() => new();
|
||||
#endif
|
||||
|
||||
public static List<Gene> CreateGeneList() => new();
|
||||
public static WorldPawnsOptimization.Cache CreateWorldPawnsCache() => new();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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/.
|
||||
|
||||
extern alias nuget;
|
||||
using nuget::JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
[PublicAPI]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors | ImplicitUseTargetFlags.WithMembers)]
|
||||
public abstract class ClassWithFishPrepatches : SingletonFactory<ClassWithFishPrepatches>
|
||||
{
|
||||
public virtual FishPrepatchHolder Patches { get; }
|
||||
public virtual bool RequiresLoadedGameForPatching => false;
|
||||
|
||||
protected ClassWithFishPrepatches() => Patches = new(GetType());
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// 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;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
public abstract class FishClassPrepatch : FishPrepatchBase
|
||||
{
|
||||
public abstract Type Type { get; }
|
||||
|
||||
public virtual void FreePatch(TypeDefinition typeDefinition)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
public abstract class FishPrepatch : FishPrepatchBase
|
||||
{
|
||||
public abstract MethodBase TargetMethodBase { get; }
|
||||
|
||||
public virtual MethodInfo? PrefixMethodInfo
|
||||
=> TryGetMethod("PREFIX", static m => m.HasAttribute<HarmonyPrefix>());
|
||||
|
||||
public virtual MethodInfo? PostfixMethodInfo
|
||||
=> TryGetMethod("POSTFIX", static m => m.HasAttribute<HarmonyPostfix>());
|
||||
|
||||
public virtual void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
{
|
||||
}
|
||||
|
||||
private MethodInfo? TryGetMethod(string nameCaseInsensitive, Predicate<MethodInfo>? predicate = null)
|
||||
=> GetType().TryGetMethod(nameCaseInsensitive, predicate);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 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/.
|
||||
|
||||
extern alias nuget;
|
||||
using nuget::JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors | ImplicitUseTargetFlags.WithMembers)]
|
||||
public abstract class FishPrepatchBase : SingletonFactory<FishPrepatchBase>, IExposable, IHasDescription
|
||||
{
|
||||
private bool _enabled;
|
||||
|
||||
public virtual string? Description => null;
|
||||
|
||||
public virtual string? Name => null;
|
||||
|
||||
public virtual bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set => _enabled = value;
|
||||
}
|
||||
|
||||
public virtual bool DefaultState
|
||||
{
|
||||
[Pure] get => true;
|
||||
}
|
||||
|
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
||||
protected FishPrepatchBase() => _enabled = DefaultState;
|
||||
|
||||
public virtual void ExposeData() => Scribe_Values.Look(ref _enabled, "enabled", DefaultState);
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
// 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/.
|
||||
|
||||
extern alias nuget;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using Mono.Cecil.Rocks;
|
||||
using Mono.Collections.Generic;
|
||||
using MonoMod.Utils;
|
||||
using nuget::JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Prepatching;
|
||||
|
||||
[PublicAPI]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
||||
public class FishPrepatchHolder : IExposable, IEnumerable<FishPrepatchBase>
|
||||
{
|
||||
public ConcurrentDictionary<Type, FishPrepatchBase> All { get; } = new();
|
||||
|
||||
public T Get<T>() where T : FishPrepatchBase => (T)All[typeof(T)];
|
||||
public void Add(FishPrepatchBase patch) => All[patch.GetType()] = patch;
|
||||
public void PatchAll(ModuleDefinition module)
|
||||
{
|
||||
var patches = All.Values;
|
||||
foreach (var patch in patches)
|
||||
{
|
||||
if (!patch.Enabled)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
switch (patch)
|
||||
{
|
||||
case FishPrepatch methodPatch:
|
||||
ApplyMethodPatch(module, methodPatch);
|
||||
break;
|
||||
case FishClassPrepatch classPatch:
|
||||
ApplyClassPatch(module, classPatch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error($"Exception while applying prepatches for {PerformanceFishMod.NAME}:\n{ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyMethodPatch(ModuleDefinition module, FishPrepatch patch)
|
||||
{
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
||||
var patchMethod = patch.TargetMethodBase
|
||||
?? ThrowHelper.ThrowInvalidOperationException<MethodBase>(
|
||||
$"Patch {patch.GetType().FullName} has no target method.");
|
||||
|
||||
if (patchMethod.IsGenericMethod && patchMethod is MethodInfo info)
|
||||
patchMethod = info.GetGenericMethodDefinition();
|
||||
|
||||
var patchType = patchMethod.ReflectedType
|
||||
?? ThrowHelper.ThrowInvalidOperationException<Type>(
|
||||
$"Method {patchMethod.FullDescription()} has no declaring type.");
|
||||
|
||||
if (patchMethod.ReflectedType != patchMethod.DeclaringType)
|
||||
{
|
||||
ThrowHelper.ThrowInvalidOperationException($"Tried to apply patch on method {
|
||||
patchMethod.Name} for type {patchMethod.ReflectedType}, but it is only declared in {
|
||||
patchMethod.DeclaringType}.");
|
||||
}
|
||||
|
||||
if (patchType.IsGenericType)
|
||||
{
|
||||
patchType = patchType.GetGenericTypeDefinition();
|
||||
patchMethod = patchMethod is MethodInfo
|
||||
? AccessTools.DeclaredMethod(patchType, patchMethod.Name, patchMethod.GetParameters().GetTypes())
|
||||
: AccessTools.Constructor(patchType, patchMethod.GetParameters().GetTypes(), patchMethod.IsStatic);
|
||||
}
|
||||
|
||||
DebugLog.Message($"Applying prepatch {patch.GetType().FullName} on method {
|
||||
patchMethod.FullDescription()}");
|
||||
|
||||
var targetType = module.GetTypeDefinition(patchType) ?? ThrowForFailureToFindType(patchType);
|
||||
var targetMethod = targetType.GetMethod(patchMethod.Name, patchMethod.GetParameters().GetTypes())
|
||||
?? ThrowForFailureToFindMethod(targetType, patchMethod);
|
||||
|
||||
// PrepatchManager.AddSecurityAttributes(targetMethod.SecurityDeclarations, module, targetMethod.FullName);
|
||||
|
||||
var ilProcessor = targetMethod.Body.GetILProcessor();
|
||||
|
||||
try
|
||||
{
|
||||
patch.Transpiler(ilProcessor, module);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Exception while applying transpiler for {patch.GetType().FullName}\n{e}\n{
|
||||
new StackTrace() /*StackTraceUtility.ExtractStackTrace()*/}");
|
||||
}
|
||||
|
||||
var patchVariables = new PatchVariables();
|
||||
|
||||
if (patch.PrefixMethodInfo is { } prefixMethod)
|
||||
{
|
||||
try
|
||||
{
|
||||
ApplyPrefix(prefixMethod, ilProcessor, module, ref patchVariables);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Exception while applying prefix for {patch.GetType().FullName}\n{e}\n{
|
||||
new StackTrace() /*StackTraceUtility.ExtractStackTrace()*/}");
|
||||
}
|
||||
}
|
||||
|
||||
if (patch.PostfixMethodInfo is { } postfixMethod)
|
||||
{
|
||||
try
|
||||
{
|
||||
ApplyPostfix(postfixMethod, ilProcessor, module, ref patchVariables);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Exception while applying postfix for {patch.GetType().FullName}\n{e}\n{
|
||||
new StackTrace() /*StackTraceUtility.ExtractStackTrace()*/}");
|
||||
}
|
||||
}
|
||||
|
||||
targetMethod.Body.OptimizeMacros();
|
||||
}
|
||||
|
||||
private static void ApplyPrefix(MethodInfo prefixMethodInfo, ILProcessor ilProcessor, ModuleDefinition module,
|
||||
ref PatchVariables patchVariables)
|
||||
{
|
||||
if (prefixMethodInfo.ReturnType != typeof(bool) && prefixMethodInfo.ReturnType != typeof(void))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException($"Prefix has invalid return type: {
|
||||
prefixMethodInfo.ReturnType.FullName}. Must be void or bool");
|
||||
}
|
||||
|
||||
var instructions = ilProcessor.instructions;
|
||||
var targetMethod = ilProcessor.GetMethod();
|
||||
|
||||
var genericParameters = GetGenericParameters(targetMethod);
|
||||
var prefixParameters = prefixMethodInfo.GetParameters();
|
||||
|
||||
var resultParameter = prefixParameters.TryGetNamed("__result");
|
||||
VerifyResultParameter(module, resultParameter, targetMethod, true);
|
||||
|
||||
var stateParameter = prefixParameters.TryGetNamed("__state");
|
||||
VerifyStateParameter(stateParameter);
|
||||
|
||||
// var skipOriginalFlag = ilProcessor.DeclareLocal(typeof(bool));
|
||||
var resultVariable = GetResultVariable(ilProcessor, ref patchVariables, resultParameter, targetMethod);
|
||||
var stateVariable = GetStateVariable(ilProcessor, ref patchVariables, stateParameter);
|
||||
var startOfOriginal = instructions[0];
|
||||
|
||||
var index = 0;
|
||||
foreach (var parameter in prefixParameters)
|
||||
{
|
||||
ilProcessor.InsertAt(index++, GetInstructionForParameter(module, parameter, resultParameter, resultVariable,
|
||||
stateParameter, stateVariable, targetMethod));
|
||||
}
|
||||
|
||||
if (genericParameters != null)
|
||||
{
|
||||
ilProcessor.InsertAt(index++, OpCodes.Call,
|
||||
module.ImportMethodWithGenericArguments(prefixMethodInfo, genericParameters));
|
||||
}
|
||||
else
|
||||
{
|
||||
ilProcessor.InsertAt(index++, OpCodes.Call, prefixMethodInfo);
|
||||
}
|
||||
|
||||
if (prefixMethodInfo.ReturnType == typeof(bool))
|
||||
{
|
||||
ilProcessor.InsertAt(index++, (OpCodes.Brtrue_S, startOfOriginal));
|
||||
|
||||
if (resultVariable != null)
|
||||
{
|
||||
ilProcessor.InsertAt(index++, (OpCodes.Ldloc_S, resultVariable));
|
||||
}
|
||||
else if (targetMethod.ReturnType.FullName != module.TypeSystem.Void.FullName)
|
||||
{
|
||||
Log.Error($"Prefix {prefixMethodInfo.FullDescription()
|
||||
} with return type of bool must assign to a __result parameter");
|
||||
}
|
||||
|
||||
ilProcessor.InsertAt(index, OpCodes.Ret);
|
||||
}
|
||||
}
|
||||
|
||||
private static VariableDefinition? GetStateVariable(ILProcessor ilProcessor, ref PatchVariables patchVariables,
|
||||
ParameterInfo? stateParameter)
|
||||
=> stateParameter != null
|
||||
? patchVariables.State ??= ilProcessor.DeclareLocal(GetUnderlyingType(stateParameter.ParameterType))
|
||||
: null;
|
||||
|
||||
private static VariableDefinition? GetResultVariable(ILProcessor ilProcessor, ref PatchVariables patchVariables,
|
||||
ParameterInfo? resultParameter, MethodDefinition targetMethod)
|
||||
=> resultParameter != null
|
||||
? patchVariables.Result ??= ilProcessor.DeclareLocal(targetMethod.ReturnType)
|
||||
: null;
|
||||
|
||||
private static void VerifyStateParameter(ParameterInfo? stateParameter)
|
||||
{
|
||||
if (stateParameter is { ParameterType.IsByRef: false })
|
||||
ThrowHelper.ThrowArgumentException("__state parameter must be declared with ref or out keyword.");
|
||||
}
|
||||
|
||||
private static Collection<GenericParameter>? GetGenericParameters(MethodDefinition targetMethod)
|
||||
=> targetMethod.HasGenericParameters ? targetMethod.GenericParameters
|
||||
: targetMethod.DeclaringType.HasGenericParameters ? targetMethod.DeclaringType.GenericParameters
|
||||
: null;
|
||||
|
||||
private static Type GetUnderlyingType(Type type) => type.IsByRef ? type.GetElementType()! : type;
|
||||
|
||||
private static void ApplyPostfix(MethodInfo postfixMethodInfo, ILProcessor ilProcessor, ModuleDefinition module,
|
||||
ref PatchVariables patchVariables)
|
||||
{
|
||||
var targetMethod = ilProcessor.GetMethod();
|
||||
|
||||
if (postfixMethodInfo.ReturnType != typeof(void)
|
||||
&& !postfixMethodInfo.ReturnType.Is(targetMethod.ReturnType))
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException($"Postfix has invalid return type: {
|
||||
postfixMethodInfo.ReturnType.FullName}. Must be void or {targetMethod.ReturnType}");
|
||||
}
|
||||
|
||||
var instructions = ilProcessor.instructions;
|
||||
var genericParameters = GetGenericParameters(targetMethod);
|
||||
var postfixParameters = postfixMethodInfo.GetParameters();
|
||||
|
||||
var resultParameter = postfixParameters.TryGetNamed("__result");
|
||||
VerifyResultParameter(module, resultParameter, targetMethod, false);
|
||||
|
||||
var stateParameter = postfixParameters.TryGetNamed("__state");
|
||||
// Verify? It would make sense to match against prefix state
|
||||
|
||||
// var skipOriginalFlag = ilProcessor.DeclareLocal(typeof(bool));
|
||||
var resultVariable = GetResultVariable(ilProcessor, ref patchVariables, resultParameter, targetMethod);
|
||||
var stateVariable = GetStateVariable(ilProcessor, ref patchVariables, stateParameter);
|
||||
|
||||
var postfixInstructions = new Collection<object>();
|
||||
|
||||
if (resultVariable != null)
|
||||
postfixInstructions.Add((OpCodes.Stloc_S, resultVariable));
|
||||
|
||||
foreach (var parameter in postfixParameters)
|
||||
{
|
||||
postfixInstructions.Add(GetInstructionForParameter(module, parameter, resultParameter, resultVariable,
|
||||
stateParameter, stateVariable, targetMethod));
|
||||
}
|
||||
|
||||
if (genericParameters != null)
|
||||
{
|
||||
postfixInstructions.Add((OpCodes.Call,
|
||||
module.ImportMethodWithGenericArguments(postfixMethodInfo, genericParameters)));
|
||||
}
|
||||
else
|
||||
{
|
||||
postfixInstructions.Add((OpCodes.Call, postfixMethodInfo));
|
||||
}
|
||||
|
||||
if (postfixMethodInfo.ReturnType == typeof(void) && resultVariable != null)
|
||||
postfixInstructions.Add((OpCodes.Ldloc_S, resultVariable));
|
||||
|
||||
// TODO: use label instead of inserting multiple times
|
||||
for (var i = 0; i < instructions.Count; i++)
|
||||
{
|
||||
if (instructions[i].OpCode != OpCodes.Ret)
|
||||
continue;
|
||||
|
||||
ilProcessor.InsertRange(i, postfixInstructions);
|
||||
|
||||
i += postfixInstructions.Count;
|
||||
}
|
||||
}
|
||||
|
||||
private static ITuple GetInstructionForParameter(ModuleDefinition module, ParameterInfo parameter,
|
||||
ParameterInfo? resultParameter, VariableDefinition? resultVariable, ParameterInfo? stateParameter,
|
||||
VariableDefinition? stateVariable, MethodDefinition targetMethod)
|
||||
=> parameter == resultParameter
|
||||
? (parameter.ParameterType.IsByRef ? OpCodes.Ldloca_S : OpCodes.Ldloc_S, resultVariable)
|
||||
: parameter == stateParameter
|
||||
? (parameter.ParameterType.IsByRef ? OpCodes.Ldloca_S : OpCodes.Ldloc_S, stateVariable)
|
||||
: GetMatchingTargetParameter(targetMethod, parameter, module);
|
||||
|
||||
private static void VerifyResultParameter(ModuleDefinition module, ParameterInfo? resultParameter,
|
||||
MethodDefinition targetMethod, bool requiresByRef)
|
||||
{
|
||||
if (resultParameter is null)
|
||||
return;
|
||||
|
||||
if (targetMethod.ReturnType.IsGeneric() ? !GetUnderlyingType(resultParameter.ParameterType).IsGeneric()
|
||||
: !GetUnderlyingType(resultParameter.ParameterType).Is(targetMethod.ReturnType))
|
||||
{
|
||||
// var firstType = targetMethod.ReturnType;
|
||||
// var secondType = GetUnderlyingType(resultParameter.ParameterType);
|
||||
// Log.Message($"Type FullName: {firstType.FullName
|
||||
// }: ContainsGenericParameter: {firstType.ContainsGenericParameter
|
||||
// }, HasGenericParameters :{firstType.HasGenericParameters
|
||||
// }, IsGenericInstance: {firstType.IsGenericInstance
|
||||
// }, Type FullName: {secondType.FullName
|
||||
// } ContainsGenericParameters: {secondType.ContainsGenericParameters
|
||||
// }, IsGenericType {secondType.IsGenericType
|
||||
// }, IsConstructedGenericType: {secondType.IsConstructedGenericType}");
|
||||
|
||||
ThrowHelper.ThrowArgumentException($"__result parameter has invalid type: {
|
||||
resultParameter.ParameterType.FullName}. Must match the target's return type: {
|
||||
targetMethod.ReturnType.FullName}");
|
||||
}
|
||||
if (requiresByRef && !resultParameter.ParameterType.IsByRef)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentException(
|
||||
"__result parameter must be declared with ref or out keyword.");
|
||||
}
|
||||
}
|
||||
|
||||
private record struct PatchVariables
|
||||
{
|
||||
public VariableDefinition? Result, State;
|
||||
}
|
||||
|
||||
private static ITuple GetMatchingTargetParameter(MethodDefinition targetMethod,
|
||||
ParameterInfo parameter, ModuleDefinition module)
|
||||
{
|
||||
var targetParameters = targetMethod.Parameters;
|
||||
var targetParameter = parameter.Name == "__instance"
|
||||
? targetMethod.Body.ThisParameter
|
||||
: targetParameters.TryGetNamed(parameter.Name);
|
||||
if (targetParameter is null)
|
||||
{
|
||||
return ThrowHelper.ThrowArgumentException<ITuple>($"No parameter named {
|
||||
parameter.Name} found in target method");
|
||||
}
|
||||
|
||||
var targetParameterType = targetParameter.ParameterType;
|
||||
var patchParameterType = parameter.ParameterType;
|
||||
|
||||
return GetUnderlyingType(targetParameter).Name != GetUnderlyingType(patchParameterType).Name
|
||||
? ThrowHelper.ThrowArgumentException<ITuple>($"Parameter has invalid type: {
|
||||
patchParameterType.FullName}. Must match the target method parameter's type: {
|
||||
targetParameterType.FullName}")
|
||||
: (patchParameterType.IsByRef && !targetParameter.ParameterType.IsByReference
|
||||
? OpCodes.Ldarga_S
|
||||
: OpCodes.Ldarg_S, targetParameter);
|
||||
}
|
||||
|
||||
private static TypeReference GetUnderlyingType(ParameterInfo prefixParameter, ModuleDefinition module)
|
||||
=> module.ImportReference(GetUnderlyingType(prefixParameter.ParameterType));
|
||||
|
||||
private static TypeReference GetUnderlyingType(ParameterDefinition prefixParameter)
|
||||
=> prefixParameter.ParameterType.IsByReference ? prefixParameter.ParameterType.GetElementType()!
|
||||
: prefixParameter.ParameterType;
|
||||
|
||||
private static TypeDefinition ThrowForFailureToFindType(Type patchType)
|
||||
=> ThrowHelper.ThrowInvalidOperationException<TypeDefinition>(
|
||||
$"Failed to find type with namespace {patchType.Namespace} and name {patchType.Name}");
|
||||
|
||||
private static MethodDefinition ThrowForFailureToFindMethod(TypeDefinition? type, MethodBase patchMethod)
|
||||
=> ThrowHelper.ThrowInvalidOperationException<MethodDefinition>(
|
||||
$"Failed to find method with type {type} and name {patchMethod.Name}");
|
||||
|
||||
private static void ApplyClassPatch(ModuleDefinition module, FishClassPrepatch patch)
|
||||
{
|
||||
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
|
||||
var patchType = patch.Type
|
||||
?? ThrowHelper.ThrowInvalidOperationException<Type>(
|
||||
$"Patch {patch.GetType().FullName} has no target type.");
|
||||
|
||||
if (patchType.IsGenericType)
|
||||
patchType = patchType.GetGenericTypeDefinition();
|
||||
|
||||
DebugLog.Message($"Applying prepatch {patch.GetType().FullName} on type {
|
||||
patchType.FullName}");
|
||||
|
||||
var targetType = module.GetType(patchType.Namespace, patchType.Name) ?? ThrowForFailureToFindType(patchType);
|
||||
|
||||
// PrepatchManager.AddSecurityAttributes(targetType.SecurityDeclarations, module);
|
||||
|
||||
patch.FreePatch(targetType);
|
||||
}
|
||||
|
||||
public FishPrepatchBase this[Type type] => All[type];
|
||||
|
||||
public FishPrepatchHolder(Type type)
|
||||
{
|
||||
_type = type;
|
||||
AddPatchesRecursively(type);
|
||||
}
|
||||
|
||||
public void Scribe() => ExposeData(); // Exposable.Scribe(this, _type.FullName);
|
||||
// directly calling ExposeData prevents creating nested nodes in the config file. Looks cleaner imo.
|
||||
|
||||
public void ExposeData()
|
||||
{
|
||||
foreach (var (type, patch) in All)
|
||||
Exposable.Scribe(patch, type.FullName ?? type.Name);
|
||||
}
|
||||
|
||||
private void AddPatchesRecursively(Type type)
|
||||
{
|
||||
if (typeof(FishPrepatchBase).IsAssignableFrom(type) && !All.ContainsKey(type))
|
||||
{
|
||||
if (PerformanceFishMod.AllPrepatchClasses is { } allPatches)
|
||||
RemoveDupes(allPatches, type);
|
||||
|
||||
All.TryAdd(type, FishPrepatchBase.Get(type));
|
||||
}
|
||||
|
||||
foreach (var nestedType in type.GetNestedTypes(AccessTools.all))
|
||||
AddPatchesRecursively(nestedType);
|
||||
}
|
||||
|
||||
private void RemoveDupes(ClassWithFishPrepatches[] patches, Type type)
|
||||
{
|
||||
foreach (var patchClass in patches)
|
||||
{
|
||||
if (patchClass.GetType() == _type
|
||||
|| !patchClass.Patches.All.ContainsKey(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
patchClass.Patches.All.TryRemove(type, out _);
|
||||
Log.Warning($"Performance Fish removed a duplicate patch from {
|
||||
patchClass.GetType().FullName}. This is likely caused by no longer valid mod configs");
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<FishPrepatchBase> GetEnumerator() => All.Values.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => All.Values.GetEnumerator();
|
||||
|
||||
private Type _type;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user