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:
bbradson
2023-06-03 20:24:38 +02:00
parent cb0a407425
commit ca9321add8
125 changed files with 11272 additions and 3681 deletions
+177 -71
View File
@@ -1,55 +1,79 @@
// Copyright (c) 2022 bradson
// Copyright (c) 2023 bradson
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
using System.Linq;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using Mono.Cecil;
using Mono.Cecil.Cil;
using PerformanceFish.Prepatching;
using static System.Reflection.Emit.OpCodes;
namespace PerformanceFish;
public class MiscOptimizations : ClassWithFishPatches
{
public class GenList_ListFullCopy_Patch : FishPatch
{
public override void TryUnpatch() { } // prevent unpatching
public override string Description => "Minor optimization. Requires a full restart to toggle off due to a bug in harmony";
public override Delegate TargetMethodGroup => GenList.ListFullCopy<object>; // harmony patches for all T types
public class MiscPrepatchedOptimizations : ClassWithFishPrepatches
{
public class GenList_ListFullCopy : FishPrepatch
{
public override string Description { get; }
= "Optimization for a basic and frequently used list copying method. Replaces a for loop with an "
+ "optimized IL instruction.";/*{(ActiveMods.GradientHair
? " Disabled due to incompatibility with Gradient Hair" : "")}";*/
// public override bool Enabled => base.Enabled && !ActiveMods.GradientHair;
public override MethodBase TargetMethodBase { get; } = methodof(GenList.ListFullCopy<object>);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ListFullCopy<object>/*Utility.CollectionExtensions.Copy<object>*/);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Prefix(IList source, ref IList __result)
=> source is null || source.Count == 0
|| (__result = FisheryLib.CollectionExtensions.TryNew(source, source.GetType().GetGenericArguments()[0])) is null;
public static List<T> ListFullCopy<T>(List<T> source) => source.Copy();
}
public class WindManager_WindManagerTick_Patch : FishPatch
public class GenCollection_FirstOrDefault : FishPrepatch
{
public override string Description => "Throttles plant sway to update at most once per frame instead of every tick. Also fixes the plant sway setting to actually disable plant sway calculations when it should. " +
"By default it simply sets values to 0 but then still goes through everything, sacrificing tps for no reason";
public override Expression<Action> TargetMethod => () => default(WindManager)!.WindManagerTick();
public override string? Description { get; }
= "Minor optimization for a RimWorld method used for list lookups, by looping in a more efficient manner "
+ "with fewer bounds checks";
public static CodeInstructions? Transpiler(CodeInstructions codes)
public override MethodBase TargetMethodBase { get; } = methodof(GenCollection.FirstOrDefault<object>);
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(FirstOrDefault<object>);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T? FirstOrDefault<T>(List<T> list, Predicate<T> predicate) => list.FirstOrDefaultFast(predicate);
}
}
public class MiscOptimizations : ClassWithFishPatches
{
public class WindManager_WindManagerTick : FishPatch
{
public override string Description { get; }
= "Throttles plant sway to update at most once per frame instead of every tick. Also fixes the plant sway "
+ "setting to actually disable plant sway calculations when it should. By default it simply sets values to "
+ "0 but then still goes through everything, sacrificing tps for no reason";
public override Expression<Action> TargetMethod { get; } = static () => default(WindManager)!.WindManagerTick();
public static CodeInstructions Transpiler(CodeInstructions instructions)
{
var find_CurrentMap = FishTranspiler.CallPropertyGetter(typeof(Find), nameof(Find.CurrentMap));
var find_CurrentMap = FishTranspiler.PropertyGetter(typeof(Find), nameof(Find.CurrentMap));
var this_map = FishTranspiler.Field(typeof(WindManager), nameof(WindManager.map));
return codes.ReplaceAt(
(codes, i)
return instructions.ReplaceAt((codes, i)
=> i - 3 >= 0
&& codes[i - 3] == find_CurrentMap
&& codes[i - 1] == this_map
&& codes[i].operand is Label, // goto return;
code
static code
=> new[]
{
code,
FishTranspiler.Call(ShouldSway),
FishTranspiler.IfFalse_Short((Label)code.operand)
}
);
code, FishTranspiler.Call(ShouldSway), FishTranspiler.IfFalse_Short((Label)code.operand)
});
}
public static bool ShouldSway()
@@ -71,29 +95,101 @@ public class MiscOptimizations : ClassWithFishPatches
private static bool _lastPrefValue;
}
public class DrawBatch_Flush_Patch : FishPatch
public class CompRottable : FishPatch
{
public override string Description => "Optimization of a fleck rendering method. Low - medium performance impact";
public override Expression<Action> TargetMethod => () => default(DrawBatch)!.Flush(default);
public override string? Description { get; }
= "Throttles rotting on misconfigured defs to only recalculate every 256 ticks, instead of constantly.";
public static CodeInstructions? Transpiler(CodeInstructions CodeInstructions, ILGenerator generator)
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(RimWorld.CompRottable), nameof(RimWorld.CompRottable.CompTick));
public static CodeInstructions Transpiler(CodeInstructions codes, ILGenerator generator)
=> Reflection.GetCodeInstructions(Replacement, generator);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Replacement(RimWorld.CompRottable instance)
{
var codes = CodeInstructions.ToList();
if ((TickHelper.TicksGame & 255) == (instance.parent.thingIDNumber & 255))
instance.Tick(256);
}
}
var instance_tmpPropertyBlocks = FishTranspiler.Field(typeof(DrawBatch), nameof(DrawBatch.tmpPropertyBlocks));
var instance_tmpPropertyBlocks_instance_propertyBlockCache_AddRange = FishTranspiler.Call<Action<HashSet<DrawBatchPropertyBlock>, List<DrawBatchPropertyBlock>>>(GenCollection.AddRange);
var instance_tmpPropertyBlocks_key_propertyBlock_Add = FishTranspiler.Call(new HashSet<DrawBatchPropertyBlock>().Add);
public class RitualObligationTrigger_Date : FirstPriorityFishPatch
{
public override string? Description { get; }
= "Literally just reorders instructions in this method. They were nonsensical, discarding 90% of the "
+ "computation.";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(RimWorld.RitualObligationTrigger_Date),
nameof(RimWorld.RitualObligationTrigger_Date.Tick));
public static CodeInstructions Transpiler(CodeInstructions codes, ILGenerator generator)
=> Reflection.GetCodeInstructions(Replacement, generator);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Replacement(RimWorld.RitualObligationTrigger_Date instance)
{
if (instance.ritual.isAnytime)
return;
if ((!instance.mustBePlayerIdeo || Faction.OfPlayerSilentFail.ideos.Has(instance.ritual.ideo))
&& instance.CurrentTickRelative() == instance.OccursOnTick())
{
instance.ritual.AddObligation(new(instance.ritual));
}
}
}
public class ReportProbablyMissingAttributesFix : FishPatch
{
public override string? Description { get; }
= """Fixes the "probably needs a StaticConstructorOnStartup attribute" log warning to display full """
+ "type names to help identify what mod it's actually from.";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.FindIncludingInnerTypes(typeof(StaticConstructorOnStartupUtility),
static t => AccessTools.FirstMethod(t, static m
=> PatchProcessor.ReadMethodBody(m).Any(static pair
=> pair.Value is string text
&& text.Contains("probably needs a StaticConstructorOnStartup attribute"))));
public static CodeInstructions Transpiler(CodeInstructions codes)
=> codes.MethodReplacer(AccessTools.PropertyGetter(typeof(MemberInfo), nameof(MemberInfo.Name)),
methodof(Reflection.FullDescription));
}
public class DrawBatch_Flush : FishPatch
{
public override string Description { get; }
= "Optimization of a fleck rendering method. Low - medium performance impact";
public override Expression<Action> TargetMethod { get; } = static () => default(DrawBatch)!.Flush(default);
public static CodeInstructions? Transpiler(CodeInstructions codeInstructions, ILGenerator generator)
{
var codes = codeInstructions.ToList();
var instance_tmpPropertyBlocks
= FishTranspiler.Field(typeof(DrawBatch), nameof(DrawBatch.tmpPropertyBlocks));
var instance_tmpPropertyBlocks_instance_propertyBlockCache_AddRange
= FishTranspiler.Call<Action<HashSet<DrawBatchPropertyBlock>, List<DrawBatchPropertyBlock>>>(
GenCollection.AddRange);
var instance_tmpPropertyBlocks_key_propertyBlock_Add
= FishTranspiler.Call(new HashSet<DrawBatchPropertyBlock>().Add);
var label_inFrontOf_instance_batches_Clear = generator.DefineLabel();
var instance_batches_Clear = FishTranspiler.Call(new Dictionary<DrawBatch.BatchKey, List<DrawBatch.BatchData>>().Clear);
var instance_batches_Clear
= FishTranspiler.Call(new Dictionary<DrawBatch.BatchKey, List<DrawBatch.BatchData>>().Clear);
codes[
codes.FindIndex(c => c == instance_batches_Clear)
- 2
].labels.Add(label_inFrontOf_instance_batches_Clear);
codes.FindIndex(c => c == instance_batches_Clear)
- 2
].labels.Add(label_inFrontOf_instance_batches_Clear);
var instance_myPropertyBlocks = FishTranspiler.Field(typeof(DrawBatch), nameof(DrawBatch.myPropertyBlocks));
var instance_myPropertyBlocks_GetEnumerator = FishTranspiler.Call(new HashSet<DrawBatchPropertyBlock>().GetEnumerator);
var instance_myPropertyBlocks_GetEnumerator
= FishTranspiler.Call(new HashSet<DrawBatchPropertyBlock>().GetEnumerator);
var addRangeRemoved = false;
var addRemoved = false;
@@ -124,6 +220,7 @@ public class MiscOptimizations : ClassWithFishPatches
yield return FishTranspiler.Call(CheckFleckLeaks);
yield return FishTranspiler.IfFalse(label_inFrontOf_instance_batches_Clear);
yield return FishTranspiler.This;
checkFleckLeaksInserted = true;
}
else
@@ -133,47 +230,49 @@ public class MiscOptimizations : ClassWithFishPatches
}
if (!addRangeRemoved || !addRemoved || !checkFleckLeaksInserted)
Log.Error($"Performance Fish failed to apply its DrawBatch.Flush() patch. Mod Settings has an option to disable this");
{
Log.Error("Performance Fish failed to apply its DrawBatch.Flush() patch. Mod Settings has an "
+ "option to disable this");
}
}
public static bool CheckFleckLeaks(DrawBatch instance)
{
if (instance.myPropertyBlocks.Count != instance.propertyBlockCache.Count)
{
instance.tmpPropertyBlocks.AddRange(instance.propertyBlockCache);
return true;
}
return false;
if (instance.myPropertyBlocks.Count == instance.propertyBlockCache.Count)
return false;
instance.tmpPropertyBlocks.AddRange(instance.propertyBlockCache);
return true;
}
#if whatItsGonnaLookLikeAfterTranspiling
public static void Flush(DrawBatch __instance, bool draw = true)
{
if (__instance.tmpPropertyBlock == null)
__instance.tmpPropertyBlock = new MaterialPropertyBlock();
__instance.tmpPropertyBlock ??= new();
__instance.tmpPropertyBlocks.Clear();
// __instance.tmpPropertyBlocks.AddRange(__instance.propertyBlockCache); // <--------------------------------------------------------- removed
// __instance.tmpPropertyBlocks.AddRange(__instance.propertyBlockCache); // <------------------------------- removed
try
{
foreach (var batch in __instance.batches)
foreach (var (batchKey, batchDatas) in __instance.batches)
{
var key = batch.Key;
try
{
foreach (var item in batch.Value)
foreach (var batchData in batchDatas)
{
var batchData = item;
if (draw)
{
__instance.tmpPropertyBlock.Clear();
if (key.propertyBlock != null)
key.propertyBlock.Write(__instance.tmpPropertyBlock);
if (key.renderInstanced)
batchKey.propertyBlock?.Write(__instance.tmpPropertyBlock);
if (batchKey.renderInstanced)
{
key.material.enableInstancing = true;
batchKey.material.enableInstancing = true;
if (batchData.hasAnyColors)
__instance.tmpPropertyBlock.SetVectorArray("_Color", batchData.colors);
Graphics.DrawMeshInstanced(key.mesh, 0, key.material, item.matrices, item.ptr, __instance.tmpPropertyBlock, UnityEngine.Rendering.ShadowCastingMode.On, receiveShadows: true, key.layer);
Graphics.DrawMeshInstanced(batchKey.mesh, 0, batchKey.material, batchData.matrices,
batchData.ptr,
__instance.tmpPropertyBlock, UnityEngine.Rendering.ShadowCastingMode.On,
receiveShadows: true, batchKey.layer);
}
else
{
@@ -183,39 +282,46 @@ public class MiscOptimizations : ClassWithFishPatches
var vector = batchData.colors[i];
if (batchData.hasAnyColors)
__instance.tmpPropertyBlock.SetColor("_Color", vector);
Graphics.DrawMesh(key.mesh, matrix, key.material, key.layer, null, 0, __instance.tmpPropertyBlock);
Graphics.DrawMesh(batchKey.mesh, matrix, batchKey.material, batchKey.layer,
null, 0,
__instance.tmpPropertyBlock);
}
}
}
batchData.Clear();
__instance.batchDataListCache.Add(batchData);
}
}
finally
{
if (key.propertyBlock != null && __instance.myPropertyBlocks.Contains(key.propertyBlock))
if (batchKey.propertyBlock != null && __instance.myPropertyBlocks.Contains(batchKey.propertyBlock))
{
// __instance.tmpPropertyBlocks.Add(key.propertyBlock); // <---------------------------------------------------------- removed
key.propertyBlock.Clear();
__instance.propertyBlockCache.Add(key.propertyBlock);
// __instance.tmpPropertyBlocks.Add(key.propertyBlock); // <-------------------------------- removed
batchKey.propertyBlock.Clear();
__instance.propertyBlockCache.Add(batchKey.propertyBlock);
}
__instance.batchListCache.Add(batch.Value);
batch.Value.Clear();
__instance.batchListCache.Add(batchDatas);
batchDatas.Clear();
}
}
}
finally
{
if (CheckFleckLeaks(__instance)) // <------------------------------------------------------------------------------------------ added
if (CheckFleckLeaks(__instance)) // <------------------------------------------------------------------- added
{
foreach (var myPropertyBlock in __instance.myPropertyBlocks)
{
if (!__instance.tmpPropertyBlocks.Contains(myPropertyBlock))
Log.Warning("Property block from FleckDrawBatch leaked!" + ((myPropertyBlock.leakDebugString == null) ? null : ("Leak debug information: \n" + myPropertyBlock.leakDebugString)));
Log.Warning("Property block from FleckDrawBatch leaked!"
+ ((myPropertyBlock.leakDebugString == null)
? null
: ("Leak debug information: \n" + myPropertyBlock.leakDebugString)));
}
var hashSet = __instance.myPropertyBlocks;
__instance.myPropertyBlocks = __instance.tmpPropertyBlocks;
__instance.tmpPropertyBlocks = hashSet;
(__instance.myPropertyBlocks, __instance.tmpPropertyBlocks)
= (__instance.tmpPropertyBlocks, __instance.myPropertyBlocks);
}
__instance.batches.Clear();