From 8067fe2da7844cfae5c1058fd983e560902f1245 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Oct 2026 09:53:41 +0000 Subject: [PATCH] Allow enumerated crafting interfaces to update every 5 ticks Instead of raising the minimum update interval of enumerated crafting interfaces, only reloads of their recipe list are throttled now. Static lists are never reloaded, so they process jobs as fast as regular crafting interfaces. Closes #253 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UeSuG8KEf7iLTGPVRTnQX3 --- .../integratedcrafting/GeneralConfig.java | 2 +- ...PartTypeInterfaceCraftingVariableBase.java | 18 ++++++++++- .../GameTestsItemsCraftEnumerated.java | 32 +++++++++++++++++++ .../PartTypeInterfaceCraftingEnumerated.java | 3 +- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/cyclops/integratedcrafting/GeneralConfig.java b/src/main/java/org/cyclops/integratedcrafting/GeneralConfig.java index 378069e94..9b19a75b2 100644 --- a/src/main/java/org/cyclops/integratedcrafting/GeneralConfig.java +++ b/src/main/java/org/cyclops/integratedcrafting/GeneralConfig.java @@ -40,7 +40,7 @@ public class GeneralConfig extends DummyConfig { @ConfigurableProperty(category = "machine", comment = "The maximum number of recipes that will be read from a list inside an enumerated crafting interface. Set to 0 for no limit.", minimalValue = 0, isCommandable = true, configLocation = ModConfig.Type.SERVER) public static int maxCraftingInterfaceEnumeratedRecipes = 4096; - @ConfigurableProperty(category = "machine", comment = "The minimal update frequency in ticks to use for enumerated crafting interfaces. Reading a list of recipes is more expensive than reading a single recipe, so this defaults higher than the regular crafting interface.", minimalValue = 1, configLocation = ModConfig.Type.SERVER) + @ConfigurableProperty(category = "machine", comment = "The minimal number of ticks between two recipe list reloads in enumerated crafting interfaces, which happen when the list variable changes. Reading a list of recipes is more expensive than reading a single recipe, so this defaults higher than the regular crafting interface update frequency.", minimalValue = 1, configLocation = ModConfig.Type.SERVER) public static int minCraftingInterfaceEnumeratedUpdateFreq = 20; @ConfigurableProperty(category = "machine", comment = "The maximum number of recipes that a crafting interface remembers crafting durations for, which are used to estimate the duration of crafting jobs. Set to 0 to disable recipe-specific estimations.", minimalValue = 0, isCommandable = true, configLocation = ModConfig.Type.SERVER) diff --git a/src/main/java/org/cyclops/integratedcrafting/core/part/PartTypeInterfaceCraftingVariableBase.java b/src/main/java/org/cyclops/integratedcrafting/core/part/PartTypeInterfaceCraftingVariableBase.java index 974981647..c80d6110d 100644 --- a/src/main/java/org/cyclops/integratedcrafting/core/part/PartTypeInterfaceCraftingVariableBase.java +++ b/src/main/java/org/cyclops/integratedcrafting/core/part/PartTypeInterfaceCraftingVariableBase.java @@ -156,7 +156,9 @@ public void update(INetwork network, IPartNetwork partNetwork, PartTarget target // Reload recipes if needed IntSet slots = state.getDelayedRecipeReloads(); - if (!slots.isEmpty()) { + long gameTime = target.getCenter().getPos().getLevel(true).getGameTime(); + if (!slots.isEmpty() && state.canReloadDelayedRecipes(gameTime)) { + state.lastDelayedRecipeReloadTick = gameTime; ICraftingNetwork craftingNetwork = network.getCapability(getNetworkCapability()).orElse(null); if (craftingNetwork != null) { IntSet slotsCopy = new IntOpenHashSet(slots); // Create a copy, to allow insertion into slots during this loop @@ -219,6 +221,7 @@ public static abstract class State

