Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,17 @@ public static <T, M> List<T> getIngredientRecipeInputs(IIngredientComponentStora
IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemory,
IIngredientCollectionMutable<T, M> extractionMemoryReusable,
boolean collectMissingIngredients, long recipeOutputQuantity) {
return getIngredientRecipeInputs(storage, ingredientComponent, recipe, simulate, simulatedExtractionMemory,
extractionMemoryReusable, collectMissingIngredients, recipeOutputQuantity, false);
}

public static <T, M> Pair<List<T>, MissingIngredients<T, M>>
getIngredientRecipeInputs(IIngredientComponentStorage<T, M> storage, IngredientComponent<T, M> ingredientComponent,
IRecipeDefinition recipe, boolean simulate,
IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemory,
IIngredientCollectionMutable<T, M> extractionMemoryReusable,
boolean collectMissingIngredients, long recipeOutputQuantity,
boolean skipReusableIngredients) {
IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();

// Quickly return if the storage is empty
Expand Down Expand Up @@ -1016,6 +1027,15 @@ public static <T, M> List<T> getIngredientRecipeInputs(IIngredientComponentStora
collectMissingIngredients ? Lists.newArrayList() : null;
for (int inputIndex = 0; inputIndex < inputAlternativePrototypes.size(); inputIndex++) {
IPrototypedIngredientAlternatives<T, M> inputPrototypes = inputAlternativePrototypes.get(inputIndex);

// If reusable ingredients should be skipped, treat them as empty (not needed yet).
// This is used when a job has dependencies, so that reusable ingredients remain available
// for other jobs to use, and will be extracted lazily in the update loop.
if (skipReusableIngredients && recipe.isInputReusable(ingredientComponent, inputIndex)) {
inputInstances.add(matcher.getEmptyInstance());
continue;
}

T firstInputInstance = null;
boolean setFirstInputInstance = false;
T inputInstance = null;
Expand Down Expand Up @@ -1310,6 +1330,15 @@ public static IMixedIngredients getRecipeInputsFromCraftingJobBuffer(CraftingJob
Map<IngredientComponent<?, ?>, IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemories,
Map<IngredientComponent<?, ?>, IIngredientCollectionMutable<?, ?>> extractionMemoriesReusable,
boolean collectMissingIngredients, long recipeOutputQuantity) {
return getRecipeInputs(storageGetter, recipe, simulate, simulatedExtractionMemories, extractionMemoriesReusable,
collectMissingIngredients, recipeOutputQuantity, false);
}

public static Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>>
getRecipeInputs(Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter, IRecipeDefinition recipe, boolean simulate,
Map<IngredientComponent<?, ?>, IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemories,
Map<IngredientComponent<?, ?>, IIngredientCollectionMutable<?, ?>> extractionMemoriesReusable,
boolean collectMissingIngredients, long recipeOutputQuantity, boolean skipReusableIngredients) {
// Determine available and missing ingredients
Map<IngredientComponent<?, ?>, List<?>> ingredientsAvailable = Maps.newIdentityHashMap();
Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> ingredientsMissing = Maps.newIdentityHashMap();
Expand All @@ -1327,7 +1356,7 @@ public static IMixedIngredients getRecipeInputsFromCraftingJobBuffer(CraftingJob
}
Pair<List<?>, MissingIngredients<?, ?>> subIngredients = getIngredientRecipeInputs(storage,
(IngredientComponent) ingredientComponent, recipe, simulate, simulatedExtractionMemory, extractionMemoryReusable,
collectMissingIngredients, recipeOutputQuantity);
collectMissingIngredients, recipeOutputQuantity, skipReusableIngredients);
List<?> subIngredientAvailable = subIngredients.getLeft();
MissingIngredients<?, ?> subIngredientsMissing = subIngredients.getRight();
if (subIngredientAvailable == null && !collectMissingIngredients) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,11 +291,10 @@ public void fillCraftingJobBufferFromStorage(CraftingJob craftingJob, Function<I
throw new IllegalStateException("Re-filling a non-empty crafting job buffer is illegal");
}
// 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.
if (!craftingJob.getDependencyCraftingJobs().isEmpty()) {
System.out.println("HAS DEPENDENCIES!!!"); // TODO: remove this statement when fixed
// TODO: if so, make sure we don't add reusable ingreds to our buffer yet, as others may use it. => add a flag to CraftingHelpers.getRecipeInputs?
}
Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>> inputResult = CraftingHelpers.getRecipeInputs(storageGetter, craftingJob.getRecipe(), false, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), true, craftingJob.getAmount());
// If this job has dependencies, skip reusable ingredients so that they remain available for other jobs.
// They will be lazily extracted in the update loop once the dependencies have finished.
boolean skipReusableIngredients = !craftingJob.getDependencyCraftingJobs().isEmpty();
Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>> inputResult = CraftingHelpers.getRecipeInputs(storageGetter, craftingJob.getRecipe(), false, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), true, craftingJob.getAmount(), skipReusableIngredients);
IMixedIngredients buffer = new MixedIngredients(inputResult.getLeft());
craftingJob.setIngredientsStorageBuffer(CraftingHelpers.compressMixedIngredients(buffer));
craftingJob.setLastMissingIngredients(inputResult.getRight());
Expand Down Expand Up @@ -508,35 +507,33 @@ public void update(INetwork network, int channel, PartPos targetPos) {
// trigger a crafting job for them if no job is running yet.
// This special case is needed because reusable ingredients are usually durability-based,
// and may be consumed _during_ a bulk crafting job.
if (pendingCraftingJob.getLastMissingIngredients().isEmpty() || true) { // TODO: after fixing the issue, remove this if statement completely
for (IngredientComponent<?, ?> component : inputs.getRight().keySet()) {
MissingIngredients<?, ?> missingIngredients = inputs.getRight().get(component);
for (MissingIngredients.Element<?, ?> element : missingIngredients.getElements()) {
if (element.isInputReusable()) {
IIngredientComponentStorage storage = CraftingHelpers.getNetworkStorage(network, channel, component, true);
for (MissingIngredients.PrototypedWithRequested alternative : element.getAlternatives()) {
// First check if we can extract it from storage.
Object extractedFromStorage = storage.extract(alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition(), false);
if (!((IIngredientMatcher) component.getMatcher()).isEmpty(extractedFromStorage)) {
pendingCraftingJob.addToIngredientsStorageBuffer((IngredientComponent<? super Object, ? extends Object>) component, extractedFromStorage);
break;
}

// Try to start crafting jobs for each alternative until one of them succeeds.
if (CraftingHelpers.isCrafting(craftingNetwork, channel,
alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition())) {
// Break loop if we have found an existing job for our dependency
// This may occur if a crafting job was triggered in a parallelized job
break;
}
CraftingJob craftingJob = CraftingHelpers.calculateAndScheduleCraftingJob(network, channel,
alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition(), true, true,
CraftingHelpers.getGlobalCraftingJobIdentifier(), null);
if (craftingJob != null) {
pendingCraftingJob.addDependency(craftingJob);
// Break loop once we have found a valid job
break;
}
for (IngredientComponent<?, ?> component : inputs.getRight().keySet()) {
MissingIngredients<?, ?> missingIngredients = inputs.getRight().get(component);
for (MissingIngredients.Element<?, ?> element : missingIngredients.getElements()) {
if (element.isInputReusable()) {
IIngredientComponentStorage storage = CraftingHelpers.getNetworkStorage(network, channel, component, true);
for (MissingIngredients.PrototypedWithRequested alternative : element.getAlternatives()) {
// First check if we can extract it from storage.
Object extractedFromStorage = storage.extract(alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition(), false);
if (!((IIngredientMatcher) component.getMatcher()).isEmpty(extractedFromStorage)) {
pendingCraftingJob.addToIngredientsStorageBuffer((IngredientComponent<? super Object, ? extends Object>) component, extractedFromStorage);
break;
}

// Try to start crafting jobs for each alternative until one of them succeeds.
if (CraftingHelpers.isCrafting(craftingNetwork, channel,
alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition())) {
// Break loop if we have found an existing job for our dependency
// This may occur if a crafting job was triggered in a parallelized job
break;
}
CraftingJob craftingJob = CraftingHelpers.calculateAndScheduleCraftingJob(network, channel,
alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition(), true, true,
CraftingHelpers.getGlobalCraftingJobIdentifier(), null);
if (craftingJob != null) {
pendingCraftingJob.addDependency(craftingJob);
// Break loop once we have found a valid job
break;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,44 @@ public void testItemsCraftDeadBushTagReusableAsDependencyCraft(GameTestHelper he
});
}

// Reproduces https://github.com/CyclopsMC/IntegratedCrafting/issues/182
// When a job with a reusable ingredient (shears) has a dependency (craft shears from iron ingots),
// the reusable ingredient should be extracted lazily after the dependency finishes.
@GameTest(template = TEMPLATE_EMPTY, timeoutTicks = TIMEOUT)
public void testItemsCraftDeadBushTagReusableNeedsCrafting(GameTestHelper helper) {
GameTestHelpersIntegratedCrafting.INetworkPositions<PartTypeInterfaceCrafting.State> positions = createBasicNetwork(helper, POS, Blocks.CRAFTING_TABLE, Blocks.CRAFTING_TABLE);

// Insert items in interface chest: no shears, but iron ingots to craft them
ChestBlockEntity chestIn = helper.getBlockEntity(POS.east());
chestIn.setItem(0, new ItemStack(Items.IRON_INGOT, 2));
chestIn.setItem(1, new ItemStack(Items.SPRUCE_SAPLING, 10));

// Add dead bush recipe with reusable shears to crafting interface 0
createDeadBushTagReusableRecipe(helper, positions);

// Add shears recipe to crafting interface 1
positions.interfaceRecipeAdders().get(1).accept(Triple.of(0, RecipeType.CRAFTING, ResourceLocation.fromNamespaceAndPath("minecraft", "shears")));

// Speed up crafting interfaces, to craft once every tick
GameTestHelpersIntegratedCrafting.setCraftingInterfaceUpdateInterval(positions.interfaces().get(0), 1);
GameTestHelpersIntegratedCrafting.setCraftingInterfaceUpdateInterval(positions.interfaces().get(1), 1);

// Enable crafting aspect in crafting writer
enableRecipeInWriter(helper, positions.writer(), new ItemStack(Items.DEAD_BUSH, 10));

helper.succeedWhen(() -> {
// Check crafting interface state
helper.assertTrue(positions.interfaceStates().get(0).isRecipeSlotValid(0), "Recipe in crafting interface is not valid");
helper.assertTrue(positions.interfaceStates().get(1).isRecipeSlotValid(0), "Recipe in crafting interface is not valid");

// Check if items have been crafted: 10 dead bushes and 1 shears (returned from reusable use)
helper.assertValueEqual(chestIn.getItem(0).getItem(), Items.DEAD_BUSH, "Slot 0 item is incorrect");
helper.assertValueEqual(chestIn.getItem(0).getCount(), 10, "Slot 0 amount is incorrect");
helper.assertValueEqual(chestIn.getItem(1).getItem(), Items.SHEARS, "Slot 1 item is incorrect");
helper.assertValueEqual(chestIn.getItem(1).getCount(), 1, "Slot 1 amount is incorrect");
});
}

@GameTest(template = TEMPLATE_EMPTY, timeoutTicks = TIMEOUT)
public void testItemsCraftCraftingTablesWithExistingPlank(GameTestHelper helper) {
GameTestHelpersIntegratedCrafting.INetworkPositions<PartTypeInterfaceCrafting.State> positions = createBasicNetwork(helper, POS, Blocks.CRAFTING_TABLE);
Expand Down
Loading