Skip to content

Commit 889b774

Browse files
committed
Fix over-estimation of storage contents when calculating jobs
This could occur when an ingredient could be partially extracted from storage and partially had to be autocrafted via a sub-job. The simulated extraction memory was being set to an amount that was too low, which caused the algorithm to incorrectly think there was more to extract. The flaw in reasoning before this commit was that the simulation extraction memory would only increment. But this is false, since it will decrement when sub-jobs are calculated, and this decrement was not taken into account. Closes #125
1 parent 0ec495f commit 889b774

3 files changed

Lines changed: 205 additions & 1 deletion

File tree

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,20 @@ protected static PartialCraftingJobCalculation calculateCraftingJobs(
464464
throw e;
465465
}
466466
}
467+
// Add remaining surplus as negatives to simulated extraction
468+
for (IngredientComponent<?, ?> surplusComponent : dependenciesOutputSurplus.keySet()) {
469+
IngredientCollectionPrototypeMap<?, ?> surplusInstances = dependenciesOutputSurplus.get(surplusComponent);
470+
if (surplusInstances != null) {
471+
for (Object instance : surplusInstances) {
472+
IngredientCollectionPrototypeMap<?, ?> simulatedExtractionMemoryInstances = simulatedExtractionMemory.get(surplusComponent);
473+
if (simulatedExtractionMemoryInstances == null) {
474+
simulatedExtractionMemoryInstances = new IngredientCollectionPrototypeMap<>(surplusComponent, true);
475+
simulatedExtractionMemory.put(surplusComponent, simulatedExtractionMemoryInstances);
476+
}
477+
((IngredientCollectionPrototypeMap) simulatedExtractionMemoryInstances).remove(instance);
478+
}
479+
}
480+
}
467481

