diff --git a/src/main/java/org/cyclops/integratedcrafting/GeneralConfig.java b/src/main/java/org/cyclops/integratedcrafting/GeneralConfig.java index 378069e94..4ad5373e0 100644 --- a/src/main/java/org/cyclops/integratedcrafting/GeneralConfig.java +++ b/src/main/java/org/cyclops/integratedcrafting/GeneralConfig.java @@ -22,6 +22,9 @@ public class GeneralConfig extends DummyConfig { @ConfigurableProperty(category = "machine", comment = "The minimal update frequency in ticks to use for crafting interfaces.", minimalValue = 1, configLocation = ModConfig.Type.SERVER) public static int minCraftingInterfaceUpdateFreq = 5; + @ConfigurableProperty(category = "machine", comment = "The minimal update frequency in ticks to use for attuned crafting interfaces.", minimalValue = 1, configLocation = ModConfig.Type.SERVER) + public static int minCraftingInterfaceAttunedUpdateFreq = 5; + @ConfigurableProperty(category = "machine", comment = "If the crafting interface should validate recipes on insertion.", isCommandable = true, configLocation = ModConfig.Type.SERVER) public static boolean validateRecipesCraftingInterface = true; @@ -40,8 +43,11 @@ 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) - public static int minCraftingInterfaceEnumeratedUpdateFreq = 20; + @ConfigurableProperty(category = "machine", comment = "The minimal update frequency in ticks to use for enumerated crafting interfaces.", minimalValue = 1, configLocation = ModConfig.Type.SERVER) + public static int minCraftingInterfaceEnumeratedUpdateFreq = 5; + + @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 update frequency.", minimalValue = 1, configLocation = ModConfig.Type.SERVER) + public static int minCraftingInterfaceEnumeratedRecipeReloadFreq = 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) public static int craftingInterfaceRecipeDurationEntries = 32; 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..6b5e67ce7 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 uses its own minimum update interval, + * 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.minCraftingInterfaceEnumeratedUpdateFreq, "Minimum update interval is incorrect"); + GameTestHelpersIntegratedCrafting.setCraftingInterfaceUpdateInterval(positions.interfaces().get(0), + GeneralConfig.minCraftingInterfaceEnumeratedUpdateFreq); + + 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.minCraftingInterfaceEnumeratedUpdateFreq, + "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/PartTypeInterfaceCraftingAttuned.java b/src/main/java/org/cyclops/integratedcrafting/part/PartTypeInterfaceCraftingAttuned.java index 1e8087de1..f4eddb698 100644 --- a/src/main/java/org/cyclops/integratedcrafting/part/PartTypeInterfaceCraftingAttuned.java +++ b/src/main/java/org/cyclops/integratedcrafting/part/PartTypeInterfaceCraftingAttuned.java @@ -224,6 +224,11 @@ public static class State extends PartTypeInterfaceCraftingBase.State disabledRecipes = Sets.newLinkedHashSet(); private int recipesVersion = 0; + @Override + protected int getDefaultUpdateInterval() { + return GeneralConfig.minCraftingInterfaceAttunedUpdateFreq; + } + protected Optional getTargetRecipeHandler() { PartPos target = getTarget().getTarget(); return IModHelpersNeoForge.get().getCapabilityHelpers().getCapability(target.getPos(), target.getSide(), Capabilities.RecipeHandler.BLOCK); diff --git a/src/main/java/org/cyclops/integratedcrafting/part/PartTypeInterfaceCraftingEnumerated.java b/src/main/java/org/cyclops/integratedcrafting/part/PartTypeInterfaceCraftingEnumerated.java index 1bcaf23ad..b060ea1ad 100644 --- a/src/main/java/org/cyclops/integratedcrafting/part/PartTypeInterfaceCraftingEnumerated.java +++ b/src/main/java/org/cyclops/integratedcrafting/part/PartTypeInterfaceCraftingEnumerated.java @@ -63,9 +63,15 @@ protected PartTypeInterfaceCraftingEnumerated getPartTypeInstance() { @Override protected int getDefaultUpdateInterval() { + return GeneralConfig.minCraftingInterfaceEnumeratedUpdateFreq; + } + + @Override + 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. - return GeneralConfig.minCraftingInterfaceEnumeratedUpdateFreq; + // Only reloads are throttled, so static lists can process jobs at the full update rate. + return GeneralConfig.minCraftingInterfaceEnumeratedRecipeReloadFreq; } @Override