1.5, added stuff, fixed stuff
- mod icon - fixes for new vanilla clipping issues - GenClosest optimization improving repairing, wardening, tending jobs - caching for new ListerBuildings methods - DynamicDrawManager single threading - Type.GetFields caching - StatParts analyzer category - removed duplicate steam warning and optionally window - added exception handling for debug menu, incidentworker, Graphic_Multi, InitializeComps, making all those now log what mod broke and not cause black screens or whatever
This commit is contained in:
@@ -3,13 +3,8 @@
|
||||
// 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 System.Linq;
|
||||
using static System.Reflection.Emit.OpCodes;
|
||||
using static PerformanceFish.TranspilerHelpers;
|
||||
|
||||
namespace PerformanceFish.JobSystem;
|
||||
|
||||
@@ -17,9 +12,17 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
{
|
||||
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),
|
||||
t => AccessTools.Field(t, "scanner") != null ? AccessTools.FirstMethod(t, m => m.Name.Contains("TryIssueJobPackage") && m.ReturnType == typeof(bool)) : null);
|
||||
public override string Description { get; }
|
||||
= "Work scanning optimization. Splits the IsForbidden and HasJobOnThing checks into separate loops, then "
|
||||
+ "sorts the IsForbidden loop, caches the result and skips any following duplicate checks";
|
||||
|
||||
public override MethodBase? TargetMethodInfo { get; }
|
||||
= AccessTools.FindIncludingInnerTypes(typeof(JobGiver_Work), static t
|
||||
=> AccessTools.Field(t, "scanner") != null
|
||||
? AccessTools.FirstMethod(t,
|
||||
static m => m.Name.Contains("TryIssueJobPackage") && m.ReturnType == typeof(bool))
|
||||
: null);
|
||||
|
||||
public static CodeInstructions Transpiler(CodeInstructions CodeInstructions)
|
||||
{
|
||||
var shouldRemove = true;
|
||||
@@ -46,29 +49,40 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
|
||||
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));
|
||||
public override string Description { get; }
|
||||
= "Work scanning optimization. Splits the IsForbidden and HasJobOnThing checks into separate loops, "
|
||||
+ "then 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 pawn = FishTranspiler.Argument(original, "pawn");
|
||||
|
||||
var scanner_pawn_PotentialWorkThingsGlobal = GetCallvirt(typeof(WorkGiver_Scanner), nameof(WorkGiver_Scanner.PotentialWorkThingsGlobal));
|
||||
var potentialWorkThingsGlobal_pawn_TryGetPrefilteredPotentialWorkThingsAnyType = GetCall(() => TryGetPrefilteredPotentialWorkThingsAnyType(null, null));
|
||||
var scanner_pawn_PotentialWorkThingsGlobal = FishTranspiler.Call(typeof(WorkGiver_Scanner),
|
||||
nameof(WorkGiver_Scanner.PotentialWorkThingsGlobal));
|
||||
var potentialWorkThingsGlobal_pawn_TryGetPrefilteredPotentialWorkThingsAnyType
|
||||
= FishTranspiler.Call(static () => TryGetPrefilteredPotentialWorkThingsAnyType(null!, null!));
|
||||
|
||||
var scanner_PotentialWorkThingRequest = GetGetPropertyCallvirt(typeof(WorkGiver_Scanner), nameof(WorkGiver_Scanner.PotentialWorkThingRequest));
|
||||
var scanner_pawn_GetCachedThingRequest = GetCall(() => GetCachedThingRequest(null, null));
|
||||
var scanner_PotentialWorkThingRequest = FishTranspiler.PropertyGetter(typeof(WorkGiver_Scanner),
|
||||
nameof(WorkGiver_Scanner.PotentialWorkThingRequest));
|
||||
var scanner_pawn_GetCachedThingRequest
|
||||
= FishTranspiler.Call(static () => GetCachedThingRequest(null!, null!));
|
||||
|
||||
foreach (var code in CodeInstructions)
|
||||
{
|
||||
@@ -97,31 +111,35 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
return originalRequest;
|
||||
|
||||
var listerThings = pawn.Map.listerThings.ThingsMatching(originalRequest);
|
||||
var collection = listerThings.Count > 100 ? (scanner.PotentialWorkThingsGlobal(pawn) as ICollection<Thing>) : null;
|
||||
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 };
|
||||
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> ?? [..processedThings];
|
||||
return new() { group = originalRequest.group, singleDef = fishCache };
|
||||
}
|
||||
public static IEnumerable<Thing> TryGetPrefilteredPotentialWorkThingsAnyType(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
|
||||
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)
|
||||
public static IEnumerable<Thing>
|
||||
GetPrefilteredPotentialWorkThingsUncached(IEnumerable<Thing>? originalWorkThings, Pawn pawn)
|
||||
{
|
||||
if (things is null)
|
||||
if (originalWorkThings is null)
|
||||
yield break;
|
||||
|
||||
foreach (var thing in things)
|
||||
foreach (var thing in originalWorkThings)
|
||||
{
|
||||
if (!thing.IsForbidden(pawn))
|
||||
yield return thing;
|
||||
@@ -130,7 +148,8 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
|
||||
public static class Generic<T> where T : Thing
|
||||
{
|
||||
public static IEnumerable<Thing> TryGetPrefilteredPotentialWorkThingsForList(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
public static IEnumerable<Thing> TryGetPrefilteredPotentialWorkThingsForList(
|
||||
IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
{
|
||||
if (!originalWorkThings.Any())
|
||||
return originalWorkThings;
|
||||
@@ -140,19 +159,20 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
|
||||
return cache;
|
||||
}
|
||||
public static T[] RecalculatePrefilteredPotentialWorkThings(IEnumerable<Thing> originalWorkThings, Pawn pawn)
|
||||
|
||||
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 inputList = (List<T>)originalWorkThings;
|
||||
Sequential_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));
|
||||
Array.Sort(cache,
|
||||
(a, b) => (pawnPosition - a.Position).LengthHorizontalSquared.CompareTo(
|
||||
(pawnPosition - b.Position).LengthHorizontalSquared));
|
||||
}
|
||||
|
||||
PrefilteredPotentialWorkThingsGlobal[originalWorkThings] = cache;
|
||||
@@ -160,6 +180,7 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
private static void Sequential_CheckForbiddenAndAddToList(List<T> inputList, Pawn pawn)
|
||||
{
|
||||
_tempThingList.Clear();
|
||||
@@ -171,82 +192,20 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
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();
|
||||
private static readonly List<T> _tempThingList = [];
|
||||
}
|
||||
|
||||
public static Dictionary<IEnumerable<Thing>, IEnumerable<Thing>> PrefilteredPotentialWorkThingsGlobal { get; } = new();
|
||||
public static Dictionary<IEnumerable<Thing>, IEnumerable<Thing>> PrefilteredPotentialWorkThingsGlobal { get; }
|
||||
= [];
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
public override MethodBase TargetMethodInfo
|
||||
=> AccessTools.PropertyGetter(typeof(ThingRequest), nameof(ThingRequest.IsUndefined));
|
||||
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
public static void Postfix(ThingRequest __instance, ref bool __result)
|
||||
{
|
||||
@@ -255,6 +214,7 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
|
||||
__result = NewResult(__instance);
|
||||
}
|
||||
|
||||
public static bool NewResult(ThingRequest __instance)
|
||||
=> __instance.group == ThingRequestGroup.Undefined && !((FishCache)__instance.singleDef).IsSingleDef;
|
||||
}
|
||||
@@ -262,7 +222,10 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
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));
|
||||
|
||||
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)
|
||||
@@ -271,15 +234,20 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
__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()));
|
||||
|| (__instance.group != ThingRequestGroup.Undefined
|
||||
&& (__instance.group == ThingRequestGroup.Nothing || __instance.group.StoreInRegion()));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
public override MethodBase? TargetMethodInfo { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(ListerThings), nameof(ListerThings.ThingsMatching));
|
||||
|
||||
public static bool Prefix(ListerThings __instance, ThingRequest req, ref List<Thing> __result)
|
||||
{
|
||||
if (req.singleDef is not FishCache)
|
||||
@@ -288,6 +256,7 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
__result = GetCache(__instance, req);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<Thing> GetCache(ListerThings __instance, ThingRequest req)
|
||||
{
|
||||
var cachedThings = ((FishCache)req.singleDef).FilteredThings ?? ListerThings.EmptyList;
|
||||
@@ -301,26 +270,35 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
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));
|
||||
=>
|
||||
[
|
||||
..TryIssueJobPackage_Patch.Generic<Thing>.TryGetPrefilteredPotentialWorkThingsForList(
|
||||
((FishCache)req.singleDef is { IsSingleDef: true } cache
|
||||
? __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 ThingDef? Def { get; private init; }
|
||||
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))
|
||||
@@ -329,15 +307,17 @@ public sealed class JobGiver_WorkPatches : ClassWithFishPatches
|
||||
_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 readonly Dictionary<ThingRequestGroup, FishCache> _dictOfGroups = new();
|
||||
private static readonly Dictionary<ThingDef, FishCache> _dictOfDefs = new();
|
||||
}
|
||||
|
||||
private static Pawn CurrentPawn { get; set; }
|
||||
private static Pawn? CurrentPawn { get; set; }
|
||||
}*/
|
||||
Reference in New Issue
Block a user