Skip to content

Commit cb3696a

Browse files
committed
Fix the reusable ingredients in chains of recipes blocking each other (#187)
If the same reusable ingredient would be used within a chain of dependent recipes, the ingredient could be used in a dependent recipe, thereby blocking the dependency recipe. This change ensures that dependency-less recipes first get to use the reusable ingredients. Closes #182
1 parent 174f003 commit cb3696a

2 files changed

Lines changed: 61 additions & 31 deletions

File tree

src/main/java/org/cyclops/integratedcrafting/core/CraftingHelpers.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,17 @@ public static <T, M> List<T> getIngredientRecipeInputs(IIngredientComponentStora
985985
IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemory,
986986
IIngredientCollectionMutable<T, M> extractionMemoryReusable,
987987
boolean collectMissingIngredients, long recipeOutputQuantity) {
988+
return getIngredientRecipeInputs(storage, ingredientComponent, recipe, simulate, simulatedExtractionMemory,
989+
extractionMemoryReusable, collectMissingIngredients, recipeOutputQuantity, false);
990+
}
991+
992+
public static <T, M> Pair<List<T>, MissingIngredients<T, M>>
993+
getIngredientRecipeInputs(IIngredientComponentStorage<T, M> storage, IngredientComponent<T, M> ingredientComponent,
994+
IRecipeDefinition recipe, boolean simulate,
995+
IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemory,
996+
IIngredientCollectionMutable<T, M> extractionMemoryReusable,
997+
boolean collectMissingIngredients, long recipeOutputQuantity,
998+
boolean skipReusableIngredients) {
988999
IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
9891000

9901001
// Quickly return if the storage is empty
@@ -1019,6 +1030,15 @@ public static <T, M> List<T> getIngredientRecipeInputs(IIngredientComponentStora
10191030
collectMissingIngredients ? Lists.newArrayList() : null;
10201031
for (int inputIndex = 0; inputIndex < inputAlternativePrototypes.size(); inputIndex++) {
10211032
IPrototypedIngredientAlternatives<T, M> inputPrototypes = inputAlternativePrototypes.get(inputIndex);
1033+
1034+
// If reusable ingredients should be skipped, treat them as empty (not needed yet).
1035+
// This is used when a job has dependencies, so that reusable ingredients remain available
1036+
// for other jobs to use, and will be extracted lazily in the update loop.
1037+
if (skipReusableIngredients && recipe.isInputReusable(ingredientComponent, inputIndex)) {
1038+
inputInstances.add(matcher.getEmptyInstance());
1039+
continue;
1040+
}
1041+
10221042
T firstInputInstance = null;
10231043
boolean setFirstInputInstance = false;
10241044
T inputInstance = null;
@@ -1313,6 +1333,15 @@ public static IMixedIngredients getRecipeInputsFromCraftingJobBuffer(CraftingJob
13131333
Map<IngredientComponent<?, ?>, IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemories,
13141334
Map<IngredientComponent<?, ?>, IIngredientCollectionMutable<?, ?>> extractionMemoriesReusable,
13151335
boolean collectMissingIngredients, long recipeOutputQuantity) {
1336+
return getRecipeInputs(storageGetter, recipe, simulate, simulatedExtractionMemories, extractionMemoriesReusable,
1337+
collectMissingIngredients, recipeOutputQuantity, false);
1338+
}
1339+
1340+
public static Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>>
1341+
getRecipeInputs(Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter, IRecipeDefinition recipe, boolean simulate,
1342+
Map<IngredientComponent<?, ?>, IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemories,
1343+
Map<IngredientComponent<?, ?>, IIngredientCollectionMutable<?, ?>> extractionMemoriesReusable,
1344+
boolean collectMissingIngredients, long recipeOutputQuantity, boolean skipReusableIngredients) {
13161345
// Determine available and missing ingredients
13171346
Map<IngredientComponent<?, ?>, List<?>> ingredientsAvailable = Maps.newIdentityHashMap();
13181347
Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> ingredientsMissing = Maps.newIdentityHashMap();
@@ -1330,7 +1359,7 @@ public static IMixedIngredients getRecipeInputsFromCraftingJobBuffer(CraftingJob
13301359
}
13311360
Pair<List<?>, MissingIngredients<?, ?>> subIngredients = getIngredientRecipeInputs(storage,
13321361
(IngredientComponent) ingredientComponent, recipe, simulate, simulatedExtractionMemory, extractionMemoryReusable,
1333-
collectMissingIngredients, recipeOutputQuantity);
1362+
collectMissingIngredients, recipeOutputQuantity, skipReusableIngredients);
13341363
List<?> subIngredientAvailable = subIngredients.getLeft();
13351364
MissingIngredients<?, ?> subIngredientsMissing = subIngredients.getRight();
13361365
if (subIngredientAvailable == null && !collectMissingIngredients) {

src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,10 @@ public void fillCraftingJobBufferFromStorage(CraftingJob craftingJob, Function<I
290290
throw new IllegalStateException("Re-filling a non-empty crafting job buffer is illegal");
291291
}
292292
// Determine the ingredients to extract. We can not reuse the ingredientsStorage value from the crafting job, as this may have been modified due to job splitting.
293-
Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>> inputResult = CraftingHelpers.getRecipeInputs(storageGetter, craftingJob.getRecipe(), false, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), true, craftingJob.getAmount());
293+
// If this job has dependencies, skip reusable ingredients so that they remain available for other jobs.
294+
// They will be lazily extracted in the update loop once the dependencies have finished.
295+
boolean skipReusableIngredients = !craftingJob.getDependencyCraftingJobs().isEmpty();
296+
Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>> inputResult = CraftingHelpers.getRecipeInputs(storageGetter, craftingJob.getRecipe(), false, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), true, craftingJob.getAmount(), skipReusableIngredients);
294297
IMixedIngredients buffer = new MixedIngredients(inputResult.getLeft());
295298
craftingJob.setIngredientsStorageBuffer(CraftingHelpers.compressMixedIngredients(buffer));
296299
craftingJob.setLastMissingIngredients(inputResult.getRight());
@@ -503,35 +506,33 @@ public void update(INetwork network, int channel, PartPos targetPos) {
503506
// trigger a crafting job for them if no job is running yet.
504507
// This special case is needed because reusable ingredients are usually durability-based,
505508
// and may be consumed _during_ a bulk crafting job.
506-
if (pendingCraftingJob.getLastMissingIngredients().isEmpty()) {
507-
for (IngredientComponent<?, ?> component : inputs.getRight().keySet()) {
508-
MissingIngredients<?, ?> missingIngredients = inputs.getRight().get(component);
509-
for (MissingIngredients.Element<?, ?> element : missingIngredients.getElements()) {
510-
if (element.isInputReusable()) {
511-
IIngredientComponentStorage storage = CraftingHelpers.getNetworkStorage(network, channel, component, true);
512-
for (MissingIngredients.PrototypedWithRequested alternative : element.getAlternatives()) {
513-
// First check if we can extract it from storage.
514-
Object extractedFromStorage = storage.extract(alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition(), false);
515-
if (!((IIngredientMatcher) component.getMatcher()).isEmpty(extractedFromStorage)) {
516-
pendingCraftingJob.addToIngredientsStorageBuffer((IngredientComponent<? super Object, ? extends Object>) component, extractedFromStorage);
517-
break;
518-
}
519-
520-
// Try to start crafting jobs for each alternative until one of them succeeds.
521-
if (CraftingHelpers.isCrafting(craftingNetwork, channel,
522-
alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition())) {
523-
// Break loop if we have found an existing job for our dependency
524-
// This may occur if a crafting job was triggered in a parallelized job
525-
break;
526-
}
527-
CraftingJob craftingJob = CraftingHelpers.calculateAndScheduleCraftingJob(network, channel,
528-
alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition(), true, true,
529-
CraftingHelpers.getGlobalCraftingJobIdentifier(), null);
530-
if (craftingJob != null) {
531-
pendingCraftingJob.addDependency(craftingJob);
532-
// Break loop once we have found a valid job
533-
break;
534-
}
509+
for (IngredientComponent<?, ?> component : inputs.getRight().keySet()) {
510+
MissingIngredients<?, ?> missingIngredients = inputs.getRight().get(component);
511+
for (MissingIngredients.Element<?, ?> element : missingIngredients.getElements()) {
512+
if (element.isInputReusable()) {
513+
IIngredientComponentStorage storage = CraftingHelpers.getNetworkStorage(network, channel, component, true);
514+
for (MissingIngredients.PrototypedWithRequested alternative : element.getAlternatives()) {
515+
// First check if we can extract it from storage.
516+
Object extractedFromStorage = storage.extract(alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition(), false);
517+
if (!((IIngredientMatcher) component.getMatcher()).isEmpty(extractedFromStorage)) {
518+
pendingCraftingJob.addToIngredientsStorageBuffer((IngredientComponent<? super Object, ? extends Object>) component, extractedFromStorage);
519+
break;
520+
}
521+
522+
// Try to start crafting jobs for each alternative until one of them succeeds.
523+
if (CraftingHelpers.isCrafting(craftingNetwork, channel,
524+
alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition())) {
525+
// Break loop if we have found an existing job for our dependency
526+
// This may occur if a crafting job was triggered in a parallelized job
527+
break;
528+
}
529+
CraftingJob craftingJob = CraftingHelpers.calculateAndScheduleCraftingJob(network, channel,
530+
alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition(), true, true,
531+
CraftingHelpers.getGlobalCraftingJobIdentifier(), null);
532+
if (craftingJob != null) {
533+
pendingCraftingJob.addDependency(craftingJob);
534+
// Break loop once we have found a valid job
535+
break;
535536
}
536537
}
537538
}

0 commit comments

Comments
 (0)