Take the maximum stack size from the slot when extracting from large slots - #49
Conversation
…slots The workaround for inventories with slots larger than a vanilla stack read its maximum stack size from ItemStack.EMPTY. Since the component rewrite an empty stack carries no MAX_STACK_SIZE component, so that reports 1 rather than 64. As a result the branch triggered for practically every extraction, and the non-simulated path pulled a single item per extractItem call: draining 1000 items out of one slot took 1001 calls instead of 16. Items that stack below 64, such as ender pearls, were affected the same way. The value now comes from the stack in the slot, which is what item handlers actually cap a single extraction at. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ACuxaa5QeiZ8mVsEQtwSq1
Wall-clock measurement, replacing the caveat in the descriptionThe PR description said the call count was measured but the wall-clock cost was not, since my counting handler was in-memory. I have now measured it against real block entities, so that caveat no longer applies. Setup: game test in Integrated Crafting, extracting 1024 oak planks in a single
Two things worth pulling out:
So this is a real performance fix rather than only a correctness tidy-up, and it scales with how much autocrafting pulls per operation. The variance across repeats is small relative to the effect, though n is small and these are dev-environment numbers, so treat them as an order of magnitude rather than a precise figure. Generated by Claude Code |
Problem
IngredientComponentStorageWrapperHandlerItemStack.ComponentStorageWrapper.storageExtractItemis the workaround for inventories whose slots hold more than a vanilla stack (added for CyclopsMC/IntegratedCrafting#106, Sophisticated Barrels). It read its "one vanilla stack" value fromItemStack.EMPTY:Since the data component rewrite that returns 1, not 64.
ItemStack.getComponents()returnsDataComponentMap.EMPTYfor an empty stack, so NeoForge'sIItemExtension.getMaxStackSize(stack)falls through togetOrDefault(DataComponents.MAX_STACK_SIZE, 1). Confirmed at runtime on 1.21.1 / NeoForge 21.1.247.Two consequences:
amount > 1 && getSlotLimit(slot) > 1holds for any ordinary chest.Math.min(amount - count, maxStackSize), so it pulls one item perextractItemcall. Measured with a counting handler: draining 1000 items out of one 4096-capacity slot took 1001 calls where 16 would do.Items that stack below 64 were affected the same way even before this, since the constant never matched their actual cap. 100 ender pearls took ~101 calls rather than 7.
Cost
Measured against real block entities: extracting 1024 oak planks in a single
extractcall, 1 warmup then 3 timed runs, reported as the mean per run, whole configuration repeated twice. Before = stock2.11.5-363, after = this branch published locally as2.11.5-DEV; the mod list in each log confirms which jar loaded.limited_diamond_barrel_1(1 slot, 8192 capacity)At the old rate, draining a full 8192-capacity barrel slot in one extraction extrapolates to roughly 62 ms, more than a whole 50 ms server tick, for a single call. After the change that is about 3 ms.
Vanilla chests were affected too, ~11-15x, which confirms the branch was triggering for essentially every extraction rather than only for oversized slots. This was never drawer-specific.
Caveat on the numbers: n is small and these are dev-environment measurements on a single machine, so treat them as an order of magnitude rather than a precise figure. The variance across repeats is small relative to the effect.
Change
Take the maximum stack size from the stack in the slot, which is what item handlers actually cap a single extraction at (
ItemStackHandler.extractItemdoesMath.min(amount, existing.getMaxStackSize())). An empty slot falls through to the plainextractItemas before, rather than short-circuiting, so handlers with an unusualgetStackInSlotkeep their old behaviour.Tests
Two additions to
TestItemStackComponentStorageWrapper, using a countingItemStackHandlerwith a drawer-like slot limit:testExtractLargeUsesFullStacksPerHandlerCall: 1000 apples out of one slot, asserts the extraction is correct and takes fewer than 40 handler calls.testExtractLargeUsesFullStacksPerHandlerCallForSmallStacks: the same for ender pearls, which stack to 16.Both fail on
master-1.21-ltsand pass with the change. The existingtestExtractLargeandtestExtractLargePartialpass either way, since the old code was correct, just slow.Validation
./gradlew buildpasses./gradlew runGameTestServer --rerunpasses, 47 game testsNote for anyone building this locally:
src/main/java/org/cyclops/commoncapabilities/apiis a git submodule, andcompileJavafails with 223 errors untilgit submodule update --inithas been run. That is unrelated to this change.🤖 Generated with Claude Code
https://claude.ai/code/session_01ACuxaa5QeiZ8mVsEQtwSq1