468482
// If at least one of our dependencies does not have a valid recipe or is not available,
469483
// go check the next recipe.
@@ -1087,7 +1101,9 @@ public static <T, M> List<T> getIngredientRecipeInputs(IIngredientComponentStora
10871101
missingAlternatives.add(new MissingIngredients.PrototypedWithRequested<>(inputPrototype, quantityMissingRelative));
10881102
inputInstance = matcher.withQuantity(inputPrototype.getPrototype(), prototypeQuantity - quantityMissingRelative);
10891103
simulatedExtractionMemoryAlternative.setQuantity(inputPrototype.getPrototype(), quantityMissingTotal);
1090-
simulatedExtractionMemoryBuffer.add(matcher.withQuantity(inputPrototype.getPrototype(), quantityMissingRelative));
1104+
// Original prototype quantity because we what can be extracted from storage and what could NOT be extracted from storage should be added to the simulation extraction memory.
1105+
// The part that could NOT be extracted will be removed later when crafting jobs are calculated for the missing elements. (not doing so would lead to over-estimation of what is in storage)
1106+
simulatedExtractionMemoryBuffer.add(matcher.withQuantity(inputPrototype.getPrototype(), prototypeQuantity));
10911107
}
10921108
} else {
10931109
// All of our quantity can be provided via our surplus in simulatedExtractionMemory

src/test/java/org/cyclops/integratedcrafting/core/RecipeHelpers.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
77
import org.cyclops.commoncapabilities.api.capability.recipehandler.PrototypedIngredientAlternativesList;
88
import org.cyclops.commoncapabilities.api.capability.recipehandler.RecipeDefinition;
9+
import org.cyclops.commoncapabilities.api.ingredient.IMixedIngredients;
910
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
1011
import org.cyclops.commoncapabilities.api.ingredient.MixedIngredients;
1112
import org.cyclops.commoncapabilities.api.ingredient.PrototypedIngredient;
13+
import org.cyclops.integratedcrafting.api.crafting.CraftingJob;
14+
import org.cyclops.integratedcrafting.api.crafting.CraftingJobDependencyGraph;
1215
import org.cyclops.integratedcrafting.ingredient.ComplexStack;
1316
import org.cyclops.integratedcrafting.ingredient.IngredientComponentStubs;
1417

@@ -32,4 +35,18 @@ public static IRecipeDefinition newSimpleRecipe(List<ComplexStack> inputs, List<
3235
return new RecipeDefinition(mapIn, new MixedIngredients(mapOut));
3336
}
3437

38+
public static IMixedIngredients collectIngredientStoragesDependencies(CraftingJobDependencyGraph graph, CraftingJob craftingJob) {
39+
return collectIngredientStoragesDependenciesInner(graph, craftingJob, new MixedIngredients(Maps.newIdentityHashMap()));
40+
}
41+
42+
private static IMixedIngredients collectIngredientStoragesDependenciesInner(CraftingJobDependencyGraph graph, CraftingJob craftingJob, IMixedIngredients ingredients) {
43+
ingredients = CraftingHelpers.mergeMixedIngredients(ingredients, craftingJob.getIngredientsStorage());
44+
45+
for (CraftingJob dependency : graph.getDependencies(craftingJob)) {
46+
ingredients = collectIngredientStoragesDependenciesInner(graph, dependency, ingredients);
47+
}
48+
49+
return ingredients;
50+
}
51+
3552
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package org.cyclops.integratedcrafting.core;
2+
3+
import com.google.common.collect.Lists;
4+
import com.google.common.collect.Maps;
5+
import com.google.common.collect.Sets;
6+
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
7+
import org.cyclops.commoncapabilities.api.ingredient.IMixedIngredients;
8+
import org.cyclops.commoncapabilities.api.ingredient.IPrototypedIngredient;
9+
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
10+
import org.cyclops.commoncapabilities.api.ingredient.storage.IIngredientComponentStorage;
11+
import org.cyclops.cyclopscore.datastructure.Wrapper;
12+
import org.cyclops.cyclopscore.ingredient.collection.IIngredientCollectionMutable;
13+
import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionPrototypeMap;
14+
import org.cyclops.cyclopscore.ingredient.storage.IngredientComponentStorageCollectionWrapper;
15+
import org.cyclops.integratedcrafting.api.crafting.CraftingJob;
16+
import org.cyclops.integratedcrafting.api.crafting.CraftingJobDependencyGraph;
17+
import org.cyclops.integratedcrafting.api.crafting.RecursiveCraftingRecipeException;
18+
import org.cyclops.integratedcrafting.api.crafting.UnknownCraftingRecipeException;
19+
import org.cyclops.integratedcrafting.ingredient.ComplexStack;
20+
import org.cyclops.integratedcrafting.ingredient.IngredientComponentStubs;
21+
import org.junit.Before;
22+
import org.junit.Test;
23+
24+
import java.util.Map;
25+
import java.util.Set;
26+
import java.util.function.Function;
27+
28+
import static org.hamcrest.CoreMatchers.equalTo;
29+
import static org.junit.Assert.assertThat;
30+
31+
/**
32+
* This corresponds to the crafting recipe of the bronze drill head.
33+
* https://github.com/CyclopsMC/IntegratedCrafting/issues/125
34+
* @author rubensworks
35+
*/
36+
public class TestCaseJobCalculationBronzeDrillHead {
37+
38+
private static final ComplexStack C_INGOT = new ComplexStack(ComplexStack.Group.A, 0, 1, null);
39+
private static final ComplexStack C_BOLT = new ComplexStack(ComplexStack.Group.A, 1, 1, null);
40+
private static final ComplexStack C_PLATE = new ComplexStack(ComplexStack.Group.A, 2, 1, null);
41+
private static final ComplexStack C_ROD = new ComplexStack(ComplexStack.Group.A, 3, 1, null);
42+
private static final ComplexStack C_DUST = new ComplexStack(ComplexStack.Group.A, 4, 1, null);
43+
private static final ComplexStack C_GEAR = new ComplexStack(ComplexStack.Group.A, 5, 1, null);
44+
private static final ComplexStack C_CURVED_PLATE = new ComplexStack(ComplexStack.Group.A, 6, 1, null);
45+
private static final ComplexStack C_RING = new ComplexStack(ComplexStack.Group.A, 7, 1, null);
46+
private static final ComplexStack C_DRILL_HEAD = new ComplexStack(ComplexStack.Group.A, 8, 1, null);
47+
private static final ComplexStack C_COPPER_DUST = new ComplexStack(ComplexStack.Group.A, 9, 1, null);
48+
private static final ComplexStack C_TIN_DUST = new ComplexStack(ComplexStack.Group.A, 10, 1, null);
49+
50+
private RecipeIndexDefault recipeIndex;
51+
private IRecipeDefinition recipeRing;
52+
private IRecipeDefinition recipeGear;
53+
private IRecipeDefinition recipeDrillHead;
54+
private IRecipeDefinition recipeDust;
55+
private IRecipeDefinition recipePlate;
56+
private IRecipeDefinition recipeCurvedPlate;
57+
private IRecipeDefinition recipeIngot;
58+
private IRecipeDefinition recipeRod;
59+
private IRecipeDefinition recipeBolt;
60+
61+
private Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter;
62+
private Map<IngredientComponent<?, ?>, IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemory;
63+
private Map<IngredientComponent<?, ?>, IIngredientCollectionMutable<?, ?>> simulatedExtractionMemoryReusable;
64+
private CraftingHelpers.IIdentifierGenerator identifierGenerator;
65+
private CraftingJobDependencyGraph craftingJobDependencyGraph;
66+
private Set<IPrototypedIngredient> parentDependencies;
67+
68+
@Before
69+
public void beforeEach() {
70+
recipeIndex = new RecipeIndexDefault();
71+
72+
recipeRing = RecipeHelpers.newSimpleRecipe(
73+
Lists.newArrayList(C_ROD),
74+
Lists.newArrayList(C_RING)
75+
);
76+
recipeIndex.addRecipe(recipeRing);
77+
78+
recipeGear = RecipeHelpers.newSimpleRecipe(
79+
Lists.newArrayList(C_PLATE, C_BOLT, C_PLATE, C_BOLT, C_RING, C_BOLT, C_PLATE, C_BOLT, C_PLATE),
80+
Lists.newArrayList(C_GEAR)
81+
);
82+
recipeIndex.addRecipe(recipeGear);
83+
84+
recipeDrillHead = RecipeHelpers.newSimpleRecipe(
85+
Lists.newArrayList(C_BOLT, C_CURVED_PLATE, C_PLATE, C_GEAR, C_ROD, C_CURVED_PLATE, C_BOLT, C_GEAR, C_BOLT),
86+
Lists.newArrayList(C_DRILL_HEAD)
87+
);
88+
recipeIndex.addRecipe(recipeDrillHead);
89+
90+
recipeDust = RecipeHelpers.newSimpleRecipe(
91+
Lists.newArrayList(C_TIN_DUST, C_COPPER_DUST.withAmount(3)),
92+
Lists.newArrayList(C_DUST.withAmount(4))
93+
);
94+
recipeIndex.addRecipe(recipeDust);
95+
96+
recipePlate = RecipeHelpers.newSimpleRecipe(
97+
Lists.newArrayList(C_INGOT),
98+
Lists.newArrayList(C_PLATE)
99+
);
100+
recipeIndex.addRecipe(recipePlate);
101+
102+
recipeCurvedPlate = RecipeHelpers.newSimpleRecipe(
103+
Lists.newArrayList(C_PLATE),
104+
Lists.newArrayList(C_CURVED_PLATE)
105+
);
106+
recipeIndex.addRecipe(recipeCurvedPlate);
107+
108+
recipeIngot = RecipeHelpers.newSimpleRecipe(
109+
Lists.newArrayList(C_DUST),
110+
Lists.newArrayList(C_INGOT)
111+
);
112+
recipeIndex.addRecipe(recipeIngot);
113+
114+
recipeRod = RecipeHelpers.newSimpleRecipe(
115+
Lists.newArrayList(C_INGOT),
116+
Lists.newArrayList(C_ROD.withAmount(2))
117+
);
118+
recipeIndex.addRecipe(recipeRod);
119+
120+
recipeBolt = RecipeHelpers.newSimpleRecipe(
121+
Lists.newArrayList(C_ROD),
122+
Lists.newArrayList(C_BOLT.withAmount(2))
123+
);
124+
recipeIndex.addRecipe(recipeBolt);
125+
126+
simulatedExtractionMemory = Maps.newIdentityHashMap();
127+
simulatedExtractionMemoryReusable = Maps.newIdentityHashMap();
128+
Wrapper<Integer> id = new Wrapper<>(0);
129+
identifierGenerator = () -> {
130+
int last = id.get();
131+
id.set(last + 1);
132+
return last;
133+
};
134+
craftingJobDependencyGraph = new CraftingJobDependencyGraph();
135+
parentDependencies = Sets.newHashSet();
136+
}
137+
138+
@Test
139+
public void testCraft1Valid() throws UnknownCraftingRecipeException, RecursiveCraftingRecipeException {
140+
// This test makes sure that multi-output recipes don't assume the storage contains more items in actually does.
141+
IngredientComponentStorageCollectionWrapper<ComplexStack, Integer> storage = new IngredientComponentStorageCollectionWrapper<>(new IngredientCollectionPrototypeMap<>(IngredientComponentStubs.COMPLEX));
142+
storage.insert(C_DUST.withAmount(8), false);
143+
storage.insert(C_COPPER_DUST.withAmount(64), false);
144+
storage.insert(C_TIN_DUST.withAmount(64), false);
145+
storageGetter = (c) -> storage;
146+
147+
CraftingJob jobMain = CraftingHelpers.calculateCraftingJobs(recipeIndex, 0, storageGetter,
148+
IngredientComponentStubs.COMPLEX, C_DRILL_HEAD, ComplexStack.Match.EXACT, true,
149+
simulatedExtractionMemory, simulatedExtractionMemoryReusable, identifierGenerator, craftingJobDependencyGraph, parentDependencies, true);
150+
151+
assertThat(jobMain.getId(), equalTo(21));
152+
assertThat(jobMain.getChannel(), equalTo(0));
153+
assertThat(jobMain.getAmount(), equalTo(1));
154+
assertThat(jobMain.getRecipe(), equalTo(recipeDrillHead));
155+
assertThat(jobMain.getIngredientsStorage().getComponents().size(), equalTo(0));
156+
157+
assertThat(craftingJobDependencyGraph.getCraftingJobs().size(), equalTo(22));
158+
assertThat(craftingJobDependencyGraph.getCraftingJobs().contains(jobMain), equalTo(true));
159+
assertThat(craftingJobDependencyGraph.getDependencies(jobMain).size(), equalTo(5));
160+
assertThat(craftingJobDependencyGraph.getDependents(jobMain).size(), equalTo(0));
161+
162+
IMixedIngredients fullStorage = RecipeHelpers.collectIngredientStoragesDependencies(craftingJobDependencyGraph, jobMain);
163+
assertThat(fullStorage.getInstances(IngredientComponentStubs.COMPLEX), equalTo(Lists.newArrayList(
164+
C_TIN_DUST.withAmount(2),
165+
C_COPPER_DUST.withAmount(6),
166+
C_DUST.withAmount(10) // This is 10 instead of 8, because we have a surplus of 2 that is used by other sub-jobs.
167+
)));
168+
}
169+
170+
171+
}

0 commit comments

Comments
 (0)