diff --git a/1.3/Assemblies/1Fishery.dll b/1.3/Assemblies/1Fishery.dll
deleted file mode 100644
index d5c8445..0000000
Binary files a/1.3/Assemblies/1Fishery.dll and /dev/null differ
diff --git a/1.3/Assemblies/PerformanceFish.dll b/1.3/Assemblies/PerformanceFish.dll
deleted file mode 100644
index 812d07a..0000000
Binary files a/1.3/Assemblies/PerformanceFish.dll and /dev/null differ
diff --git a/1.3/Assemblies/System.Runtime.CompilerServices.Unsafe.dll b/1.3/Assemblies/System.Runtime.CompilerServices.Unsafe.dll
deleted file mode 100644
index c5ba4e4..0000000
Binary files a/1.3/Assemblies/System.Runtime.CompilerServices.Unsafe.dll and /dev/null differ
diff --git a/1.4/Assemblies/0PrepatcherAPI.dll b/1.4/Assemblies/0PrepatcherAPI.dll
deleted file mode 100644
index d869ece..0000000
Binary files a/1.4/Assemblies/0PrepatcherAPI.dll and /dev/null differ
diff --git a/1.4/Assemblies/1Fishery.dll b/1.4/Assemblies/1Fishery.dll
deleted file mode 100644
index 25f63b3..0000000
Binary files a/1.4/Assemblies/1Fishery.dll and /dev/null differ
diff --git a/1.4/Assemblies/1Fishery.pdb b/1.4/Assemblies/1Fishery.pdb
deleted file mode 100644
index 0904abb..0000000
Binary files a/1.4/Assemblies/1Fishery.pdb and /dev/null differ
diff --git a/1.4/Assemblies/PerformanceFish.dll b/1.4/Assemblies/PerformanceFish.dll
index 52892d3..ddf6940 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 a6e4688..76ee268 100644
Binary files a/1.4/Assemblies/PerformanceFish.pdb and b/1.4/Assemblies/PerformanceFish.pdb differ
diff --git a/1.4/Assemblies/System.Runtime.CompilerServices.Unsafe.dll b/1.4/Assemblies/System.Runtime.CompilerServices.Unsafe.dll
deleted file mode 100644
index c5ba4e4..0000000
Binary files a/1.4/Assemblies/System.Runtime.CompilerServices.Unsafe.dll and /dev/null differ
diff --git a/1.4/Defs/GasDefs.xml b/1.4/Defs/GasDefs.xml
new file mode 100644
index 0000000..6d2ec84
--- /dev/null
+++ b/1.4/Defs/GasDefs.xml
@@ -0,0 +1,42 @@
+
+
+
+ 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.4/Defs/MainButtonDefs.xml b/1.4/Defs/MainButtonDefs.xml
new file mode 100644
index 0000000..ab0b83a
--- /dev/null
+++ b/1.4/Defs/MainButtonDefs.xml
@@ -0,0 +1,14 @@
+
+
+
+
\ No newline at end of file
diff --git a/About/About.xml b/About/About.xml
index 3f7aa32..9e3e869 100644
--- a/About/About.xml
+++ b/About/About.xml
@@ -3,7 +3,6 @@
Performance Fish
bs.performance
- 1.3
1.4
bradson
diff --git a/Source/PerformanceFish/AccessToolsCaching.cs b/Source/PerformanceFish/AccessToolsCaching.cs
index 80912db..ed46b9f 100644
--- a/Source/PerformanceFish/AccessToolsCaching.cs
+++ b/Source/PerformanceFish/AccessToolsCaching.cs
@@ -7,9 +7,9 @@ using System.Linq;
namespace PerformanceFish;
-public class AccessToolsCaching : ClassWithFishPatches
+public sealed class AccessToolsCaching : ClassWithFishPatches
{
- public class AllTypes : FishPatch
+ public sealed class AllTypes : FishPatch
{
public override string? Description { get; } = "Caches AccessTools.AllTypes to only recalculate when the "
+ "loaded assembly count changes. Improves load times.";
diff --git a/Source/PerformanceFish/AllocationProfiling.cs b/Source/PerformanceFish/AllocationProfiling.cs
new file mode 100644
index 0000000..7a70edd
--- /dev/null
+++ b/Source/PerformanceFish/AllocationProfiling.cs
@@ -0,0 +1,207 @@
+// 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 notWorking
+
+using System.Linq;
+using System.Reflection.Emit;
+using JetBrains.Annotations;
+
+namespace PerformanceFish;
+
+[UsedImplicitly]
+public sealed class AllocationProfiling : ClassWithFishPatches
+{
+ public override bool RequiresLoadedGameForPatching => false;
+
+ public static bool Active
+ {
+ get => _active;
+ set
+ {
+ return;
+ _active = value;
+ FishPatch.Get().Enabled = value;
+ }
+ }
+
+ public static FishTable Data = new();
+ private static bool _active;
+
+ ///
+ /// The idea here was to patch object..ctor, fetch the type, count calls and get the allocation count for every
+ /// type by doing so, while ignoring structs which are expected to not construct as full object with type.
+ /// This however doesn't work. The IL method bodies of class constructors do contain calls to object..ctor, but
+ /// they appear to only go through for static constructor invocations and literal object construction. Everything
+ /// else somehow skips the call.
+ ///
+ public sealed class ObjectPatch : FishPatch
+ {
+ public override bool DefaultState => true;
+
+ public override MethodBase? TargetMethodInfo { get; }
+ = MainTabWindow._objectConstructor;
+ // = AccessTools.Constructor(typeof(NotAClass));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void P0stf1x(object __instance)
+ {
+ CallCount++;
+
+ ref var typeData = ref Data.GetOrAddReference(__instance.GetType());
+ typeData.Count++;
+
+ // if ((typeData.Count & 1023) != 0)
+ // return;
+
+ typeData.StackTraces.GetOrAddReference(StackTraceUtility.ExtractStackTrace())++;
+ }
+
+ public static CodeInstructions Transpiler(CodeInstructions instructions)
+ {
+ var codes = instructions.AsOrToList();
+ Log.Message(string.Join("\n", codes));
+ var retIndex = codes.FindIndex(static code => code.opcode == OpCodes.Ret);
+ if (retIndex == -1)
+ retIndex = codes.Count - 1;
+
+ codes.Insert(retIndex, new(OpCodes.Ldarg_0));
+ codes.Insert(retIndex + 1, new(OpCodes.Call, methodof(P0stf1x)));
+
+ return codes;
+ }
+
+ // ReSharper disable once VirtualMemberCallInConstructor
+ //private ObjectPatch() => TryPatch();
+
+ public static class NotAClass
+ {
+
+ }
+ }
+
+ public static ulong CallCount;
+
+ public record struct TypeData
+ {
+ public int Count;
+ public FishTable StackTraces = new();
+
+ [UsedImplicitly]
+ public TypeData()
+ {
+ }
+ }
+
+ [UsedImplicitly]
+ public sealed class MainTabWindow : RimWorld.MainTabWindow
+ {
+ public MainTabWindow()
+ {
+ draggable = true;
+ resizeable = true;
+ absorbInputAroundWindow = false;
+ closeOnClickedOutside = false;
+ }
+
+ private GUIScope.ScrollViewStatus _scrollViewStatus = new();
+ private Listing_Standard _listing = new();
+
+ public override void PreOpen()
+ {
+ Active = true;
+ base.PreOpen();
+ }
+
+ public override void PostClose()
+ {
+ base.PostClose();
+ Active = false;
+ }
+
+ internal static MethodBase _objectConstructor
+ = (MethodBase)MethodBodyReader.GetInstructions(null, AccessTools.DeclaredConstructor(typeof(PreceptComp)))
+ .First(static code => code.opcode == OpCodes.Call
+ && code.operand is MethodBase method
+ && method.Name.Contains("ctor"))
+ .operand;
+ // = typeof(object).GetMembers(AccessTools.all).OfType()
+ // .First(static member => member.Name.Contains("ctor"));
+ // = AccessTools.Constructor(typeof(ObjectPatch.NotAClass));
+
+ public override void DoWindowContents(Rect inRect)
+ {
+ absorbInputAroundWindow = false;
+ closeOnClickedOutside = false;
+
+ if (GUIHelper.CanSkip)
+ return;
+
+ Widgets.Label(inRect with { height = Text.LineHeight }, $"Data: {Data}, count: {
+ Data.Count.ToStringCached()}, callCount: {CallCount.ToString()}");
+ inRect.yMin += Text.LineHeight;
+
+ var objectPatches = Harmony.GetPatchInfo(_objectConstructor);
+
+ var patchWidgetLabel = $"Patches on {_objectConstructor.FullDescription()}: pre {
+ objectPatches?.Prefixes?.Select(static patch => patch.PatchMethod.FullDescription()).ToStringSafeEnumerable()}, post: {
+ objectPatches?.Postfixes?.Select(static patch => patch.PatchMethod.FullDescription()).ToStringSafeEnumerable()}, trans: {
+ objectPatches?.Transpilers?.Select(static patch => patch.PatchMethod.FullDescription()).ToStringSafeEnumerable()}, final: {
+ objectPatches?.Finalizers?.Select(static patch => patch.PatchMethod.FullDescription()).ToStringSafeEnumerable()}, Active? {
+ Active.ToString()}, Enabled? {FishPatch.Get().Enabled.ToString()}";
+
+ var patchWidgetHeight = Text.CalcHeight(patchWidgetLabel, inRect.width);
+ Widgets.Label(inRect with { height = patchWidgetHeight }, patchWidgetLabel);
+ inRect.yMin += patchWidgetHeight;
+
+ var objectMembers = typeof(object).GetMembers(AccessTools.all).OfType()
+ .Where(static member => member.Name.Contains("ctor"));
+ var objectMembersLabel = $"object members:\n{string.Join("\n --- ", objectMembers.Select(static member
+ => member.FullDescription()))}";
+ var objectMembersHeight = Text.CalcHeight(objectMembersLabel, inRect.width);
+ Widgets.Label(inRect with { height = objectMembersHeight }, objectMembersLabel);
+ inRect.yMin += objectMembersHeight;
+
+ using var scrollView = new GUIScope.ScrollableListingStandard(inRect, _scrollViewStatus, _listing);
+ var listing = _listing;
+
+ using var keyValuePairsPooled = new PooledArray>(Data);
+ keyValuePairsPooled.Sort(_typeDataComparer);
+
+ var keyValuePairs = keyValuePairsPooled.Array;
+ var typeCount = keyValuePairsPooled.Length;
+
+ for (var i = 0; i < typeCount; i++)
+ {
+ if (scrollView.TryCull(Text.LineHeight))
+ continue;
+
+ var kvp = keyValuePairs[i];
+ var labelRect = listing.GetRect(Text.LineHeight);
+
+ Widgets.Label(labelRect,
+ string.Concat(kvp.Key, " ", kvp.Value.Count.ToStringCached()));
+
+ if (Mouse.IsOver(labelRect))
+ {
+ var traces = kvp.Value.StackTraces;
+ using var traceArray = new PooledArray>(traces);
+ traceArray.Sort(_traceComparer);
+ // listing.Label(traceArray.Array[0].Key);
+ listing.Label(traceArray.FirstOrDefault(static kvp => !kvp.Key.Contains("cctor")).Key ?? "NULL");
+ // TooltipHandler.TipRegion(labelRect,
+ // string.Join("\n", traceArray.Select(static kvp => kvp.Key)));
+ }
+ }
+ }
+
+ private static IComparer> _typeDataComparer
+ = Comparer>.Create(static (x, y) => x.Value.Count.CompareTo(y.Value.Count));
+
+ private static IComparer> _traceComparer
+ = Comparer>.Create(static (x, y) => x.Value.CompareTo(y.Value));
+ }
+}
+#endif
\ No newline at end of file
diff --git a/Source/PerformanceFish/AnalyzerFixes.cs b/Source/PerformanceFish/AnalyzerFixes.cs
index 8dfe3b0..02084af 100644
--- a/Source/PerformanceFish/AnalyzerFixes.cs
+++ b/Source/PerformanceFish/AnalyzerFixes.cs
@@ -15,17 +15,31 @@ public static class AnalyzerFixes
{
public static void Patch()
{
- var modBase = AccessTools.Constructor(Reflection.Type("PerformanceAnalyzer", "Analyzer.Modbase"),
- new[] { typeof(ModContentPack) });
- if (modBase is null)
- return /*false*/;
+ var entryPoint = AccessTools.DeclaredMethod(ModCompatibility.Types.PerformanceAnalyzer.Window_Analyzer,
+ "LoadEntries") as MethodBase;
- var success = PerformanceFishMod.Harmony!.Patch(modBase, postfix: new(methodof(XmlParser.CollectXmlData)))
- != null;
+ var prefixEntryPoint = false;
+ if (entryPoint != null)
+ {
+ prefixEntryPoint = true;
+ }
+ else
+ {
+ entryPoint = AccessTools.DeclaredConstructor(ModCompatibility.Types.PerformanceAnalyzer.ModBase,
+ [typeof(ModContentPack)]);
+ }
+
+ if (entryPoint is null)
+ return /*false*/;
+
+ var entryPatchMethod = new HarmonyMethod(methodof(XmlParser.CollectXmlData));
+
+ var success = PerformanceFishMod.Harmony!.Patch(entryPoint, prefix: prefixEntryPoint ? entryPatchMethod : null,
+ postfix: prefixEntryPoint ? null : entryPatchMethod) != null;
var methodTransplanting = Reflection.MethodInfo("PerformanceAnalyzer", "Analyzer.Profiling.MethodTransplanting",
"UpdateMethod",
- new[] { typeof(Type), typeof(MethodInfo) });
+ [typeof(Type), typeof(MethodInfo)]);
if (methodTransplanting is null
|| PerformanceFishMod.Harmony.Patch(methodTransplanting, transpiler: new(methodof(UpdateMethod_Transpiler)))
@@ -145,7 +159,7 @@ public static class AnalyzerFixes
public static class OverridingMethodUtility
{
- public static HashSet PatchedOverrides = new();
+ public static HashSet PatchedOverrides = [];
public static void ClearCaches() => PatchedOverrides.Clear();
@@ -247,7 +261,7 @@ public static class AnalyzerFixes
public static CodeInstructions Parse_Transpiler(CodeInstructions CodeInstructions)
{
- var codes = CodeInstructions.ToList();
+ var codes = CodeInstructions.AsOrToList();
var parseMethod = FishTranspiler.Call("PerformanceAnalyzer", "Analyzer.Profiling.XmlParser", "ParseMethod");
var errorMethod = FishTranspiler.Call("PerformanceAnalyzer", "Analyzer.Profiling.ThreadSafeLogger", "Error");
@@ -357,7 +371,7 @@ public static class AnalyzerFixes
private static Action MakeGUIController_AddEntryMethod()
{
- var analyzer_Profiling_EntryType = Reflection.Type("PerformanceAnalyzer", "Analyzer.Profiling.Entry");
+ var analyzer_Profiling_EntryType = ModCompatibility.Types.PerformanceAnalyzer.ProfilingEntry;
if (analyzer_Profiling_EntryType == null)
Log.Error("analyzer_Profiling_EntryType is null");
@@ -378,13 +392,13 @@ public static class AnalyzerFixes
Log.Error("entry_Create is null");*/
var dictionary_Add = AccessTools.Method(
- typeof(Dictionary<,>).MakeGenericType(new[] { analyzer_Profiling_EntryType, typeof(Type) }),
- "Add", new[] { analyzer_Profiling_EntryType, typeof(Type) });
+ typeof(Dictionary<,>).MakeGenericType([analyzer_Profiling_EntryType, typeof(Type)]),
+ "Add", [analyzer_Profiling_EntryType, typeof(Type)]);
if (dictionary_Add == null)
Log.Error("dictionary_Add is null");
var dynamicMethod = new DynamicMethod("GUIController_AddEntryMethod", typeof(void),
- new[] { typeof(int), typeof(object), typeof(Type) });
+ [typeof(int), typeof(object), typeof(Type)]);
var il = dynamicMethod.GetILGenerator();
il.Emit(FishTranspiler.Argument(0)); //Category
@@ -416,6 +430,7 @@ public static class AnalyzerFixes
//Copied from Wiri's analyzer fork, with above fixes integrated. The steam version lacks this functionality
public static class XmlParser
{
+ [SuppressMessage("Security", "CA3075")]
public static void CollectXmlData()
{
foreach (var dir in ModLister.AllActiveModDirs)
@@ -484,7 +499,7 @@ public static class AnalyzerFixes
var type = DynamicTypeBuilder_CreateType(node.Name, methods);
var entry = Entry_Create(node.Name, category, type, false, true);
- if (tip != string.Empty)
+ if (!string.IsNullOrEmpty(tip))
Entry_tip(entry) = tip;
GUIController_AddSpecificEntry(category, entry, type);
diff --git a/Source/PerformanceFish/Cache/BitCellGrid.cs b/Source/PerformanceFish/Cache/BitCellGrid.cs
new file mode 100644
index 0000000..9e99ea6
--- /dev/null
+++ b/Source/PerformanceFish/Cache/BitCellGrid.cs
@@ -0,0 +1,43 @@
+// Copyright (c) 2023 bradson
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+namespace PerformanceFish.Cache;
+
+public sealed class BitCellGrid
+{
+ private long[] _innerArray;
+ private CellIndices _cellIndices;
+
+ public BitCellGrid(Map map)
+ {
+ _innerArray = null!;
+ _cellIndices = null!;
+ map.InvokeWhenCellIndicesReady(Initialize);
+ }
+
+ private void Initialize(Map map)
+ {
+ _cellIndices = map.cellIndices;
+ _innerArray = new long[(Math.Max(0, _cellIndices.NumGridCells - 1) >> 6) + 1];
+ }
+
+ public bool this[int index]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => (_innerArray[index >> 6] & (1L << (index & 63))) != 0L;
+ set
+ {
+ ref var bucket = ref _innerArray[index >> 6];
+ bucket ^= (-(long)value.AsInt() ^ bucket) & (1L << (index & 63));
+ }
+ }
+
+ public bool this[in IntVec3 c]
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this[c.CellToIndex(_cellIndices)];
+ set => this[c.CellToIndex(_cellIndices)] = value;
+ }
+}
\ No newline at end of file
diff --git a/Source/PerformanceFish/Cache/ByIndex.cs b/Source/PerformanceFish/Cache/ByIndex.cs
index b50b7e3..600525d 100644
--- a/Source/PerformanceFish/Cache/ByIndex.cs
+++ b/Source/PerformanceFish/Cache/ByIndex.cs
@@ -194,7 +194,7 @@ public class ByIndex : ByIndex where T : notnull where TResult : new()
{
- private static FishTable, TResult> _get
- = Utility.AddNew, TResult>();
+ public int Key;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe ByInt(T key) => Key = FunctionPointers.IndexGetter.Default(key);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ByInt(int key) => Key = key;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(ByInt other) => Key == other.Key;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override int GetHashCode() => Key;
+
+ private static object _valueInitializerLock = new();
+
+ 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
@@ -40,6 +75,15 @@ public record struct ByInt where T : notnull where TResult : new()
[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));
@@ -47,19 +91,8 @@ public record struct ByInt where T : notnull where TResult : new()
[MethodImpl(MethodImplOptions.NoInlining)]
public static ref TResult GetExistingReference(T key) => ref Get.GetReference(new(key));
- public int Key;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public unsafe ByInt(T key) => Key = FunctionPointers.IndexGetter.Default(key);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ByInt(int key) => Key = key;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool Equals(ByInt other) => Key == other.Key;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public override int GetHashCode() => Key;
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static FishTable, TResult> InitializeNew() => Utility.AddNew(ValueInitializer);
}
[PublicAPI]
diff --git a/Source/PerformanceFish/Cache/ByMap.cs b/Source/PerformanceFish/Cache/ByMap.cs
index ed800bc..7c02aeb 100644
--- a/Source/PerformanceFish/Cache/ByMap.cs
+++ b/Source/PerformanceFish/Cache/ByMap.cs
@@ -29,9 +29,9 @@ public static class ByMap where T : new()
private static Func