{ protected int ticksAfterReload = 0; + protected long lastDelayedRecipeReloadTick = Integer.MIN_VALUE; private final SimpleInventory inventoryVariables; private final List> variableEvaluators; @@ -463,6 +466,19 @@ private void setSlotRecipes(int slot, List recipes) { } } + /** + * @return The minimal number of ticks between two reloads of invalidated recipe variables. + */ + protected int getMinDelayedRecipeReloadInterval() { + return 0; + } + + protected boolean canReloadDelayedRecipes(long gameTime) { + // Never throttle during initialization, as dependency variables may still be loading. + return ticksAfterReload <= 1 + || gameTime - lastDelayedRecipeReloadTick >= getMinDelayedRecipeReloadInterval(); + } + protected void delayedReloadRecipe(int slot) { this.delayedRecipeReloads.add(slot); } diff --git a/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsItemsCraftEnumerated.java b/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsItemsCraftEnumerated.java index 6da336a84..7887ed2f9 100644 --- a/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsItemsCraftEnumerated.java +++ b/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsItemsCraftEnumerated.java @@ -15,6 +15,7 @@ import net.neoforged.neoforge.gametest.GameTestHolder; import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; import org.apache.commons.lang3.tuple.Pair; +import org.cyclops.integratedcrafting.GeneralConfig; import org.cyclops.integratedcrafting.Reference; import org.cyclops.integratedcrafting.part.PartTypeInterfaceCraftingEnumerated; import org.cyclops.integratedcrafting.part.PartTypes; @@ -99,6 +100,37 @@ public void testItemsCraftEnumeratedCraftingTable(GameTestHelper helper) { }); } + /** + * The enumerated interface can process jobs as fast as the regular interface, + * as only reloads of its list are throttled. + */ + @GameTest(template = TEMPLATE_EMPTY, timeoutTicks = TIMEOUT) + public void testItemsCraftEnumeratedMinUpdateInterval(GameTestHelper helper) { + GameTestHelpersIntegratedCrafting.INetworkPositions positions = + createBasicNetwork(helper, POS, PartTypes.INTERFACE_CRAFTING_ENUMERATED, Blocks.CRAFTING_TABLE); + PartTypeInterfaceCraftingEnumerated.State state = positions.interfaceStates().get(0); + + helper.assertValueEqual(PartTypes.INTERFACE_CRAFTING_ENUMERATED.getMinimumUpdateInterval(state), + GeneralConfig.minCraftingInterfaceUpdateFreq, "Minimum update interval is incorrect"); + GameTestHelpersIntegratedCrafting.setCraftingInterfaceUpdateInterval(positions.interfaces().get(0), + GeneralConfig.minCraftingInterfaceUpdateFreq); + + ChestBlockEntity chestIn = helper.getBlockEntity(POS.east()); + chestIn.setItem(0, new ItemStack(Items.OAK_PLANKS, 64)); + + state.getInventoryVariables().setItem(0, createVariableForRecipeList(helper.getLevel(), List.of( + Pair.of(RecipeType.CRAFTING, ResourceLocation.fromNamespaceAndPath("minecraft", "chest")) + ))); + + enableRecipeInWriter(helper, positions.writer(), new ItemStack(Items.CHEST)); + + helper.succeedWhen(() -> { + helper.assertValueEqual(state.getUpdateInterval(), GeneralConfig.minCraftingInterfaceUpdateFreq, + "Update interval is incorrect"); + helper.assertValueEqual(chestIn.getItem(1).getItem(), Items.CHEST, "Slot 1 item is incorrect"); + }); + } + /** * Replacing the list variable re-indexes the interface with the new recipes. */ diff --git a/src/main/java/org/cyclops/integratedcrafting/part/PartTypeInterfaceCraftingEnumerated.java b/src/main/java/org/cyclops/integratedcrafting/part/PartTypeInterfaceCraftingEnumerated.java index 1bcaf23ad..cfcd9ba8f 100644 --- a/src/main/java/org/cyclops/integratedcrafting/part/PartTypeInterfaceCraftingEnumerated.java +++ b/src/main/java/org/cyclops/integratedcrafting/part/PartTypeInterfaceCraftingEnumerated.java @@ -62,9 +62,10 @@ protected PartTypeInterfaceCraftingEnumerated getPartTypeInstance() { } @Override - protected int getDefaultUpdateInterval() { + protected int getMinDelayedRecipeReloadInterval() { // Reading a whole list of recipes is more expensive than reading a single recipe, // and reader-backed list variables are invalidated on every reader tick. + // Only reloads are throttled, so static lists can process jobs at the regular rate. return GeneralConfig.minCraftingInterfaceEnumeratedUpdateFreq; }