diff --git a/src/main/java/org/cyclops/integratedcrafting/api/crafting/ICraftingInterface.java b/src/main/java/org/cyclops/integratedcrafting/api/crafting/ICraftingInterface.java index 29a43b7d2..2bda7f00d 100644 --- a/src/main/java/org/cyclops/integratedcrafting/api/crafting/ICraftingInterface.java +++ b/src/main/java/org/cyclops/integratedcrafting/api/crafting/ICraftingInterface.java @@ -91,6 +91,8 @@ public interface ICraftingInterface { * This may fall back to the average duration over all recipes of this interface, * as recipe-specific durations are only remembered for a limited number of recipes, * and are forgotten once they become outdated. + * This is never shorter than the interval at which this interface can start operations, + * so that recipes that produce their outputs instantly are not estimated as taking no time. */ public long getEstimatedRecipeDuration(IRecipeDefinition recipe); diff --git a/src/main/java/org/cyclops/integratedcrafting/api/network/ICraftingNetwork.java b/src/main/java/org/cyclops/integratedcrafting/api/network/ICraftingNetwork.java index 8a6fbcf46..daf84b7ef 100644 --- a/src/main/java/org/cyclops/integratedcrafting/api/network/ICraftingNetwork.java +++ b/src/main/java/org/cyclops/integratedcrafting/api/network/ICraftingNetwork.java @@ -168,6 +168,8 @@ public Iterator getCraftingJobs(int channel, IngredientCompo * @param recipe A recipe. * @return The estimated duration in ticks of a single crafting operation of the given recipe, * based on the operations that the crafting interfaces performed before, or -1 if unknown. + * As with {@link ICraftingInterface#getEstimatedRecipeDuration(IRecipeDefinition)}, + * this accounts for the interval at which those interfaces start operations. */ public long getEstimatedRecipeDuration(int channel, IRecipeDefinition recipe); diff --git a/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java b/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java index 78dc63be6..21dd3e665 100644 --- a/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java +++ b/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java @@ -350,14 +350,28 @@ public long getCraftingJobEntryStartTick(int craftingJobId) { } /** + * Only the time between starting a crafting operation and its outputs coming back in is measured. + * Recipes that produce their outputs within the tick they are started in, such as regular crafting + * recipes, therefore measure as taking no time at all, which would estimate whole crafting jobs away. + * + * In blocking mode, a single operation is started per update, so an operation occupies this handler + * for a full update interval, however quickly the recipe itself is done. + * In non-blocking mode, as many operations are started as the target accepts, so there is no such + * lower bound, and the given interval is ignored. + * * @param recipe A recipe. + * @param updateInterval The number of ticks between two updates of this handler. * @return The estimated duration in ticks of a single crafting operation of the given recipe, * based on the operations that were performed by this handler before, or -1 if unknown. * This falls back to the average duration over all recipes * when the given recipe itself was not crafted recently. */ - public long getEstimatedRecipeDuration(IRecipeDefinition recipe) { - return getRecipeDurationStatistics().getEstimatedDuration(recipe, getCurrentTick()); + public long getEstimatedRecipeDuration(IRecipeDefinition recipe, long updateInterval) { + long recipeDuration = getRecipeDurationStatistics().getEstimatedDuration(recipe, getCurrentTick()); + if (recipeDuration < 0) { + return -1; + } + return isBlockingJobsMode() ? Math.max(recipeDuration, updateInterval) : recipeDuration; } /** diff --git a/src/main/java/org/cyclops/integratedcrafting/core/part/PartTypeInterfaceCraftingBase.java b/src/main/java/org/cyclops/integratedcrafting/core/part/PartTypeInterfaceCraftingBase.java index d1decd8fe..f2ff716b8 100644 --- a/src/main/java/org/cyclops/integratedcrafting/core/part/PartTypeInterfaceCraftingBase.java +++ b/src/main/java/org/cyclops/integratedcrafting/core/part/PartTypeInterfaceCraftingBase.java @@ -381,7 +381,7 @@ public long getCraftingJobEntryStartTick(int craftingJobId) { @Override public long getEstimatedRecipeDuration(IRecipeDefinition recipe) { - return craftingJobHandler.getEstimatedRecipeDuration(recipe); + return craftingJobHandler.getEstimatedRecipeDuration(recipe, getUpdateInterval()); } @Override diff --git a/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsItemsCraft.java b/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsItemsCraft.java index dc1d7cc90..e7261320e 100644 --- a/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsItemsCraft.java +++ b/src/main/java/org/cyclops/integratedcrafting/gametest/GameTestsItemsCraft.java @@ -118,10 +118,12 @@ public void testItemsCraftChestOneRecipeDuration(GameTestHelper helper) { // Check if items have been crafted helper.assertValueEqual(chestIn.getItem(1).getItem(), Items.CHEST, "Slot 1 item is incorrect"); - // Check if the duration of the crafted recipe was measured + // Check if the duration of the crafted recipe was measured. + // A chest is crafted within the tick it is started in, so its measured duration is zero, + // and the interval at which the interface starts operations is what is left of it. PartTypeInterfaceCrafting.State interfaceState = positions.interfaceStates().get(0); for (IRecipeDefinition recipe : interfaceState.getRecipes()) { - helper.assertTrue(interfaceState.getEstimatedRecipeDuration(recipe) >= 0, + helper.assertTrue(interfaceState.getEstimatedRecipeDuration(recipe) >= interfaceState.getUpdateInterval(), "No crafting duration was measured for the crafted recipe"); } }); diff --git a/src/test/java/org/cyclops/integratedcrafting/core/TestCraftingJobHandler.java b/src/test/java/org/cyclops/integratedcrafting/core/TestCraftingJobHandler.java index df2ee0e60..f11f4449c 100644 --- a/src/test/java/org/cyclops/integratedcrafting/core/TestCraftingJobHandler.java +++ b/src/test/java/org/cyclops/integratedcrafting/core/TestCraftingJobHandler.java @@ -63,28 +63,52 @@ protected CraftingJob newCraftingJob(int id, int amount) { @Test public void testRecipeDurationUnknown() { - assertThat(handler.getEstimatedRecipeDuration(recipeA), equalTo(-1L)); + assertThat(handler.getEstimatedRecipeDuration(recipeA, 0), equalTo(-1L)); } @Test public void testRecipeDurationSingle() { handler.reportRecipeDuration(recipeA, 100); - assertThat(handler.getEstimatedRecipeDuration(recipeA), equalTo(100L)); + assertThat(handler.getEstimatedRecipeDuration(recipeA, 0), equalTo(100L)); } @Test public void testRecipeDurationSmoothed() { handler.reportRecipeDuration(recipeA, 100); handler.reportRecipeDuration(recipeA, 200); - assertThat(handler.getEstimatedRecipeDuration(recipeA), equalTo(125L)); + assertThat(handler.getEstimatedRecipeDuration(recipeA, 0), equalTo(125L)); handler.reportRecipeDuration(recipeA, 200); - assertThat(handler.getEstimatedRecipeDuration(recipeA), equalTo(144L)); + assertThat(handler.getEstimatedRecipeDuration(recipeA, 0), equalTo(144L)); + } + + @Test + public void testRecipeDurationIsAtLeastOneUpdate() { + handler.reportRecipeDuration(recipeA, 0); + assertThat(handler.getEstimatedRecipeDuration(recipeA, 5), equalTo(5L)); + } + + @Test + public void testRecipeDurationLongerThanOneUpdateIsKept() { + handler.reportRecipeDuration(recipeA, 100); + assertThat(handler.getEstimatedRecipeDuration(recipeA, 5), equalTo(100L)); + } + + @Test + public void testUnknownRecipeDurationStaysUnknownWithUpdateInterval() { + assertThat(handler.getEstimatedRecipeDuration(recipeA, 5), equalTo(-1L)); + } + + @Test + public void testRecipeDurationIsNotBoundedInNonBlockingMode() { + TickingCraftingJobHandler nonBlockingHandler = new TickingCraftingJobHandler(false); + nonBlockingHandler.reportRecipeDuration(recipeA, 0); + assertThat(nonBlockingHandler.getEstimatedRecipeDuration(recipeA, 5), equalTo(0L)); } @Test public void testRecipeDurationFallsBackToAverage() { handler.reportRecipeDuration(recipeA, 100); - assertThat(handler.getEstimatedRecipeDuration(recipeB), equalTo(100L)); + assertThat(handler.getEstimatedRecipeDuration(recipeB, 0), equalTo(100L)); } @Test @@ -130,7 +154,7 @@ public void testCraftingOperationIsMeasured() { assertThat(craftingJob.getAmount(), equalTo(1)); assertThat(craftingJob.getAmountTotal(), equalTo(2)); - assertThat(handler.getEstimatedRecipeDuration(recipeA), equalTo(60L)); + assertThat(handler.getEstimatedRecipeDuration(recipeA, 0), equalTo(60L)); assertThat(handler.getCraftingJobEntryStartTick(1), equalTo(-1L)); } @@ -147,12 +171,12 @@ public void testParallelCraftingOperationsAreMeasured() { handler.setCurrentTick(200); handler.onCraftingJobEntryFinished(craftingNetwork, 1); - assertThat(handler.getEstimatedRecipeDuration(recipeA), equalTo(100L)); + assertThat(handler.getEstimatedRecipeDuration(recipeA, 0), equalTo(100L)); assertThat(handler.getCraftingJobEntryStartTick(1), equalTo(120L)); handler.setCurrentTick(220); handler.onCraftingJobEntryFinished(craftingNetwork, 1); - assertThat(handler.getEstimatedRecipeDuration(recipeA), equalTo(100L)); + assertThat(handler.getEstimatedRecipeDuration(recipeA, 0), equalTo(100L)); assertThat(handler.getCraftingJobEntryStartTick(1), equalTo(-1L)); } @@ -210,7 +234,7 @@ public void testRecipeDurationsSurviveSerialization() { TickingCraftingJobHandler deserialized = new TickingCraftingJobHandler(); deserialized.readFromNBT(null, tag); - assertThat(deserialized.getEstimatedRecipeDuration(recipeA), equalTo(100L)); + assertThat(deserialized.getEstimatedRecipeDuration(recipeA, 0), equalTo(100L)); assertThat(deserialized.getRecipeDurationStatistics().getEntryCount(), equalTo(0)); } @@ -219,7 +243,11 @@ protected static class TickingCraftingJobHandler extends CraftingJobHandler { private long currentTick; public TickingCraftingJobHandler() { - super(1, true, Collections.emptyList(), new ICraftingResultsSink() { + this(true); + } + + public TickingCraftingJobHandler(boolean blockingJobsMode) { + super(1, blockingJobsMode, Collections.emptyList(), new ICraftingResultsSink() { @Override public void addResult(IngredientComponent ingredientComponent, T instance) {