diff --git a/1.4/Assemblies/PerformanceFish.dll b/1.4/Assemblies/PerformanceFish.dll index 3800413..7577b4a 100644 Binary files a/1.4/Assemblies/PerformanceFish.dll and b/1.4/Assemblies/PerformanceFish.dll differ diff --git a/1.4/Assemblies/PerformanceFish.pdb b/1.4/Assemblies/PerformanceFish.pdb index 08bf6a8..ebdbbe0 100644 Binary files a/1.4/Assemblies/PerformanceFish.pdb and b/1.4/Assemblies/PerformanceFish.pdb differ diff --git a/Source/PerformanceFish/Defs/DefDatabasePatches.cs b/Source/PerformanceFish/Defs/DefDatabasePatches.cs index 5a97455..332e742 100644 --- a/Source/PerformanceFish/Defs/DefDatabasePatches.cs +++ b/Source/PerformanceFish/Defs/DefDatabasePatches.cs @@ -3,8 +3,7 @@ // 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/. -// crashes to desktop somehow -/*using Mono.Cecil; +using Mono.Cecil; using Mono.Cecil.Cil; using PerformanceFish.Prepatching; @@ -14,37 +13,46 @@ public sealed class DefDatabasePatches : ClassWithFishPrepatches { public sealed class GetNamedPatch : FishPrepatch { + public override string? Description { get; } + = "Optimizes the method with fewer instructions and faster dictionary lookup function"; + public override MethodBase TargetMethodBase { get; } = methodof(DefDatabase.GetNamed); public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) - => ilProcessor.ReplaceBodyWith(GetNamed); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T? GetNamed(string defName, bool errorOnFail = true) where T : Def - { - var value = DefDatabase.defsByName.TryGetValue(defName); - - if (errorOnFail && value is null) - LogMissingDef(defName); - - return value; - } - - [MethodImpl(MethodImplOptions.NoInlining)] - public static void LogMissingDef(string defName) where T : Def - => Log.Error($"Failed to find {typeof(T)} named {defName}. There are { - DefDatabase.defsList.Count} defs of this type loaded."); + => ilProcessor.ReplaceBodyWith(Replacement.GetNamed); } public sealed class GetByShortHashPatch : FishPrepatch { + public override string? Description { get; } + = "Optimizes the method with fewer instructions and faster dictionary lookup function"; + public override MethodBase TargetMethodBase { get; } = methodof(DefDatabase.GetByShortHash); public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) - => ilProcessor.ReplaceBodyWith(GetByShortHash); - + => ilProcessor.ReplaceBodyWith(Replacement.GetByShortHash); + } + + public static class Replacement where T : Def + { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T? GetByShortHash(ushort shortHash) where T : Def + public static T? GetNamed(string defName, bool errorOnFail = true) + { + var value = DefDatabase.defsByName.TryGetValue(defName); + + if (errorOnFail && value is null) + LogMissingDef(defName); + + return value; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void LogMissingDef(string defName) + => Log.Error($"Failed to find {typeof(T)} named '{defName}'. DefDatabase<{ + typeof(T)}> contains a total of {DefDatabase.defsList.Count} loaded defs."); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T? GetByShortHash(ushort shortHash) => DefDatabase.defsByShortHash.TryGetValue(shortHash); } -}*/ \ No newline at end of file +} \ No newline at end of file diff --git a/Source/PerformanceFish/FishSettings.cs b/Source/PerformanceFish/FishSettings.cs index 1d07f23..3fd9602 100644 --- a/Source/PerformanceFish/FishSettings.cs +++ b/Source/PerformanceFish/FishSettings.cs @@ -26,7 +26,7 @@ public sealed class FishSettings : ModSettings if (!TryScribeVersion()) goto DoneLoading; - Scribe_Values.Look(ref _threadingEnabled, "ThreadingEnabled"); + Scribe_Values.Look(ref ThreadingEnabled, "ThreadingEnabled"); Scribe_Values.Look(ref MothballEverything, "MothballEverything"); Scribe_Values.Look(ref ImproveHaulingAccuracy, "ImproveHaulingAccuracy", true); @@ -204,6 +204,10 @@ public sealed class FishSettings : ModSettings ls.CheckboxLabeled("Improve hauling accuracy", ref ImproveHaulingAccuracy, ImproveHaulingAccuracyDescription); ls.Gap(); ls.CheckboxLabeled("Mothball everything", ref MothballEverything, MothballEverythingDescription); + ls.Gap(); + ls.CheckboxLabeled("Allow threading", ref ThreadingEnabled, "Currently only affects the gas grid. " + + "Experimental and disabled by default. Requires a high gas coverage on the map to outperform the single " + + "threaded variant. Changes to this setting require a restart."); ls.Gap(); @@ -317,23 +321,9 @@ public sealed class FishSettings : ModSettings public static bool MothballEverything, - ImproveHaulingAccuracy = true; - - public static bool ThreadingEnabled - { - get => false; //_threadingEnabled; - set - { - if (value == _threadingEnabled) - return; - - ThreadingPatches.ForEach(p => p.Enabled = value); - _threadingEnabled = value; - } - } - - private static bool _threadingEnabled; - public static List ThreadingPatches { get; } = []; + ImproveHaulingAccuracy = true, + ThreadingEnabled; + private static Rect _scrollRect = new(0f, 0f, 500f, 9001f); private static Vector2 _scrollPosition; diff --git a/Source/PerformanceFish/GasGridOptimization.cs b/Source/PerformanceFish/GasGridOptimization.cs index 4306a13..82bfdd7 100644 --- a/Source/PerformanceFish/GasGridOptimization.cs +++ b/Source/PerformanceFish/GasGridOptimization.cs @@ -8,6 +8,7 @@ // #define GAS_DEBUG_L3 // individual cell changes using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; @@ -18,9 +19,6 @@ namespace PerformanceFish; public sealed class GasGridOptimization : ClassWithFishPrepatches { - // public static readonly List ParallelGasGrids = new(); - // public static GasGrid[] RegisteredGasGrids = Array.Empty(); - public sealed class SetDirectPatch : FishPrepatch { public override bool ShowSettings => false; @@ -56,8 +54,6 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches typeof(Notify_ThingSpawnedPatch), typeof(ExposeDataPatch), typeof(MouseOverReadOutOnGUI), typeof(DebugToolsGeneralPushGas) ]; - - // public static EventWaitHandle? WaitHandle { get; private set; } public static object? MonitorObject { get; private set; } @@ -65,8 +61,8 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches = "Optimizes the gas grid to utilize highly performant bit manipulation for much faster processing of " + "cells, usually in buckets of 64 simultaneously checked cells, and threading to process the different " + "gas types in parallel. Additionally exposes gas to xml through the PerformanceFish.GasDef type. " - + "Performance impact scales with map size, but is generally large. Currently only utilizing single core " - + "parallelism, threading is planned"; + + "Performance impact scales with map size, but is generally large. Threading requires the threading " + + "enabled setting to be turned on. Default is off"; public override MethodBase TargetMethodBase { get; } = AccessTools.DeclaredMethod(typeof(GasGrid), nameof(GasGrid.Tick)); @@ -76,102 +72,57 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches public static void ReplacementBody(GasGrid __instance) { - // Parallel.For(0, ParallelGasGrids.Length, static i => ParallelGasGrids[i].Tick()); - // (WaitHandle ?? Setup()).Set(); - - // if (!RegisteredGasGrids.Contains(__instance)) - // Setup(__instance); - - // ParallelNoAlloc.PulseRegisteredBackgroundWorkers(__instance.MonitorObject()); - var gasGrids = __instance.ParallelGasGrids(); - - for (var i = 1; i < gasGrids.Length; i++) - gasGrids[i].Tick(); - if (gasGrids.Length < 1) return; + if (FishSettings.ThreadingEnabled + && TryGetRegisteredGasGridWorkers(__instance, out var monitorObject)) + { + TryPulseRegisteredGasGridWorkers(gasGrids, monitorObject); + } + else + { + for (var i = 1; i < gasGrids.Length; i++) + gasGrids[i].Tick(); + } + var firstGasGrid = gasGrids[0]; firstGasGrid.Tick(); __instance.cycleIndexDiffusion = firstGasGrid.CycleIndexDiffusion; __instance.cycleIndexDissipation = firstGasGrid.CycleIndexDissipation; + + if (FishSettings.ThreadingEnabled) + WaitForGasGridWorkers(gasGrids); } - // [MemberNotNull(nameof(WaitHandle))] - // public static EventWaitHandle Setup() - // { - // WaitHandle = new ManualResetEvent(false); - // for (var i = ParallelGasGrids.Length; i-- > 1;) - // { - // var gasGrid = ParallelGasGrids[i]; - // ThreadPool.RegisterWaitForSingleObject(WaitHandle, (_, _) => gasGrid.Tick(), null, -1, false); - // } - // - // return WaitHandle; - // } + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool TryGetRegisteredGasGridWorkers(GasGrid __instance, + [NotNullWhen(true)] out object? monitorObject) + => (monitorObject = __instance.map.GasGridMonitorObject()) != null + && ParallelNoAlloc.HasAnyRegisteredWorkers(monitorObject); -// public static ParallelGasGrid[] Setup(GasGrid instance) -// { -// // lock (ParallelGasGrids) -// // { -// // if (RegisteredGasGrids.Contains(instance)) -// // { -// // // only possible if 2 threads try registering the same gas grid instance. Rimthreaded? Maybe throw? -// // SpinWait.SpinUntil(static () => MonitorObject != null); -// // #pragma warning disable CS8774 -// // return; -// // #pragma warning restore CS8774 -// // } -// // -// // RegisteredGasGrids = RegisteredGasGrids.Add(instance); -// // } -// -// return new ParallelGasGrid[DefDatabase.DefCount]; -// -// var gasDefsList = DefDatabase.AllDefsListForReading; -// -// var newGasGrids = new ParallelGasGrid[gasDefsList.Count]; -// try -// { -// for (var i = 0; i < newGasGrids.Length; i++) -// newGasGrids[i] = new(instance.map, gasDefsList[i]); -// -// // ParallelGasGrids.AddRangeFast(newGasGrids); -// -// instance.MonitorObject() = ParallelNoAlloc.RegisterBackgroundWaitingWorkers(MakeTickActions(newGasGrids, -// Math.Max(newGasGrids.Length - 1, 0), 1)); // 1st grid ticked on main thread -// } -// catch (Exception e) -// { -// Log.Error($"Caught exception in map with ID {instance.map.uniqueID} and hashCode {instance.map.GetHashCode()}: {e}"); -// return Array.Empty(); -// } -// -// return newGasGrids; -// } - - private static Action[] MakeTickActions(T gasGrids, int count, int startIndex) - where T : IList + [MethodImpl(MethodImplOptions.NoInlining)] + public static void TryPulseRegisteredGasGridWorkers(ParallelGasGrid[] gasGrids, object monitorObject) { - var actions = new Action[count]; - count += startIndex; - for (var i = startIndex; i < count; i++) + if (gasGrids.Length > 1) + ParallelNoAlloc.PulseRegisteredBackgroundWorkers(monitorObject); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void WaitForGasGridWorkers(ParallelGasGrid[] gasGrids) + { + if (gasGrids.Length <= 1) + return; + + var currentTick = TickHelper.TicksGame; + for (var i = 1; i < gasGrids.Length;) { - var gasGrid = gasGrids[i]; - actions[i - startIndex] = () => gasGrid.Tick(); + Thread.MemoryBarrier(); + if (gasGrids[i].LastFinishedTick == currentTick) + i++; } - - return actions; - } - - // for save-game compatibility. The two cycle indices get scribed with ExposeData - public static void AdvanceCycleIndex(ref int cycleIndex, int area, int increment) - { - cycleIndex += increment; - if (cycleIndex >= area) - cycleIndex = 0; } } @@ -491,10 +442,13 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches { private ulong[] _gasCoverageForIndicesInRandomOrder; private byte[] _gasDensity; + private volatile int _lastFinishedTick = -1; public event Action? Ticked; public GasDef GasDef { get; private set; } + + public int LastFinishedTick => _lastFinishedTick; public int DissipationRate => GasDef.dissipationRate; @@ -783,8 +737,10 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches public void Tick() { - if (!CalculateGasEffects) + try { + if (!CalculateGasEffects) + { #if GAS_DEBUG_L1 if (CheckCurrentlyContainsGas()) { @@ -792,34 +748,38 @@ public sealed class GasGridOptimization : ClassWithFishPrepatches Debug.LogIncorrectAnyGasEverAdded(this); } #endif - - return; - } - - Ticked?.Invoke(this); + return; + } - var area = Map.Area; - // CellsInRandomOrder = Map.cellsInRandomOrder.GetAll(); - // UpdateCellIndicesInRandomOrder(); - // Assigned in PostInitialize instead. Ludeon was likely doing this due to component construction order in - // Map.ConstructComponents + Ticked?.Invoke(this); + + var area = Map.Area; + // CellsInRandomOrder = Map.cellsInRandomOrder.GetAll(); + // UpdateCellIndicesInRandomOrder(); + // Assigned in PostInitialize instead. Ludeon was likely doing this due to component construction order in + // Map.ConstructComponents #if GAS_DEBUG_L2 using var previousDensity = new PooledArray(_gasDensity.Length); Array.Copy(_gasDensity, 0, previousDensity.BackingArray, 0, _gasDensity.Length); #endif - _dissipationTicker.TickCells(area, (area + 63) >> 6); // Mathf.CeilToInt(area * (1f / 64f)) + _dissipationTicker.TickCells(area, (area + 63) >> 6); // Mathf.CeilToInt(area * (1f / 64f)) - if (Diffuses) - _diffusionTicker.TickCells(area, (area + 31) >> 5); // Mathf.CeilToInt(area * (1f / 32f)) + if (Diffuses) + _diffusionTicker.TickCells(area, (area + 31) >> 5); // Mathf.CeilToInt(area * (1f / 32f)) #if GAS_DEBUG_L2 Debug.LogTotalGasChange(this, previousDensity); #endif - if (Map.IsHashIntervalTick(600)) - RecalculateEverHadGas(); + if (Map.IsHashIntervalTick(600)) + RecalculateEverHadGas(); + } + finally + { + _lastFinishedTick = TickHelper.TicksGame; + } } [MethodImpl(MethodImplOptions.NoInlining)] diff --git a/Source/PerformanceFish/Hauling/HaulDestinationManagerPatches.cs b/Source/PerformanceFish/Hauling/HaulDestinationManagerPatches.cs index a1cf443..50aff3a 100644 --- a/Source/PerformanceFish/Hauling/HaulDestinationManagerPatches.cs +++ b/Source/PerformanceFish/Hauling/HaulDestinationManagerPatches.cs @@ -50,4 +50,43 @@ public sealed class HaulDestinationManagerPatches : ClassWithFishPrepatches __instance.Cache().OnPriorityChanged(__instance); } } + + public sealed class CompareSlotGroupPrioritiesDescendingPatch : FishPrepatch + { + public override List LinkedPatches { get; } + = [typeof(StoreUtilityPrepatches.TryFindBestBetterStoreCellForPatch)]; + + public override string? Description { get; } + = "Modifies storage sort order within the haul destination manager to put groups of linked storages first " + + "within their priority, descending by size and then loadID. " + + "StoreUtilityPrepatches:TryFindBestBetterStoreCellFor requires this"; + + public override MethodBase TargetMethodBase { get; } + = methodof(HaulDestinationManager.CompareSlotGroupPrioritiesDescending); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Postfix(int __result, SlotGroup a, SlotGroup b) + { + if (__result != 0) + return __result; + + var storageSettingsGroupA = a.TryGetStorageGroup(); + var storageSettingsGroupB = b.TryGetStorageGroup(); + + return storageSettingsGroupA == null + ? storageSettingsGroupB == null + ? 0 + : 1 + : storageSettingsGroupB == null + ? -1 + : CompareStorageSettingsGroups(storageSettingsGroupA, storageSettingsGroupB); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + public static int CompareStorageSettingsGroups(StorageGroup a, StorageGroup b) + { + var result = b.MemberCount.CompareTo(a.MemberCount); + return result != 0 ? result : a.loadID.CompareTo(b.loadID); + } + } } \ No newline at end of file diff --git a/Source/PerformanceFish/Hauling/RimfactoryExtensionCacheValue.cs b/Source/PerformanceFish/Hauling/RimfactoryExtensionCacheValue.cs index 86e077b..fce3331 100644 --- a/Source/PerformanceFish/Hauling/RimfactoryExtensionCacheValue.cs +++ b/Source/PerformanceFish/Hauling/RimfactoryExtensionCacheValue.cs @@ -18,10 +18,12 @@ public record struct RimfactoryExtensionCacheValue ? slotGroupParent.def.modExtensions?.Find(_extensionPredicate) : null; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool HasExtension(Thing slotGroupParent) - => RimfactoryTypes.ModExtension != null - && (slotGroupParent.def.modExtensions?.Exists(_extensionPredicate) - ?? false); + => RimfactoryTypes.ModExtension != null && TestExtension(slotGroupParent); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool TestExtension(Thing slotGroupParent) => slotGroupParent.TryGetRimfactoryExtension() != null; static RimfactoryExtensionCacheValue() => EnsureInitialized(); diff --git a/Source/PerformanceFish/Hauling/StorageSettingsPatches.cs b/Source/PerformanceFish/Hauling/StorageSettingsPatches.cs index f7c005a..9d91a84 100644 --- a/Source/PerformanceFish/Hauling/StorageSettingsPatches.cs +++ b/Source/PerformanceFish/Hauling/StorageSettingsPatches.cs @@ -8,7 +8,6 @@ using System.Diagnostics; using System.Linq; using FisheryLib.Pools; -using PerformanceFish.Events; namespace PerformanceFish.Hauling; @@ -17,64 +16,11 @@ public sealed class StorageSettingsPatches : ClassWithFishPatches public sealed class AllowedToAcceptPatch : FirstPriorityFishPatch { public override string Description { get; } - = "StorageSettings and capacity caching. High impact in colonies with multiple full storage buildings."; + = "Caches results of storage settings checks for things when hauling"; public override Expression TargetMethod { get; } = static () => default(StorageSettings)!.AllowedToAccept(default(Thing)); - protected internal override void OnPatchingCompleted() => ThingEvents.Initialized += TryRegister; - - public static void TryRegister(Thing thing) - { - var thingEvents = thing.Events(); - - if (thing is ISlotGroupParent) - thingEvents.Spawned += InitializeSlotGroupParent; - - if (!thing.IsItem()) - return; - - thingEvents.RegisteredAtThingGrid += TryNotifyReceivedThing; - thingEvents.DeregisteredAtThingGrid += TryNotifyLostThing; - } - - public static void TryNotifyReceivedThing(Thing thing, Map map, in IntVec3 cell) - { - if (cell.GetSlotGroupParent(map) is { } parent and Thing) - parent.GetStoreSettings()?.Cache().Notify_ReceivedThing(thing); - } - - public static void TryNotifyLostThing(Thing thing, Map map, in IntVec3 cell) - { - if (cell.GetSlotGroupParent(map) is { } parent and Thing) - parent.GetStoreSettings()?.Cache().Notify_LostThing(thing); - } - - public static void InitializeSlotGroupParent(Thing thing, Map map) - { - var slotGroupParent = (ISlotGroupParent)thing; - - if (slotGroupParent.GetStoreSettings() is not { } storeSettings) - return; - - ref var cache = ref storeSettings.Cache(); - cache.Initialize(thing); - - var slotCellsList = slotGroupParent.AllSlotCellsList(); - for (var i = slotCellsList.Count; i-- > 0;) - { - var thingsAtCell = slotCellsList[i].GetThingListUnchecked(map); - for (var j = thingsAtCell.Count; j-- > 0;) - { - var storedThing = thingsAtCell[j]; - if (!storedThing.IsItem() || cache.Contains(storedThing)) - continue; - - cache.Notify_ReceivedThing(storedThing); - } - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Prefix(StorageSettings __instance, Thing t, ref bool __result, out bool __state) { @@ -83,28 +29,11 @@ public sealed class StorageSettingsPatches : ClassWithFishPatches if (StorageSettingsCache.IsDirty(cachedAllowedToAcceptValue)) return __state = true; - __result = cachedAllowedToAcceptValue.AsBool() && CapacityAllows(__instance, t); + __result = cachedAllowedToAcceptValue.AsBool() /*&& CapacityAllows(__instance, t)*/; return __state = false; } - [MethodImpl(MethodImplOptions.NoInlining)] - public static bool CapacityAllows(StorageSettings __instance, Thing t) - { - if (__instance.owner is not Thing ownerThing) - return true; - - ref var cache = ref __instance.Cache(); - Debug.VerifyItemCount(ownerThing, ref cache); - - return cache.FreeSlots > 0 - || (t.TryGetMap() is { } map && t.Position.GetSlotGroupParent(map) == ownerThing) - || AcceptsForStacking(ref cache, t); - } - - public static bool AcceptsForStacking(ref StorageSettingsCache cache, Thing t) - => cache.StoredThingsOfDef(t.def).ContainsThingStackableWith(t); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Postfix(StorageSettings __instance, Thing t, bool __result, bool __state) { @@ -121,18 +50,61 @@ public sealed class StorageSettingsPatches : ClassWithFishPatches public sealed class TryNotifyChanged_Patch : FishPatch { + public override List LinkedPatches { get; } = [typeof(AllowedToAcceptPatch)]; + public static event Action? Changed; public override string Description { get; } = "StorageSettings cache resetting"; - public override Expression TargetMethod { get; } - = static () => default(StorageSettings)!.TryNotifyChanged(); + public override MethodBase? TargetMethodInfo { get; } + = AccessTools.DeclaredMethod(typeof(StorageSettings), nameof(StorageSettings.TryNotifyChanged)); public static void Postfix(StorageSettings __instance) { __instance.Cache().OnSettingsChanged(__instance); + + if (__instance.owner is StorageGroup group) + NotifyGroupMembers(group); + Changed?.Invoke(__instance); } + + public static void NotifyGroupMembers(StorageGroup group) + { + var groupMembers = group.members; + for (var i = 0; i < groupMembers.Count; i++) + { + var thingStoreSettings = groupMembers[i].ThingStoreSettings; + thingStoreSettings.Cache().OnSettingsChanged(thingStoreSettings); + } + } + } + + public static void InitializeSlotGroupParent(Thing thing, Map map) + { + var slotGroupParent = (ISlotGroupParent)thing; + + slotGroupParent.TryGetGroupStoreSettings()?.Cache().Initialize(null); + + if (slotGroupParent.GetThingStoreSettings() is not { } storeSettings) + return; + + ref var cache = ref storeSettings.Cache(); + cache.Initialize(thing); + + var slotCellsList = slotGroupParent.AllSlotCellsList(); + for (var i = slotCellsList.Count; i-- > 0;) + { + var thingsAtCell = slotCellsList[i].GetThingListUnchecked(map); + for (var j = thingsAtCell.Count; j-- > 0;) + { + var storedThing = thingsAtCell[j]; + if (!storedThing.IsItem() || cache.Contains(storedThing)) + continue; + + cache.Notify_ReceivedThing(storedThing); + } + } } public record struct StorageSettingsCache @@ -153,7 +125,7 @@ public sealed class StorageSettingsPatches : ClassWithFishPatches public event Action? SettingsChanged; - public int TotalSlots => _parent!.TrueTotalSlots(_lwmProps, _rimfactoryExtension); + public int TotalSlots => _parent?.TrueTotalSlots(_lwmProps, _rimfactoryExtension) ?? 0xFFFFFFF; public List StoredThingsOfDef(ThingDef def) => _storedThingsByDef.GetOrAdd(def.shortHash); @@ -163,13 +135,13 @@ public sealed class StorageSettingsPatches : ClassWithFishPatches public void Update(int thingIDNumber, bool allowedToAccept) => AllowedToAccept[thingIDNumber] = allowedToAccept.AsInt(); - public void Initialize(Thing slotGroupParent) + public void Initialize(Thing? slotGroupParent) { _parent = slotGroupParent; _storedThingsByDef.Clear(); AllowedToAccept.Clear(); - _lwmProps = slotGroupParent.TryGetLwmCompProperties(); - _rimfactoryExtension = slotGroupParent.TryGetRimfactoryExtension(); + _lwmProps = slotGroupParent?.TryGetLwmCompProperties(); + _rimfactoryExtension = slotGroupParent?.TryGetRimfactoryExtension(); FreeSlots = TotalSlots; } diff --git a/Source/PerformanceFish/Hauling/StoreUtilityPrepatches.cs b/Source/PerformanceFish/Hauling/StoreUtilityPrepatches.cs index 267472b..8817142 100644 --- a/Source/PerformanceFish/Hauling/StoreUtilityPrepatches.cs +++ b/Source/PerformanceFish/Hauling/StoreUtilityPrepatches.cs @@ -3,6 +3,10 @@ // 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/. +// #define STORAGE_GROUP_DEBUG + +using System.Diagnostics; +using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; using PerformanceFish.Events; @@ -89,13 +93,15 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches { public override List LinkedPatches { get; } = [ - typeof(SlotGroupPrepatches.Notify_AddedCellPatch), typeof(SlotGroupPrepatches.Notify_LostCellPatch) + typeof(SlotGroupPrepatches.Notify_AddedCellPatch), typeof(SlotGroupPrepatches.Notify_LostCellPatch), + typeof(StorageSettingsPatches.AllowedToAcceptPatch) ]; public override string? Description { get; } = "Optimizes hauling by splitting storage areas into smaller regions with cached capacity information. " + "Additionally makes lookups more thorough when having the 'Improve hauling accuracy' setting enabled, " - + "raising the odds of getting closer cells as haul destinations"; + + "raising the odds of getting closer cells as haul destinations. StorageSettingsPatches:AllowedToAccept " + + "requires this patch due to shared caches"; public override MethodBase TargetMethodBase { get; } = methodof(StoreUtility.TryFindBestBetterStoreCellForWorker); @@ -109,6 +115,7 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches if (thing is ISlotGroupParent) { thingEvents.Spawned += NotifySlotGroupCellCountChanged; + thingEvents.Spawned += StorageSettingsPatches.InitializeSlotGroupParent; thingEvents.DeSpawned += NotifySlotGroupCellCountChanged; } @@ -126,10 +133,20 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches => slotGroupParent.GetSlotGroup()?.NotifyCellCountChanged(); public static void TryNotifyReceivedThing(Thing thing, Map map, in IntVec3 cell) - => map.StorageDistrictGrid()[cell]?.AddThing(thing); + { + if (cell.GetSlotGroupParent(map) is { } parent and Thing) + parent.GetThingStoreSettings()?.Cache().Notify_ReceivedThing(thing); + + map.StorageDistrictGrid()[cell]?.AddThing(thing); + } public static void TryNotifyLostThing(Thing thing, Map map, in IntVec3 cell) - => map.StorageDistrictGrid()[cell]?.RemoveThing(thing); + { + if (cell.GetSlotGroupParent(map) is { } parent and Thing) + parent.GetThingStoreSettings()?.Cache().Notify_LostThing(thing); + + map.StorageDistrictGrid()[cell]?.RemoveThing(thing); + } public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module) => ilProcessor.ReplaceBodyWith(ReplacementBody); @@ -138,7 +155,7 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches bool needAccurateResult, ref IntVec3 closestSlot, ref float closestDistSquared, ref StoragePriority foundPriority) { - if (slotGroup == null || !slotGroup.parent.Accepts(t)) + if (slotGroup == null || !slotGroup.parent.Accepts(t) || !CapacityAllows(slotGroup, t)) return; var thingPosition = t.SpawnedOrAnyParentSpawned ? t.PositionHeld : carrier.PositionHeld; @@ -150,7 +167,7 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches : 0; StartOfDistrictLoop: - var districts = slotGroup.Districts(); + var districts = GetDistricts(slotGroup); for (var districtIndex = 0; districtIndex < districts.Length; districtIndex++) { @@ -186,6 +203,26 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches } } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static StorageDistrict[] GetDistricts(SlotGroup slotGroup) => slotGroup.Districts(); + // wrapper for analyzer compatibility, as it otherwise throws on ref returns + + [MethodImpl(MethodImplOptions.NoInlining)] + public static bool CapacityAllows(SlotGroup slotGroup, Thing t) + { + var storageSettings = slotGroup.GetThingStoreSettings(); + if (storageSettings?.owner is not Thing) + return true; + + ref var cache = ref storageSettings.Cache(); + StorageSettingsPatches.Debug.VerifyItemCount((Thing)storageSettings.owner, ref cache); + + return cache.FreeSlots > 0 || AcceptsForStacking(ref cache, t); + } + + public static bool AcceptsForStacking(ref StorageSettingsPatches.StorageSettingsCache cache, Thing t) + => cache.StoredThingsOfDef(t.def).ContainsThingStackableWith(t); } public sealed class TryFindBestBetterStoreCellForPatch : FishPrepatch @@ -210,6 +247,11 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches foundCell = IntVec3.Invalid; return false; } + +#if STORAGE_GROUP_DEBUG + if (!Debug.LoggedOnce && !(Current.Game?.tickManager?.Paused ?? true)) + Debug.LogStuff(listInPriorityOrder); +#endif var foundPriority = currentPriority; var closestDistSquared = (float)int.MaxValue; @@ -232,8 +274,38 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches while (groupsOfPriorityCount-- > 0) { - StoreUtility.TryFindBestBetterStoreCellForWorker(t, carrier, map, faction, listInPriorityOrder[i++], - needAccurateResult, ref closestSlot, ref closestDistSquared, ref foundPriority); + var slotGroup = listInPriorityOrder[i++]; + int otherGroupMembers; + var storageGroup = slotGroup.TryGetStorageGroup(); + if (storageGroup != null) + { + otherGroupMembers = storageGroup.MemberCount - 1; + Debug.VerifyStorageGroup(otherGroupMembers, listInPriorityOrder, i, storageGroup); + + groupsOfPriorityCount -= otherGroupMembers; + + if (!storageGroup.Accepts(t)) + { + i += otherGroupMembers; + continue; + } + } + else + { + otherGroupMembers = 0; + } + + while (true) + { + StoreUtility.TryFindBestBetterStoreCellForWorker(t, carrier, map, faction, slotGroup, + needAccurateResult, ref closestSlot, ref closestDistSquared, ref foundPriority); + + if (otherGroupMembers <= 0) + break; + + otherGroupMembers--; + slotGroup = listInPriorityOrder[i++]; + } } } @@ -260,6 +332,40 @@ public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches // remove for any storage instead of only valid storage to prevent further haul attempts until the next // automatic ListerHaulablesTick cycle } + + public static class Debug + { + [Conditional("STORAGE_GROUP_DEBUG")] + public static void VerifyStorageGroup(int otherGroupMembers, List listInPriorityOrder, int i, + StorageGroup storageGroup) + { + for (var b = otherGroupMembers; b-- > 0;) + { + var otherSlotGroup = listInPriorityOrder[i + b]; + if (otherSlotGroup.TryGetStorageGroup() == storageGroup) + continue; + + Log.Error($"Incorrect haul destination order! Storage '{ + otherSlotGroup.parent}' was expected to have Storage group '{storageGroup}', but had '{ + otherSlotGroup.TryGetStorageGroup().ToStringSafe()}' instead"); + } + } + + [Conditional("STORAGE_GROUP_DEBUG")] + public static void LogStuff(List listInPriorityOrder) + { + LoggedOnce = true; + var storageGroups = listInPriorityOrder.Select(static slotGroup => slotGroup.TryGetStorageGroup()) + .Where(Is.NotNull).Distinct().ToList(); + Log.Message($"SlotGroup count: {listInPriorityOrder.Count}, StorageGroup count: { + storageGroups.Count}, slotGroups in storage groups: { + storageGroups.Select(static group => group!.MemberCount).Sum()}, outside of storage groups: { + listInPriorityOrder.Select(static slotGroup => slotGroup.TryGetStorageGroup()) + .Where(Is.Null).Count()}"); + } + + public static bool LoggedOnce; + } } public sealed class GetSlotGroupPatch : FishPrepatch diff --git a/Source/PerformanceFish/JobSystem/JobGiver_HaulOptimization.cs b/Source/PerformanceFish/JobSystem/JobGiver_HaulOptimization.cs index 202747e..6cf45ab 100644 --- a/Source/PerformanceFish/JobSystem/JobGiver_HaulOptimization.cs +++ b/Source/PerformanceFish/JobSystem/JobGiver_HaulOptimization.cs @@ -43,11 +43,11 @@ public sealed class JobGiver_HaulOptimization : ClassWithFishPatches public static List SortedThingsPotentiallyNeedingHauling(List things, Pawn pawn) { - _comparer.rootCell = pawn.Position; + ((ThingPositionComparer)_comparer.Target).rootCell = pawn.Position; things.Sort(_comparer); return things; } - private static WorkGiver_DoBillOptimization.ThingPositionComparer _comparer = new(); + private static Comparison _comparer = new ThingPositionComparer().Compare; } } \ No newline at end of file diff --git a/Source/PerformanceFish/JobSystem/WorkGiver_DoBillOptimization.cs b/Source/PerformanceFish/JobSystem/WorkGiver_DoBillOptimization.cs index 9ee418f..329c4bf 100644 --- a/Source/PerformanceFish/JobSystem/WorkGiver_DoBillOptimization.cs +++ b/Source/PerformanceFish/JobSystem/WorkGiver_DoBillOptimization.cs @@ -172,7 +172,7 @@ public sealed class WorkGiver_DoBillPrepatches : ClassWithFishPrepatches } } - private static readonly WorkGiver_DoBillOptimization.ThingPositionComparer _insertAtCorrectPositionComparer + private static readonly ThingPositionComparer _insertAtCorrectPositionComparer = new(); public static void ActualLoop(List list, Region r, Pawn pawn, Predicate baseValidator, @@ -599,16 +599,6 @@ public sealed class WorkGiver_DoBillOptimization : ClassWithFishPatches } } - public sealed class ThingPositionComparer : IComparer - { - public IntVec3 rootCell; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Compare(Thing x, Thing y) - => (x.Position - rootCell).LengthHorizontalSquared.CompareTo( - (y.Position - rootCell).LengthHorizontalSquared); - } - public record struct RecipeIngredientCacheValue { public IngredientCacheList? thingDefs; diff --git a/Source/PerformanceFish/JobSystem/WorkGiver_HaulOptimization.cs b/Source/PerformanceFish/JobSystem/WorkGiver_HaulOptimization.cs index 3a92005..0241f91 100644 --- a/Source/PerformanceFish/JobSystem/WorkGiver_HaulOptimization.cs +++ b/Source/PerformanceFish/JobSystem/WorkGiver_HaulOptimization.cs @@ -29,10 +29,10 @@ public sealed class WorkGiver_HaulOptimization : ClassWithFishPatches private static void Sort(List list, Pawn pawn) { - _comparer.rootCell = pawn.Position; + ((ThingPositionComparer)_comparer.Target).rootCell = pawn.Position; list.Sort(_comparer); } - private static WorkGiver_DoBillOptimization.ThingPositionComparer _comparer = new(); + private static Comparison _comparer = new ThingPositionComparer().Compare; } } \ No newline at end of file diff --git a/Source/PerformanceFish/ParallelNoAlloc.cs b/Source/PerformanceFish/ParallelNoAlloc.cs index 96dc308..94f4d2a 100644 --- a/Source/PerformanceFish/ParallelNoAlloc.cs +++ b/Source/PerformanceFish/ParallelNoAlloc.cs @@ -100,6 +100,15 @@ public static class ParallelNoAlloc return monitorObject; } + public static bool HasAnyRegisteredWorkers(object? monitorObject) + { + Guard.IsNotNull(monitorObject); + + return monitorObject is MonitorObject.Group typedMonitorObject + ? typedMonitorObject.Subscribers.Length > 0 + : ThrowHelper.ThrowArgumentException(); + } + public static void DeregisterBackgroundWaitingWorker(object monitorObject, Action action) { Guard.IsNotNull(action); @@ -140,6 +149,8 @@ public static class ParallelNoAlloc typedMonitorObject.Pulse(); } + internal static void ClearAll() => _groupWorkers.Clear(); + private abstract class Worker : IFishPoolable { public Thread Thread { get; private set; } @@ -217,13 +228,10 @@ public static class ParallelNoAlloc public void Invoke(Action action) { - Guard.IsNotAssignableToType(MonitorObject); - if (Pulsed) Spin(); - + Action = action; - MonitorObject.Pulse(); } diff --git a/Source/PerformanceFish/Patching/FishPatch.cs b/Source/PerformanceFish/Patching/FishPatch.cs index 765d121..4bd0b6a 100644 --- a/Source/PerformanceFish/Patching/FishPatch.cs +++ b/Source/PerformanceFish/Patching/FishPatch.cs @@ -101,7 +101,7 @@ public abstract class FishPatch : SingletonFactory, IExposable, IHasD : Description + $"{(Description?.EndsWith(".") ?? true ? "" : ".")} Disabled for compatibility with { IncompatibleModIDs.Select(static id => ActiveMods.TryGetModMetaData(id)?.Name) - .Where(static id => id != null).ToCommaList(true)}"; + .Where(Is.NotNull).ToCommaList(true)}"; public virtual Delegate? TargetMethodGroup => null; public virtual IEnumerable? TargetMethodGroups => null; diff --git a/Source/PerformanceFish/PerformanceFish.csproj b/Source/PerformanceFish/PerformanceFish.csproj index 660cea5..e136c69 100644 --- a/Source/PerformanceFish/PerformanceFish.csproj +++ b/Source/PerformanceFish/PerformanceFish.csproj @@ -17,7 +17,7 @@ enable True v1.4 - 0.3.999.8 + 0.3.999.9 1701;1702;IL2121;CS8500;CS9082;CA1000;CA1002;CA1003;CA1707;CA1010;CA1715;CA1716;CA1724;CA1031;CA1033; CA1034;CA1043;CA1051;CA1305;CA1310;CA1710;CA1711;CA1810;CA1812;CA1819;CA1851;CA2002;CA5394;CA1725;CA1062 diff --git a/Source/PerformanceFish/PerformanceFishMod.cs b/Source/PerformanceFish/PerformanceFishMod.cs index 894a663..184afa5 100644 --- a/Source/PerformanceFish/PerformanceFishMod.cs +++ b/Source/PerformanceFish/PerformanceFishMod.cs @@ -151,8 +151,8 @@ public sealed class PerformanceFishMod : Mod public static void LogPatchCount() { var allFishPatches = AllPatchClasses!.SelectMany(static patchClass => patchClass.Patches.All.Values).ToList(); - var allFishPrepatches - = AllPrepatchClasses!.SelectMany(static patchClass => patchClass.Patches.All.Values).ToList(); + var allFishPrepatches = AllPrepatchClasses!.SelectMany(static patchClass => patchClass.Patches.All.Values) + .ToList(); var prefixCount = SumFor(allFishPatches, static fishPatch => fishPatch.PrefixMethodInfo); var postfixCount = SumFor(allFishPatches, static fishPatch => fishPatch.PostfixMethodInfo); @@ -174,10 +174,13 @@ public sealed class PerformanceFishMod : Mod (prepatcherClassPatchCount == 1 ? "" : "es")}. That's a total of {prefixCount + postfixCount + transpilerCount + prepatcherPrefixCount + prepatcherPostfixCount + prepatcherTranspilerCount + prepatcherClassPatchCount} patches. Amazing!\n\n" - + $"The following methods were patched:\n{string.Join("\n", allFishPatches.SelectMany(static fishPatch - => fishPatch.TargetMethodInfos).Concat(allFishPrepatches.Select(static fishPrepatch - => (fishPrepatch as FishPrepatch)?.TargetMethodBase)).Where(static method - => method != null).Select(static method => method.FullDescription()))}\n" + + $"The following methods were patched:\n{string.Join("\n", + allFishPatches + .SelectMany(static fishPatch => fishPatch.TargetMethodInfos) + .Concat(allFishPrepatches + .Select(static fishPrepatch => (fishPrepatch as FishPrepatch)?.TargetMethodBase)) + .Where(Is.NotNull) + .Select(static method => method.FullDescription()))}\n" + $"==================================================================================="); } diff --git a/Source/PerformanceFish/PrepatcherFields.cs b/Source/PerformanceFish/PrepatcherFields.cs index 49bf80a..1664455 100644 --- a/Source/PerformanceFish/PrepatcherFields.cs +++ b/Source/PerformanceFish/PrepatcherFields.cs @@ -74,6 +74,11 @@ public static class PrepatcherFields [MethodImpl(MethodImplOptions.AggressiveInlining)] public static extern GasGridOptimization.ParallelGasGrid[] ParallelGasGrids(this GasGrid gasGrid); + [PrepatcherField] + [ValueInitializer(nameof(CreateGroupMonitorObject))] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static extern object GasGridMonitorObject(this Map map); + [PrepatcherField] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static extern ref Texture2D? ExpandingIconCache(this WorldObject worldObject); @@ -151,6 +156,9 @@ public static class PrepatcherFields public static Listers.Haulables.Cache CreateListerHaulablesCache() => new(); public static HaulDestinationManagerCache CreateHaulDestinationManagerCache() => new(); + public static object CreateGroupMonitorObject() + => ParallelNoAlloc.RegisterBackgroundWaitingWorkers(Array.Empty()); + public static GasGridOptimization.ParallelGasGrid[] CreateParallelGasGridArray(GasGrid gasGrid) { DefDatabase.SetIndices(); @@ -185,6 +193,17 @@ public static class PrepatcherFields for (var i = 0; i < grids.Length; i++) grids[i] = new(map, gasDefs[i]); + if (FishSettings.ThreadingEnabled) + { + var monitorObject = map.GasGridMonitorObject(); + + for (var i = 1; i < grids.Length; i++) + { + var grid = grids[i]; + ParallelNoAlloc.RegisterBackgroundWaitingWorker(monitorObject, () => grid.Tick()); + } + } + return grids; } diff --git a/Source/PerformanceFish/Prepatching/FishPrepatch.cs b/Source/PerformanceFish/Prepatching/FishPrepatch.cs index 2e2d957..be25dba 100644 --- a/Source/PerformanceFish/Prepatching/FishPrepatch.cs +++ b/Source/PerformanceFish/Prepatching/FishPrepatch.cs @@ -3,6 +3,7 @@ // 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; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; @@ -75,8 +76,7 @@ public abstract class FishPrepatch : FishPrepatchBase } catch (Exception e) { - Log.Error($"Exception while applying transpiler for {GetType().FullName}\n{e}\n{ - Environment.StackTrace /*StackTraceUtility.ExtractStackTrace()*/}"); + Log.Error($"Exception while applying transpiler for {GetType().FullName}\n{e}\n{new StackTrace(true)}"); } var patchVariables = new PatchVariables(); @@ -89,8 +89,7 @@ public abstract class FishPrepatch : FishPrepatchBase } catch (Exception e) { - Log.Error($"Exception while applying prefix for {GetType().FullName}\n{e}\n{ - Environment.StackTrace /*StackTraceUtility.ExtractStackTrace()*/}"); + Log.Error($"Exception while applying prefix for {GetType().FullName}\n{e}\n{new StackTrace(true)}"); } } @@ -102,8 +101,7 @@ public abstract class FishPrepatch : FishPrepatchBase } catch (Exception e) { - Log.Error($"Exception while applying postfix for {GetType().FullName}\n{e}\n{ - Environment.StackTrace /*StackTraceUtility.ExtractStackTrace()*/}"); + Log.Error($"Exception while applying postfix for {GetType().FullName}\n{e}\n{new StackTrace(true)}"); } } diff --git a/Source/PerformanceFish/Prepatching/FishPrepatchBase.cs b/Source/PerformanceFish/Prepatching/FishPrepatchBase.cs index 17cf458..1b2d8e0 100644 --- a/Source/PerformanceFish/Prepatching/FishPrepatchBase.cs +++ b/Source/PerformanceFish/Prepatching/FishPrepatchBase.cs @@ -51,7 +51,7 @@ public abstract class FishPrepatchBase : SingletonFactory, IEx : Description + $"{(Description?.EndsWith(".") ?? true ? "" : ".")} Disabled for compatibility with { IncompatibleModIDs.Select(static id => ActiveMods.TryGetModMetaData(id)?.Name) - .Where(static id => id != null).ToCommaList(true)}"; + .Where(Is.NotNull).ToCommaList(true)}"; public virtual string? Name => null; diff --git a/Source/PerformanceFish/Prepatching/FishStash.cs b/Source/PerformanceFish/Prepatching/FishStash.cs index c734a43..ec400a8 100644 --- a/Source/PerformanceFish/Prepatching/FishStash.cs +++ b/Source/PerformanceFish/Prepatching/FishStash.cs @@ -74,9 +74,11 @@ public unsafe struct FishStash : IEquatable internal void InitializeActivePrepatchIDs() { - var activePrepatches = PerformanceFishMod.AllPrepatchClasses!.SelectMany(static patchClass - => patchClass.Patches.Where(static patch - => patch.Enabled).Select(static patch => patch.IDNumber)).ToArray(); + var activePrepatches = PerformanceFishMod.AllPrepatchClasses! + .SelectMany(static patchClass => patchClass.Patches + .Where(static patch => patch.Enabled) + .Select(static patch => patch.IDNumber)) + .ToArray(); var activePrepatchCount = activePrepatches.Length; _activePrepatchCount = activePrepatchCount; diff --git a/Source/PerformanceFish/Prepatching/PrepatchManager.cs b/Source/PerformanceFish/Prepatching/PrepatchManager.cs index d2fc6ce..0b78e32 100644 --- a/Source/PerformanceFish/Prepatching/PrepatchManager.cs +++ b/Source/PerformanceFish/Prepatching/PrepatchManager.cs @@ -13,6 +13,7 @@ using Mono.Collections.Generic; using nuget::JetBrains.Annotations; using PerformanceFish.ModCompatibility; using CollectionExtensions = PerformanceFish.Utility.CollectionExtensions; +using FieldAttributes = Mono.Cecil.FieldAttributes; using MethodBody = Mono.Cecil.Cil.MethodBody; namespace PerformanceFish.Prepatching; @@ -29,6 +30,20 @@ public static class PrepatchManager ThrowHelper.ThrowInvalidOperationException("Tried running free patches a second time. This should " + "never happen. Cancelling now to avoid further issues."); } + + var gasFields = module.GetTypeDefinition(typeof(Gas))!.Fields; + if (gasFields.Any(static field => field.Name == VERIFICATION_FIELD_NAME)) + { // TODO: fix unity logger breaking when getting here + UnityEngine.Debug.Log("Detected second prepatch attempt. This is known to happen after using " + + "Prepatcher's mod manager. Cancelling and restarting now to prevent issues"); + GenCommandLine.Restart(); + } + else + { + gasFields.Add(new(VERIFICATION_FIELD_NAME, + FieldAttributes.Private | FieldAttributes.Static | FieldAttributes.Literal, + module.TypeSystem.Byte)); + } Log.Message(LogPatches.PERFORMANCE_FISH_WELCOME_MESSAGE); if (!ActiveMods.Prepatcher) @@ -84,7 +99,9 @@ public static class PrepatchManager Verse.Log.Message($"Performance Fish finished applying prepatches in { stopwatch.ElapsedSecondsAccurate():N7} seconds"); } - + + private const string VERIFICATION_FIELD_NAME = "PerformanceFishS1ngl3Pr3p4tchV3r1f1c4t10nF13ld"; + // private static readonly FieldInfo? _monoAssemblyField = AccessTools.Field(typeof(Assembly), "_mono_assembly"); // // internal static unsafe void SetReflectionOnly(Assembly asm, bool value, out bool previousValue) diff --git a/Source/PerformanceFish/System/ReflectionCaching.cs b/Source/PerformanceFish/System/ReflectionCaching.cs index c99a38e..28b453a 100644 --- a/Source/PerformanceFish/System/ReflectionCaching.cs +++ b/Source/PerformanceFish/System/ReflectionCaching.cs @@ -3,6 +3,7 @@ // 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; using System.Diagnostics.CodeAnalysis; using System.Reflection.Emit; using System.Runtime.InteropServices; @@ -745,7 +746,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches cache.Task = _fieldSetterFailureTask; Log.Error($"FieldInfo.SetValue has been called on '{ info.FullDescription()}'. This is a const that gets copied at compile time. An attempt at " - + $"setting a value will always fail."); + + $"setting a value will always fail.\n{new StackTrace(true)}"); return false; } @@ -778,7 +779,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches { Log.Error($"PerformanceFish failed to generate an optimized delegate for { info.FullDescription()}. Reverting to default behaviour instead.\n{ex}\n{ - Environment.StackTrace}"); + new StackTrace(true)}"); return null; } } @@ -821,8 +822,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches catch (Exception ex) { Log.Error($"PerformanceFish failed to generate an optimized delegate for { - info.FullDescription()}. Reverting to default behaviour instead.\n{ex}\n{ - Environment.StackTrace}"); + info.FullDescription()}. Reverting to default behaviour instead.\n{ex}\n{new StackTrace(true)}"); return null; } } @@ -960,7 +960,7 @@ public sealed class ReflectionCaching : ClassWithFishPatches { Log.Warning($"PerformanceFish failed to generate an optimized delegate for { Reflection.FullDescription(methodBase)}. Reverting to default behaviour instead.\n{ex}\n{ - Environment.StackTrace}"); + new StackTrace(true)}"); return null; } } diff --git a/Source/PerformanceFish/Utility/Is.cs b/Source/PerformanceFish/Utility/Is.cs new file mode 100644 index 0000000..3157f22 --- /dev/null +++ b/Source/PerformanceFish/Utility/Is.cs @@ -0,0 +1,20 @@ +// https://stackoverflow.com/a/18363848 + +using System.Diagnostics.CodeAnalysis; + +namespace PerformanceFish.Utility; + +public static class Is +{ + public static bool Null(T? value) where T : class => value == null; + + public static bool NotNull([NotNullWhen(true)] T? value) where T : class => value != null; + + public static bool Null(T? nullableValue) where T : struct => !nullableValue.HasValue; + + public static bool NotNull([NotNullWhen(true)] T? nullableValue) where T : struct => nullableValue.HasValue; + + public static bool HasValue([NotNullWhen(true)] T? nullableValue) where T : struct => nullableValue.HasValue; + + public static bool HasNoValue(T? nullableValue) where T : struct => !nullableValue.HasValue; +} \ No newline at end of file diff --git a/Source/PerformanceFish/Utility/MiscExtensions.cs b/Source/PerformanceFish/Utility/MiscExtensions.cs index 0310507..640484a 100644 --- a/Source/PerformanceFish/Utility/MiscExtensions.cs +++ b/Source/PerformanceFish/Utility/MiscExtensions.cs @@ -29,9 +29,29 @@ public static class MiscExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsItem(this Thing thing) => thing.def.category == ThingCategory.Item; + public static StorageSettings? TryGetGroupStoreSettings(this ISlotGroupParent slotGroupParent) + => slotGroupParent.TryGetStorageGroup()?.GetStoreSettings(); + + public static StorageGroup? TryGetStorageGroup(this SlotGroup slotGroup) => slotGroup.parent.TryGetStorageGroup(); + + public static StorageGroup? TryGetStorageGroup(this ISlotGroupParent slotGroupParent) + => slotGroupParent is IStorageGroupMember groupMember ? groupMember.Group : null; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Accepts(this StorageGroup storageGroup, Thing t) + => storageGroup.GetStoreSettings().AllowedToAccept(t); + public static ISlotGroupParent? GetSlotGroupParent(this in IntVec3 position, Map map) => map.haulDestinationManager.SlotGroupAt(position)?.parent; + public static StorageSettings? GetThingStoreSettings(this SlotGroup slotGroup) + => slotGroup.parent.GetThingStoreSettings(); + + public static StorageSettings? GetThingStoreSettings(this ISlotGroupParent slotGroupParent) + => slotGroupParent is IStorageGroupMember groupMember + ? groupMember.ThingStoreSettings + : slotGroupParent.GetStoreSettings(); + public static ISlotGroupParent? StoringSlotGroupParent(this Thing thing) => thing.Position.GetSlotGroupParent(thing.GetMap()); diff --git a/Source/PerformanceFish/Utility/ThingPositionComparer.cs b/Source/PerformanceFish/Utility/ThingPositionComparer.cs new file mode 100644 index 0000000..e8f0958 --- /dev/null +++ b/Source/PerformanceFish/Utility/ThingPositionComparer.cs @@ -0,0 +1,16 @@ +// 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.Utility; + +public sealed class ThingPositionComparer : IComparer +{ + public IntVec3 rootCell; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Compare(Thing x, Thing y) + => (x.Position - rootCell).LengthHorizontalSquared.CompareTo( + (y.Position - rootCell).LengthHorizontalSquared); +} \ No newline at end of file diff --git a/Source/PerformanceFish/UtilityPatches.cs b/Source/PerformanceFish/UtilityPatches.cs index b3a8a32..ad2e39b 100644 --- a/Source/PerformanceFish/UtilityPatches.cs +++ b/Source/PerformanceFish/UtilityPatches.cs @@ -49,6 +49,7 @@ public sealed class UtilityPatches : ClassWithFishPatches public static void Postfix() { Cache.Utility.Clear(); + ParallelNoAlloc.ClearAll(); foreach (var patch in PerformanceFishMod.AllPatchClasses!) { if (patch.RequiresLoadedGameForPatching) diff --git a/Source/PerformanceFish/UtilityPrepatches.cs b/Source/PerformanceFish/UtilityPrepatches.cs index 9f9935a..c25ffa4 100644 --- a/Source/PerformanceFish/UtilityPrepatches.cs +++ b/Source/PerformanceFish/UtilityPrepatches.cs @@ -3,6 +3,7 @@ // 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; using PerformanceFish.Prepatching; namespace PerformanceFish; @@ -30,8 +31,7 @@ public sealed class UtilityPrepatches : ClassWithFishPrepatches } catch (Exception e) { - Log.Error($"Exception caught within OnAssembliesLoaded:\n{e}\n{ - Environment.StackTrace /*StackTraceUtility.ExtractStackTrace()*/}"); + Log.Error($"Exception caught within OnAssembliesLoaded:\n{e}\n{new StackTrace(true)}"); } } }