diff --git a/src/main/java/org/cyclops/integratedcrafting/core/CraftingHelpers.java b/src/main/java/org/cyclops/integratedcrafting/core/CraftingHelpers.java index b64a7a473..2f8413e42 100644 --- a/src/main/java/org/cyclops/integratedcrafting/core/CraftingHelpers.java +++ b/src/main/java/org/cyclops/integratedcrafting/core/CraftingHelpers.java @@ -1622,6 +1622,13 @@ public static boolean insertIngredientCrafting(IngredientComponent IIngredientMatcher matcher = ingredientComponent.getMatcher(); IIngredientComponentStorage storage = ingredientComponent.getStorage(capabilityProvider, side); List instances = ingredients.getInstances(ingredientComponent); + if (simulate) { + // Stack equal instances, as simulated insertions do not take into account each other. + // Otherwise, instances that each fit separately could be accepted while they don't fit together. + Map, List> instancesMap = Maps.newIdentityHashMap(); + instancesMap.put(ingredientComponent, instances); + instances = compressMixedIngredients(new MixedIngredients(instancesMap)).getInstances(ingredientComponent); + } List failedInstances = Lists.newArrayList(); boolean ok = true; for (T instance : instances) { @@ -1664,6 +1671,25 @@ public static boolean insertCrafting(Function, PartPos IMixedIngredients ingredients, INetwork network, int channel, boolean simulate) { + return insertCrafting(targetGetter, ingredients, getNetworkStorageGetter(network, channel, false), simulate); + } + + /** + * Insert the ingredients of all applicable ingredient component types into the target to make it start crafting. + * + * If insertion in non-simulation mode fails, + * ingredients will be inserted into the given fallback storage. + * + * @param targetGetter A function to get the target position. + * @param ingredients The ingredients to insert. + * @param storageFallbackGetter A function to get the storage to insert failed ingredients into. + * @param simulate If insertion should be simulated. + * @return If all instances could be inserted. + */ + public static boolean insertCrafting(Function, PartPos> targetGetter, + IMixedIngredients ingredients, + Function, IIngredientComponentStorage> storageFallbackGetter, + boolean simulate) { Map, BlockEntity> tileMap = Maps.newIdentityHashMap(); // First, check if we can find valid tiles for all ingredient components @@ -1679,10 +1705,10 @@ public static boolean insertCrafting(Function, PartPos // Next, insert the instances into the respective tiles boolean ok = true; for (Map.Entry, BlockEntity> entry : tileMap.entrySet()) { - IIngredientComponentStorage storageNetwork = simulate ? null : getNetworkStorage(network, channel, entry.getKey(), false); + IIngredientComponentStorage storageFallback = simulate ? null : storageFallbackGetter.apply(entry.getKey()); if (!insertIngredientCrafting((IngredientComponent) entry.getKey(), entry.getValue(), targetGetter.apply(entry.getKey()).getSide(), ingredients, - storageNetwork, simulate)) { + storageFallback, simulate)) { ok = false; } } diff --git a/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java b/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java index 7331c49f5..a094278f3 100644 --- a/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java +++ b/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java @@ -321,6 +321,28 @@ public void unmarkCraftingJobProcessing(CraftingJob craftingJob) { } } + /** + * Undo a single crafting operation that was added via {@link #addCraftingJobProcessingPendingIngredientsEntry}. + * Other operations of the same job, which may still be running in non-blocking mode, are kept. + * If no operations remain, the job is moved back to the pending jobs. + * @param craftingJob The crafting job. + * @param pendingIngredients The exact pending ingredients entry that was added for the operation. + * @return If the operation was still running. + */ + protected boolean removeCraftingJobProcessingPendingIngredientsEntry(CraftingJob craftingJob, + Map, + List>> pendingIngredients) { + List, List>>> pendingIngredientsEntries = + this.processingCraftingJobsPendingIngredients.get(craftingJob.getId()); + if (pendingIngredientsEntries == null || !pendingIngredientsEntries.removeIf(entry -> entry == pendingIngredients)) { + return false; + } + if (pendingIngredientsEntries.isEmpty()) { + unmarkCraftingJobProcessing(craftingJob); + } + return true; + } + public void addCraftingJobProcessingPendingIngredientsEntry(CraftingJob craftingJob, Map, List>> pendingIngredients) { @@ -578,8 +600,9 @@ protected boolean insertCrafting(PartPos target, IMixedIngredients ingredients, } } - // Fallback to default crafting insertion - return CraftingHelpers.insertCrafting(targetGetter, ingredients, network, channel, simulate); + // Fallback to default crafting insertion, failed ingredients go back to the job + return CraftingHelpers.insertCrafting(targetGetter, ingredients, + CraftingHelpers.getCraftingJobBufferStorageGetter(craftingJob), simulate); } protected void insertLoopNonBlocking(INetwork network, int channel, PartPos targetPos, CraftingJob craftingJob) { @@ -609,8 +632,9 @@ protected boolean consumeAndInsertCrafting(boolean blockingMode, INetwork networ this.pendingCraftingJobs.remove(startingCraftingJob.getId()); // Update state with expected outputs - addCraftingJobProcessingPendingIngredientsEntry(startingCraftingJob, - CraftingHelpers.getRecipeOutputs(startingCraftingJob.getRecipe())); + Map, List>> pendingIngredients = + CraftingHelpers.getRecipeOutputs(startingCraftingJob.getRecipe()); + addCraftingJobProcessingPendingIngredientsEntry(startingCraftingJob, pendingIngredients); // Register listeners for pending ingredients for (IngredientComponent component : startingCraftingJob.getRecipe().getOutput().getComponents()) { @@ -619,17 +643,19 @@ protected boolean consumeAndInsertCrafting(boolean blockingMode, INetwork networ // Push the ingredients to the crafting interface if (!insertCrafting(targetPos, ingredients, recipe, startingCraftingJob, network, channel, false)) { - // Unregister listeners again for pending ingredients - for (IngredientComponent component : startingCraftingJob.getRecipe().getOutput().getComponents()) { - unregisterIngredientObserver(component, network); - } - // If we reach this point, the target does not accept the recipe inputs, // even though they were acceptable in simulation mode. - // The failed ingredients were already re-inserted into the network at this point, - // so we mark the job as failed, and add it again to the queue. + // The failed ingredients were already put back into the job's buffer at this point. + // Only this operation is undone, as others of this job may still be running in non-blocking mode. + if (removeCraftingJobProcessingPendingIngredientsEntry(startingCraftingJob, pendingIngredients)) { + // Unregister listeners again for pending ingredients + for (IngredientComponent component : startingCraftingJob.getRecipe().getOutput().getComponents()) { + unregisterIngredientObserver(component, network); + } + } + + // Mark the job as failed, it will be added again to the queue once no operations are running. startingCraftingJob.setInvalidInputs(true); - unmarkCraftingJobProcessing(startingCraftingJob); return false; } else { return true;