Added gas grid optimization, various despawn optimizations, new hauling optimizations utilizing events and regions, bugfixes for old patches and for vanilla functions too, as well as smaller additions. 1.3 files removed.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Events;
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish.Hauling;
|
||||
|
||||
public sealed class GridsUtilityPatches : ClassWithFishPrepatches
|
||||
{
|
||||
public sealed class GetItemCountPatch : FishPrepatch
|
||||
{
|
||||
public override int Priority => HarmonyLib.Priority.First;
|
||||
|
||||
public override string? Description { get; }
|
||||
= "Caches item counts in a grid, to not have to repeat counting on every request.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(GridsUtility.GetItemCount);
|
||||
|
||||
protected internal override void BeforeHarmonyPatching() => ThingEvents.Initialized += TryRegister;
|
||||
|
||||
public static void TryRegister(Thing thing)
|
||||
{
|
||||
if (!thing.IsItem())
|
||||
return;
|
||||
|
||||
var thingEvents = thing.Events();
|
||||
thingEvents.RegisteredAtThingGrid += Increment;
|
||||
thingEvents.DeregisteredAtThingGrid += Decrement;
|
||||
}
|
||||
|
||||
public static void Increment(Thing thing, Map map, in IntVec3 cell)
|
||||
=> map.ItemCountGrid()[cell]++;
|
||||
|
||||
public static void Decrement(Thing thing, Map map, in IntVec3 cell)
|
||||
{
|
||||
ref var itemCount = ref map.ItemCountGrid()[cell];
|
||||
itemCount--;
|
||||
|
||||
if (itemCount < 0)
|
||||
{
|
||||
itemCount = 0;
|
||||
ErrorForInvalidCount(thing);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ErrorForInvalidCount(Thing thing)
|
||||
=> Log.Error($"Tried decrementing item count at cell {thing.Position} for thing '{
|
||||
thing}', but it was already 0. This should never happen.");
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ReplacementBody(IntVec3 c, Map map) => map.ItemCountGrid()[c];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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.Hauling;
|
||||
|
||||
public record struct HaulDestinationManagerCache
|
||||
{
|
||||
public static readonly int StoragePriorityCount = Enum.GetValues(typeof(StoragePriority)).Length;
|
||||
|
||||
public event Action<HaulDestinationManager>? PriorityChanged;
|
||||
|
||||
public int[] GroupCountByPriority = new int[StoragePriorityCount];
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public void OnPriorityChanged(HaulDestinationManager manager)
|
||||
{
|
||||
var slotGroupsByPriority = manager.AllGroupsListInPriorityOrder;
|
||||
|
||||
Array.Clear(GroupCountByPriority, 0, GroupCountByPriority.Length);
|
||||
for (var i = slotGroupsByPriority.Count; i-- > 0;)
|
||||
GroupCountByPriority[(int)slotGroupsByPriority[i].Settings.Priority]++;
|
||||
|
||||
PriorityChanged?.Invoke(manager);
|
||||
}
|
||||
|
||||
public HaulDestinationManagerCache()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish.Hauling;
|
||||
|
||||
public sealed class HaulDestinationManagerPatches : ClassWithFishPrepatches
|
||||
{
|
||||
public sealed class Notify_HaulDestinationChangedPriorityPatch : FishPrepatch
|
||||
{
|
||||
public override bool ShowSettings => false;
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(HaulDestinationManager),
|
||||
nameof(HaulDestinationManager.Notify_HaulDestinationChangedPriority));
|
||||
|
||||
public static void Postfix(HaulDestinationManager __instance)
|
||||
=> __instance.Cache().OnPriorityChanged(__instance);
|
||||
}
|
||||
|
||||
public sealed class AddHaulDestinationPatch : FishPrepatch
|
||||
{
|
||||
public override bool ShowSettings => false;
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(HaulDestinationManager),
|
||||
nameof(HaulDestinationManager.AddHaulDestination));
|
||||
|
||||
public static void Postfix(HaulDestinationManager __instance, IHaulDestination haulDestination)
|
||||
{
|
||||
if (haulDestination is ISlotGroupParent)
|
||||
__instance.Cache().OnPriorityChanged(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RemoveHaulDestinationPatch : FishPrepatch
|
||||
{
|
||||
public override bool ShowSettings => false;
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(HaulDestinationManager),
|
||||
nameof(HaulDestinationManager.RemoveHaulDestination));
|
||||
|
||||
public static void Postfix(HaulDestinationManager __instance, IHaulDestination haulDestination)
|
||||
{
|
||||
if (haulDestination is ISlotGroupParent)
|
||||
__instance.Cache().OnPriorityChanged(__instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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/.
|
||||
|
||||
global using LwmCompCache = PerformanceFish.Cache.ByReference<Verse.Thing, PerformanceFish.Hauling.LwmCompCacheValue>;
|
||||
|
||||
namespace PerformanceFish.Hauling;
|
||||
|
||||
public record struct LwmCompCacheValue
|
||||
{
|
||||
public CompProperties? CompProperties;
|
||||
|
||||
public static CompProperties? TryGetLwmProps(Thing slotGroupParent)
|
||||
=> ModCompatibility.Types.LWM.CompProperties != null
|
||||
? slotGroupParent.def.comps?.Find(static props
|
||||
=> props.GetType() == ModCompatibility.Types.LWM.CompProperties)
|
||||
: null;
|
||||
|
||||
static LwmCompCacheValue() => EnsureInitialized();
|
||||
|
||||
public static void EnsureInitialized()
|
||||
{
|
||||
if (Interlocked.Exchange(ref _initialized, 1) == 0)
|
||||
LwmCompCache.ValueInitializer = static key => new() { CompProperties = TryGetLwmProps(key) };
|
||||
}
|
||||
|
||||
private static volatile int _initialized;
|
||||
|
||||
public static AccessTools.FieldRef<CompProperties, int>?
|
||||
LwmMaxNumberStacks = TryGetLwmField("maxNumberStacks"),
|
||||
LwmMinNumberStacks = TryGetLwmField("minNumberStacks");
|
||||
|
||||
private static AccessTools.FieldRef<CompProperties, int>? TryGetLwmField(string name)
|
||||
=> ModCompatibility.Types.LWM.CompProperties is { } propsType
|
||||
? AccessTools.FieldRefAccess<CompProperties, int>(AccessTools.Field(propsType, name))
|
||||
: null;
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using FisheryLib.Pools;
|
||||
|
||||
namespace PerformanceFish.Hauling;
|
||||
|
||||
public static class SlotGroupExtensions
|
||||
{
|
||||
public const int
|
||||
MINIMUM_DISTRICT_COUNT = 3,
|
||||
MINIMUM_DISTRICT_SLOTS = 9,
|
||||
MINIMUM_DISTRICT_CELLS = 3;
|
||||
|
||||
public static bool ShouldHaveDistricts([NotNullWhen(true)] this SlotGroup? slotGroup, out int cellCountIfTrue,
|
||||
out int totalSlotsIfTrue)
|
||||
{
|
||||
if (slotGroup is null || slotGroup.parent is Building { Spawned: false })
|
||||
{
|
||||
Unsafe.SkipInit(out cellCountIfTrue);
|
||||
goto False;
|
||||
}
|
||||
|
||||
cellCountIfTrue = slotGroup.CellsList.Count;
|
||||
if (cellCountIfTrue >= MINIMUM_DISTRICT_COUNT * MINIMUM_DISTRICT_CELLS)
|
||||
{
|
||||
return (totalSlotsIfTrue = slotGroup.GetTotalSlots(cellCountIfTrue))
|
||||
>= MINIMUM_DISTRICT_COUNT * MINIMUM_DISTRICT_SLOTS;
|
||||
}
|
||||
|
||||
False:
|
||||
Unsafe.SkipInit(out totalSlotsIfTrue);
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void ResetAndRemakeDistricts(this SlotGroup slotGroup)
|
||||
{
|
||||
slotGroup.ResetDistricts();
|
||||
slotGroup.NotifyCellCountChanged();
|
||||
}
|
||||
|
||||
public static void ResetDistricts(this SlotGroup slotGroup)
|
||||
{
|
||||
ref var districts = ref slotGroup.Districts();
|
||||
|
||||
for (var i = districts.Length; i-- > 0;)
|
||||
districts[i].Dispose();
|
||||
|
||||
districts = StorageDistrict.GetDefaultArray();
|
||||
}
|
||||
|
||||
public static void NotifyCellCountChanged(this SlotGroup slotGroup)
|
||||
{
|
||||
if (slotGroup.ShouldHaveDistricts(out var cellCount, out var totalSlots))
|
||||
slotGroup.AdjustDistricts(totalSlots, cellCount);
|
||||
else
|
||||
slotGroup.ResetDistricts();
|
||||
}
|
||||
|
||||
public static void AdjustDistricts(this SlotGroup slotGroup, int totalSlots, int cellCount,
|
||||
Span<IntVec3> cellsToAdd = default, Span<IntVec3> cellsToRemove = default)
|
||||
{
|
||||
var districtCount = Math.Min(totalSlots / MINIMUM_DISTRICT_SLOTS, cellCount / MINIMUM_DISTRICT_COUNT);
|
||||
ref var districts = ref slotGroup.Districts();
|
||||
|
||||
using var cellBufferPooled = new PooledIList<List<IntVec3>>();
|
||||
var cellBuffer = cellBufferPooled.List;
|
||||
var hasDistricts = districts.Length > 1;
|
||||
|
||||
if (cellsToRemove.Length > 0)
|
||||
{
|
||||
if (hasDistricts)
|
||||
{
|
||||
for (var i = cellsToRemove.Length; i-- > 0;)
|
||||
{
|
||||
for (var j = districts.Length; j-- > 0;)
|
||||
{
|
||||
if (districts[j].TryRemoveCell(cellsToRemove[i]))
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Tried removing cells from districts in slotGroup of {
|
||||
slotGroup.parent}, but none exist.");
|
||||
}
|
||||
}
|
||||
|
||||
if (cellsToAdd.Length > 0 && hasDistricts)
|
||||
{
|
||||
for (var i = cellsToAdd.Length; i-- > 0;)
|
||||
cellBuffer.Add(cellsToAdd[i]);
|
||||
}
|
||||
|
||||
if (hasDistricts)
|
||||
{
|
||||
if (districts.Length > districtCount)
|
||||
{
|
||||
for (var i = districtCount - 1; i < districts.Length; i++)
|
||||
cellBuffer.AddRange(districts[i].Cells);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cellBuffer.AddRange(slotGroup.CellsList);
|
||||
}
|
||||
|
||||
if (districts.Length != districtCount)
|
||||
{
|
||||
if (hasDistricts)
|
||||
Array.Resize(ref districts, districtCount);
|
||||
else
|
||||
districts = new StorageDistrict[districtCount];
|
||||
|
||||
for (var i = 0; i < districts.Length; i++)
|
||||
((StorageDistrict?[])districts)[i] ??= new(slotGroup);
|
||||
}
|
||||
|
||||
var districtSize = cellCount / districtCount;
|
||||
var finalDistrictSize = cellCount - (districtSize * (districtCount - 1));
|
||||
|
||||
for (var i = 0; i < districtCount; i++)
|
||||
{
|
||||
var district = districts[i];
|
||||
var desiredDistrictSize = i != districtCount - 1 ? districtSize : finalDistrictSize;
|
||||
|
||||
while (district.Cells.Count > desiredDistrictSize)
|
||||
cellBuffer.Add(district.PopCell());
|
||||
}
|
||||
|
||||
for (var i = 0; i < districtCount; i++)
|
||||
{
|
||||
var district = districts[i];
|
||||
|
||||
while (district.Cells.Count < districtSize)
|
||||
district.AddCell(cellBuffer.Pop());
|
||||
}
|
||||
|
||||
while (cellBuffer.Count > 0)
|
||||
districts[^1].AddCell(cellBuffer.Pop());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish.Hauling;
|
||||
|
||||
public sealed class SlotGroupPrepatches : ClassWithFishPrepatches
|
||||
{
|
||||
public sealed class Notify_AddedCellPatch : FishPrepatch
|
||||
{
|
||||
public override bool ShowSettings => false;
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(SlotGroup), nameof(SlotGroup.Notify_AddedCell));
|
||||
|
||||
public static void Postfix(SlotGroup __instance, IntVec3 c)
|
||||
{
|
||||
if (__instance.ShouldHaveDistricts(out var cellCount, out var totalSlots))
|
||||
__instance.AdjustDistricts(totalSlots, cellCount, stackalloc IntVec3[] { c });
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Notify_LostCellPatch : FishPrepatch
|
||||
{
|
||||
public override bool ShowSettings => false;
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= AccessTools.DeclaredMethod(typeof(SlotGroup), nameof(SlotGroup.Notify_LostCell));
|
||||
|
||||
public static void Postfix(SlotGroup __instance, IntVec3 c)
|
||||
{
|
||||
if (__instance.Districts().Length <= 1)
|
||||
return;
|
||||
|
||||
if (__instance.ShouldHaveDistricts(out var cellCount, out var totalSlots))
|
||||
__instance.AdjustDistricts(totalSlots, cellCount, cellsToRemove: stackalloc IntVec3[] { c });
|
||||
else
|
||||
__instance.ResetDistricts();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// 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.Hauling;
|
||||
|
||||
public class StorageDistrict
|
||||
{
|
||||
public int FreeSlots = 0xFFFFFFF;
|
||||
|
||||
private int _storedThingCount;
|
||||
|
||||
public int TotalSlots => Parent.GetTotalSlots(Cells.Count);
|
||||
|
||||
public List<IntVec3> Cells { get; } = [];
|
||||
|
||||
private FishTable<ushort, List<Thing>> _storedThingsByDef = new();
|
||||
|
||||
public SlotGroup Parent { get; }
|
||||
|
||||
public Map Map { get; } = null!;
|
||||
|
||||
public StorageDistrict(SlotGroup? parent)
|
||||
{
|
||||
Parent = parent!;
|
||||
|
||||
if (parent != null)
|
||||
Map = parent.Map;
|
||||
}
|
||||
|
||||
public void AddCell(in IntVec3 cell)
|
||||
{
|
||||
Cells.Add(cell);
|
||||
var cellIndex = new CellIndex(cell, Map);
|
||||
Map.StorageDistrictGrid()[cellIndex] = this;
|
||||
|
||||
FreeSlots = TotalSlots - _storedThingCount;
|
||||
|
||||
var thingsAtCell = cellIndex.GetThingList(Map);
|
||||
for (var i = 0; i < thingsAtCell.Count; i++)
|
||||
{
|
||||
var thing = thingsAtCell[i];
|
||||
if (!thing.IsItem())
|
||||
continue;
|
||||
|
||||
AddThing(thing);
|
||||
}
|
||||
}
|
||||
|
||||
public IntVec3 PopCell()
|
||||
{
|
||||
var cell = Cells.Pop();
|
||||
DeregisterCell(cell);
|
||||
return cell;
|
||||
}
|
||||
|
||||
public bool TryRemoveCell(in IntVec3 cell)
|
||||
{
|
||||
if (!Cells.Remove(cell))
|
||||
return false;
|
||||
|
||||
DeregisterCell(cell);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void DeregisterCell(in IntVec3 cell)
|
||||
{
|
||||
var cellIndex = new CellIndex(cell, Map);
|
||||
|
||||
ref var districtGridSlot = ref Map.StorageDistrictGrid()[cellIndex];
|
||||
if (districtGridSlot == this)
|
||||
districtGridSlot = null;
|
||||
|
||||
var thingsAtCell = cellIndex.GetThingList(Map);
|
||||
for (var i = 0; i < thingsAtCell.Count; i++)
|
||||
{
|
||||
var thing = thingsAtCell[i];
|
||||
if (!thing.IsItem())
|
||||
continue;
|
||||
|
||||
RemoveThing(thing);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddThing(Thing thing)
|
||||
{
|
||||
StoredThingsOfDef(thing.def).Add(thing);
|
||||
|
||||
FreeSlots = TotalSlots - ++_storedThingCount;
|
||||
}
|
||||
|
||||
public void RemoveThing(Thing thing)
|
||||
{
|
||||
if (!StoredThingsOfDef(thing.def).Remove(thing))
|
||||
ThrowHelper.ThrowInvalidOperationException();
|
||||
|
||||
FreeSlots = TotalSlots - --_storedThingCount;
|
||||
}
|
||||
|
||||
public List<Thing> StoredThingsOfDef(ThingDef def) => _storedThingsByDef.GetOrAdd(def.shortHash);
|
||||
|
||||
public bool CapacityAllows(Thing t) => FreeSlots > 0 || StoredThingsOfDef(t.def).ContainsThingStackableWith(t);
|
||||
|
||||
public static StorageDistrict[] GetDefaultArray() => _defaultDistrictArray;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (this == _defaultDistrict)
|
||||
return;
|
||||
|
||||
var districtGrid = Map.StorageDistrictGrid();
|
||||
var cells = Cells;
|
||||
|
||||
for (var i = cells.Count; i-- > 0;)
|
||||
{
|
||||
ref var district = ref districtGrid[cells[i]];
|
||||
if (district == this)
|
||||
district = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static StorageDistrict _defaultDistrict = new(null);
|
||||
private static StorageDistrict[] _defaultDistrictArray = [_defaultDistrict];
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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.Hauling;
|
||||
|
||||
public static class StorageExtensions
|
||||
{
|
||||
public static int TrueItemsPerCell(this Thing slotGroupParent)
|
||||
=> slotGroupParent.TrueItemsPerCell(slotGroupParent.TryGetLwmCompProperties());
|
||||
|
||||
public static int TrueItemsPerCell(this Thing slotGroupParent, CompProperties? lwmProps)
|
||||
=> lwmProps != null
|
||||
? Math.Max(LwmCompCacheValue.LwmMinNumberStacks!(lwmProps), LwmCompCacheValue.LwmMaxNumberStacks!(lwmProps))
|
||||
: slotGroupParent is Building building
|
||||
? building.MaxItemsInCell
|
||||
: 1;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static CompProperties? TryGetLwmCompProperties(this Thing thing)
|
||||
=> LwmCompCache.GetOrAdd(thing).CompProperties;
|
||||
|
||||
public static int GetTotalSlots(this SlotGroup slotGroup) => slotGroup.GetTotalSlots(slotGroup.CellsList.Count);
|
||||
|
||||
public static int GetTotalSlots(this SlotGroup slotGroup, int cellCount)
|
||||
=> slotGroup.parent is Building storage
|
||||
? storage.TrueItemsPerCell() * cellCount
|
||||
: cellCount;
|
||||
|
||||
static StorageExtensions() => LwmCompCacheValue.EnsureInitialized();
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
// 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/.
|
||||
|
||||
// #define StorageSettings_DEBUG
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using FisheryLib.Pools;
|
||||
using PerformanceFish.Events;
|
||||
|
||||
namespace PerformanceFish.Hauling;
|
||||
|
||||
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.";
|
||||
|
||||
public override Expression<Action> 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)
|
||||
{
|
||||
var cachedAllowedToAcceptValue = __instance.Cache().AllowedToAccept.GetOrAdd(t.thingIDNumber);
|
||||
|
||||
if (StorageSettingsCache.IsDirty(cachedAllowedToAcceptValue))
|
||||
return __state = true;
|
||||
|
||||
__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)
|
||||
{
|
||||
if (!__state)
|
||||
return;
|
||||
|
||||
UpdateCache(__instance, t, __result);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void UpdateCache(StorageSettings __instance, Thing t, bool __result)
|
||||
=> __instance.Cache().Update(t.thingIDNumber, __result && t is { def: not null, thingIDNumber: not -1 });
|
||||
}
|
||||
|
||||
public sealed class TryNotifyChanged_Patch : FishPatch
|
||||
{
|
||||
public static event Action<StorageSettings>? Changed;
|
||||
|
||||
public override string Description { get; } = "StorageSettings cache resetting";
|
||||
|
||||
public override Expression<Action> TargetMethod { get; }
|
||||
= static () => default(StorageSettings)!.TryNotifyChanged();
|
||||
|
||||
public static void Postfix(StorageSettings __instance)
|
||||
{
|
||||
__instance.Cache().OnSettingsChanged(__instance);
|
||||
Changed?.Invoke(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
public record struct StorageSettingsCache
|
||||
{
|
||||
public FishTable<int, int> AllowedToAccept = new();
|
||||
|
||||
private Thing? _parent;
|
||||
|
||||
public int FreeSlots = 0xFFFFFFF;
|
||||
|
||||
private int _storedThingCount;
|
||||
|
||||
private FishTable<ushort, List<Thing>> _storedThingsByDef = new();
|
||||
|
||||
private CompProperties? _lwmProps;
|
||||
|
||||
public event Action<StorageSettings>? SettingsChanged;
|
||||
|
||||
public int TotalSlots => _parent!.TrueItemsPerCell(_lwmProps) * _parent!.SlotGroupCellCount();
|
||||
|
||||
public List<Thing> StoredThingsOfDef(ThingDef def) => _storedThingsByDef.GetOrAdd(def.shortHash);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsDirty(int allowedToAcceptValue) => allowedToAcceptValue == int.MaxValue;
|
||||
|
||||
public void Update(int thingIDNumber, bool allowedToAccept)
|
||||
=> AllowedToAccept[thingIDNumber] = allowedToAccept.AsInt();
|
||||
|
||||
public void Initialize(Thing slotGroupParent)
|
||||
{
|
||||
_parent = slotGroupParent;
|
||||
_storedThingsByDef.Clear();
|
||||
AllowedToAccept.Clear();
|
||||
_lwmProps = slotGroupParent.TryGetLwmCompProperties();
|
||||
FreeSlots = TotalSlots;
|
||||
}
|
||||
|
||||
public bool Contains(Thing thing) => StoredThingsOfDef(thing.def).Contains(thing);
|
||||
|
||||
public void Notify_ReceivedThing(Thing thing)
|
||||
{
|
||||
if (_parent is null)
|
||||
return;
|
||||
|
||||
StoredThingsOfDef(thing.def).Add(thing);
|
||||
FreeSlots = TotalSlots - ++_storedThingCount;
|
||||
Debug.VerifyItemCount(_parent, ref this);
|
||||
}
|
||||
|
||||
public void Notify_LostThing(Thing thing)
|
||||
{
|
||||
if (_parent is null)
|
||||
return;
|
||||
|
||||
StoredThingsOfDef(thing.def).Remove(thing);
|
||||
FreeSlots = TotalSlots - --_storedThingCount;
|
||||
|
||||
Debug.VerifyItemCount(_parent, ref this);
|
||||
}
|
||||
|
||||
public void OnSettingsChanged(StorageSettings storageSettings)
|
||||
{
|
||||
AllowedToAccept.Clear();
|
||||
|
||||
if (_parent?.IsSpawned() ?? false)
|
||||
FreeSlots = TotalSlots - _storedThingCount;
|
||||
|
||||
SettingsChanged?.Invoke(storageSettings);
|
||||
}
|
||||
|
||||
public StorageSettingsCache() => AllowedToAccept.ValueInitializer = static _ => int.MaxValue;
|
||||
}
|
||||
|
||||
public static class Debug
|
||||
{
|
||||
[Conditional("StorageSettings_DEBUG")]
|
||||
public static void LogMessage(PooledStringHandler stringHandler) => Log.Message(stringHandler);
|
||||
|
||||
[Conditional("StorageSettings_DEBUG")]
|
||||
public static void VerifyItemCount(Thing storageThing, ref StorageSettingsCache cache)
|
||||
{
|
||||
if (storageThing.TryGetMap() is not { } ownerMap || storageThing is not ISlotGroupParent slotGroup)
|
||||
return;
|
||||
|
||||
var realTotalSlots = cache.TotalSlots;
|
||||
var realTotalItemCount = slotGroup.AllSlotCellsList().Sum(cell => cell.GetItemCount(ownerMap));
|
||||
if (cache.FreeSlots == realTotalSlots - realTotalItemCount)
|
||||
return;
|
||||
|
||||
Log.Error($"StorageSettings cache for '{storageThing}' has incorrect free slots count of '{
|
||||
cache.FreeSlots}'. Real total item count: '{realTotalItemCount}', real total slots: '{
|
||||
realTotalSlots}', real free slots: '{realTotalSlots - realTotalItemCount}'");
|
||||
|
||||
Current.Game.tickManager.Pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
// Copyright (c) 2023 bradson
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using PerformanceFish.Events;
|
||||
using PerformanceFish.Listers;
|
||||
using PerformanceFish.Prepatching;
|
||||
|
||||
namespace PerformanceFish.Hauling;
|
||||
|
||||
public sealed class StoreUtilityPrepatches : ClassWithFishPrepatches
|
||||
{
|
||||
public sealed class NoStorageBlockersInPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Optimizes this frequently accessed storage check by caching fully blocked cells in a bit array and "
|
||||
+ "reordering instructions to make better use of caches.";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(StoreUtility.NoStorageBlockersIn);
|
||||
|
||||
protected internal override void OnPatchingCompleted() => ThingEvents.Initialized += TryRegister;
|
||||
|
||||
public static void TryRegister(Thing thing)
|
||||
{
|
||||
if (!IsStorageBlocker(thing.def))
|
||||
return;
|
||||
|
||||
var thingEvents = thing.Events();
|
||||
thingEvents.RegisteredAtThingGrid += SetStorageBlocker;
|
||||
thingEvents.DeregisteredAtThingGrid += TryUnsetStorageBlocker;
|
||||
}
|
||||
|
||||
public static bool IsStorageBlocker(ThingDef thingDef)
|
||||
=> (thingDef.entityDefToBuild != null && thingDef.entityDefToBuild.passability != 0)
|
||||
|| (thingDef.passability != 0
|
||||
&& thingDef.surfaceType == SurfaceType.None
|
||||
&& thingDef.category != ThingCategory.Item);
|
||||
|
||||
public static void SetStorageBlocker(Thing thing, Map map, in IntVec3 cell)
|
||||
=> map.StorageBlockerGrid()[cell] = true;
|
||||
|
||||
public static void TryUnsetStorageBlocker(Thing thing, Map map, in IntVec3 cell)
|
||||
{
|
||||
var thingsList = map.thingGrid.ThingsListAt(cell);
|
||||
|
||||
for (var i = thingsList.Count; i-- > 0;)
|
||||
{
|
||||
var otherThing = thingsList[i];
|
||||
if (otherThing != thing && IsStorageBlocker(otherThing.def))
|
||||
return;
|
||||
}
|
||||
|
||||
map.StorageBlockerGrid()[cell] = false;
|
||||
}
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static bool ReplacementBody(IntVec3 c, Map map, Thing thing)
|
||||
{
|
||||
if (map.StorageBlockerGrid()[c])
|
||||
return false;
|
||||
|
||||
if (c.GetItemCount(map) < c.GetMaxItemsAllowedInCell(map))
|
||||
return true;
|
||||
|
||||
var thingsAtCell = map.thingGrid.ThingsListAt(c);
|
||||
var thingCount = thingsAtCell.Count;
|
||||
for (var i = 0; i < thingCount; i++)
|
||||
{
|
||||
var thingAtCell = thingsAtCell[i];
|
||||
|
||||
if (thingAtCell.stackCount < thingAtCell.def.stackLimit
|
||||
&& thingAtCell.CanStackWith(thing)
|
||||
&& thingAtCell.def.EverStorable(false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TryFindBestBetterStoreCellForWorkerPatch : FishPrepatch
|
||||
{
|
||||
public override List<Type> LinkedPatches { get; } =
|
||||
[
|
||||
typeof(SlotGroupPrepatches.Notify_AddedCellPatch), typeof(SlotGroupPrepatches.Notify_LostCellPatch)
|
||||
];
|
||||
|
||||
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";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= methodof(StoreUtility.TryFindBestBetterStoreCellForWorker);
|
||||
|
||||
protected internal override void OnPatchingCompleted() => ThingEvents.Initialized += TryRegister;
|
||||
|
||||
public static void TryRegister(Thing thing)
|
||||
{
|
||||
var thingEvents = thing.Events();
|
||||
|
||||
if (thing is ISlotGroupParent)
|
||||
{
|
||||
thingEvents.Spawned += NotifySlotGroupCellCountChanged;
|
||||
thingEvents.DeSpawned += NotifySlotGroupCellCountChanged;
|
||||
}
|
||||
|
||||
if (!thing.IsItem())
|
||||
return;
|
||||
|
||||
thingEvents.RegisteredAtThingGrid += TryNotifyReceivedThing;
|
||||
thingEvents.DeregisteredAtThingGrid += TryNotifyLostThing;
|
||||
}
|
||||
|
||||
public static void NotifySlotGroupCellCountChanged(Thing slotGroupParent, Map map)
|
||||
=> NotifySlotGroupCellCountChanged(slotGroupParent);
|
||||
|
||||
public static void NotifySlotGroupCellCountChanged(Thing slotGroupParent)
|
||||
=> slotGroupParent.GetSlotGroup()?.NotifyCellCountChanged();
|
||||
|
||||
public static void TryNotifyReceivedThing(Thing thing, Map map, in IntVec3 cell)
|
||||
=> map.StorageDistrictGrid()[cell]?.AddThing(thing);
|
||||
|
||||
public static void TryNotifyLostThing(Thing thing, Map map, in IntVec3 cell)
|
||||
=> map.StorageDistrictGrid()[cell]?.RemoveThing(thing);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static void ReplacementBody(Thing t, Pawn carrier, Map map, Faction faction, SlotGroup? slotGroup,
|
||||
bool needAccurateResult, ref IntVec3 closestSlot, ref float closestDistSquared,
|
||||
ref StoragePriority foundPriority)
|
||||
{
|
||||
if (slotGroup == null || !slotGroup.parent.Accepts(t))
|
||||
return;
|
||||
|
||||
var thingPosition = t.SpawnedOrAnyParentSpawned ? t.PositionHeld : carrier.PositionHeld;
|
||||
var slotGroupCells = slotGroup.CellsList;
|
||||
|
||||
var maxValidCellsToCheck
|
||||
= !FishSettings.ImproveHaulingAccuracy && needAccurateResult
|
||||
? Mathf.FloorToInt(slotGroupCells.Count * Rand.Range(0.005f, 0.018f))
|
||||
: 0;
|
||||
|
||||
StartOfDistrictLoop:
|
||||
var districts = slotGroup.Districts();
|
||||
|
||||
for (var districtIndex = 0; districtIndex < districts.Length; districtIndex++)
|
||||
{
|
||||
var district = districts[districtIndex];
|
||||
|
||||
if (!district.CapacityAllows(t))
|
||||
continue;
|
||||
|
||||
var districtCells = districts.Length > 1 ? district.Cells : slotGroupCells;
|
||||
|
||||
for (var i = 0; i < districtCells.Count; i++)
|
||||
{
|
||||
var slotGroupCell = districtCells[i];
|
||||
var cellDistance = (float)(thingPosition - slotGroupCell).LengthHorizontalSquared;
|
||||
|
||||
if (cellDistance > closestDistSquared
|
||||
|| !StoreUtility.IsGoodStoreCell(slotGroupCell, map, t, carrier, faction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (districts.Length > 1 && slotGroupCell.GetSlotGroup(map) != district.Parent)
|
||||
{
|
||||
district.Parent.ResetAndRemakeDistricts(); // TODO: figure out why this happens
|
||||
goto StartOfDistrictLoop;
|
||||
}
|
||||
|
||||
closestSlot = slotGroupCell;
|
||||
closestDistSquared = cellDistance;
|
||||
foundPriority = slotGroup.Settings.Priority;
|
||||
if (!FishSettings.ImproveHaulingAccuracy && (districtIndex * i) + i >= maxValidCellsToCheck)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TryFindBestBetterStoreCellForPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Optimizes a loop in the method to make use of cached storage priorities for fewer comparisons against "
|
||||
+ "those. Also removes haulables from ListerHaulables if the found best store cell turns out to be the "
|
||||
+ "current cell";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; } = methodof(StoreUtility.TryFindBestBetterStoreCellFor);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static bool ReplacementBody(Thing t, Pawn carrier, Map map,
|
||||
StoragePriority currentPriority, Faction faction, out IntVec3 foundCell, bool needAccurateResult = true)
|
||||
{
|
||||
var haulDestinationManager = map.haulDestinationManager;
|
||||
List<SlotGroup> listInPriorityOrder = haulDestinationManager.AllGroupsListInPriorityOrder;
|
||||
if (listInPriorityOrder.Count == 0)
|
||||
{
|
||||
foundCell = IntVec3.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
var foundPriority = currentPriority;
|
||||
var closestDistSquared = (float)int.MaxValue;
|
||||
var closestSlot = IntVec3.Invalid;
|
||||
|
||||
var groupCountByPriority = GetCachedGroupCountByPriority(haulDestinationManager);
|
||||
var i = 0;
|
||||
for (var priorityGroupIndex = groupCountByPriority.Length; priorityGroupIndex-- > 0;)
|
||||
{
|
||||
var groupsOfPriorityCount = groupCountByPriority[priorityGroupIndex];
|
||||
|
||||
if ((StoragePriority)priorityGroupIndex < foundPriority) // succeeded finding a higher priority storage
|
||||
break;
|
||||
|
||||
if ((StoragePriority)priorityGroupIndex <= currentPriority) // current storage is highest valid priority
|
||||
{
|
||||
TryRemoveFromListerHaulables(t, currentPriority);
|
||||
break;
|
||||
}
|
||||
|
||||
while (groupsOfPriorityCount-- > 0)
|
||||
{
|
||||
StoreUtility.TryFindBestBetterStoreCellForWorker(t, carrier, map, faction, listInPriorityOrder[i++],
|
||||
needAccurateResult, ref closestSlot, ref closestDistSquared, ref foundPriority);
|
||||
}
|
||||
}
|
||||
|
||||
if (!closestSlot.IsValid)
|
||||
{
|
||||
foundCell = IntVec3.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
foundCell = closestSlot;
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int[] GetCachedGroupCountByPriority(HaulDestinationManager haulDestinationManager)
|
||||
=> haulDestinationManager.Cache().GroupCountByPriority;
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void TryRemoveFromListerHaulables(Thing t, StoragePriority currentPriority)
|
||||
{
|
||||
if (t.IsInAnyStorage() /*t.IsInValidStorage()*/ && t.TryGetMapHeld() is { } map)
|
||||
map.listerHaulables.TryRemoveDirectly(t);
|
||||
|
||||
// remove for any storage instead of only valid storage to prevent further haul attempts until the next
|
||||
// automatic ListerHaulablesTick cycle
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GetSlotGroupPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Optimization for this method by reducing its amount of instructions without changing results";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= methodof((Func<Thing, SlotGroup>)StoreUtility.GetSlotGroup);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static SlotGroup? ReplacementBody(Thing thing)
|
||||
=> thing.TryGetMap() is { } map ? thing.Position.GetSlotGroup(map) : null;
|
||||
}
|
||||
|
||||
public sealed class CurrentHaulDestinationOfPatch : FishPrepatch
|
||||
{
|
||||
public override string? Description { get; }
|
||||
= "Optimization for this method by reducing its amount of instructions without changing results";
|
||||
|
||||
public override MethodBase TargetMethodBase { get; }
|
||||
= methodof(StoreUtility.CurrentHaulDestinationOf);
|
||||
|
||||
public override void Transpiler(ILProcessor ilProcessor, ModuleDefinition module)
|
||||
=> ilProcessor.ReplaceBodyWith(ReplacementBody);
|
||||
|
||||
public static IHaulDestination? ReplacementBody(Thing t)
|
||||
=> t.TryGetMap() is { } map
|
||||
? map.haulDestinationManager.SlotGroupParentAt(t.Position)
|
||||
: t.ParentHolder as IHaulDestination;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user