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
@@ -0,0 +1,25 @@
// 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.JobSystem;
public class DisabledWorkTypesOptimization : ClassWithFishPatches
{
public class PawnGetDisabledWorkTypes : FishPatch
{
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.";
public override MethodBase? TargetMethodInfo { get; }
= AccessTools.Method(typeof(Pawn), nameof(Pawn.GetDisabledWorkTypes));
[HarmonyPriority(Priority.VeryLow)]
public static bool Prefix(Pawn __instance, bool permanentOnly, out List<WorkTypeDef> __result)
=> (__result = permanentOnly
? __instance.cachedDisabledWorkTypesPermanent
: __instance.cachedDisabledWorkTypes) is null;
}
}
@@ -1,24 +1,28 @@
// 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.Threading.Tasks;
#if disabledForNow
using System.Threading.Tasks;
using Verse.AI;
namespace PerformanceFish.JobSystem;
public class GenClosestPatches : ClassWithFishPatches
{*/
#if disabledForNow
{
public class ClosestThing_Global_Patch : FishPatch
{
public override string Description => "Threading for job scanning. Disabled and unstable.";
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 Expression<Action> TargetMethod => () => GenClosest.ClosestThing_Global(default, null, default, null, null);
public override Expression<Action> 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 IntVec3 center;
@@ -27,18 +31,32 @@ public class GenClosestPatches : ClassWithFishPatches
public float closestDistSquared;
public Predicate<Thing>? validator;
}
public static Thing? ClosestThing_Global_Replacement(IntVec3 center, IEnumerable searchSet, float maxDistance = 99999f, Predicate<Thing>? validator = null, Func<Thing, float>? priorityGetter = null)
public static Thing? ClosestThing_Global_Replacement(IntVec3 center, IEnumerable? searchSet,
float maxDistance = 99999f, Predicate<Thing>? validator = null, Func<Thing, float>? 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 };
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 (ThreadingEnabled
&& validator != null
&& JobGiver_Work_Predicate.GetScanner(predicate) is WorkGiver_DoBill
/*or WorkGiver_ConstructDeliverResources or WorkGiver_ConstructFinishFrames*/
)
{
if (searchSet is IList<Thing> thingList)
{
@@ -47,15 +65,16 @@ public class GenClosestPatches : ClassWithFishPatches
try
{
Parallel.For<(IList<Thing> list, Variables vars)>(0, thingList.Count,
() => (thingList, variables),
(i, loop, listAndVars) => (listAndVars.list, ProcessThreaded(listAndVars.list[i], listAndVars.vars)),
_ => { });
() => (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");
@@ -98,86 +117,112 @@ public class GenClosestPatches : ClassWithFishPatches
}
return chosen;
void Process<T>(T t, Variables variables) where T : Thing
{
if (t.Spawned)
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)))
{
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)
{
var num2 = 0f;
if (variables.priorityGetter != null)
{
num2 = variables.priorityGetter(t);
if (num2 < bestPrio || (num2 == bestPrio && num >= closestDistSquared))
return;
}
chosen = t;
closestDistSquared = variables.closestDistSquared = num;
bestPrio = num2;
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 t, Variables variables) where T : Thing
{
if (t.Spawned)
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)))
{
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)))
{
lock (Lock)
{
var num2 = 0f;
if (variables.priorityGetter != null)
{
num2 = variables.priorityGetter(t);
if (num2 < bestPrio || (num2 == bestPrio && num >= closestDistSquared))
return variables;
}
else
{
if (num >= closestDistSquared)
return variables;
}
chosen = t;
closestDistSquared = variables.closestDistSquared = num;
bestPrio = num2;
}
}
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);
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));
= (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);
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));
= (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<T> : JobGiver_Work_PredicateAccess
{
public override WorkGiver_Scanner? GetScanner(object? obj) => obj is T t ? Scanner(t) : null;
public static AccessTools.FieldRef<T, WorkGiver_Scanner> Scanner { get; } = AccessTools.FieldRefAccess<T, WorkGiver_Scanner>("scanner");
public static AccessTools.FieldRef<T, WorkGiver_Scanner> Scanner { get; }
= AccessTools.FieldRefAccess<T, WorkGiver_Scanner>("scanner");
}
private abstract class GenClosest_PredicateAccess
{
public abstract Predicate<Thing>? GetValidator(object? obj);
}
private class GenClosest_PredicateAccessImplementation<T> : GenClosest_PredicateAccess
{
public override Predicate<Thing>? GetValidator(object? obj) => obj is T t ? Validator(t) : null;
public static AccessTools.FieldRef<T, Predicate<Thing>> Validator { get; } = AccessTools.FieldRefAccess<T, Predicate<Thing>>("validator");
public static AccessTools.FieldRef<T, Predicate<Thing>> Validator { get; }
= AccessTools.FieldRefAccess<T, Predicate<Thing>>("validator");
}
}
#endif
@@ -187,8 +232,10 @@ public class GenClosestPatches : ClassWithFishPatches
{
protected CanReach_Patch() => FishSettings.ThreadingPatches.Add(this);
public override bool DefaultState => false;
public override string Description => "Lock for threading. Only needed with threading enabled and off by default";
public override Expression<Action> TargetMethod => () => default(Reachability)!.CanReach(default, null, default, default);
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)
@@ -1,43 +1,54 @@
// 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 PerformanceFish.ModCompatibility;
namespace PerformanceFish.JobSystem;
public class JobGiver_HaulOptimization : ClassWithFishPatches
{
public class PotentialWorkThingsGlobal_Patch : FishPatch
{
public override string Description => "Sorts haulables by distance before running expensive hauling calculations on them to avoid checks on far away items";
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(JobGiver_Haul), nameof(JobGiver_Haul.TryGiveJob));
public static CodeInstructions? Transpiler(CodeInstructions codes, MethodBase method)
{
var pawn_Map_listerHaulables_ThingsPotentiallyNeedingHauling = FishTranspiler.Call(() => default(ListerHaulables)!.ThingsPotentiallyNeedingHauling());
public class PotentialWorkThingsGlobal_Patch : FishPatch
{
public override string Description { get; }
= "Sorts haulables by distance before running expensive hauling calculations on them to avoid checks on "
+ "far away items";
try
{
return codes.InsertAfter(
c => c == pawn_Map_listerHaulables_ThingsPotentiallyNeedingHauling,
new CodeInstruction[]
{
FishTranspiler.Argument(method, "pawn"),
FishTranspiler.Call(SortedThingsPotentiallyNeedingHauling)
});
}
catch (Exception ex)
{
Log.Error($"{ex}");
//Log.Error("Performance Fish failed to apply its JobGiver_Haul transpiler. This particular optimization will be doing nothing now");
return null;
}
}
public static List<Thing> SortedThingsPotentiallyNeedingHauling(List<Thing> things, Pawn pawn)
{
_comparer.rootCell = pawn.Position;
things.Sort(_comparer);
return things;
}
private static WorkGiver_DoBillOptimization.ThingPositionComparer _comparer = new();
}
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());
try
{
return codes.InsertAfter(c => c == pawn_Map_listerHaulables_ThingsPotentiallyNeedingHauling,
new CodeInstruction[]
{
FishTranspiler.Argument(method, "pawn"),
FishTranspiler.Call(SortedThingsPotentiallyNeedingHauling)
});
}
catch (Exception ex)
{
Log.Error($"{ex}");
return null;
}
}
public static List<Thing> SortedThingsPotentiallyNeedingHauling(List<Thing> things, Pawn pawn)
{
_comparer.rootCell = pawn.Position;
things.Sort(_comparer);
return things;
}
private static WorkGiver_DoBillOptimization.ThingPositionComparer _comparer = new();
}
}
@@ -1,4 +1,4 @@
// 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/.
@@ -1,40 +1,61 @@
// 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.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection.Emit;
using Verse.AI;
using IngredientCacheList = PerformanceFish.Utility.KeyedList<Verse.IngredientCount, (System.Collections.Generic.List<Verse.ThingDef> Defs, bool Found)>;
using RecipeIngredientCache = PerformanceFish.Cache.ByIndex<RimWorld.Bill, PerformanceFish.JobSystem.WorkGiver_DoBillOptimization.RecipeIngredientCacheValue>;
using IngredientCacheList
= PerformanceFish.Utility.KeyedList<Verse.IngredientCount, (System.Collections.Generic.List<Verse.ThingDef> Defs,
bool Found)>;
using RecipeIngredientCache
= PerformanceFish.Cache.ByIndex<RimWorld.Bill,
PerformanceFish.JobSystem.WorkGiver_DoBillOptimization.RecipeIngredientCacheValue>;
namespace PerformanceFish.JobSystem;
public class WorkGiver_DoBillOptimization : ClassWithFishPatches
{
public static List<Type> BlackListedModExtensions => _blackListedModExtensions ??= new() { Reflection.Type("VFEAncients", "VFEAncients.RecipeExtension_Mend") };
private static List<Type>? _blackListedModExtensions;
public static List<Type?> BlackListedModExtensions
=> _blackListedModExtensions ??= new() { Reflection.Type("VFEAncients", "VFEAncients.RecipeExtension_Mend") };
public class TryFindBestIngredientsHelper_InnerDelegate_Patch : FishPatch
private static List<Type?>? _blackListedModExtensions;
public class TryFindBestIngredientsHelper_InnerDelegate_Patch : FirstPriorityFishPatch
{
public override string Description => "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 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 MethodBase TargetMethodInfo
=> AccessTools.FirstMethod(DelegateInstanceType, m
=> m.GetParameters() is { Length: 1 } parms
&& parms[0].ParameterType == typeof(Region)
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; }
= AccessTools.FirstMethod(DelegateInstanceType, static m
=> m.GetParameters() is { Length: 1 } parameterInfos
&& parameterInfos[0].ParameterType == typeof(Region)
&& m.ReturnType == typeof(bool)
&& PatchProcessor.ReadMethodBody(m).Any(pair
=> (pair.Key == OpCodes.Call || pair.Key == OpCodes.Callvirt)
&& pair.Value is MethodInfo info
&& MethodBodyReader.GetInstructions(null, m).Any(static instruction
=> (instruction.opcode == OpCodes.Call || instruction.opcode == OpCodes.Callvirt)
&& 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 static CodeInstructions Transpiler(CodeInstructions codeInstructions, MethodBase method)
=> Reflection.MakeReplacementCall(AccessTools.Method(
typeof(TryFindBestIngredientsHelper_InnerDelegate_Patch), nameof(Replacement),
generics: new[] { DelegateInstanceType }));
public static bool Replacement<T>(T __instance, Region r) where T : class
{
@@ -54,46 +75,47 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
#if V1_4
|| bill.billStack?.billGiver is Building_MechGestator
#endif
|| (BlackListedModExtensions.Exists(b => b != null) && IsBlackListed(bill))) // <-- Fuck these too
|| (BlackListedModExtensions.Exists(static b => b != null)
&& IsBlackListed(bill))) // <-- Fuck these too
{
list = GetDefaultList(r);
}
else
{
ref var cache = ref RecipeIngredientCache.Get.GetReference(bill);
if (cache.thingDefs is null || cache.ShouldRefreshNow)
if (cache.thingDefs is null || cache.Dirty)
{
cache.thingDefs = UpdateCache(bill, r, cache.thingDefs);
cache.ShouldRefreshNow = false;
RecipeIngredientCache.Get[bill] = cache;
cache.Update(bill);
}
list = GetList(r.listerThings.listsByDef, cache.thingDefs);
}
}
ActualLoop(list, r, instance.Pawn, instance.BaseValidator, instance.BillGiverIsPawn);
//list.Clear();
instance.RegionsProcessed++;
if (WorkGiver_DoBill.newRelevantThings.Count > 0 && instance.RegionsProcessed > instance.AdjacentRegionsAvailable)
{
//WorkGiver_DoBill.relevantThings.AddRange(WorkGiver_DoBill.newRelevantThings);
InsertAtCorrectPosition(WorkGiver_DoBill.relevantThings, WorkGiver_DoBill.newRelevantThings, instance.Pawn);
if (WorkGiver_DoBill.newRelevantThings.Count <= 0
|| instance.RegionsProcessed <= instance.AdjacentRegionsAvailable)
return false;
//WorkGiver_DoBill.relevantThings.AddRange(WorkGiver_DoBill.newRelevantThings);
InsertAtCorrectPosition(WorkGiver_DoBill.relevantThings, WorkGiver_DoBill.newRelevantThings,
instance.Pawn);
WorkGiver_DoBill.newRelevantThings.Clear();
if (!instance.FoundAllIngredientsAndChoose(WorkGiver_DoBill.relevantThings))
return false;
instance.FoundAll = true;
return true;
WorkGiver_DoBill.newRelevantThings.Clear();
if (instance.FoundAllIngredientsAndChoose(WorkGiver_DoBill.relevantThings))
{
instance.FoundAll = true;
return true;
}
}
return false;
}
private static bool HasNoFilters(Bill bill)
=> bill.ingredientFilter is null
|| bill.recipe.fixedIngredientFilter is null;
|| bill.recipe.fixedIngredientFilter is null;
/*private static bool HasNoFilters(Bill bill)
{
@@ -113,13 +135,13 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
return false;
}*/
private static bool IsBlackListed(Bill bill)
private static bool IsBlackListed(Bill? bill)
=> bill is null
|| (bill.recipe.modExtensions?.Exists(e
=> BlackListedModExtensions.Contains(e.GetType()))
?? false);
|| bill.recipe.modExtensions.ExistsAndNotNull(static e
=> BlackListedModExtensions.Contains(e.GetType()));
private static List<Thing> GetDefaultList(Region r) => r.ListerThings.ThingsMatching(ThingRequest.ForGroup(ThingRequestGroup.HaulableEver));
private 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)
{
@@ -139,18 +161,24 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
private static readonly ThingPositionComparer _insertAtCorrectPositionComparer = new();
public static void ActualLoop(List<Thing> list, Region r, Pawn pawn, Predicate<Thing> baseValidator, bool billGiverIsPawn)
public static void ActualLoop(List<Thing> list, Region r, Pawn pawn, Predicate<Thing> baseValidator,
bool billGiverIsPawn)
{
var count = list.Count;
for (var i = 0; i < count; i++)
{
var thing = list[i];
if (!WorkGiver_DoBill.processedThings.Contains(thing)
&& ReachabilityWithinRegion.ThingFromRegionListerReachable(thing, r, PathEndMode.ClosestTouch, pawn) && !(billGiverIsPawn && thing.def.IsMedicine) && baseValidator(thing))
if (WorkGiver_DoBill.processedThings.Contains(thing)
|| !ReachabilityWithinRegion.ThingFromRegionListerReachable(thing, r, PathEndMode.ClosestTouch,
pawn)
|| (billGiverIsPawn && thing.def.IsMedicine)
|| !baseValidator(thing))
{
WorkGiver_DoBill.newRelevantThings.Add(thing);
WorkGiver_DoBill.processedThings.Add(thing);
continue;
}
WorkGiver_DoBill.newRelevantThings.Add(thing);
WorkGiver_DoBill.processedThings.Add(thing);
}
}
@@ -170,13 +198,14 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
var count = defs.Count;
for (var j = 0; j < count; j++)
{
if (listsByDef.TryGetValue(defs[j], out var listByDef) && listByDef.Any())
if (!listsByDef.TryGetValue(defs[j], out var listByDef)
|| !listByDef.Any()
|| (i > 0 && !set.Add(defs[j])))
{
if (i > 0 && !set.Add(defs[j]))
continue;
list.AddRange(listByDef);
continue;
}
list.AddRange(listByDef);
}
}
@@ -190,7 +219,8 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
var listsByDef = r.Map.listerThings.listsByDef;
var result = cacheList ?? new(3);
for (var i = 0; i < ingredients.Count; i++) // generally not larger than 3 in vanilla and anything that tries to be
for (var i = 0; i < ingredients.Count;
i++) // generally not larger than 3 in vanilla and anything that tries to be
{
if (result.TryGetValue(ingredients[i], out var ingredientList))
{
@@ -203,15 +233,14 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
}
var filter = ingredients[i].filter;
var allowedDefCount = filter.allowedDefs.Count;
if (allowedDefCount < 1)
continue;
if (allowedDefCount == 1)
switch (filter.allowedDefs.Count)
{
ingredientList.Defs.Add(filter.allowedDefs.First());
continue;
case < 1:
continue;
case 1:
ingredientList.Defs.Add(filter.allowedDefs.First());
continue;
}
var smallestFilter = bill.ingredientFilter;
@@ -220,19 +249,25 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
OrderFiltersBySize(ref smallestFilter, ref mediumFilter, ref largestFilter);
var cachedSmallestList = AllowedDefsListCache.GetValue(smallestFilter).defs;
var cachedSmallestList = AllowedDefsListCache
.GetAndCheck<ThingFilterPatches.AllowedDefsListCacheValue>(smallestFilter).Defs;
if (cachedSmallestList.Count != smallestFilter.allowedDefs.Count)
{
// Log.Warning($"Cached list count: {cachedSmallestList.Count}, actual filter list count: {smallestFilter.allowedDefs.Count}. Forcing a refresh now to fix.");
// Log.Warning($"Cached list count: {cachedSmallestList.Count}, actual filter list count: {
// smallestFilter.allowedDefs.Count}. Forcing a refresh now to fix.");
ThingFilterPatches.ForceSynchronizeCache(smallestFilter);
// Log.Warning($"New cached list count: {cachedSmallestList.Count}, actual filter list count: {smallestFilter.allowedDefs.Count}.");
// Log.Warning($"New cached list count: {cachedSmallestList.Count}, actual filter list count: {
// smallestFilter.allowedDefs.Count}.");
// didn't trigger in 1.3, but now does. Fuck
}
var smallestListCount = cachedSmallestList.Count;
for (var k = 0; k < smallestListCount; k++)
{
var def = cachedSmallestList[k];
if (listsByDef.TryGetValue(def, out var listByDef) && listByDef.Any()
if (listsByDef.TryGetValue(def, out var listByDef)
&& listByDef.Any()
&& mediumFilter.Allows(def)
&& largestFilter.Allows(def))
{
@@ -244,7 +279,8 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
return result;
}
private static void OrderFiltersBySize(ref ThingFilter smallestFilter, ref ThingFilter mediumFilter, ref ThingFilter largestFilter)
private static void OrderFiltersBySize(ref ThingFilter smallestFilter, ref ThingFilter mediumFilter,
ref ThingFilter largestFilter)
{
if (smallestFilter.allowedDefs.Count > mediumFilter.allowedDefs.Count)
(smallestFilter, mediumFilter) = (mediumFilter, smallestFilter);
@@ -261,9 +297,10 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
}
private static Type DelegateInstanceType { get; }
= AccessTools.FirstInner(typeof(WorkGiver_DoBill), t
=> AccessTools.FirstMethod(t, m
=> m.Name.Contains($"<{nameof(WorkGiver_DoBill.TryFindBestIngredientsHelper)}>")) != null
= AccessTools.FirstInner(typeof(WorkGiver_DoBill), static t
=> AccessTools.FirstMethod(t, static m
=> m.Name.Contains($"<{nameof(WorkGiver_DoBill.TryFindBestIngredientsHelper)}>"))
!= null
&& AccessTools.Field(t, "pawn") != null
&& AccessTools.Field(t, "baseValidator") != null
&& AccessTools.Field(t, "billGiverIsPawn") != null
@@ -272,25 +309,30 @@ 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.");
?? throw new("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), t
=> AccessTools.FirstMethod(t, m
=> m.Name.Contains($"<{nameof(WorkGiver_DoBill.TryFindBestBillIngredients)}>")) != null
&& AccessTools.Field(t, "bill") != null
= AccessTools.FirstInner(typeof(WorkGiver_DoBill), static t
=> AccessTools.FirstMethod(t, static m
=> m.Name.Contains($"<{nameof(WorkGiver_DoBill.TryFindBestBillIngredients)}>"))
!= null
&& AccessTools.Field(t, "bill") != null
//&& 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.");
?? throw new("Performance Fish failed to find the thingValidator type for its WorkGiver_DoBill patch. "
+ "This likely flat out breaks the entire patch.");
[SuppressMessage("Style", "IDE1006:Naming Styles")]
// ReSharper disable once InconsistentNaming
private static DelegateInstance _delegateInstance { get; }
= (DelegateInstance)Activator.CreateInstance(typeof(DelegateInstanceImplementation<>).MakeGenericType(DelegateInstanceType));
= (DelegateInstance)Activator.CreateInstance(
typeof(DelegateInstanceImplementation<>).MakeGenericType(DelegateInstanceType));
[SuppressMessage("Style", "IDE1006:Naming Styles")]
// ReSharper disable once InconsistentNaming
private static ThingValidatorInstance _thingValidatorInstance { get; }
= (ThingValidatorInstance)Activator.CreateInstance(typeof(ThingValidatorInstanceImplementation<>).MakeGenericType(ThingValidatorInstanceType));
= (ThingValidatorInstance)Activator.CreateInstance(
typeof(ThingValidatorInstanceImplementation<>).MakeGenericType(ThingValidatorInstanceType));
private abstract class DelegateInstance
{
@@ -318,23 +360,75 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
return this;
}
public override Pawn Pawn { get => _pawn(_instance); set => _pawn(_instance) = value; }
public override Predicate<Thing> BaseValidator { get => _baseValidator(_instance); set => _baseValidator(_instance) = value; }
public override bool BillGiverIsPawn { get => _billGiverIsPawn(_instance); set => _billGiverIsPawn(_instance) = value; }
public override int RegionsProcessed { get => _regionsProcessed(_instance); set => _regionsProcessed(_instance) = value; }
public override int AdjacentRegionsAvailable { get => _adjacentRegionsAvailable(_instance); set => _adjacentRegionsAvailable(_instance) = value; }
public override Predicate<List<Thing>> FoundAllIngredientsAndChoose { get => _foundAllIngredientsAndChoose(_instance); set => _foundAllIngredientsAndChoose(_instance) = value; }
public override bool FoundAll { get => _foundAll(_instance); set => _foundAll(_instance) = value; }
public override Predicate<Thing> ThingValidator { get => _thingValidator(_instance); set => _thingValidator(_instance) = value; }
public override Pawn Pawn
{
get => _pawn(_instance);
set => _pawn(_instance) = value;
}
public override Predicate<Thing> BaseValidator
{
get => _baseValidator(_instance);
set => _baseValidator(_instance) = value;
}
public override bool BillGiverIsPawn
{
get => _billGiverIsPawn(_instance);
set => _billGiverIsPawn(_instance) = value;
}
public override int RegionsProcessed
{
get => _regionsProcessed(_instance);
set => _regionsProcessed(_instance) = value;
}
public override int AdjacentRegionsAvailable
{
get => _adjacentRegionsAvailable(_instance);
set => _adjacentRegionsAvailable(_instance) = value;
}
public override Predicate<List<Thing>> FoundAllIngredientsAndChoose
{
get => _foundAllIngredientsAndChoose(_instance);
set => _foundAllIngredientsAndChoose(_instance) = value;
}
public override bool FoundAll
{
get => _foundAll(_instance);
set => _foundAll(_instance) = value;
}
public override Predicate<Thing> ThingValidator
{
get => _thingValidator(_instance);
set => _thingValidator(_instance) = value;
}
private static AccessTools.FieldRef<T, Pawn> _pawn = AccessTools.FieldRefAccess<T, Pawn>("pawn");
private static AccessTools.FieldRef<T, Predicate<Thing>> _baseValidator = AccessTools.FieldRefAccess<T, Predicate<Thing>>("baseValidator");
private static AccessTools.FieldRef<T, bool> _billGiverIsPawn = AccessTools.FieldRefAccess<T, bool>("billGiverIsPawn");
private static AccessTools.FieldRef<T, int> _regionsProcessed = AccessTools.FieldRefAccess<T, int>("regionsProcessed");
private static AccessTools.FieldRef<T, int> _adjacentRegionsAvailable = AccessTools.FieldRefAccess<T, int>("adjacentRegionsAvailable");
private static AccessTools.FieldRef<T, Predicate<List<Thing>>> _foundAllIngredientsAndChoose = AccessTools.FieldRefAccess<T, Predicate<List<Thing>>>("foundAllIngredientsAndChoose");
private static AccessTools.FieldRef<T, Predicate<Thing>> _baseValidator
= AccessTools.FieldRefAccess<T, Predicate<Thing>>("baseValidator");
private static AccessTools.FieldRef<T, bool> _billGiverIsPawn
= AccessTools.FieldRefAccess<T, bool>("billGiverIsPawn");
private static AccessTools.FieldRef<T, int> _regionsProcessed
= AccessTools.FieldRefAccess<T, int>("regionsProcessed");
private static AccessTools.FieldRef<T, int> _adjacentRegionsAvailable
= AccessTools.FieldRefAccess<T, int>("adjacentRegionsAvailable");
private static AccessTools.FieldRef<T, Predicate<List<Thing>>> _foundAllIngredientsAndChoose
= AccessTools.FieldRefAccess<T, Predicate<List<Thing>>>("foundAllIngredientsAndChoose");
private static AccessTools.FieldRef<T, bool> _foundAll = AccessTools.FieldRefAccess<T, bool>("foundAll");
private static AccessTools.FieldRef<T, Predicate<Thing>> _thingValidator = AccessTools.FieldRefAccess<T, Predicate<Thing>>("thingValidator");
private static AccessTools.FieldRef<T, Predicate<Thing>> _thingValidator
= AccessTools.FieldRefAccess<T, Predicate<Thing>>("thingValidator");
}
private abstract class ThingValidatorInstance
@@ -364,7 +458,11 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
}
}
public override Bill Bill { get => _bill(_instance); set => _bill(_instance) = value; }
public override Bill Bill
{
get => _bill(_instance);
set => _bill(_instance) = value;
}
//public override Thing BillGiver { get => _billGiver(_instance); set => _billGiver(_instance) = value; }
private static AccessTools.FieldRef<T, Bill> _bill = AccessTools.FieldRefAccess<T, Bill>("bill");
@@ -373,9 +471,20 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
public class TryFindBestIngredientsHelper_Patch : FishPatch
{
public override string Description => "Part of the DoBill optimization";
public override string Description { get; } = "Part of the DoBill optimization";
public override Delegate TargetMethodGroup => WorkGiver_DoBill.TryFindBestIngredientsHelper;
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)
{
@@ -399,24 +508,43 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
public class TryFindBestIngredientsInSet_NoMixHelper_Patch : FishPatch
{
public override string Description => "Part of the DoBill optimization";
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 "
+ "enough of both is found. This patch removes ingredients from the lookup when enough of them is found, "
+ "allowing the patched method to skip counting them and go straight to the other missing ingredient. In "
+ "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 Delegate TargetMethodGroup => WorkGiver_DoBill.TryFindBestIngredientsInSet_NoMixHelper;
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)
=> alreadySorted = alreadySorted
|| (bill?.billStack is { } stack
|| (bill?.billStack is { } stack
#if V1_4
&& stack.billGiver is not Building_MechGestator
// ReSharper disable once MergeIntoPattern
&& stack.billGiver is not Building_MechGestator
#endif
);
);
public static CodeInstructions? Transpiler(CodeInstructions codes, MethodBase method)
public static CodeInstructions Transpiler(CodeInstructions instructions, MethodBase method,
ILGenerator generator)
{
var i_variable = FishTranspiler.FirstLocalVariable(method, typeof(int));
var new_i_label = generator.DefineLabel();
return codes.ReplaceAt(
(codes, i)
return instructions.ReplaceAt((codes, i)
=> i + 3 < codes.Count
&& codes[i] == i_variable.Load()
&& codes[i + 1] == FishTranspiler.Constant(1) // i++;
@@ -425,22 +553,33 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
code
=> new[]
{
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
}
);
code.WithLabels(new_i_label)
});
// if (!flag)
// {
// if (missingIngredients == null)
// return false;
// missingIngredients.Add(ingredientCount);
// }
// else
// {
// MarkIngredientCountAsFound(ingredientCount, bill);
// }
}
public static void MarkIngredientCountAsFound(IngredientCount ingredientCount, Bill bill)
public static void MarkIngredientCountAsFound(IngredientCount ingredientCount, Bill? bill)
{
if (bill?.billStack is not { } billStack
#if V1_4
|| billStack.billGiver is Building_MechGestator
#endif
)
)
{
return;
}
@@ -451,7 +590,7 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
ref var cacheItem = ref cache._items[cache.IndexOfKey(ingredientCount)];
if ((cache.Count == 1 && cacheItem.Value.Defs.Count == 1)
if ((cache.Count == 1 /*&& cacheItem.Value.Defs.Count == 1*/)
|| bill.recipe?.workerCounterClass != typeof(RecipeWorkerCounter))
{
return;
@@ -465,25 +604,24 @@ public class WorkGiver_DoBillOptimization : ClassWithFishPatches
{
public IntVec3 rootCell;
public int Compare(Thing x, Thing y) => (x.Position - rootCell).LengthHorizontalSquared.CompareTo((y.Position - rootCell).LengthHorizontalSquared);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Compare(Thing x, Thing y)
=> (x.Position - rootCell).LengthHorizontalSquared.CompareTo(
(y.Position - rootCell).LengthHorizontalSquared);
}
public struct RecipeIngredientCacheValue
public record struct RecipeIngredientCacheValue
{
public IngredientCacheList thingDefs;
public IngredientCacheList? thingDefs;
private int _nextRefreshTick;
public RecipeIngredientCacheValue(IngredientCacheList thingDefs)
{
this.thingDefs = thingDefs;
_nextRefreshTick = Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
}
public void Update(Bill bill)
=> _nextRefreshTick = TickHelper.Add(GenTicks.TickRareInterval * 2, bill.loadID);
public bool ShouldRefreshNow
public bool Dirty
{
get => _nextRefreshTick < Current.Game.tickManager.TicksGame;
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => TickHelper.Past(_nextRefreshTick);
}
}
}
@@ -1,29 +1,38 @@
// 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 PerformanceFish.ModCompatibility;
namespace PerformanceFish.JobSystem;
public class WorkGiver_HaulOptimization : ClassWithFishPatches
{
public class PotentialWorkThingsGlobal_Patch : FishPatch
{
public override string Description => "Sorts haulables by distance before running expensive hauling calculations on them to avoid checks on far away items";
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(WorkGiver_Haul), nameof(WorkGiver_Haul.PotentialWorkThingsGlobal));
public static IEnumerable<Thing> Postfix(IEnumerable<Thing> __result, Pawn pawn)
{
if (pawn != null && __result is List<Thing> list)
Sort(list, pawn);
return __result;
}
private static void Sort(List<Thing> list, Pawn pawn)
{
_comparer.rootCell = pawn.Position;
list.Sort(_comparer);
public class PotentialWorkThingsGlobal_Patch : FishPatch
{
public override bool Enabled => base.Enabled && !ActiveMods.Multiplayer;
//Log.ErrorOnce(list.Select(t => t.Position.DistanceToSquared(pawn.Position)).ToStringSafeEnumerable(), 84736285);
}
private static WorkGiver_DoBillOptimization.ThingPositionComparer _comparer = new();
}
public override string Description { get; }
= "Sorts haulables by distance before running expensive hauling calculations on them to avoid checks on "
+ "far away items";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(WorkGiver_Haul), nameof(WorkGiver_Haul.PotentialWorkThingsGlobal));
public static IEnumerable<Thing> Postfix(IEnumerable<Thing> __result, Pawn? pawn)
{
if (pawn != null && __result is List<Thing> list)
Sort(list, pawn);
return __result;
}
private static void Sort(List<Thing> list, Pawn pawn)
{
_comparer.rootCell = pawn.Position;
list.Sort(_comparer);
}
private static WorkGiver_DoBillOptimization.ThingPositionComparer _comparer = new();
}
}
@@ -1,4 +1,4 @@
// 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/.
@@ -11,18 +11,21 @@ public class WorkGiver_MergeOptimization : ClassWithFishPatches
{
public class JobOnThing_Patch : FishPatch
{
public override string Description => "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";
public override MethodBase TargetMethodInfo => AccessTools.Method(typeof(WorkGiver_Merge), nameof(WorkGiver_Merge.JobOnThing));
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";
public override MethodBase TargetMethodInfo { get; }
= AccessTools.Method(typeof(WorkGiver_Merge), nameof(WorkGiver_Merge.JobOnThing));
public static bool Prefix(Thing t, ref Job? __result)
{
if (t.GetSlotGroup() is null)
{
RemoveFromListerMergeables(t);
__result = null;
return false;
}
return true;
if (t.GetSlotGroup() is not null)
return true;
RemoveFromListerMergeables(t);
__result = null;
return false;
}
private static void RemoveFromListerMergeables(Thing t)