diff --git a/1.3/Assemblies/1Fishery.dll b/1.3/Assemblies/1Fishery.dll
index a26c4b7..d5c8445 100644
Binary files a/1.3/Assemblies/1Fishery.dll and b/1.3/Assemblies/1Fishery.dll differ
diff --git a/1.3/Assemblies/PerformanceFish.dll b/1.3/Assemblies/PerformanceFish.dll
index 2e54c87..812d07a 100644
Binary files a/1.3/Assemblies/PerformanceFish.dll and b/1.3/Assemblies/PerformanceFish.dll differ
diff --git a/1.4/Assemblies/0PrepatcherAPI.dll b/1.4/Assemblies/0PrepatcherAPI.dll
new file mode 100644
index 0000000..2306a3b
Binary files /dev/null and b/1.4/Assemblies/0PrepatcherAPI.dll differ
diff --git a/1.4/Assemblies/1Fishery.dll b/1.4/Assemblies/1Fishery.dll
index 46ac14d..25f63b3 100644
Binary files a/1.4/Assemblies/1Fishery.dll and b/1.4/Assemblies/1Fishery.dll differ
diff --git a/1.4/Assemblies/1Fishery.pdb b/1.4/Assemblies/1Fishery.pdb
new file mode 100644
index 0000000..0904abb
Binary files /dev/null and b/1.4/Assemblies/1Fishery.pdb differ
diff --git a/1.4/Assemblies/PerformanceFish.dll b/1.4/Assemblies/PerformanceFish.dll
index deecc48..2f9de8f 100644
Binary files a/1.4/Assemblies/PerformanceFish.dll and b/1.4/Assemblies/PerformanceFish.dll differ
diff --git a/1.4/Assemblies/PerformanceFish.pdb b/1.4/Assemblies/PerformanceFish.pdb
new file mode 100644
index 0000000..0c87319
Binary files /dev/null and b/1.4/Assemblies/PerformanceFish.pdb differ
diff --git a/About/About.xml b/About/About.xml
index 968c5d4..3f7aa32 100644
--- a/About/About.xml
+++ b/About/About.xml
@@ -15,13 +15,43 @@
Ludeon.RimWorld.Ideology
Ludeon.RimWorld.Biotech
UnlimitedHugs.HugsLib
+ OskarPotocki.VanillaFactionsExpanded.Core
+ Taranchuk.PerformanceOptimizer
- brrainz.harmony
- me.samboycoding.betterloading
zetrith.prepatcher
+ brrainz.harmony
+ bs.fishery
+ me.samboycoding.betterloading
Ludeon.RimWorld
bs.betterlog
+
+
+ brrainz.harmony
+ Harmony
+ steam://url/CommunityFilePage/2009463077
+ https://github.com/pardeike/HarmonyRimWorld/releases/latest
+
+
+ zetrith.prepatcher
+ Prepatcher
+ steam://url/CommunityFilePage/2934420800
+ https://github.com/Zetrith/Prepatcher
+
+
+ bs.fishery
+ Fishery
+ n/a
+ https://github.com/bbradson/Fishery
+
+
+
+ majorhoff.rimthreaded
+ telardo.NoLaggyBed
+ GwinnBleidd.MothballedAndDeadPawns
+ VSE.OskarObnoxious
+ vanillaexpanded.storytellers.rick
+
download more tps
\ No newline at end of file
diff --git a/DubsAnalyzer.xml b/DubsAnalyzer.xml
index e991d11..be4f39f 100644
--- a/DubsAnalyzer.xml
+++ b/DubsAnalyzer.xml
@@ -118,7 +118,17 @@
-
+
+ Tick methods of comps attached to Things. Any ThingWithComps can have these, and any mod can add new ones
+ Tick
+
+ Verse.ThingComp:CompTick
+ Verse.ThingComp:CompTickRare
+ Verse.ThingComp:CompTickLong
+
+
+
+
Update methods of comps attached to Things. Any ThingWithComps can have these, and any mod can add new ones
Update
@@ -127,8 +137,8 @@
Verse.ThingComp:PostDrawExtraSelectionOverlays
Verse.ThingComp:PostPrintOnto
-
-
+
+
Ticking health stuff ond other things implanted in pawn bodies
Tick
@@ -138,6 +148,31 @@
+
+ Ticking methods of abilities. Cooldown, effects, combat
+ Tick
+
+ RimWorld.Ability:AbilityTick
+ VFECore.Abilities.Ability:Tick
+
+
+
+
+ Ticking methods of precepts. Ideology stuff.
+ Tick
+
+ RimWorld.Precept:Tick
+
+
+
+
+ Ticking methods of genes. Yes, all the genes tick.
+ Tick
+
+ Verse.Gene:Tick
+
+
+
Comps attached to Hediffs. Often responsible for the actual logic
Tick
@@ -146,6 +181,14 @@
+
+ Comps attached to abilities
+ Tick
+
+ RimWorld.AbilityComp:CompTick
+
+
+
Settlements, caravans etc
Tick
diff --git a/README.md b/README.md
index 817c55b..d9db69f 100644
--- a/README.md
+++ b/README.md
@@ -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))
\ No newline at end of file
diff --git a/Source/PerformanceFish.Tests/PerformanceFish.Tests.csproj b/Source/PerformanceFish.Tests/PerformanceFish.Tests.csproj
index 1e8c988..32284c9 100644
--- a/Source/PerformanceFish.Tests/PerformanceFish.Tests.csproj
+++ b/Source/PerformanceFish.Tests/PerformanceFish.Tests.csproj
@@ -58,8 +58,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/Source/PerformanceFish.Tests/ReflectionCaching.cs b/Source/PerformanceFish.Tests/ReflectionCaching.cs
index 7ec0ba0..d68fad0 100644
--- a/Source/PerformanceFish.Tests/ReflectionCaching.cs
+++ b/Source/PerformanceFish.Tests/ReflectionCaching.cs
@@ -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(ref randomNumbers[0]), 4 * COUNT);
+ //
+ // var fishTable = new FishTable();
+ // 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();
+ 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();
+ 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(allTypes[i].TypeHandle.GetHashCode()));
+ }
+
+ for (var i = 0; i < count; i++)
+ {
+ Assert.True(allTypeHandles[i].Equals(allTypes[i].TypeHandle));
+ }
+
+ Assert.True(success);
+
+ VerifyCount(fishTable, allTypeHandles.Length);
+
+ Assert.Equal(count, addedEntries);
+ }
+
+ private static void VerifyNumbers(int[] expectedNumbers, RuntimeTypeHandle[] types,
+ FishTable fishTable, int count)
+ {
+ for (var i = 0; i < count; i++)
+ {
+ var number = expectedNumbers[i];
+ var type = types[i];
+
+ try
+ {
+ Assert.True(number.Equals(fishTable[type.Value]));
+ }
+ catch (Exception e)
+ {
+ throw new($"number: {number}, hashCode: {type.GetHashCode()}, i: {i}, size: {
+ fishTable.Count} {e}");
+ }
+ }
+ }
+
+ private static void VerifyCount(FishTable 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 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);
-
- Assert.Equal(result, field.GetValue(null));
+ System.ReflectionCaching.Test.GetValue(field, null, ref result);
+
+ 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 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 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(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 value, string label, T defaultValue, params object[] ctorArgs)
+ => $"value: {value}, label: {label}, defaultValue: {defaultValue}, ctorArgs: {
+ ctorArgs.ToStringSafeEnumerable()}";
+ }
}
\ No newline at end of file
diff --git a/Source/PerformanceFish/.editorconfig b/Source/PerformanceFish/.editorconfig
index 14c2131..7178472 100644
--- a/Source/PerformanceFish/.editorconfig
+++ b/Source/PerformanceFish/.editorconfig
@@ -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
diff --git a/Source/PerformanceFish/AccessToolsCaching.cs b/Source/PerformanceFish/AccessToolsCaching.cs
new file mode 100644
index 0000000..80912db
--- /dev/null
+++ b/Source/PerformanceFish/AccessToolsCaching.cs
@@ -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 __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 __result, bool __state)
+ {
+ if (!__state)
+ return;
+
+ UpdateCache(ref __result);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void UpdateCache(ref IEnumerable __result) => __result = _cachedTypes = __result.ToArray();
+
+ private static Type[] _cachedTypes = Type.EmptyTypes;
+ private static int _cachedAssemblyCount;
+ }
+}
\ No newline at end of file
diff --git a/Source/PerformanceFish/AnalyzerFixes.cs b/Source/PerformanceFish/AnalyzerFixes.cs
index 418f892..8dfe3b0 100644
--- a/Source/PerformanceFish/AnalyzerFixes.cs
+++ b/Source/PerformanceFish/AnalyzerFixes.cs
@@ -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",
- new[] { typeof(Type), typeof(MethodInfo) });
+ 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,14 +53,22 @@ 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)
{
- if (AccessTools.EnumeratorMoveNext(meth) is { } moveNext)
- meth = moveNext;
+ 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 PanelLogs_Postfix(IEnumerable 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;
@@ -75,7 +90,7 @@ public static class AnalyzerFixes
private static bool IsValidMethodForOverriding([NotNullWhen(true)] MethodInfo? info)
=> info is not null
- && (info.IsVirtual || info.IsAbstract || info.GetBaseDefinition().DeclaringType != info.DeclaringType);
+ && (info.IsVirtual || info.IsAbstract || info.GetBaseDefinition().DeclaringType != info.DeclaringType);
public enum Category
{
@@ -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 ProfileLog_meth
=> _profileLog_meth
- ??= AccessTools.FieldRefAccess(Reflection.FieldInfo("PerformanceAnalyzer", "Analyzer.Profiling.ProfileLog", "meth"));
+ ??= AccessTools.FieldRefAccess(Reflection.FieldInfo("PerformanceAnalyzer",
+ "Analyzer.Profiling.ProfileLog", "meth"));
+
private static AccessTools.FieldRef? _profileLog_meth;
public static Func Modbase_Harmony
=> _modbase_Harmony
- ??= (Func)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Modbase", "get_Harmony")
- .CreateDelegate(typeof(Func));
+ ??= (Func)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Modbase", "get_Harmony")!
+ .CreateDelegate(typeof(Func));
+
private static Func? _modbase_Harmony;
public static Action GUIController_AddEntry
=> _guiController_AddEntry
- ??= (Action)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "AddEntry")
- .CreateDelegate(typeof(Action));
+ ??= (Action)Reflection
+ .MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "AddEntry")!
+ .CreateDelegate(typeof(Action));
+
private static Action? _guiController_AddEntry;
public static Action GUIController_SwapToEntry
=> _guiController_SwapToEntry
- ??= (Action)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "SwapToEntry")
- .CreateDelegate(typeof(Action));
+ ??= (Action)Reflection
+ .MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "SwapToEntry")!
+ .CreateDelegate(typeof(Action));
+
private static Action? _guiController_SwapToEntry;
public static Func Utility_GetSignature
=> _utility_GetSignature
- ??= (Func)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "GetSignature")
- .CreateDelegate(typeof(Func));
+ ??= (Func)Reflection
+ .MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "GetSignature")!
+ .CreateDelegate(typeof(Func));
+
private static Func? _utility_GetSignature;
public static Func GUIController_CurrentCategory
=> _guiController_CurrentCategory
- ??= (Func)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController", "get_CurrentCategory")
- .CreateDelegate(typeof(Func));
+ ??= (Func)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.GUIController",
+ "get_CurrentCategory")!
+ .CreateDelegate(typeof(Func));
+
private static Func? _guiController_CurrentCategory;
public static Action Utility_Warn
=> _utility_Warn
- ??= (Action)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "Warn")
- .CreateDelegate(typeof(Action));
+ ??= (Action)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "Warn")!
+ .CreateDelegate(typeof(Action));
+
private static Action? _utility_Warn;
public static Action Utility_ReportException
=> _utility_ReportException
- ??= (Action)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "ReportException")
- .CreateDelegate(typeof(Action));
+ ??= (Action)Reflection
+ .MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "ReportException")!
+ .CreateDelegate(typeof(Action));
+
private static Action? _utility_ReportException;
public static Action Utility_Error
=> _utility_Error
- ??= (Action)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "Error")
- .CreateDelegate(typeof(Action));
+ ??= (Action)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "Error")!
+ .CreateDelegate(typeof(Action));
+
private static Action? _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, IEnumerable>>(GenCollection.AddRange);
+ yield return FishTranspiler.Call, IEnumerable>>(GenCollection
+ .AddRange);
+
i += 2; //removing OpCodes.Pop
}
else if (codes[i] == errorMethod)
@@ -239,24 +276,41 @@ public static class AnalyzerFixes
public static IEnumerable ParseMethod_Fixed(string info, XmlNode node)
=> node.NodeType == XmlNodeType.Comment
- ? Enumerable.Empty()
- : ParseMethod(info);
+ ? Enumerable.Empty()
+ : ParseMethod(info);
private static IEnumerable ParseMethod(string info)
{
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 ThreadSafeLogger_Error
=> _threadSafeLogger_Error
- ??= (Action)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.ThreadSafeLogger", "Error")
- .CreateDelegate(typeof(Action));
+ ??= (Action)Reflection
+ .MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.ThreadSafeLogger", "Error")!
+ .CreateDelegate(typeof(Action));
+
private static Action? _threadSafeLogger_Error;
public static Func, Type> DynamicTypeBuilder_CreateType
=> _dynamicTypeBuilder_CreateType
- ??= (Func, Type>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.DynamicTypeBuilder", "CreateType")
- .CreateDelegate(typeof(Func, Type>));
+ ??= (Func, Type>)Reflection.MethodInfo("PerformanceAnalyzer",
+ "Analyzer.Profiling.DynamicTypeBuilder", "CreateType")!
+ .CreateDelegate(typeof(Func, Type>));
+
private static Func, Type>? _dynamicTypeBuilder_CreateType;
public static Func> Utility_GetTypeMethods
=> _utility_GetTypeMethods
- ??= (Func>)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Utility", "GetTypeMethods")
- .CreateDelegate(typeof(Func>));
+ ??= (Func>)Reflection.MethodInfo("PerformanceAnalyzer",
+ "Analyzer.Profiling.Utility", "GetTypeMethods")!
+ .CreateDelegate(typeof(Func>));
+
private static Func>? _utility_GetTypeMethods;
public static AccessTools.FieldRef Entry_tip
=> _entry_tip
- ??= AccessTools.FieldRefAccess(Type.GetType("Analyzer.Profiling.Entry, PerformanceAnalyzer"), "tip");
+ ??= AccessTools.FieldRefAccess(Type.GetType("Analyzer.Profiling.Entry, PerformanceAnalyzer"),
+ "tip");
+
private static AccessTools.FieldRef? _entry_tip;
- public static Action GUIController_AddSpecificEntry => _guiController_AddSpecificEntry ??= MakeGUIController_AddEntryMethod();
+ public static Action GUIController_AddSpecificEntry
+ => _guiController_AddSpecificEntry ??= MakeGUIController_AddEntryMethod();
+
private static Action? _guiController_AddSpecificEntry;
+
private static Action MakeGUIController_AddEntryMethod()
{
var analyzer_Profiling_EntryType = Reflection.Type("PerformanceAnalyzer", "Analyzer.Profiling.Entry");
@@ -312,17 +377,19 @@ 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) }),
- "Add", 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
+ il.Emit(FishTranspiler.Argument(0)); //Category
il.Emit(FishTranspiler.Call(gUIController_Tab!)); //Tab(Category)
- il.Emit(FishTranspiler.Field(tab_entries!)); //tab.entries
+ il.Emit(FishTranspiler.Field(tab_entries!)); //tab.entries
/*il.Emit(FishTranspiler.Argument(2)); //type
il.Emit(FishTranspiler.Call(memberInfo_Name)); //type.Name
il.Emit(FishTranspiler.LoadConstant(4)); //Category.Modder
@@ -330,16 +397,20 @@ public static class AnalyzerFixes
il.Emit(FishTranspiler.LoadConstant(0)); //closeable = false
il.Emit(FishTranspiler.LoadConstant(1)); //dynGen = true
il.Emit(FishTranspiler.Call(entry_Create)); //Create(string, Category, Type, bool, bool)*/
- il.Emit(FishTranspiler.Argument(1)); //entry
- il.Emit(FishTranspiler.Argument(2)); //type
+ il.Emit(FishTranspiler.Argument(1)); //entry
+ il.Emit(FishTranspiler.Argument(2)); //type
il.Emit(FishTranspiler.Call(dictionary_Add!)); //entries.Add(Entry, Type)
il.Emit(FishTranspiler.Return);
return (Action)dynamicMethod.CreateDelegate(typeof(Action));
}
- public static Func Entry_Create => _entry_Create
- ??= (Func)Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Entry", "Create").CreateDelegate(typeof(Func));
+ public static Func Entry_Create
+ => _entry_Create
+ ??= (Func)Reflection
+ .MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.Entry", "Create")!
+ .CreateDelegate(typeof(Func));
+
private static Func? _entry_Create;
//Copied from Wiri's analyzer fork, with above fixes integrated. The steam version lacks this functionality
@@ -350,72 +421,93 @@ public static class AnalyzerFixes
foreach (var dir in ModLister.AllActiveModDirs)
{
var xmlFiles = dir.GetFiles("DubsAnalyzer.xml");
- if (xmlFiles.Length != 0)
- {
- foreach (var file in xmlFiles)
- {
- var doc = new XmlDocument();
- doc.Load(file.OpenRead());
+ if (xmlFiles.Length == 0)
+ continue;
- Parse(doc);
- }
+ foreach (var file in xmlFiles)
+ {
+ var doc = new XmlDocument();
+ doc.Load(file.OpenRead());
+
+ Parse(doc);
}
}
}
- // 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();
+ var tip = string.Empty;
+ var category = (int)Category.Modder;
+
+ ForEach(node.ChildNodes, child =>
{
- var methods = new HashSet();
- var tip = string.Empty;
- var category = (int)Category.Modder;
- ForEach(node.ChildNodes, child =>
+ switch (child.Name.ToUpperInvariant())
{
- switch (child.Name.ToLowerInvariant())
- {
- case "category":
- category = (int)Enum.Parse(typeof(Category), child.InnerText, true); //child.InnerText;
- break;
- case "tooltip":
- case "tip":
- case "description":
- tip += child.InnerText;
- break;
- case "methods":
- case "method":
- AddMethodsFromChildNodes(child, methods, ParseMethod);
- break;
- case "derivedmethods":
- AddMethodsFromChildNodes(child, methods, ParseDerivedMethods);
- break;
- case "types":
- case "type":
- AddMethodsFromChildNodes(child, methods, ParseTypeMethods);
- break;
- case "derivedtypes":
- AddMethodsFromChildNodes(child, methods, ParseDerivedTypeMethods);
- break;
- 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'");
- 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);
+ case "CATEGORY":
+ category = (int)Enum.Parse(typeof(Category), child.InnerText, true);
+ break;
+ case "TOOLTIP":
+ case "TIP":
+ case "DESCRIPTION":
+ tip += child.InnerText;
+ break;
+ case "METHODS":
+ case "METHOD":
+ AddMethodsFromChildNodes(child, methods, ParseMethod);
+ break;
+ case "DERIVEDMETHODS":
+ AddMethodsFromChildNodes(child, methods, ParseDerivedMethods);
+ break;
+ case "TYPES":
+ case "TYPE":
+ AddMethodsFromChildNodes(child, methods, ParseTypeMethods);
+ break;
+ case "DERIVEDTYPES":
+ AddMethodsFromChildNodes(child, methods, ParseDerivedTypeMethods);
+ break;
+ 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'");
+ 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;
- private static void AddMethodsFromChildNodes(XmlNode node, HashSet methods, Func> func)
- => ForEach(node.ChildNodes, childNode
- => methods.AddRange(func(childNode.InnerText)));
+ GUIController_AddSpecificEntry(category, entry, type);
+ }
+
+ private static void AddMethodsFromChildNodes(XmlNode node, HashSet methods,
+ Func> 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 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 ParseTypeMethods(string str) => Utility_GetTypeMethods(AccessTools.TypeByName(str), false);
+ private static IEnumerable ParseTypeMethods(string str)
+ => Utility_GetTypeMethods(AccessTools.TypeByName(str), false);
private static IEnumerable ParseNestedTypeMethods(string str)
{
diff --git a/Source/PerformanceFish/Cache/ByIndex.cs b/Source/PerformanceFish/Cache/ByIndex.cs
index 2de54f8..b50b7e3 100644
--- a/Source/PerformanceFish/Cache/ByIndex.cs
+++ b/Source/PerformanceFish/Cache/ByIndex.cs
@@ -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 : IList, ICollection
{
- private static ByIndex _get = new();
+ private static ByIndex _get = Utility.AddNew>();
[ThreadStatic]
private static ByIndex? _getThreadStatic;
public static ByIndex Get
- => /*UnityData.IsInMainThread ? _get
- :*/ _getThreadStatic ??= Utility.AddNew>();
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _getThreadStatic ??= Utility.AddNew>();
+ }
- public static ByIndex GetDirectly => _get;
+ public static ByIndex GetDirectly
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _get;
+ }
static ByIndex() => Utility.All.Add(_get);
@@ -112,15 +118,21 @@ public class ByIndex : IList, ICollection
public class ByIndex : ByIndex
where T_in : notnull
{
- private static ByIndex _get = new();
+ private static ByIndex _get = Utility.AddNew>();
[ThreadStatic]
private static ByIndex? _getThreadStatic;
public new static ByIndex Get
- => /*UnityData.IsInMainThread ? _get
- :*/ _getThreadStatic ??= Utility.AddNew>();
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _getThreadStatic ??= Utility.AddNew>();
+ }
- public new static ByIndex GetDirectly => _get;
+ public new static ByIndex GetDirectly
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _get;
+ }
static ByIndex() => Utility.All.Add(_get);
@@ -142,7 +154,6 @@ public class ByIndex : ByIndex
[return: MaybeNull]
public unsafe ref T_out GetReference(T_in key)
=> ref GetReference(FunctionPointers.IndexGetter.Default(key))!;
-
}
///
@@ -151,15 +162,21 @@ public class ByIndex : ByIndex
public class ByIndex : ByIndex>
where T_first : notnull where T_second : notnull
{
- private static ByIndex _get = new();
+ private static ByIndex _get = Utility.AddNew>();
[ThreadStatic]
private static ByIndex? _getThreadStatic;
public new static ByIndex Get
- => /*UnityData.IsInMainThread ? _get
- :*/ _getThreadStatic ??= Utility.AddNew>();
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _getThreadStatic ??= Utility.AddNew>();
+ }
- public new static ByIndex GetDirectly => _get;
+ public new static ByIndex GetDirectly
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _get;
+ }
static ByIndex() => Utility.All.Add(_get);
@@ -179,7 +196,7 @@ public class ByIndex : ByIndex
- where T_in : notnull
- where T_result : IIsRefreshable
+
+#pragma warning disable CS9091
+[PublicAPI]
+public record struct ByInt where T : notnull where TResult : new()
{
- private static Dictionary, T_result> _get = new();
+ private static FishTable, TResult> _get
+ = Utility.AddNew, TResult>();
+
[ThreadStatic]
- private static Dictionary, T_result>? _getThreadStatic;
+ private static FishTable, TResult>? _getThreadStatic;
- public static Dictionary, T_result> Get
- => /*UnityData.IsInMainThread ? _get
- :*/ _getThreadStatic ??= Utility.AddNew, T_result>>();
+ public static FishTable, TResult> Get
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _getThreadStatic ??= Utility.AddNew, TResult>();
+ }
- public static Dictionary, T_result> GetDirectly => _get;
+ public static FishTable, 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>(ref key));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref TResult GetOrAddReference(ByInt key) => ref Get.GetOrAddReference(key);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref TResult GetOrAddReference(ref ByInt key) => ref Get.GetOrAddReference(ref key);
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static ref TResult GetExistingReference(int key)
+ => ref Get.GetReference(Unsafe.As>(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.Default(key);
+ public unsafe ByInt(T key) => Key = FunctionPointers.IndexGetter.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();
- 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(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As>(ref key))));
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- public static unsafe ref T_result TryGetReferenceChecked(int key, out bool result)
- {
- ref var value = ref Unsafe.AsRef(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As>(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 bool Equals(ByInt other) => Key == other.Key;
- public static T_result GetValueAndRefreshNow(T_in key)
- {
- var cache = Get.TryGetValue(new(key));
- cache ??= Reflection.New();
- cache.ShouldRefreshNow = false;
- Get[new(key)] = cache.SetNewValue(key);
- return cache;
- }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override int GetHashCode() => Key;
}
-public struct ByIntRefreshable : IEquatable>
- where T_first : notnull where T_second : notnull
- where T_result : IIsRefreshable, T_result>
+[PublicAPI]
+public record struct ByInt
+ where T1 : notnull where T2 : notnull where TResult : new()
{
- private static EqualityComparer> _comparer = new Comparer();
- private class Comparer : EqualityComparer>
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override bool Equals(ByIntRefreshable x, ByIntRefreshable y)
- => x.First == y.First
- && x.Second == y.Second;
+ private static FishTable, TResult> _get
+ = Utility.AddNew, TResult>();
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override int GetHashCode(ByIntRefreshable obj)
- => HashCode.Combine(obj.First, obj.Second);
- }
-
- private static Dictionary, T_result> _get = new(_comparer);
[ThreadStatic]
- private static Dictionary, T_result>? _getThreadStatic;
+ private static FishTable, TResult>? _getThreadStatic;
- public static Dictionary, T_result> Get
- => /*UnityData.IsInMainThread ? _get
- :*/ _getThreadStatic ??= Utility.AddNew, T_result>>(_comparer);
-
- public static Dictionary, T_result> GetDirectly => _get;
-
- static ByIntRefreshable() => Utility.All.Add(_get);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe ref T_result GetValue(ref ByIntRefreshable key)
+ public static FishTable, TResult> Get
{
- // CS8347 without this crap
- ref var cache = ref Unsafe.AsRef(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)));
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _getThreadStatic ??= Utility.AddNew, TResult>();
+ }
- if (Unsafe.IsNullRef(ref cache))
+ private static ref FishTable, TResult> GetCacheRef
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
{
- Get[key] = Reflection.New();
- cache = ref Unsafe.AsRef(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref key)))!;
- goto RefreshCache;
+ _getThreadStatic ??= Utility.AddNew, TResult>();
+ return ref _getThreadStatic!;
}
-
- if (!cache.ShouldRefreshNow)
- return ref cache!;
-
- RefreshCache:
- cache.ShouldRefreshNow = false;
- Get[key] = cache.SetNewValue(key);
-
- return ref cache!;
}
+ public static FishTable, TResult> GetDirectly
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _get;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref TResult GetOrAddReference(int first, int second)
+ => ref Get.GetOrAddReference(new() { First = first, Second = second });
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ref TResult GetOrAddReference(in ByInt key)
+ => ref Get.GetOrAddReference(ref Unsafe.AsRef(in key));
+
+ // [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ // public static ref TResult GetOrAddReference(long key) // different, worse, GetHashCode method
+ // => ref Unsafe.As, TResult>, FishTable>(ref GetCacheRef)
+ // .GetOrAddReference(key);
+
[MethodImpl(MethodImplOptions.NoInlining)]
- public static unsafe ref T_result TryGetReferenceUnsafe(int second, int first)
- => ref Unsafe.AsRef(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As>(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(Unsafe.AsPointer(ref Get.TryGetReferenceUnsafe(ref Unsafe.As>(ref first))));
- result = !Unsafe.IsNullRef(ref value) && !value.ShouldRefreshNow;
- return ref value!;
+ First = FunctionPointers.IndexGetter.Default(first);
+ Second = FunctionPointers.IndexGetter.Default(second);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool TryGetValue(ByIntRefreshable key, [MaybeNullWhen(false)] out T_result value)
- => Get.TryGetValue(key, out value)
- && !value.ShouldRefreshNow;
-
- public static T_result GetValueAndRefreshNow(ByIntRefreshable key)
- {
- var cache = Get.TryGetValue(ref key);
- cache ??= Reflection.New();
- 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.Default(first);
- Second = FunctionPointers.IndexGetter.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 other)
- => First == other.First
- && Second == other.Second;
+ public bool Equals(ByInt other)
+ => Unsafe.As, long>(ref this) == Unsafe.As, 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 cache
- && Equals(cache);
-
- public static bool operator ==(ByIntRefreshable left, ByIntRefreshable right)
- => left.Equals(right);
-
- public static bool operator !=(ByIntRefreshable left, ByIntRefreshable right)
- => !(left == right);
+ public override int GetHashCode() => HashCode.Combine(First, Second);
}
-public struct ByInt : IEquatable>
- where T_first : notnull where T_second : notnull where T_third : notnull
- where T_result : IIsRefreshable, T_result>
+[PublicAPI]
+public record struct ByInt
+ where T1 : notnull where T2 : notnull where T3 : notnull where TResult : new()
{
- private static Dictionary, T_result> _get = new();
+ private static FishTable, TResult> _get
+ = Utility.AddNew, TResult>();
+
[ThreadStatic]
- private static Dictionary, T_result>? _getThreadStatic;
+ private static FishTable