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