Added gas grid optimization, various despawn optimizations, new hauling optimizations utilizing events and regions, bugfixes for old patches and for vanilla functions too, as well as smaller additions. 1.3 files removed.

This commit is contained in:
bbradson
2024-01-27 10:24:24 +01:00
parent 0247ad59a1
commit c8b764de68
151 changed files with 7318 additions and 2145 deletions
@@ -3,12 +3,16 @@
// 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 PerformanceFish.ModCompatibility;
namespace PerformanceFish.JobSystem;
public class DisabledWorkTypesOptimization : ClassWithFishPatches
public sealed class DisabledWorkTypesOptimization : ClassWithFishPatches
{
public class PawnGetDisabledWorkTypes : FishPatch
public sealed class PawnGetDisabledWorkTypes : FishPatch
{
public override List<string> IncompatibleModIDs { get; } = [PackageIDs.MULTIPLAYER];
public override string? Description { get; }
= "Essentially fixes a bug in Pawn.GetDisabledWorkTypes that causes the method to discard its cache and "
+ "constantly keep recalculating. With the patch applied it'll return early when a valid cache exists.";
@@ -9,9 +9,9 @@ using Verse.AI;
namespace PerformanceFish.JobSystem;
public class GenClosestPatches : ClassWithFishPatches
public sealed class GenClosestPatches : ClassWithFishPatches
{
public class ClosestThing_Global_Patch : FishPatch
public sealed class ClosestThing_Global_Patch : FishPatch
{
public override string Description { get; } = "Threading for job scanning. Disabled and unstable.";
public override bool DefaultState => false;
@@ -226,29 +226,4 @@ public class GenClosestPatches : ClassWithFishPatches
}
}
#endif
#if threadingEnabled
public class CanReach_Patch : FishPatch
{
protected CanReach_Patch() => FishSettings.ThreadingPatches.Add(this);
public override bool DefaultState => false;
public override string Description { get; }
= "Lock for threading. Only needed with threading enabled and off by default";
public override Expression<Action> TargetMethod { get; }
= static () => default(Reachability)!.CanReach(default, null, default, default);
public static void Prefix()
{
if (FishSettings.ThreadingEnabled)
Monitor.Enter(Lock);
}
public static Exception Finalizer(Exception __exception)
{
if (FishSettings.ThreadingEnabled)
Monitor.Exit(Lock);
return __exception;
}
private static object Lock { get; } = new();
}
#endif
//}
@@ -7,22 +7,21 @@ using PerformanceFish.ModCompatibility;
namespace PerformanceFish.JobSystem;
public class JobGiver_HaulOptimization : ClassWithFishPatches
public sealed class JobGiver_HaulOptimization : ClassWithFishPatches
{
public class PotentialWorkThingsGlobal_Patch : FishPatch
public sealed class PotentialWorkThingsGlobal_Patch : FishPatch
{
public override List<string> IncompatibleModIDs { get; } = [PackageIDs.MULTIPLAYER];
public override string Description { get; }
= "Sorts haulables by distance before running expensive hauling calculations on them to avoid checks on "
+ "far away items";
+ "far away items";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(JobGiver_Haul), nameof(JobGiver_Haul.TryGiveJob));
public static CodeInstructions? Transpiler(CodeInstructions codes, MethodBase method)
{
if (ActiveMods.Multiplayer) // PUAH has known issues with sorting and multiplayer
return codes;
var pawn_Map_listerHaulables_ThingsPotentiallyNeedingHauling
= FishTranspiler.Call(static () => default(ListerHaulables)!.ThingsPotentiallyNeedingHauling());
@@ -13,9 +13,9 @@ using static PerformanceFish.TranspilerHelpers;
namespace PerformanceFish.JobSystem;
public class JobGiver_WorkPatches : ClassWithFishPatches
public sealed class JobGiver_WorkPatches : ClassWithFishPatches
{
public class TryIssueJobPackage_InnerDelegate_Patch : FishPatch
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),
@@ -44,7 +44,7 @@ public class JobGiver_WorkPatches : ClassWithFishPatches
}
}
public class TryIssueJobPackage_Patch : FishPatch
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));
@@ -243,7 +243,7 @@ public class JobGiver_WorkPatches : ClassWithFishPatches
public static Dictionary<IEnumerable<Thing>, IEnumerable<Thing>> PrefilteredPotentialWorkThingsGlobal { get; } = new();
}
public class ThingRequest_IsUndefined_Patch : FishPatch
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));
@@ -259,7 +259,7 @@ public class JobGiver_WorkPatches : ClassWithFishPatches
=> __instance.group == ThingRequestGroup.Undefined && !((FishCache)__instance.singleDef).IsSingleDef;
}
public class ThingRequest_CanBeFoundInRegion_Patch : FirstPriorityFishPatch
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));
@@ -276,7 +276,7 @@ public class JobGiver_WorkPatches : ClassWithFishPatches
|| (__instance.group != ThingRequestGroup.Undefined && (__instance.group == ThingRequestGroup.Nothing || __instance.group.StoreInRegion()));
}
public class ListerThings_ThingsMatching_Patch : FirstPriorityFishPatch
public sealed class ListerThings_ThingsMatching_Patch : FirstPriorityFishPatch
{
public override string Description => "Part of the work scanning optimization";
public override Expression<Action> TargetMethod => () => new ListerThings(default).ThingsMatching(default);
@@ -5,44 +5,55 @@
using System.Linq;
using System.Reflection.Emit;
using Mono.Cecil;
using Mono.Cecil.Cil;
using PerformanceFish.Prepatching;
using Verse.AI;
using IngredientCacheList
= PerformanceFish.Utility.KeyedList<Verse.IngredientCount, (System.Collections.Generic.List<Verse.ThingDef> Defs,
bool Found)>;
using OpCodes = System.Reflection.Emit.OpCodes;
using RecipeIngredientCache
= PerformanceFish.Cache.ByIndex<RimWorld.Bill,
PerformanceFish.JobSystem.WorkGiver_DoBillOptimization.RecipeIngredientCacheValue>;
namespace PerformanceFish.JobSystem;
public class WorkGiver_DoBillOptimization : ClassWithFishPatches
public sealed class WorkGiver_DoBillPrepatches : ClassWithFishPrepatches
{
public static List<Type?> BlackListedModExtensions
=> _blackListedModExtensions ??= new() { Reflection.Type("VFEAncients", "VFEAncients.RecipeExtension_Mend") };
public static List<Type> BlackListedModExtensions
=> _blackListedModExtensions ??= InitializeBlackListedModExtensions();
private static List<Type?>? _blackListedModExtensions;
public class TryFindBestIngredientsHelper_InnerDelegate_Patch : FirstPriorityFishPatch
[MethodImpl(MethodImplOptions.NoInlining)]
private static List<Type> InitializeBlackListedModExtensions()
{
var list = new List<Type>();
if (ModCompatibility.Types.VFEAncients.RecipeExtensionMend is { } VFEAncientsRecipeExtension)
list.Add(VFEAncientsRecipeExtension);
return list;
}
private static List<Type>? _blackListedModExtensions;
public sealed class TryFindBestIngredientsHelper_InnerDelegate_Patch : FishPrepatch
{
public override List<string> IncompatibleModIDs { get; }
= [ModCompatibility.PackageIDs.USE_BEST_MATERIALS];
public override List<Type> LinkedPatches { get; } =
[
typeof(WorkGiver_DoBillOptimization.TryFindBestIngredientsHelper_Patch),
typeof(WorkGiver_DoBillOptimization.TryFindBestIngredientsInSet_NoMixHelper_Patch)
];
public override string Description { get; }
= "Optimizes WorkGiver_DoBill to only check things that get accepted by a bill's filter for its "
+ "ingredient lookup, instead of looping over everything and then checking filters afterwards. "
+ "Also caches these filter acceptances. Large performance impact.";
public override bool Enabled
{
set
{
if (base.Enabled == value)
return;
Get<TryFindBestIngredientsHelper_Patch>().Enabled
= Get<TryFindBestIngredientsInSet_NoMixHelper_Patch>().Enabled
= base.Enabled = value;
}
}
public override MethodBase TargetMethodInfo { get; }
public override MethodBase TargetMethodBase { get; }
= AccessTools.FirstMethod(DelegateInstanceType, static m
=> m.GetParameters() is { Length: 1 } parameterInfos
&& parameterInfos[0].ParameterType == typeof(Region)
@@ -52,12 +63,15 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
&& instruction.operand is MethodInfo info
&& info == AccessTools.Method(typeof(ListerThings), nameof(ListerThings.ThingsMatching))));
public static CodeInstructions Transpiler(CodeInstructions codeInstructions, MethodBase method)
=> Reflection.MakeReplacementCall(AccessTools.Method(
typeof(TryFindBestIngredientsHelper_InnerDelegate_Patch), nameof(Replacement),
generics: new[] { DelegateInstanceType }));
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
public static bool Replacement<T>(T __instance, Region r) where T : class
// 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);
var thingValidator = _thingValidatorInstance.Get(instance.ThingValidator.Target);
@@ -75,7 +89,7 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
#if V1_4
|| bill.billStack?.billGiver is Building_MechGestator
#endif
|| (BlackListedModExtensions.Exists(static b => b != null)
|| (BlackListedModExtensions.Count > 0
&& IsBlackListed(bill))) // <-- Fuck these too
{
list = GetDefaultList(r);
@@ -89,7 +103,7 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
cache.Update(bill);
}
list = GetList(r.listerThings.listsByDef, cache.thingDefs);
list = GetList(r, cache.thingDefs);
}
}
@@ -110,10 +124,9 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
instance.FoundAll = true;
return true;
}
private static bool HasNoFilters(Bill bill)
public static bool HasNoFilters(Bill bill)
=> bill.ingredientFilter is null
|| bill.recipe.fixedIngredientFilter is null;
@@ -135,13 +148,13 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
return false;
}*/
private static bool IsBlackListed(Bill? bill)
public static bool IsBlackListed(Bill? bill)
=> bill is null
|| bill.recipe.modExtensions.ExistsAndNotNull(static e
=> BlackListedModExtensions.Contains(e.GetType()));
private static List<Thing> GetDefaultList(Region r)
=> r.ListerThings.ThingsMatching(ThingRequest.ForGroup(ThingRequestGroup.HaulableEver));
public static List<Thing> GetDefaultList(Region r)
=> r.listerThings.ThingsMatching(ThingRequest.ForGroup(ThingRequestGroup.HaulableEver));
public static void InsertAtCorrectPosition(List<Thing> list, List<Thing> thingsToInsert, Pawn pawn)
{
@@ -159,7 +172,8 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
}
}
private static readonly ThingPositionComparer _insertAtCorrectPositionComparer = new();
private static readonly WorkGiver_DoBillOptimization.ThingPositionComparer _insertAtCorrectPositionComparer
= new();
public static void ActualLoop(List<Thing> list, Region r, Pawn pawn, Predicate<Thing> baseValidator,
bool billGiverIsPawn)
@@ -182,8 +196,10 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
}
}
public static List<Thing> GetList(Dictionary<ThingDef, List<Thing>> listsByDef, IngredientCacheList cacheList)
public static List<Thing> GetList(Region region, IngredientCacheList cacheList)
{
var listsByDef = region.listerThings.listsByDef;
var list = _tempListForIngredients;
list.Clear();
var set = _tempSetForIngredientDefs;
@@ -216,7 +232,7 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
{
var recipe = bill.recipe;
var ingredients = recipe.ingredients;
var listsByDef = r.Map.listerThings.listsByDef;
var listsByDef = r.GetMap().listerThings.listsByDef;
var result = cacheList ?? new(3);
for (var i = 0; i < ingredients.Count;
@@ -229,7 +245,7 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
}
else
{
result[ingredients[i]] = ingredientList = new(new(), false);
result[ingredients[i]] = ingredientList = new([], false);
}
var filter = ingredients[i].filter;
@@ -291,8 +307,8 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
(mediumFilter, largestFilter) = (largestFilter, mediumFilter);
}
private static readonly List<Thing> _tempListForIngredients = new();
private static readonly HashSet<ThingDef> _tempSetForIngredientDefs = new();
private static readonly List<Thing> _tempListForIngredients = [];
private static readonly HashSet<ThingDef> _tempSetForIngredientDefs = [];
}
private static Type DelegateInstanceType { get; }
@@ -308,8 +324,8 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
&& AccessTools.Field(t, "foundAllIngredientsAndChoose") != null
&& AccessTools.Field(t, "foundAll") != null
&& AccessTools.Field(t, "thingValidator") != null)
?? throw new("Performance Fish failed to find the target type for its WorkGiver_DoBill patch. "
+ "This means that specific patch won't apply.");
?? ThrowHelper.ThrowInvalidOperationException<Type>("Performance Fish failed to find the target type "
+ "for its WorkGiver_DoBill patch. This means that specific patch won't apply.");
private static Type ThingValidatorInstanceType { get; }
= AccessTools.FirstInner(typeof(WorkGiver_DoBill), static t
@@ -320,20 +336,20 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
//&& AccessTools.Field(t, "chosen") != null
//&& AccessTools.Field(t, "billGiver") != null
/*&& AccessTools.Field(t, "pawn") != null*/)
?? throw new("Performance Fish failed to find the thingValidator type for its WorkGiver_DoBill patch. "
+ "This likely flat out breaks the entire patch.");
?? ThrowHelper.ThrowInvalidOperationException<Type>("Performance Fish failed to find the thingValidator "
+ "type for its WorkGiver_DoBill patch. This likely flat out breaks the entire patch.");
// ReSharper disable once InconsistentNaming
private static DelegateInstance _delegateInstance { get; }
public static DelegateInstance _delegateInstance { get; }
= (DelegateInstance)Activator.CreateInstance(
typeof(DelegateInstanceImplementation<>).MakeGenericType(DelegateInstanceType));
// ReSharper disable once InconsistentNaming
private static ThingValidatorInstance _thingValidatorInstance { get; }
public static ThingValidatorInstance _thingValidatorInstance { get; }
= (ThingValidatorInstance)Activator.CreateInstance(
typeof(ThingValidatorInstanceImplementation<>).MakeGenericType(ThingValidatorInstanceType));
private abstract class DelegateInstance
public abstract class DelegateInstance
{
public abstract DelegateInstance Get(object obj);
@@ -347,7 +363,7 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
public abstract Predicate<Thing> ThingValidator { get; set; }
}
private class DelegateInstanceImplementation<T> : DelegateInstance where T : class
private sealed class DelegateInstanceImplementation<T> : DelegateInstance where T : class
{
#pragma warning disable CS8618
private T _instance;
@@ -430,7 +446,7 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
= AccessTools.FieldRefAccess<T, Predicate<Thing>>("thingValidator");
}
private abstract class ThingValidatorInstance
public abstract class ThingValidatorInstance
{
public abstract ThingValidatorInstance? Get(object obj);
@@ -438,7 +454,7 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
//public abstract Thing BillGiver { get; set; }
}
private class ThingValidatorInstanceImplementation<T> : ThingValidatorInstance where T : class
private sealed class ThingValidatorInstanceImplementation<T> : ThingValidatorInstance where T : class
{
#pragma warning disable CS8618
private T _instance;
@@ -467,27 +483,21 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
private static AccessTools.FieldRef<T, Bill> _bill = AccessTools.FieldRefAccess<T, Bill>("bill");
//private static AccessTools.FieldRef<T, Thing> _billGiver = AccessTools.FieldRefAccess<T, Thing>("billGiver");
}
}
public class TryFindBestIngredientsHelper_Patch : FishPatch
public sealed class WorkGiver_DoBillOptimization : ClassWithFishPatches
{
public sealed class TryFindBestIngredientsHelper_Patch : FishPatch
{
public override bool ShowSettings => false;
public override string Description { get; } = "Part of the DoBill optimization";
public override bool Enabled
{
set
{
if (base.Enabled == value)
return;
Get<TryFindBestIngredientsInSet_NoMixHelper_Patch>().Enabled = base.Enabled = value;
}
}
public override Delegate TargetMethodGroup { get; } = WorkGiver_DoBill.TryFindBestIngredientsHelper;
public static void Postfix(Predicate<Thing> thingValidator)
{
var thingValidatorInstance = _thingValidatorInstance.Get(thingValidator.Target);
var thingValidatorInstance = WorkGiver_DoBillPrepatches._thingValidatorInstance.Get(thingValidator.Target);
if (thingValidatorInstance is null)
return;
@@ -505,8 +515,10 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
}
}
public class TryFindBestIngredientsInSet_NoMixHelper_Patch : FishPatch
public sealed class TryFindBestIngredientsInSet_NoMixHelper_Patch : FishPatch
{
public override List<Type> LinkedPatches { get; } = [typeof(TryFindBestIngredientsHelper_Patch)];
public override string Description { get; }
= "Part of the DoBill optimization. Bills that need multiple ingredients, like for example steel and "
+ "components for guns, normally scan their surroundings for all those ingredients and count them, until "
@@ -515,17 +527,6 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
+ "case of steel and components that could mean not having to go over 100s of steel stacks at times. "
+ "Prevents large spikes from these situations, does nothing for all other bills.";
public override bool Enabled
{
set
{
if (base.Enabled == value)
return;
Get<TryFindBestIngredientsHelper_Patch>().Enabled = base.Enabled = value;
}
}
public override Delegate TargetMethodGroup { get; } = WorkGiver_DoBill.TryFindBestIngredientsInSet_NoMixHelper;
public static void Prefix(ref bool alreadySorted, Bill? bill)
@@ -549,16 +550,15 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
&& codes[i + 1] == FishTranspiler.Constant(1) // i++;
&& codes[i + 2] == FishTranspiler.Add
&& codes[i + 3] == i_variable.Store(),
code
=> new[]
{
code =>
[
new(OpCodes.Br_S, new_i_label), // else
FishTranspiler.FirstLocalVariable(method, typeof(IngredientCount))
.WithLabels(code.ExtractLabels()),
FishTranspiler.FirstArgument(method, typeof(Bill)),
FishTranspiler.Call(MarkIngredientCountAsFound),
code.WithLabels(new_i_label)
});
]);
// if (!flag)
// {
@@ -599,7 +599,7 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
}
}
public class ThingPositionComparer : IComparer<Thing>
public sealed class ThingPositionComparer : IComparer<Thing>
{
public IntVec3 rootCell;
@@ -7,15 +7,15 @@ using PerformanceFish.ModCompatibility;
namespace PerformanceFish.JobSystem;
public class WorkGiver_HaulOptimization : ClassWithFishPatches
public sealed class WorkGiver_HaulOptimization : ClassWithFishPatches
{
public class PotentialWorkThingsGlobal_Patch : FishPatch
public sealed class PotentialWorkThingsGlobal_Patch : FishPatch
{
public override bool Enabled => base.Enabled && !ActiveMods.Multiplayer;
public override List<string> IncompatibleModIDs { get; } = [PackageIDs.MULTIPLAYER];
public override string Description { get; }
= "Sorts haulables by distance before running expensive hauling calculations on them to avoid checks on "
+ "far away items";
+ "far away items";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(WorkGiver_Haul), nameof(WorkGiver_Haul.PotentialWorkThingsGlobal));
@@ -7,13 +7,13 @@ using Verse.AI;
namespace PerformanceFish.JobSystem;
public class WorkGiver_MergeOptimization : ClassWithFishPatches
public sealed class WorkGiver_MergeOptimization : ClassWithFishPatches
{
public class JobOnThing_Patch : FishPatch
public sealed class JobOnThing_Patch : FishPatch
{
public override string Description { get; }
= "Removes mergeables from ListerMergeables if they no longer qualify as mergeable when trying to check "
+ "for a merging job on them. Vanilla RimWorld just fails and keeps them in";
+ "for a merging job on them. Vanilla RimWorld just fails and keeps them in";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(WorkGiver_Merge), nameof(WorkGiver_Merge.JobOnThing));
@@ -23,14 +23,16 @@ public class WorkGiver_MergeOptimization : ClassWithFishPatches
if (t.GetSlotGroup() is not null)
return true;
RemoveFromListerMergeables(t);
TryRemoveFromListerMergeables(t);
__result = null;
return false;
}
private static void RemoveFromListerMergeables(Thing t)
private static void TryRemoveFromListerMergeables(Thing t)
{
var map = t.MapHeld;
if (t.TryGetMapHeld() is not { } map)
return;
map.listerMergeables.TryRemove(t);
map.listerHaulables.Check(t);
}