From 11c0215dd26f75d83ad81ece784cae7839178075 Mon Sep 17 00:00:00 2001 From: Bryan Koch Date: Sat, 28 Mar 2026 05:45:57 -1000 Subject: [PATCH] Improve Performance of NetworkInventoryHandler (#1096) Co-authored-by: Martin Robertz Co-authored-by: hiroscho --- ...NetworkInventoryHandlerFunctionalTest.java | 89 +++++++++++++++++++ .../java/appeng/api/storage/IMEInventory.java | 40 ++++++++- .../appeng/me/cache/CraftingGridCache.java | 24 +++-- .../appeng/me/storage/MEInventoryHandler.java | 25 +++--- .../me/storage/NetworkInventoryHandler.java | 18 +++- .../storage/StorageBusInventoryHandler.java | 40 +++++---- .../appeng/util/item/NetworkItemList.java | 16 ++++ 7 files changed, 208 insertions(+), 44 deletions(-) create mode 100644 src/functionalTest/java/appeng/test/me/storage/NetworkInventoryHandlerFunctionalTest.java diff --git a/src/functionalTest/java/appeng/test/me/storage/NetworkInventoryHandlerFunctionalTest.java b/src/functionalTest/java/appeng/test/me/storage/NetworkInventoryHandlerFunctionalTest.java new file mode 100644 index 000000000..c2f2e8fd5 --- /dev/null +++ b/src/functionalTest/java/appeng/test/me/storage/NetworkInventoryHandlerFunctionalTest.java @@ -0,0 +1,89 @@ +package appeng.test.me.storage; + +import static appeng.util.item.AEItemStackType.ITEM_STACK_TYPE; +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; + +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.World; +import net.minecraft.world.WorldProviderSurface; +import net.minecraft.world.WorldServer; +import net.minecraft.world.WorldSettings; +import net.minecraft.world.WorldSettings.GameType; +import net.minecraft.world.WorldType; +import net.minecraftforge.common.DimensionManager; + +import org.junit.jupiter.api.Test; + +import appeng.api.storage.data.IAEItemStack; +import appeng.api.storage.data.IItemList; +import appeng.me.cache.SecurityCache; +import appeng.me.storage.NetworkInventoryHandler; +import appeng.test.DummySaveHandler; +import appeng.test.mockme.MockAESystem; +import appeng.test.mockme.MockGrid; +import appeng.test.mockme.MockGridNode; +import appeng.util.item.ItemList; + +public class NetworkInventoryHandlerFunctionalTest { + + private static World createDummyWorld() { + if (!DimensionManager.isDimensionRegistered(256)) { + DimensionManager.registerProviderType(256, WorldProviderSurface.class, false); + DimensionManager.registerDimension(256, 256); + } + return new WorldServer( + MinecraftServer.getServer(), + new DummySaveHandler(), + "DummyTestWorld", + 256, + new WorldSettings(256, GameType.SURVIVAL, false, false, WorldType.DEFAULT), + MinecraftServer.getServer().theProfiler) { + + @Override + public File getChunkSaveLocation() { + return new File("dummy-ignoreme"); + } + }; + } + + @Test + public void testGetAvailableItems() { + // Setup + World dummyWorld = createDummyWorld(); + MockAESystem mockSystem = new MockAESystem(dummyWorld); + MockGrid mockGrid = mockSystem.grid; + MockGridNode mockNode = mockGrid.rootNode; + SecurityCache mockSecurity = mockSystem.grid.getCache(SecurityCache.class); + NetworkInventoryHandler handler = new NetworkInventoryHandler<>(ITEM_STACK_TYPE, mockSecurity); + IItemList inputList = new ItemList(); + + // Execute + IItemList result = handler.getAvailableItems(inputList, 1); + + // Verify + assertNotNull(result, "Result should not be null"); + assertSame(inputList, result, "Returned list should be the same instance as the input"); + assertEquals(inputList.size(), result.size(), "Result size should match input size"); + } + + @Test + public void testGetAvailableItemsWithFilter() { + // Setup + World dummyWorld = createDummyWorld(); + MockAESystem mockSystem = new MockAESystem(dummyWorld); + MockGrid mockGrid = mockSystem.grid; + MockGridNode mockNode = mockGrid.rootNode; + SecurityCache mockSecurity = mockSystem.grid.getCache(SecurityCache.class); + NetworkInventoryHandler handler = new NetworkInventoryHandler<>(ITEM_STACK_TYPE, mockSecurity); + IItemList inputList = new ItemList(); + + // Execute + IItemList result = handler.getAvailableItems(inputList, 1); + + // Verify + assertNotNull(result, "Result should not be null"); + assertEquals(inputList.size(), result.size(), "Result size should match input size"); + } +} diff --git a/src/main/java/appeng/api/storage/IMEInventory.java b/src/main/java/appeng/api/storage/IMEInventory.java index f8c7c840b..dd61713d2 100644 --- a/src/main/java/appeng/api/storage/IMEInventory.java +++ b/src/main/java/appeng/api/storage/IMEInventory.java @@ -17,6 +17,8 @@ import static appeng.util.item.AEFluidStackType.FLUID_STACK_TYPE; import static appeng.util.item.AEItemStackType.ITEM_STACK_TYPE; import java.util.Collection; +import java.util.Optional; +import java.util.function.Predicate; import javax.annotation.Nonnull; @@ -28,6 +30,7 @@ import appeng.api.storage.data.IAEStack; import appeng.api.storage.data.IAEStackType; import appeng.api.storage.data.IItemList; import appeng.util.IterationCounter; +import appeng.util.item.NetworkItemList; /** * AE's Equivalent to IInventory, used to reading contents, and manipulating contents of ME Inventories. @@ -90,6 +93,38 @@ public interface IMEInventory { return ret; } + /** + * Optional filtered variant of {@link #getAvailableItems(IItemList, int)}. + * + * @param out the IItemList the results will be written to + * @param iteration numeric id for this iteration, use {@link appeng.util.IterationCounter#fetchNewId()} to avoid + * conflicts + * @param filter optional filter + * @return returns same list that was passed in, is passed out. Network-aware implementations may instead return a + * filtered {@link NetworkItemList} to preserve per-network accounting during nested scans. + */ + default IItemList getAvailableItems(IItemList out, int iteration, + Optional> filter) { + if (!filter.isPresent()) { + return getAvailableItems(out, iteration); + } + + final Predicate predicate = filter.get(); + final IItemList allItems = (IItemList) getStackType().createPrimitiveList(); + final IItemList availableItems = getAvailableItems(allItems, iteration); + + if (availableItems instanceof NetworkItemList) { + return ((NetworkItemList) availableItems).createFilteredView(predicate); + } + + for (final StackType item : availableItems) { + if (predicate.test(item)) { + out.add(item); + } + } + return out; + } + /** * Request a report of how many of a single item type are available in storage. It falls back to * {@link IMEInventory#getAvailableItems} if a more optimized implementation is not present. @@ -120,7 +155,10 @@ public interface IMEInventory { @SuppressWarnings("unchecked") // changing the generic StackType to be correct here is too much of a breaking API // change default StackType getAvailableItem(@Nonnull StackType request, int iteration) { - return getAvailableItems((IItemList) getStackType().createList(), iteration).findPrecise(request); + return getAvailableItems( + (IItemList) getStackType().createList(), + iteration, + Optional.of(s -> s.isSameType(request))).findPrecise(request); } /** diff --git a/src/main/java/appeng/me/cache/CraftingGridCache.java b/src/main/java/appeng/me/cache/CraftingGridCache.java index 560967071..4bcad59ba 100644 --- a/src/main/java/appeng/me/cache/CraftingGridCache.java +++ b/src/main/java/appeng/me/cache/CraftingGridCache.java @@ -23,6 +23,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import java.util.TreeSet; import java.util.UUID; @@ -31,6 +32,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; +import java.util.function.Predicate; import java.util.stream.StreamSupport; import javax.annotation.Nonnull; @@ -455,18 +457,28 @@ public class CraftingGridCache @Override public IItemList getAvailableItems(final IItemList out, int iteration) { + return getAvailableItems(out, iteration, Optional.empty()); + } + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public IItemList getAvailableItems(final IItemList out, int iteration, + Optional> filter) { + final IAEStackType outType = out.getStackType(); + final Predicate predicate = filter.orElse(null); + // add craftable items! for (final IAEStack stack : this.craftableItems.keySet()) { - IAEStackType type = out.getStackType(); - if (type == null || type == stack.getStackType()) { - out.addCrafting(stack); + if ((outType == null || outType == stack.getStackType()) + && (predicate == null || predicate.test((IAEStack) stack))) { + out.addCrafting((IAEStack) stack); } } for (final IAEStack stack : emitableItems) { - IAEStackType type = out.getStackType(); - if (type == null || type == stack.getStackType()) { - out.addCrafting(stack); + if ((outType == null || outType == stack.getStackType()) + && (predicate == null || predicate.test((IAEStack) stack))) { + out.addCrafting((IAEStack) stack); } } diff --git a/src/main/java/appeng/me/storage/MEInventoryHandler.java b/src/main/java/appeng/me/storage/MEInventoryHandler.java index 79deac005..7936da26a 100644 --- a/src/main/java/appeng/me/storage/MEInventoryHandler.java +++ b/src/main/java/appeng/me/storage/MEInventoryHandler.java @@ -10,6 +10,7 @@ package appeng.me.storage; +import java.util.Optional; import java.util.function.Predicate; import javax.annotation.Nonnull; @@ -122,6 +123,11 @@ public class MEInventoryHandler> implements IMEInventoryHa @Override public IItemList getAvailableItems(final IItemList out, int iteration) { + return this.getAvailableItems(out, iteration, Optional.empty()); + } + + @Override + public IItemList getAvailableItems(final IItemList out, int iteration, Optional> preFilter) { if (!this.hasReadAccess && !isVisible()) { return out; } @@ -129,9 +135,12 @@ public class MEInventoryHandler> implements IMEInventoryHa if (out instanceof ItemFilterList) return getAvailableItemsFilter(out, iteration); if (this.isExtractFilterActive() && !this.myExtractPartitionList.isEmpty()) { - return this.filterAvailableItems(out, iteration); + Predicate extractFilter = this.getExtractFilterCondition(); + Predicate effectiveFilter = preFilter.map(extractFilter::and).orElse(extractFilter); + return this.internal.getAvailableItems(out, iteration, Optional.of(effectiveFilter)); } else { - return this.internal.getAvailableItems(out, iteration); + return !preFilter.isPresent() ? this.internal.getAvailableItems(out, iteration) + : this.internal.getAvailableItems(out, iteration, preFilter); } } @@ -141,18 +150,6 @@ public class MEInventoryHandler> implements IMEInventoryHa return bool; } - protected IItemList filterAvailableItems(IItemList out, int iteration) { - final IItemList allAvailableItems = this.internal - .getAvailableItems((IItemList) this.internal.getStackType().createList(), iteration); - Predicate filterCondition = this.getExtractFilterCondition(); - for (T item : allAvailableItems) { - if (filterCondition.test(item)) { - out.add(item); - } - } - return out; - } - protected IItemList getAvailableItemsFilter(IItemList out, int iteration) { if (!getPartitionList().isEmpty() && getWhitelist() == IncludeExclude.WHITELIST) { for (T is : myPartitionList.getItems()) { diff --git a/src/main/java/appeng/me/storage/NetworkInventoryHandler.java b/src/main/java/appeng/me/storage/NetworkInventoryHandler.java index 6ec59ff94..cd12ff40e 100644 --- a/src/main/java/appeng/me/storage/NetworkInventoryHandler.java +++ b/src/main/java/appeng/me/storage/NetworkInventoryHandler.java @@ -17,6 +17,8 @@ import java.util.Collection; import java.util.Comparator; import java.util.LinkedList; import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; import javax.annotation.Nonnull; @@ -303,7 +305,13 @@ public class NetworkInventoryHandler> implements IMENetwor @Override @SuppressWarnings("unchecked") - public IItemList getAvailableItems(IItemList out, int iteration) { + public IItemList getAvailableItems(IItemList out, int iteration) { + return getAvailableItems(out, iteration, Optional.empty()); + } + + @SuppressWarnings("unchecked") + @Override + public IItemList getAvailableItems(IItemList out, int iteration, Optional> filter) { if (this.diveIteration(this, Actionable.SIMULATE, iteration)) { return this.iterationItems == null ? out : this.iterationItems; } @@ -313,7 +321,7 @@ public class NetworkInventoryHandler> implements IMENetwor final NetworkItemList networkItemList = new NetworkItemList<>( this, - () -> (IItemList) getStackType().createList()); + () -> (IItemList) getStackType().createPrimitiveList()); this.iterationItems = networkItemList; final IItemList currentNetworkItemList = isIgnoreCrafting @@ -328,7 +336,7 @@ public class NetworkInventoryHandler> implements IMENetwor continue; // ignore any attempts to read self } final IItemList passedInList = getPrimitiveItemList(); - final IItemList passedOutList = inv.getAvailableItems(passedInList, iteration); + final IItemList passedOutList = inv.getAvailableItems(passedInList, iteration, filter); if (externalNetworkInventory != null && passedOutList instanceof NetworkItemList) { networkItemList.addNetworkItems(externalNetworkInventory, passedOutList); @@ -418,7 +426,9 @@ public class NetworkInventoryHandler> implements IMENetwor } } if (readsFromOtherNetwork) { - final T stack = this.getAvailableItems(getPrimitiveItemList(), iteration).findPrecise(request); + final T stack = this + .getAvailableItems(getPrimitiveItemList(), iteration, Optional.of(s -> s.isSameType(request))) + .findPrecise(request); count = addStackCount(stack, count); } else { if (this.diveIteration(this, Actionable.SIMULATE, iteration)) { diff --git a/src/main/java/appeng/me/storage/StorageBusInventoryHandler.java b/src/main/java/appeng/me/storage/StorageBusInventoryHandler.java index 23b5dbabe..31206034b 100644 --- a/src/main/java/appeng/me/storage/StorageBusInventoryHandler.java +++ b/src/main/java/appeng/me/storage/StorageBusInventoryHandler.java @@ -1,5 +1,6 @@ package appeng.me.storage; +import java.util.Optional; import java.util.function.Predicate; import appeng.api.storage.IMEInventory; @@ -19,38 +20,39 @@ public class StorageBusInventoryHandler> extends MEInvento @Override public IItemList getAvailableItems(final IItemList out, int iteration) { + return this.getAvailableItems(out, iteration, Optional.empty()); + } + + @Override + public IItemList getAvailableItems(final IItemList out, int iteration, Optional> preFilter) { if (!this.hasReadAccess && !isVisible()) { return out; } if (out instanceof ItemFilterList) return this.getAvailableItemsFilter(out, iteration); + Predicate filterCondition = preFilter.orElse(null); + if (this.isExtractFilterActive() && !this.getExtractPartitionList().isEmpty()) { - return this.filterAvailableItems(out, iteration); - } else { - return this.getAvailableItems(out, iteration, e -> true); + Predicate extractFilter = this.getExtractFilterCondition(); + filterCondition = filterCondition == null ? extractFilter : extractFilter.and(filterCondition); } - } - @Override - protected IItemList filterAvailableItems(IItemList out, int iteration) { - Predicate filterCondition = this.getExtractFilterCondition(); - return getAvailableItems(out, iteration, filterCondition); - } + final IItemList availableItems = this.getInternal().getAvailableItems( + (IItemList) this.getStackType().createList(), + iteration, + Optional.of(filterCondition)); - @SuppressWarnings("unchecked") - private IItemList getAvailableItems(IItemList out, int iteration, Predicate filterCondition) { - final IItemList availableItems = this.getInternal() - .getAvailableItems((IItemList) this.getStackType().createList(), iteration); if (availableItems instanceof NetworkItemList) { - NetworkItemList networkItemList = new NetworkItemList<>((NetworkItemList) availableItems); - networkItemList.addFilter(filterCondition); - return networkItemList; + // when we cross between networks on a NetworkInventoryHandler dive, we need to break the "out" contract to + // avoid modifying the passed in list which belongs to a different network (and would cause double-counting + // for triangle-shaped networks) + return availableItems; } else { + // for non-cross-network dives, we need to honor the "out" contract + // and put the results in the passed in list for (T items : availableItems) { - if (filterCondition.test(items)) { - out.add(items); - } + out.add(items); } return out; } diff --git a/src/main/java/appeng/util/item/NetworkItemList.java b/src/main/java/appeng/util/item/NetworkItemList.java index b467b0686..6fd3ea699 100644 --- a/src/main/java/appeng/util/item/NetworkItemList.java +++ b/src/main/java/appeng/util/item/NetworkItemList.java @@ -169,6 +169,22 @@ public class NetworkItemList implements IItemList { return buildFinalItemList(out); } + public NetworkItemList createFilteredView(Predicate filter) { + final NetworkItemList copy = new NetworkItemList<>(this); + + if (this.predicates.isEmpty()) { + copy.predicates.add(filter); + } else { + // if there were existing filters then we need to add the final filter + // as AND instead of OR + for (Predicate existingFilter : this.predicates) { + copy.predicates.add(existingFilter.and(filter)); + } + } + + return copy; + } + /** * Writes all available items to the provided list. *