Lots of new optimizations utilizing prepatcher
To start, this mod now requires Prepatcher and Fishery. New optimizations include: gene ticking improvements, world object improvements, hediff optimizations, world pawn gc optimizations, better mothballing, dds support, ContentFinder caching, additions and changes to reflection caching, as well as various other additions. Multiplayer support has been improved too, but should be considered highly experimental. The WorkGiver_DoBill bug is fixed in this build. Multiple patches aiming to improve RimWorld's load time have been added too.
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
extern alias nuget;
|
||||
using nuget::JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Patching;
|
||||
|
||||
[PublicAPI]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors | ImplicitUseTargetFlags.WithMembers)]
|
||||
public abstract class ClassWithFishPatches : SingletonFactory<ClassWithFishPatches>, IHasFishPatch
|
||||
{
|
||||
public virtual FishPatchHolder Patches { get; }
|
||||
public virtual bool RequiresLoadedGameForPatching => false;
|
||||
|
||||
protected ClassWithFishPatches() => Patches = new(GetType());
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.Patching;
|
||||
|
||||
public abstract class FirstPriorityFishPatch : FishPatch
|
||||
{
|
||||
public override int PrefixMethodPriority => Priority.First;
|
||||
public override int PostfixMethodPriority => Priority.First;
|
||||
public override int TranspilerMethodPriority => Priority.First;
|
||||
public override int FinalizerMethodPriority => Priority.First;
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
extern alias nuget;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using nuget::JetBrains.Annotations;
|
||||
using PerformanceFish.Cache;
|
||||
|
||||
namespace PerformanceFish.Patching;
|
||||
|
||||
[PublicAPI]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors | ImplicitUseTargetFlags.WithMembers)]
|
||||
public abstract class FishPatch : SingletonFactory<FishPatch>, IExposable, IHasDescription
|
||||
{
|
||||
public virtual bool ShouldBenchmark => false;
|
||||
|
||||
public virtual bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (value == _enabled)
|
||||
return;
|
||||
|
||||
_enabled = value;
|
||||
if (value)
|
||||
{
|
||||
if (ShouldBePatched)
|
||||
TryPatch();
|
||||
}
|
||||
else
|
||||
{
|
||||
var shouldBePatched = ShouldBePatched;
|
||||
TryUnpatch();
|
||||
ShouldBePatched = shouldBePatched;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
|
||||
public virtual bool DefaultState
|
||||
{
|
||||
[Pure] get => true;
|
||||
}
|
||||
|
||||
public virtual int PrefixMethodPriority => TryGetPriority(PrefixMethodInfo);
|
||||
public virtual int PostfixMethodPriority => TryGetPriority(PostfixMethodInfo);
|
||||
public virtual int TranspilerMethodPriority => TryGetPriority(TranspilerMethodInfo);
|
||||
public virtual int FinalizerMethodPriority => TryGetPriority(FinalizerMethodInfo);
|
||||
public MethodInfo? HarmonyMethodInfo => HarmonyMethodInfos.FirstOrDefault();
|
||||
public List<MethodInfo> HarmonyMethodInfos { get; } = new();
|
||||
public MethodInfo? BenchmarkHarmonyMethodInfo => BenchmarkHarmonyMethodInfos.FirstOrDefault();
|
||||
public List<MethodInfo> BenchmarkHarmonyMethodInfos { get; } = new();
|
||||
private bool Patched { get; set; }
|
||||
private bool ShouldBePatched { get; set; }
|
||||
|
||||
public virtual string? Name => null;
|
||||
public virtual string? Description => null;
|
||||
|
||||
public virtual Delegate? TargetMethodGroup => null;
|
||||
public virtual IEnumerable<Delegate>? TargetMethodGroups => null;
|
||||
public virtual Expression<Action>? TargetMethod => null;
|
||||
public virtual IEnumerable<Expression<Action>>? TargetMethods => null;
|
||||
|
||||
public virtual MethodBase? TargetMethodInfo
|
||||
=> TargetMethod != null
|
||||
? SymbolExtensions.GetMethodInfo(TargetMethod)
|
||||
: TargetMethodGroup?.Method;
|
||||
|
||||
public virtual IEnumerable<MethodBase> TargetMethodInfos
|
||||
=> _targetMethodInfos
|
||||
??= (TargetMethods?.Select(SymbolExtensions.GetMethodInfo)
|
||||
?? TargetMethodGroups?.Select(static m => m.Method))?.Cast<MethodBase>().ToArray()
|
||||
?? (TargetMethodInfo != null!
|
||||
? new[] { TargetMethodInfo }
|
||||
: Array.Empty<MethodBase>());
|
||||
|
||||
private MethodBase[]? _targetMethodInfos;
|
||||
|
||||
public virtual MethodInfo? ReversePatchMethodInfo
|
||||
=> TryGetMethod("REVERSEPATCH",
|
||||
static m => m.HasAttribute<HarmonyReversePatch>() && !m.HasAttribute<HarmonyTranspiler>());
|
||||
|
||||
public virtual MethodInfo? ReversePatchTranspilerMethodInfo
|
||||
=> TryGetMethod("REVERSEPATCHTRANSPILER",
|
||||
static m => m.HasAttribute<HarmonyTranspiler>() && m.HasAttribute<HarmonyReversePatch>());
|
||||
|
||||
public virtual MethodInfo? PrefixMethodInfo
|
||||
=> TryGetMethod("PREFIX", static m => m.HasAttribute<HarmonyPrefix>());
|
||||
|
||||
public virtual MethodInfo? PostfixMethodInfo
|
||||
=> TryGetMethod("POSTFIX", static m => m.HasAttribute<HarmonyPostfix>());
|
||||
|
||||
public virtual MethodInfo? TranspilerMethodInfo
|
||||
=> TryGetMethod("TRANSPILER",
|
||||
static m => m.HasAttribute<HarmonyTranspiler>() && !m.HasAttribute<HarmonyReversePatch>());
|
||||
|
||||
public virtual MethodInfo? FinalizerMethodInfo
|
||||
=> TryGetMethod("FINALIZER", static m => m.HasAttribute<HarmonyFinalizer>());
|
||||
|
||||
internal Type ParentType => GetType().DeclaringType;
|
||||
|
||||
internal IHasFishPatch? ParentClass
|
||||
=> PerformanceFishMod.AllPatchClasses?.FirstOrDefault(patchClass => patchClass.GetType() == ParentType);
|
||||
|
||||
internal bool IsBenchmarking => ShouldBenchmark && !FinishedBenchmarking;
|
||||
internal bool FinishedBenchmarking;
|
||||
|
||||
// ReSharper disable once VirtualMemberCallInConstructor
|
||||
protected FishPatch() => _enabled = DefaultState;
|
||||
|
||||
public virtual MethodInfo? TryPatch()
|
||||
{
|
||||
if ((ParentClass?.RequiresLoadedGameForPatching ?? false) && Current.ProgramState != ProgramState.Playing)
|
||||
return HarmonyMethodInfo;
|
||||
|
||||
ShouldBePatched = true;
|
||||
if (!Enabled || Patched)
|
||||
return HarmonyMethodInfo;
|
||||
|
||||
if (!TargetMethodInfos.Any())
|
||||
{
|
||||
Log.Error($"Tried to apply patches for {GetType().Name}, but it lacks a target method for patching.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (PrefixMethodInfo is null
|
||||
&& PostfixMethodInfo is null
|
||||
&& TranspilerMethodInfo is null
|
||||
&& FinalizerMethodInfo is null
|
||||
&& ReversePatchMethodInfo is null)
|
||||
{
|
||||
Log.Error($"Tried to apply patches for {
|
||||
GetType().Name}, but there are none. This is likely not intended.");
|
||||
return null;
|
||||
}
|
||||
|
||||
ApplyPatch();
|
||||
|
||||
return HarmonyMethodInfo;
|
||||
}
|
||||
|
||||
protected virtual void ApplyPatch()
|
||||
{
|
||||
Patched = true;
|
||||
foreach (var method in TargetMethodInfos)
|
||||
{
|
||||
DebugLog.Message($"Performance Fish is applying {GetType().Name} on {method.FullDescription()}");
|
||||
|
||||
try
|
||||
{
|
||||
if (IsBenchmarking)
|
||||
{
|
||||
Patched = false;
|
||||
|
||||
if (AccessTools.EnumeratorMoveNext(method) is { } moveNext)
|
||||
{
|
||||
BenchmarkHarmonyMethodInfos.Add(PerformanceFishMod.Harmony!.Patch(moveNext,
|
||||
prefix: new(methodof(Benchmarking.Prefix), Priority.First + 1),
|
||||
postfix: new(methodof(Benchmarking.Postfix), Priority.Last - 1)));
|
||||
}
|
||||
|
||||
BenchmarkHarmonyMethodInfos.Add(PerformanceFishMod.Harmony!.Patch(method,
|
||||
prefix: new(methodof(Benchmarking.Prefix), Priority.First + 1),
|
||||
postfix: new(methodof(Benchmarking.Postfix), Priority.Last - 1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ReversePatchMethodInfo != null)
|
||||
{
|
||||
HarmonyMethodInfos.Add(Harmony.ReversePatch(method, new(ReversePatchMethodInfo),
|
||||
ReversePatchTranspilerMethodInfo));
|
||||
}
|
||||
|
||||
if (PrefixMethodInfo != null
|
||||
|| PostfixMethodInfo != null
|
||||
|| TranspilerMethodInfo != null
|
||||
|| FinalizerMethodInfo != null)
|
||||
{
|
||||
HarmonyMethodInfos.Add(PerformanceFishMod.Harmony!.Patch(method,
|
||||
prefix: TryMakeHarmonyMethod(PrefixMethodInfo, PrefixMethodPriority),
|
||||
postfix: TryMakeHarmonyMethod(PostfixMethodInfo, PostfixMethodPriority),
|
||||
transpiler: TryMakeHarmonyMethod(TranspilerMethodInfo, TranspilerMethodPriority),
|
||||
finalizer: TryMakeHarmonyMethod(FinalizerMethodInfo, FinalizerMethodPriority)));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception while trying to patch {
|
||||
method.FullDescription()} with {PrefixMethodInfo.FullDescription()}, {
|
||||
PostfixMethodInfo.FullDescription()}, {TranspilerMethodInfo.FullDescription()}, {
|
||||
FinalizerMethodInfo.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static HarmonyMethod? TryMakeHarmonyMethod(MethodInfo? method, int priority = -1, string[]? before = null,
|
||||
string[]? after = null, bool? debug = null)
|
||||
=> method != null ? new(method, priority, before, after, debug) : null;
|
||||
|
||||
public virtual void TryUnpatch()
|
||||
{
|
||||
ShouldBePatched = false;
|
||||
if ( /*!Enabled &&*/ Patched)
|
||||
ApplyUnpatching();
|
||||
}
|
||||
|
||||
protected virtual void ApplyUnpatching()
|
||||
{
|
||||
Patched = false;
|
||||
foreach (var method in TargetMethodInfos)
|
||||
{
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
TryUnpatchMethod(method, i switch
|
||||
{
|
||||
0 => PrefixMethodInfo,
|
||||
1 => PostfixMethodInfo,
|
||||
2 => TranspilerMethodInfo,
|
||||
_ => FinalizerMethodInfo
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryUnpatchMethod(MethodBase targetMethod, MethodInfo? patchMethod)
|
||||
{
|
||||
if (patchMethod is null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
PerformanceFishMod.Harmony.Unpatch(targetMethod, patchMethod);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Performance Fish encountered an exception when unpatching {
|
||||
patchMethod.FullDescription()} from {targetMethod.FullDescription()}:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
private MethodInfo? TryGetMethod(string nameCaseInsensitive, Predicate<MethodInfo>? predicate = null)
|
||||
=> GetType().TryGetMethod(nameCaseInsensitive, predicate);
|
||||
|
||||
private static int TryGetPriority(MethodInfo? info)
|
||||
=> info?.TryGetAttribute<HarmonyPriority>()?.info.priority ?? Priority.Normal;
|
||||
|
||||
public virtual void ExposeData() => Scribe_Values.Look(ref _enabled, "enabled", DefaultState);
|
||||
|
||||
public override string ToString() => GetType().FullName;
|
||||
}
|
||||
|
||||
internal static class Benchmarking
|
||||
{
|
||||
internal static int SwitchFrame = int.MaxValue;
|
||||
internal static int EndFrame = int.MaxValue;
|
||||
|
||||
public static void Prefix(MethodBase __originalMethod, out Results __state)
|
||||
{
|
||||
__state = ByReference<MethodBase, Results>.Get.GetOrAdd(__originalMethod);
|
||||
if (__state.Stopwatch is null)
|
||||
ByReference<MethodBase, Results>.Get.GetReference(__originalMethod).Setup(__originalMethod);
|
||||
|
||||
__state.Stopwatch!.Start();
|
||||
}
|
||||
|
||||
public static void Postfix(Results __state)
|
||||
{
|
||||
__state.Stopwatch!.Stop();
|
||||
__state.Count++;
|
||||
|
||||
//Log.Message($"SwitchFrame is {SwitchFrame}, EndFrame is {EndFrame}, CurrentFrame is {Time.frameCount}");
|
||||
|
||||
//if (__state.Stopwatch.ElapsedMilliseconds > 100 || __state.Count > 100000)
|
||||
if (__state.Switched ? GenTicks.TicksGame > EndFrame : GenTicks.TicksGame > SwitchFrame)
|
||||
PrintResultsAndStop(__state);
|
||||
}
|
||||
|
||||
public static void PrintResultsAndStop(Results results)
|
||||
{
|
||||
var stopwatch = results.Stopwatch!;
|
||||
Log.Message($"{results.Method.FullDescription()} took {stopwatch.ElapsedMilliSecondsAccurate()} ms for {
|
||||
results.Count} calls. This equals {
|
||||
stopwatch.ElapsedNanoSeconds() / results.Count} ns per call on average.");
|
||||
|
||||
results.Count = 0;
|
||||
results.Switched = true;
|
||||
stopwatch.Reset();
|
||||
|
||||
var fishPatch = PerformanceFishMod.AllPatchClasses!
|
||||
.SelectMany(static patchClass => patchClass.Patches)
|
||||
.First(patch => patch.TargetMethodInfos.Contains(results.Method));
|
||||
|
||||
if (fishPatch.FinishedBenchmarking)
|
||||
{
|
||||
foreach (var target in fishPatch.TargetMethodInfos)
|
||||
{
|
||||
if (AccessTools.EnumeratorMoveNext(target) is { } moveNext)
|
||||
{
|
||||
PerformanceFishMod.Harmony!.Unpatch(moveNext, methodof(Prefix));
|
||||
PerformanceFishMod.Harmony.Unpatch(moveNext, methodof(Postfix));
|
||||
}
|
||||
|
||||
PerformanceFishMod.Harmony!.Unpatch(target, methodof(Prefix));
|
||||
PerformanceFishMod.Harmony.Unpatch(target, methodof(Postfix));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fishPatch.FinishedBenchmarking = true;
|
||||
fishPatch.TryPatch();
|
||||
}
|
||||
}
|
||||
|
||||
public class Results
|
||||
{
|
||||
public int Count;
|
||||
public Stopwatch? Stopwatch;
|
||||
public MethodBase? Method;
|
||||
public bool Switched;
|
||||
|
||||
public void Setup(MethodBase method)
|
||||
{
|
||||
Stopwatch = new();
|
||||
Method = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
namespace PerformanceFish.Patching;
|
||||
|
||||
public static class FishPatchExtensions
|
||||
{
|
||||
public static void PatchAll(this IEnumerable<FishPatch> patches)
|
||||
{
|
||||
foreach (var patch in patches)
|
||||
patch.TryPatch();
|
||||
}
|
||||
|
||||
public static void UnpatchAll(this IEnumerable<FishPatch> patches)
|
||||
{
|
||||
foreach (var patch in patches)
|
||||
patch.TryUnpatch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
extern alias nuget;
|
||||
using System.Collections.Concurrent;
|
||||
using nuget::JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Patching;
|
||||
|
||||
[PublicAPI]
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
||||
public class FishPatchHolder : IExposable, IEnumerable<FishPatch>
|
||||
{
|
||||
public ConcurrentDictionary<Type, FishPatch> All { get; } = new();
|
||||
|
||||
public T Get<T>() where T : FishPatch => (T)All[typeof(T)];
|
||||
public void Add(FishPatch patch) => All[patch.GetType()] = patch;
|
||||
public void PatchAll() => All.Values.PatchAll();
|
||||
public void UnpatchAll() => All.Values.UnpatchAll();
|
||||
public FishPatch this[Type type] => All[type];
|
||||
|
||||
public FishPatchHolder(Type type)
|
||||
{
|
||||
_type = type;
|
||||
AddPatchesRecursively(type);
|
||||
}
|
||||
|
||||
public void Scribe() => ExposeData(); // Exposable.Scribe(this, _type.FullName);
|
||||
// directly calling ExposeData prevents creating nested nodes in the config file. Looks cleaner imo.
|
||||
|
||||
public void ExposeData()
|
||||
{
|
||||
foreach (var (type, patch) in All)
|
||||
Exposable.Scribe(patch, type.FullName ?? type.Name);
|
||||
}
|
||||
|
||||
private void AddPatchesRecursively(Type type)
|
||||
{
|
||||
if (typeof(FishPatch).IsAssignableFrom(type) && !All.ContainsKey(type))
|
||||
{
|
||||
if (PerformanceFishMod.AllPatchClasses is { } allPatches)
|
||||
RemoveDupes(allPatches, type);
|
||||
|
||||
All.TryAdd(type, FishPatch.Get(type));
|
||||
}
|
||||
|
||||
foreach (var nestedType in type.GetNestedTypes(AccessTools.all))
|
||||
AddPatchesRecursively(nestedType);
|
||||
}
|
||||
|
||||
private void RemoveDupes(IHasFishPatch[] patches, Type type)
|
||||
{
|
||||
foreach (var patchClass in patches)
|
||||
{
|
||||
if (patchClass.GetType() == _type
|
||||
|| !patchClass.Patches.All.ContainsKey(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
patchClass.Patches[type].Enabled = false;
|
||||
patchClass.Patches.All.TryRemove(type, out _);
|
||||
Log.Warning($"Performance Fish removed a duplicate patch from {patchClass.GetType().FullName
|
||||
}. This is likely caused by no longer valid mod configs");
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<FishPatch> GetEnumerator() => All.Values.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => All.Values.GetEnumerator();
|
||||
|
||||
private Type _type;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
extern alias nuget;
|
||||
using nuget::JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Patching;
|
||||
|
||||
[PublicAPI]
|
||||
public interface IHasDescription
|
||||
{
|
||||
public string? Description { get; }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
extern alias nuget;
|
||||
using nuget::JetBrains.Annotations;
|
||||
|
||||
namespace PerformanceFish.Patching;
|
||||
|
||||
[PublicAPI]
|
||||
public interface IHasFishPatch
|
||||
{
|
||||
public FishPatchHolder Patches { get; }
|
||||
public bool RequiresLoadedGameForPatching { get; }
|
||||
}
|
||||
Reference in New Issue
Block a user