Performance Fish
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
// Copyright (c) 2022 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;
|
||||
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 bool DefaultState => false;
|
||||
public static bool ThreadingEnabled { get; set; }
|
||||
public override Expression<Action> TargetMethod => () => 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;
|
||||
public float maxDistanceSquared;
|
||||
public Func<Thing, float>? priorityGetter;
|
||||
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)
|
||||
{
|
||||
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 };
|
||||
|
||||
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 (searchSet is IList<Thing> thingList)
|
||||
{
|
||||
Log.Message($"Running threading code for {validator.Target.GetType()}");
|
||||
Exception? exception = null;
|
||||
try
|
||||
{
|
||||
Parallel.For<(IList<Thing> list, Variables vars)>(0, thingList.Count,
|
||||
() => (thingList, variables),
|
||||
(i, loop, listAndVars) => (listAndVars.list, ProcessThreaded(listAndVars.list[i], listAndVars.vars)),
|
||||
_ => { });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exception = ex;
|
||||
Log.Error($"Caught error while threading:\n{ex}");
|
||||
}
|
||||
if (exception is null)
|
||||
Log.Message("Finished threading code without errors");
|
||||
|
||||
return chosen;
|
||||
}
|
||||
}
|
||||
|
||||
switch (searchSet)
|
||||
{
|
||||
case IList<Thing> thingList:
|
||||
{
|
||||
for (var i = 0; i < thingList.Count; i++)
|
||||
Process(thingList[i], variables);
|
||||
break;
|
||||
}
|
||||
case IList<Pawn> pawnList:
|
||||
{
|
||||
for (var j = 0; j < pawnList.Count; j++)
|
||||
Process(pawnList[j], variables);
|
||||
break;
|
||||
}
|
||||
case IList<Building> buildingList:
|
||||
{
|
||||
for (var k = 0; k < buildingList.Count; k++)
|
||||
Process(buildingList[k], variables);
|
||||
break;
|
||||
}
|
||||
case IList<IAttackTarget> attackTargetList:
|
||||
{
|
||||
for (var l = 0; l < attackTargetList.Count; l++)
|
||||
Process((Thing)attackTargetList[l], variables);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
foreach (Thing item in searchSet)
|
||||
Process(item, variables);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return chosen;
|
||||
void Process<T>(T t, Variables variables) where T : Thing
|
||||
{
|
||||
if (t.Spawned)
|
||||
{
|
||||
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)
|
||||
{
|
||||
num2 = variables.priorityGetter(t);
|
||||
if (num2 < bestPrio || (num2 == bestPrio && num >= closestDistSquared))
|
||||
return;
|
||||
}
|
||||
chosen = t;
|
||||
closestDistSquared = variables.closestDistSquared = num;
|
||||
bestPrio = num2;
|
||||
}
|
||||
}
|
||||
}
|
||||
Variables ProcessThreaded<T>(T t, Variables variables) where T : Thing
|
||||
{
|
||||
if (t.Spawned)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
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);
|
||||
private static JobGiver_Work_PredicateAccess JobGiver_Work_Predicate { get; }
|
||||
= (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);
|
||||
private static GenClosest_PredicateAccess GenClosest_Predicate { get; }
|
||||
= (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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if threadingEnabled
|
||||
public class CanReach_Patch : FishPatch
|
||||
{
|
||||
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 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
|
||||
//}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2022 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 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());
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,343 @@
|
||||
// Copyright (c) 2022 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.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
using static PerformanceFish.TranspilerHelpers;
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
public class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
{
|
||||
public 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),
|
||||
t => AccessTools.Field(t, "scanner") != null ? AccessTools.FirstMethod(t, m => m.Name.Contains("TryIssueJobPackage") && m.ReturnType == typeof(bool)) : null);
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
{
|
||||
var shouldRemove = true;
|
||||
foreach (var code in CodeInstructions)
|
||||
{
|
||||
if (shouldRemove)
|
||||
{
|
||||
if (code.Branches(out _))
|
||||
shouldRemove = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (code.opcode == Ret)
|
||||
shouldRemove = true;
|
||||
yield return code;
|
||||
}
|
||||
}
|
||||
//Replaces
|
||||
//(Thing t) => !t.IsForbidden(pawn) && scanner.HasJobOnThing(pawn, t);
|
||||
//with
|
||||
//(Thing t) => scanner.HasJobOnThing(pawn, t);
|
||||
}
|
||||
}
|
||||
|
||||
public 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));
|
||||
public static void Prefix(Pawn pawn)
|
||||
{
|
||||
PrefilteredPotentialWorkThingsGlobal.Clear();
|
||||
FishCache.ClearAll();
|
||||
CurrentPawn = pawn;
|
||||
}
|
||||
public static void Postfix()
|
||||
{
|
||||
PrefilteredPotentialWorkThingsGlobal.Clear();
|
||||
FishCache.ClearAll();
|
||||
CurrentPawn = null;
|
||||
}
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions, MethodBase original)
|
||||
{
|
||||
var pawn = GetLdarg(original, "pawn");
|
||||
|
||||
var scanner_pawn_PotentialWorkThingsGlobal = GetCallvirt(typeof(WorkGiver_Scanner), nameof(WorkGiver_Scanner.PotentialWorkThingsGlobal));
|
||||
var potentialWorkThingsGlobal_pawn_TryGetPrefilteredPotentialWorkThingsAnyType = GetCall(() => TryGetPrefilteredPotentialWorkThingsAnyType(null, null));
|
||||
|
||||
var scanner_PotentialWorkThingRequest = GetGetPropertyCallvirt(typeof(WorkGiver_Scanner), nameof(WorkGiver_Scanner.PotentialWorkThingRequest));
|
||||
var scanner_pawn_GetCachedThingRequest = GetCall(() => GetCachedThingRequest(null, null));
|
||||
|
||||
foreach (var code in CodeInstructions)
|
||||
{
|
||||
if (code == scanner_pawn_PotentialWorkThingsGlobal)
|
||||
{
|
||||
yield return code;
|
||||
yield return pawn;
|
||||
yield return potentialWorkThingsGlobal_pawn_TryGetPrefilteredPotentialWorkThingsAnyType;
|
||||
}
|
||||
else if (code == scanner_PotentialWorkThingRequest)
|
||||
{
|
||||
yield return pawn;
|
||||
yield return scanner_pawn_GetCachedThingRequest;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ThingRequest GetCachedThingRequest(WorkGiver_Scanner scanner, Pawn pawn)
|
||||
{
|
||||
var originalRequest = scanner.PotentialWorkThingRequest;
|
||||
if (originalRequest is { singleDef: null, group: ThingRequestGroup.Undefined })
|
||||
return originalRequest;
|
||||
|
||||
var listerThings = pawn.Map.listerThings.ThingsMatching(originalRequest);
|
||||
var collection = listerThings.Count > 100 ? (scanner.PotentialWorkThingsGlobal(pawn) as ICollection<Thing>) : null;
|
||||
|
||||
var processedThings = TryGetPrefilteredPotentialWorkThingsAnyType(collection is null || listerThings.Count < collection.Count * 4 ? listerThings : collection, pawn);
|
||||
var fishCache = originalRequest.singleDef != null ? FishCache.ForDef(originalRequest.singleDef) : FishCache.ForGroup(originalRequest.group);
|
||||
fishCache.FilteredThings = processedThings as List<Thing> ?? new(processedThings);
|
||||
return new ThingRequest() { group = originalRequest.group, singleDef = fishCache };
|
||||
}
|
||||
public static IEnumerable<Thing> TryGetPrefilteredPotentialWorkThingsAnyType(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
=> originalWorkThings switch
|
||||
{
|
||||
List<Thing> => Generic<Thing>.TryGetPrefilteredPotentialWorkThingsForList(originalWorkThings, pawn),
|
||||
List<Pawn> => Generic<Pawn>.TryGetPrefilteredPotentialWorkThingsForList(originalWorkThings, pawn),
|
||||
_ => GetPrefilteredPotentialWorkThingsUncached(originalWorkThings, pawn)
|
||||
};
|
||||
public static IEnumerable<Thing> GetPrefilteredPotentialWorkThingsUncached(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
=> (originalWorkThings is not ICollection<Thing> collection || collection.Count < 2048)
|
||||
? CheckForbidden(originalWorkThings, pawn)
|
||||
: Generic<Thing>.Threaded_CheckForbiddenAndAddToAnyCollection(collection, pawn);
|
||||
|
||||
public static IEnumerable<Thing> CheckForbidden(IEnumerable<Thing> things, Pawn pawn)
|
||||
{
|
||||
if (things is null)
|
||||
yield break;
|
||||
|
||||
foreach (var thing in things)
|
||||
{
|
||||
if (!thing.IsForbidden(pawn))
|
||||
yield return thing;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Generic<T> where T : Thing
|
||||
{
|
||||
public static IEnumerable<Thing> TryGetPrefilteredPotentialWorkThingsForList(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
{
|
||||
if (!originalWorkThings.Any())
|
||||
return originalWorkThings;
|
||||
|
||||
if (!PrefilteredPotentialWorkThingsGlobal.TryGetValue(originalWorkThings, out var cache))
|
||||
cache = RecalculatePrefilteredPotentialWorkThings(originalWorkThings, pawn);
|
||||
|
||||
return cache;
|
||||
}
|
||||
public static T[] RecalculatePrefilteredPotentialWorkThings(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
{
|
||||
var inputList = originalWorkThings as List<T>;
|
||||
if (inputList.Count < 512)
|
||||
Sequential_CheckForbiddenAndAddToList(inputList, pawn);
|
||||
else
|
||||
Threaded_CheckForbiddenAndAddToList(inputList, pawn);
|
||||
|
||||
var cache = _tempThingList.ToArray();
|
||||
if (cache.Length < 1024)
|
||||
{
|
||||
var pawnPosition = pawn.Position;
|
||||
Array.Sort(cache, (a, b) => (pawnPosition - a.Position).LengthHorizontalSquared.CompareTo((pawnPosition - b.Position).LengthHorizontalSquared));
|
||||
}
|
||||
|
||||
PrefilteredPotentialWorkThingsGlobal[originalWorkThings] = cache;
|
||||
_tempThingList.Clear();
|
||||
|
||||
return cache;
|
||||
}
|
||||
private static void Sequential_CheckForbiddenAndAddToList(List<T> inputList, Pawn pawn)
|
||||
{
|
||||
_tempThingList.Clear();
|
||||
var tempThingList = _tempThingList;
|
||||
var count = inputList.Count;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (!inputList[i].IsForbidden(pawn))
|
||||
tempThingList.Add(inputList[i]);
|
||||
}
|
||||
}
|
||||
private static void Threaded_CheckForbiddenAndAddToList(List<T> inputList, Pawn pawn)
|
||||
{
|
||||
_tempThingList.Clear();
|
||||
#if DEBUG
|
||||
DebugLog.Message("Running threading code");
|
||||
var stopwatch = new System.Diagnostics.Stopwatch();
|
||||
stopwatch.Start();
|
||||
#endif
|
||||
var partitioner = Partitioner.Create(0, inputList.Count);
|
||||
|
||||
Parallel.ForEach<Tuple<int, int>, (List<T> localList, List<T> sourceList, Pawn pawn)>(partitioner,
|
||||
() => new(new(), inputList, pawn),
|
||||
(range, state, vars) =>
|
||||
{
|
||||
for (int i = range.Item1; i < range.Item2; i++)
|
||||
{
|
||||
var item = vars.sourceList[i];
|
||||
if (!item.IsForbidden(vars.pawn))
|
||||
vars.localList.Add(item);
|
||||
}
|
||||
return vars;
|
||||
},
|
||||
vars =>
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_tempThingList.AddRange(vars.localList);
|
||||
}
|
||||
});
|
||||
#if DEBUG
|
||||
stopwatch.Stop();
|
||||
DebugLog.Message($"Finished threading code without errors in {stopwatch.Elapsed}");
|
||||
#endif
|
||||
}
|
||||
public static List<T> Threaded_CheckForbiddenAndAddToAnyCollection(ICollection<T> inputCollection, Pawn pawn)
|
||||
{
|
||||
_tempThingList.Clear();
|
||||
#if DEBUG
|
||||
DebugLog.Message("Running threading code");
|
||||
var stopwatch = new System.Diagnostics.Stopwatch();
|
||||
stopwatch.Start();
|
||||
#endif
|
||||
|
||||
Parallel.ForEach<T, (List<T> localList, Pawn pawn)>(inputCollection,
|
||||
() => new(new(), pawn),
|
||||
(thing, state, vars) =>
|
||||
{
|
||||
if (!thing.IsForbidden(vars.pawn))
|
||||
vars.localList.Add(thing);
|
||||
return vars;
|
||||
},
|
||||
vars =>
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_tempThingList.AddRange(vars.localList);
|
||||
}
|
||||
});
|
||||
#if DEBUG
|
||||
stopwatch.Stop();
|
||||
DebugLog.Message($"Finished threading code without errors in {stopwatch.Elapsed}");
|
||||
#endif
|
||||
return _tempThingList;
|
||||
}
|
||||
|
||||
private static object _lock { get; } = new();
|
||||
private static List<T> _tempThingList { get; } = new();
|
||||
}
|
||||
|
||||
public static Dictionary<IEnumerable<Thing>, IEnumerable<Thing>> PrefilteredPotentialWorkThingsGlobal { get; } = new();
|
||||
}
|
||||
|
||||
public 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));
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
public static void Postfix(ThingRequest __instance, ref bool __result)
|
||||
{
|
||||
if (__instance.singleDef is not FishCache)
|
||||
return;
|
||||
|
||||
__result = NewResult(__instance);
|
||||
}
|
||||
public static bool NewResult(ThingRequest __instance)
|
||||
=> __instance.group == ThingRequestGroup.Undefined && !((FishCache)__instance.singleDef).IsSingleDef;
|
||||
}
|
||||
|
||||
public 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));
|
||||
public static bool Prefix(ThingRequest __instance, ref bool __result)
|
||||
{
|
||||
if (__instance.singleDef is not FishCache)
|
||||
return true;
|
||||
|
||||
__result = NewResult(__instance);
|
||||
return false;
|
||||
}
|
||||
public static bool NewResult(ThingRequest __instance)
|
||||
=> ((FishCache)__instance.singleDef).IsSingleDef
|
||||
|| (__instance.group != ThingRequestGroup.Undefined && (__instance.group == ThingRequestGroup.Nothing || __instance.group.StoreInRegion()));
|
||||
}
|
||||
|
||||
public 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);
|
||||
public static bool Prefix(ListerThings __instance, ThingRequest req, ref List<Thing> __result)
|
||||
{
|
||||
if (req.singleDef is not FishCache)
|
||||
return true;
|
||||
|
||||
__result = GetCache(__instance, req);
|
||||
return false;
|
||||
}
|
||||
public static List<Thing> GetCache(ListerThings __instance, ThingRequest req)
|
||||
{
|
||||
var cachedThings = ((FishCache)req.singleDef).FilteredThings ?? ListerThings.EmptyList;
|
||||
if (__instance.use == ListerThingsUse.Region && cachedThings.Count > 20)
|
||||
{
|
||||
var regionalThings = GetForRegion(__instance, req);
|
||||
return regionalThings.Count < cachedThings.Count ? regionalThings : cachedThings;
|
||||
}
|
||||
else
|
||||
{
|
||||
return cachedThings;
|
||||
}
|
||||
}
|
||||
public static List<Thing> GetForRegion(ListerThings __instance, ThingRequest req)
|
||||
=> new(TryIssueJobPackage_Patch.Generic<Thing>.TryGetPrefilteredPotentialWorkThingsForList(
|
||||
(((FishCache)req.singleDef) is var cache && cache.IsSingleDef
|
||||
? __instance.listsByDef.TryGetValue(cache.Def, out var value) ? value : null
|
||||
: __instance.listsByGroup.TryGetItem((int)req.group, out var item) ? item : null) ?? ListerThings.EmptyList, CurrentPawn));
|
||||
}
|
||||
|
||||
public class FishCache : ThingDef
|
||||
{
|
||||
public bool IsSingleDef => Def != null;
|
||||
public ThingDef Def { get; private set; }
|
||||
public List<Thing> FilteredThings { get; set; }
|
||||
public static FishCache ForDef(ThingDef def)
|
||||
{
|
||||
if (_dictOfDefs.TryGetValue(def, out var value))
|
||||
return value;
|
||||
|
||||
_dictOfDefs[def] = value = new(def.defName) { Def = def };
|
||||
return value;
|
||||
}
|
||||
public static FishCache ForGroup(ThingRequestGroup group)
|
||||
{
|
||||
if (_dictOfGroups.TryGetValue(group, out var value))
|
||||
return value;
|
||||
|
||||
_dictOfGroups[group] = value = new(group.ToString());
|
||||
return value;
|
||||
}
|
||||
public static void ClearAll()
|
||||
{
|
||||
_dictOfGroups.Clear();
|
||||
_dictOfDefs.Clear();
|
||||
}
|
||||
private FishCache(string name) => defName = name;
|
||||
private static Dictionary<ThingRequestGroup, FishCache> _dictOfGroups { get; } = new();
|
||||
private static Dictionary<ThingDef, FishCache> _dictOfDefs { get; } = new();
|
||||
}
|
||||
|
||||
private static Pawn CurrentPawn { get; set; }
|
||||
}*/
|
||||
@@ -0,0 +1,468 @@
|
||||
// Copyright (c) 2022 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>;
|
||||
|
||||
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 class TryFindBestIngredientsHelper_InnerDelegate_Patch : FishPatch
|
||||
{
|
||||
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 MethodBase TargetMethodInfo
|
||||
=> AccessTools.FirstMethod(DelegateInstanceType, m
|
||||
=> m.GetParameters() is { Length: 1 } parms
|
||||
&& parms[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
|
||||
&& 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 bool Replacement<T>(T __instance, Region r) where T : class
|
||||
{
|
||||
var instance = _delegateInstance.Get(__instance);
|
||||
var thingValidator = _thingValidatorInstance.Get(instance.ThingValidator.Target);
|
||||
|
||||
List<Thing> list;
|
||||
if (thingValidator is null) // <-- TryFindBestFixedIngredients, used by biosculptors, goes here. Fuck them
|
||||
{
|
||||
list = GetDefaultList(r);
|
||||
}
|
||||
else
|
||||
{
|
||||
var bill = thingValidator.Bill;
|
||||
|
||||
if (HasNoFilters(bill) // apparently applies to a rim reborn benches?
|
||||
|| (BlackListedModExtensions.Exists(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)
|
||||
{
|
||||
cache.thingDefs = UpdateCache(bill, r, cache.thingDefs);
|
||||
cache.ShouldRefreshNow = false;
|
||||
RecipeIngredientCache.Get[bill] = cache;
|
||||
}
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
/*private static bool HasNoFilters(Bill bill)
|
||||
{
|
||||
if (bill.ingredientFilter is null
|
||||
|| bill.recipe.fixedIngredientFilter is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var ingredients = bill.recipe.ingredients;
|
||||
for (var i = 0; i < ingredients.Count; i++)
|
||||
{
|
||||
if (ingredients[i].filter is null)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}*/
|
||||
|
||||
private static bool IsBlackListed(Bill bill)
|
||||
=> bill is null
|
||||
|| (bill.recipe.modExtensions?.Exists(e
|
||||
=> BlackListedModExtensions.Contains(e.GetType()))
|
||||
?? false);
|
||||
|
||||
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)
|
||||
{
|
||||
var count = thingsToInsert.Count;
|
||||
var comparer = _insertAtCorrectPositionComparer;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var thing = thingsToInsert[i];
|
||||
comparer.rootCell = WorkGiver_DoBill.GetBillGiverRootCell(thingsToInsert[i], pawn);
|
||||
|
||||
var index = ~list.BinarySearch(thing, comparer);
|
||||
if (index < 0)
|
||||
index = ~index;
|
||||
list.Insert(index, thing);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly ThingPositionComparer _insertAtCorrectPositionComparer = new();
|
||||
|
||||
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))
|
||||
{
|
||||
WorkGiver_DoBill.newRelevantThings.Add(thing);
|
||||
WorkGiver_DoBill.processedThings.Add(thing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Thing> GetList(Dictionary<ThingDef, List<Thing>> listsByDef, IngredientCacheList cacheList)
|
||||
{
|
||||
var list = _tempListForIngredients;
|
||||
list.Clear();
|
||||
var set = _tempSetForIngredientDefs;
|
||||
set.Clear();
|
||||
|
||||
for (var i = 0; i < cacheList.Count; i++)
|
||||
{
|
||||
var (defs, found) = cacheList[i].Value;
|
||||
if (found)
|
||||
continue;
|
||||
|
||||
var count = defs.Count;
|
||||
for (var j = 0; j < count; j++)
|
||||
{
|
||||
if (listsByDef.TryGetValue(defs[j], out var listByDef) && listByDef.Any())
|
||||
{
|
||||
if (i > 0 && !set.Add(defs[j]))
|
||||
continue;
|
||||
|
||||
list.AddRange(listByDef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static IngredientCacheList UpdateCache(Bill bill, Region r, IngredientCacheList? cacheList)
|
||||
{
|
||||
var recipe = bill.recipe;
|
||||
var ingredients = recipe.ingredients;
|
||||
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
|
||||
{
|
||||
if (result.TryGetValue(ingredients[i], out var ingredientList))
|
||||
{
|
||||
ingredientList.Defs.Clear();
|
||||
ingredientList.Found = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
result[ingredients[i]] = ingredientList = new(new(), false);
|
||||
}
|
||||
|
||||
var filter = ingredients[i].filter;
|
||||
var allowedDefCount = filter.allowedDefs.Count;
|
||||
|
||||
if (allowedDefCount < 1)
|
||||
continue;
|
||||
|
||||
if (allowedDefCount == 1)
|
||||
{
|
||||
ingredientList.Defs.Add(filter.allowedDefs.First());
|
||||
continue;
|
||||
}
|
||||
|
||||
var smallestFilter = bill.ingredientFilter;
|
||||
var mediumFilter = recipe.fixedIngredientFilter;
|
||||
var largestFilter = filter;
|
||||
|
||||
OrderFiltersBySize(ref smallestFilter, ref mediumFilter, ref largestFilter);
|
||||
|
||||
var cachedSmallestList = AllowedDefsListCache.GetValue(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.");
|
||||
ThingFilterPatches.ForceSynchronizeCache(smallestFilter);
|
||||
// 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()
|
||||
&& mediumFilter.Allows(def)
|
||||
&& largestFilter.Allows(def))
|
||||
{
|
||||
ingredientList.Defs.Add(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void OrderFiltersBySize(ref ThingFilter smallestFilter, ref ThingFilter mediumFilter, ref ThingFilter largestFilter)
|
||||
{
|
||||
if (smallestFilter.allowedDefs.Count > mediumFilter.allowedDefs.Count)
|
||||
(smallestFilter, mediumFilter) = (mediumFilter, smallestFilter);
|
||||
|
||||
if (smallestFilter.allowedDefs.Count > largestFilter.allowedDefs.Count)
|
||||
(smallestFilter, largestFilter) = (largestFilter, smallestFilter);
|
||||
|
||||
if (mediumFilter.allowedDefs.Count > largestFilter.allowedDefs.Count)
|
||||
(mediumFilter, largestFilter) = (largestFilter, mediumFilter);
|
||||
}
|
||||
|
||||
private static readonly List<Thing> _tempListForIngredients = new();
|
||||
private static readonly HashSet<ThingDef> _tempSetForIngredientDefs = new();
|
||||
}
|
||||
|
||||
private static Type DelegateInstanceType { get; }
|
||||
= AccessTools.FirstInner(typeof(WorkGiver_DoBill), t
|
||||
=> AccessTools.FirstMethod(t, m
|
||||
=> m.Name.Contains($"<{nameof(WorkGiver_DoBill.TryFindBestIngredientsHelper)}>")) != null
|
||||
&& AccessTools.Field(t, "pawn") != null
|
||||
&& AccessTools.Field(t, "baseValidator") != null
|
||||
&& AccessTools.Field(t, "billGiverIsPawn") != null
|
||||
&& AccessTools.Field(t, "regionsProcessed") != null
|
||||
&& AccessTools.Field(t, "adjacentRegionsAvailable") != null
|
||||
&& 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.");
|
||||
|
||||
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.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.");
|
||||
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles")]
|
||||
private static DelegateInstance _delegateInstance { get; }
|
||||
= (DelegateInstance)Activator.CreateInstance(typeof(DelegateInstanceImplementation<>).MakeGenericType(DelegateInstanceType));
|
||||
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles")]
|
||||
private static ThingValidatorInstance _thingValidatorInstance { get; }
|
||||
= (ThingValidatorInstance)Activator.CreateInstance(typeof(ThingValidatorInstanceImplementation<>).MakeGenericType(ThingValidatorInstanceType));
|
||||
|
||||
private abstract class DelegateInstance
|
||||
{
|
||||
public abstract DelegateInstance Get(object obj);
|
||||
|
||||
public abstract Pawn Pawn { get; set; }
|
||||
public abstract Predicate<Thing> BaseValidator { get; set; }
|
||||
public abstract bool BillGiverIsPawn { get; set; }
|
||||
public abstract int RegionsProcessed { get; set; }
|
||||
public abstract int AdjacentRegionsAvailable { get; set; }
|
||||
public abstract Predicate<List<Thing>> FoundAllIngredientsAndChoose { get; set; }
|
||||
public abstract bool FoundAll { get; set; }
|
||||
public abstract Predicate<Thing> ThingValidator { get; set; }
|
||||
}
|
||||
|
||||
private class DelegateInstanceImplementation<T> : DelegateInstance where T : class
|
||||
{
|
||||
#pragma warning disable CS8618
|
||||
private T _instance;
|
||||
#pragma warning restore CS8618
|
||||
|
||||
public override DelegateInstance Get(object obj)
|
||||
{
|
||||
_instance = (T)obj;
|
||||
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; }
|
||||
|
||||
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, bool> _foundAll = AccessTools.FieldRefAccess<T, bool>("foundAll");
|
||||
private static AccessTools.FieldRef<T, Predicate<Thing>> _thingValidator = AccessTools.FieldRefAccess<T, Predicate<Thing>>("thingValidator");
|
||||
}
|
||||
|
||||
private abstract class ThingValidatorInstance
|
||||
{
|
||||
public abstract ThingValidatorInstance? Get(object obj);
|
||||
|
||||
public abstract Bill Bill { get; set; }
|
||||
//public abstract Thing BillGiver { get; set; }
|
||||
}
|
||||
|
||||
private class ThingValidatorInstanceImplementation<T> : ThingValidatorInstance where T : class
|
||||
{
|
||||
#pragma warning disable CS8618
|
||||
private T _instance;
|
||||
#pragma warning restore CS8618
|
||||
|
||||
public override ThingValidatorInstance? Get(object obj)
|
||||
{
|
||||
if (obj is T t)
|
||||
{
|
||||
_instance = t;
|
||||
return this;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
//private static AccessTools.FieldRef<T, Thing> _billGiver = AccessTools.FieldRefAccess<T, Thing>("billGiver");
|
||||
}
|
||||
|
||||
public class TryFindBestIngredientsHelper_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the DoBill optimization";
|
||||
|
||||
public override Delegate TargetMethodGroup => WorkGiver_DoBill.TryFindBestIngredientsHelper;
|
||||
|
||||
public static void Postfix(Predicate<Thing> thingValidator)
|
||||
{
|
||||
var thingValidatorInstance = _thingValidatorInstance.Get(thingValidator.Target);
|
||||
if (thingValidatorInstance is null)
|
||||
return;
|
||||
|
||||
var cache = RecipeIngredientCache.Get[thingValidatorInstance.Bill].thingDefs;
|
||||
if (cache is null)
|
||||
return;
|
||||
|
||||
var items = cache._items;
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
ref var item = ref items[i];
|
||||
if (item.Value.Found)
|
||||
item = new(item.Key, new(item.Value.Defs, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TryFindBestIngredientsInSet_NoMixHelper_Patch : FishPatch
|
||||
{
|
||||
public override string Description => "Part of the DoBill optimization";
|
||||
|
||||
public override Delegate TargetMethodGroup => WorkGiver_DoBill.TryFindBestIngredientsInSet_NoMixHelper;
|
||||
|
||||
public static void Prefix(ref bool alreadySorted) => alreadySorted = true;
|
||||
|
||||
public static CodeInstructions? Transpiler(CodeInstructions codes, MethodBase method)
|
||||
{
|
||||
var i_variable = FishTranspiler.FirstLocalVariable(method, typeof(int));
|
||||
|
||||
return codes.ReplaceAt(
|
||||
(codes, i)
|
||||
=> i + 3 < codes.Count
|
||||
&& codes[i] == i_variable.Load()
|
||||
&& codes[i + 1] == FishTranspiler.Constant(1) // i++;
|
||||
&& codes[i + 2] == FishTranspiler.Add
|
||||
&& codes[i + 3] == i_variable.Store(),
|
||||
code
|
||||
=> new[]
|
||||
{
|
||||
FishTranspiler.FirstLocalVariable(method, typeof(IngredientCount))
|
||||
.WithLabels(code.ExtractLabels()),
|
||||
FishTranspiler.FirstArgument(method, typeof(Bill)),
|
||||
FishTranspiler.Call(MarkIngredientCountAsFound),
|
||||
code
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static void MarkIngredientCountAsFound(IngredientCount ingredientCount, Bill bill)
|
||||
{
|
||||
if (bill is null)
|
||||
return;
|
||||
|
||||
var cache = RecipeIngredientCache.Get[bill].thingDefs;
|
||||
if (cache is null)
|
||||
return;
|
||||
|
||||
ref var cacheItem = ref cache._items[cache.IndexOfKey(ingredientCount)];
|
||||
|
||||
cacheItem = new(cacheItem.Key, new(cacheItem.Value.Defs, true));
|
||||
}
|
||||
}
|
||||
|
||||
public class ThingPositionComparer : IComparer<Thing>
|
||||
{
|
||||
public IntVec3 rootCell;
|
||||
|
||||
public int Compare(Thing x, Thing y) => (x.Position - rootCell).LengthHorizontalSquared.CompareTo((y.Position - rootCell).LengthHorizontalSquared);
|
||||
}
|
||||
|
||||
public struct RecipeIngredientCacheValue
|
||||
{
|
||||
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 bool ShouldRefreshNow
|
||||
{
|
||||
get => _nextRefreshTick < Current.Game.tickManager.TicksGame;
|
||||
set => _nextRefreshTick = value ? 0 : Current.Game.tickManager.TicksGame + 384 + Math.Abs(Rand.Int % 256);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2022 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 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);
|
||||
|
||||
//Log.ErrorOnce(list.Select(t => t.Position.DistanceToSquared(pawn.Position)).ToStringSafeEnumerable(), 84736285);
|
||||
}
|
||||
private static WorkGiver_DoBillOptimization.ThingPositionComparer _comparer = new();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user