Skip to content

Commit b8a82b5

Browse files
committed
Merge remote-tracking branch 'origin/master-1.21-lts' into master-1.21
2 parents af2537a + 0e91c92 commit b8a82b5

22 files changed

Lines changed: 786 additions & 180 deletions

CHANGELOG-1.20.1.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,24 @@
11
# Changelog for Minecraft 1.20.1
22
All notable changes to this project will be documented in this file.
33

4+
<a name="1.20.1-1.4.0"></a>
5+
## [1.20.1-1.4.0](/compare/1.20.1-1.3.3...1.20.1-1.4.0) - 2025-12-31 14:42:03
6+
7+
8+
### Changed
9+
* Add dedicated storage per crafting job
10+
11+
When a crafting job is started, ingredients are immediately moved from
12+
general storage to the new storage buffers per crafting job. This avoids
13+
issues where ingredients can be consumed elsewhere (e.g. exporters or
14+
other crafting jobs) before it is used by the crafting job.
15+
16+
This also improves overall performance, as it is not necessary to run synchronous observers anymore.
17+
18+
Closes #112
19+
420
<a name="1.20.1-1.3.3"></a>
5-
## [1.20.1-1.3.3](/compare/1.20.1-1.3.2...1.20.1-1.3.3) - 2025-11-21 19:51:37
21+
## [1.20.1-1.3.3](/compare/1.20.1-1.3.2...1.20.1-1.3.3) - 2025-11-21 19:51:37 +0100
622

723

824
### Changed

CHANGELOG-1.21.1.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,24 @@
11
# Changelog for Minecraft 1.21.1
22
All notable changes to this project will be documented in this file.
33

4+
<a name="1.21.1-1.4.0"></a>
5+
## [1.21.1-1.4.0](https://github.com/CyclopsMC/IntegratedCrafting/compare/1.21.1-1.3.4...1.21.1-1.4.0) - 2025-12-31 14:44:30
6+
7+
8+
### Changed
9+
* Add dedicated storage per crafting job
10+
11+
When a crafting job is started, ingredients are immediately moved from
12+
general storage to the new storage buffers per crafting job. This avoids
13+
issues where ingredients can be consumed elsewhere (e.g. exporters or
14+
other crafting jobs) before it is used by the crafting job.
15+
16+
This also improves overall performance, as it is not necessary to run synchronous observers anymore.
17+
18+
Closes #112
19+
420
<a name="1.21.1-1.3.4"></a>
5-
## [1.21.1-1.3.4](https://github.com/CyclopsMC/IntegratedCrafting/compare/1.21.1-1.3.3...1.21.1-1.3.4) - 2025-11-21 19:52:19
21+
## [1.21.1-1.3.4](https://github.com/CyclopsMC/IntegratedCrafting/compare/1.21.1-1.3.3...1.21.1-1.3.4) - 2025-11-21 19:52:19 +0100
622

723

824
### Added

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ org.gradle.caching=true
3232

3333
# Dependencies
3434
cyclopscore_version=1.25.5-871
35-
integrateddynamics_version=1.24.1-1419
35+
integrateddynamics_version=1.24.1-1455
3636
commoncapabilities_version=2.9.7-303
3737
integratedtunnels_version=1.8.30-534
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
As always, don't forget to backup your world before updating!
2+
Requires CyclopsCore version 1.18.4 or higher.
3+
4+
Changes:
5+
* Add dedicated storage per crafting job
6+
7+
When a crafting job is started, ingredients are immediately moved from
8+
general storage to the new storage buffers per crafting job. This avoids
9+
issues where ingredients can be consumed elsewhere (e.g. exporters or
10+
other crafting jobs) before it is used by the crafting job.
11+
12+
This also improves overall performance, as it is not necessary to run synchronous observers anymore.
13+
14+
Closes #112
15+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
As always, don't forget to backup your world before updating!
2+
Requires CyclopsCore version 1.26.2 or higher.
3+
4+
Changes:
5+
* Add dedicated storage per crafting job
6+
7+
When a crafting job is started, ingredients are immediately moved from
8+
general storage to the new storage buffers per crafting job. This avoids
9+
issues where ingredients can be consumed elsewhere (e.g. exporters or
10+
other crafting jobs) before it is used by the crafting job.
11+
12+
This also improves overall performance, as it is not necessary to run synchronous observers anymore.
13+
14+
Closes #112
15+

src/main/java/org/cyclops/integratedcrafting/api/crafting/CraftingJob.java

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,20 @@
55
import it.unimi.dsi.fastutil.ints.IntList;
66
import net.minecraft.world.level.storage.ValueInput;
77
import net.minecraft.world.level.storage.ValueOutput;
8+
import org.apache.commons.compress.utils.Lists;
89
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
10+
import org.cyclops.commoncapabilities.api.ingredient.IIngredientMatcher;
911
import org.cyclops.commoncapabilities.api.ingredient.IMixedIngredients;
1012
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
13+
import org.cyclops.commoncapabilities.api.ingredient.MixedIngredients;
14+
import org.cyclops.cyclopscore.ingredient.collection.IngredientList;
15+
import org.cyclops.cyclopscore.ingredient.storage.IngredientComponentStorageSlottedCollectionWrapper;
1116
import org.cyclops.integratedcrafting.core.CraftingHelpers;
1217
import org.cyclops.integratedcrafting.core.MissingIngredients;
1318

1419
import javax.annotation.Nullable;
20+
import java.util.Iterator;
21+
import java.util.List;
1522
import java.util.Map;
1623
import java.util.Objects;
1724

@@ -26,7 +33,8 @@ public class CraftingJob {
2633
private final IntList dependencyCraftingJobs;
2734
private final IntList dependentCraftingJobs;
2835
private int amount;
29-
private IMixedIngredients ingredientsStorage;
36+
private IMixedIngredients ingredientsStorage; // Total to extract from storage (simulated and immutable)
37+
private IMixedIngredients ingredientsStorageBuffer; // The actual ingredients from storage, which are consumed over time.
3038
private Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> lastMissingIngredients;
3139
private long startTick;
3240
private boolean invalidInputs;
@@ -40,6 +48,7 @@ public CraftingJob(int id, int channel, IRecipeDefinition recipe, int amount, IM
4048
this.recipe = recipe;
4149
this.amount = amount;
4250
this.ingredientsStorage = ingredientsStorage;
51+
this.ingredientsStorageBuffer = new MixedIngredients(Maps.newIdentityHashMap());
4352
this.lastMissingIngredients = Maps.newIdentityHashMap();
4453
this.dependencyCraftingJobs = new IntArrayList();
4554
this.dependentCraftingJobs = new IntArrayList();
@@ -97,6 +106,85 @@ public void setIngredientsStorage(IMixedIngredients ingredientsStorage) {
97106
this.ingredientsStorage = ingredientsStorage;
98107
}
99108

109+
public IMixedIngredients getIngredientsStorageBuffer() {
110+
return ingredientsStorageBuffer;
111+
}
112+
113+
public void setIngredientsStorageBuffer(IMixedIngredients ingredientsStorageBuffer) {
114+
this.ingredientsStorageBuffer = ingredientsStorageBuffer;
115+
}
116+
117+
public <T, M> long getMissingIngredientQuantity(IngredientComponent<T, M> ingredientComponent, T instance) {
118+
long quantityMissing = 0;
119+
MissingIngredients<?, ?> missingIngredients = this.lastMissingIngredients.get(ingredientComponent);
120+
if (missingIngredients != null) {
121+
IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
122+
for (MissingIngredients.Element<?, ?> element : missingIngredients.getElements()) {
123+
for (MissingIngredients.PrototypedWithRequested<?, ?> alternative : element.getAlternatives()) {
124+
if (matcher.matches(instance, (T) alternative.getRequestedPrototype().getPrototype(), matcher.withoutCondition((M) alternative.getRequestedPrototype().getCondition(), ingredientComponent.getPrimaryQuantifier().getMatchCondition()))) {
125+
quantityMissing += alternative.getQuantityMissing();
126+
}
127+
}
128+
}
129+
}
130+
return quantityMissing;
131+
}
132+
133+
public <T, M> void addToIngredientsStorageBuffer(IngredientComponent<T, M> ingredientComponent, T instance) {
134+
// Add instance to the buffer
135+
IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
136+
IMixedIngredients buffer = this.getIngredientsStorageBuffer();
137+
if (!buffer.getComponents().contains(ingredientComponent)) {
138+
Map<IngredientComponent<?, ?>, List<?>> mixedIngredientsRaw = Maps.newIdentityHashMap();
139+
for (IngredientComponent<?, ?> component : buffer.getComponents()) {
140+
mixedIngredientsRaw.put(component, buffer.getInstances(component));
141+
}
142+
List<T> list = Lists.newArrayList();
143+
mixedIngredientsRaw.put(ingredientComponent, list);
144+
list.add(instance);
145+
buffer = new MixedIngredients(mixedIngredientsRaw);
146+
this.setIngredientsStorageBuffer(buffer);
147+
} else {
148+
List<T> instances = buffer.getInstances(ingredientComponent);
149+
if (!instances.stream().anyMatch(matcher::isEmpty)) {
150+
// Make sure we have at least one empty slot available, to guarantee insertion can succeed.
151+
instances.add(matcher.getEmptyInstance());
152+
}
153+
T remaining = new IngredientComponentStorageSlottedCollectionWrapper<>(new IngredientList<>(ingredientComponent, instances), Integer.MAX_VALUE, Integer.MAX_VALUE).insert(instance, false);
154+
if (!matcher.isEmpty(remaining)) {
155+
throw new IllegalStateException(String.format("Unable to insert %s into the crafting job buffer, remaining: ", instances, remaining));
156+
}
157+
}
158+
159+
// If the instance was a missing ingredient, remove it
160+
long instanceQuantity = matcher.getQuantity(instance);
161+
MissingIngredients<?, ?> missingIngredients = this.lastMissingIngredients.get(ingredientComponent);
162+
if (missingIngredients != null) {
163+
Iterator<? extends MissingIngredients.Element<?, ?>> it = missingIngredients.getElements().iterator();
164+
boolean removed = false;
165+
while (it.hasNext() && instanceQuantity > 0) {
166+
MissingIngredients.Element<?, ?> element = it.next();
167+
for (MissingIngredients.PrototypedWithRequested<?, ?> alternative : element.getAlternatives()) {
168+
if (matcher.matches(instance, (T) alternative.getRequestedPrototype().getPrototype(), matcher.withoutCondition((M) alternative.getRequestedPrototype().getCondition(), ingredientComponent.getPrimaryQuantifier().getMatchCondition()))) {
169+
long missingQuantityToConsume = Math.min(alternative.getQuantityMissing(), instanceQuantity);
170+
alternative.setQuantityMissing(alternative.getQuantityMissing() - missingQuantityToConsume);
171+
instanceQuantity -= missingQuantityToConsume;
172+
if (alternative.getQuantityMissing() == 0) {
173+
removed = true;
174+
it.remove();
175+
}
176+
break;
177+
}
178+
}
179+
}
180+
if (removed) {
181+
if (missingIngredients.getElements().isEmpty()) {
182+
this.lastMissingIngredients.remove(ingredientComponent);
183+
}
184+
}
185+
}
186+
}
187+
100188
/**
101189
* @return The ingredients that were missing for 1 job amount. This will mostly be an empty map.
102190
*/
@@ -149,6 +237,7 @@ public static void serialize(ValueOutput valueOutput, CraftingJob craftingJob) {
149237
valueOutput.putIntArray("dependents", craftingJob.getDependentCraftingJobs().toIntArray());
150238
valueOutput.putInt("amount", craftingJob.amount);
151239
IMixedIngredients.serialize(valueOutput.child("ingredientsStorage"), craftingJob.ingredientsStorage);
240+
IMixedIngredients.serialize(valueOutput.child("ingredientsStorageBuffer"), craftingJob.ingredientsStorageBuffer);
152241
MissingIngredients.serialize(valueOutput.child("lastMissingIngredients"), craftingJob.lastMissingIngredients);
153242
valueOutput.putLong("startTick", craftingJob.startTick);
154243
valueOutput.putBoolean("invalidInputs", craftingJob.invalidInputs);
@@ -164,6 +253,7 @@ public static CraftingJob deserialize(ValueInput valueInput) {
164253
IRecipeDefinition recipe = IRecipeDefinition.deserialize(valueInput.child("recipe").orElseThrow());
165254
int amount = valueInput.getInt("amount").orElseThrow();
166255
IMixedIngredients ingredientsStorage = IMixedIngredients.deserialize(valueInput.child("ingredientsStorage").orElseThrow());
256+
IMixedIngredients ingredientsStorageBuffer = valueInput.child("ingredientsStorageBuffer").map(IMixedIngredients::deserialize).orElseGet(() -> new MixedIngredients(Maps.newIdentityHashMap())); // TODO: rm backwards-compat in nextmajor (use orElseThrow)
167257
CraftingJob craftingJob = new CraftingJob(id, channel, recipe, amount, ingredientsStorage);
168258
for (int dependency : valueInput.getIntArray("dependencies").orElseThrow()) {
169259
craftingJob.dependencyCraftingJobs.add(dependency);
@@ -178,6 +268,7 @@ public static CraftingJob deserialize(ValueInput valueInput) {
178268
craftingJob.setInvalidInputs(valueInput.getBooleanOr("invalidInputs", false));
179269
valueInput.getString("initiatorUuid").ifPresent(craftingJob::setInitiatorUuid);
180270
craftingJob.setIgnoreDependencyCheck(valueInput.getBooleanOr("ignoreDependencyCheck", false));
271+
craftingJob.setIngredientsStorageBuffer(ingredientsStorageBuffer);
181272
return craftingJob;
182273
}
183274

@@ -203,6 +294,9 @@ public boolean equals(Object obj) {
203294
}
204295

205296
public CraftingJob clone(CraftingHelpers.IIdentifierGenerator identifierGenerator) {
297+
if (!this.getIngredientsStorageBuffer().isEmpty()) {
298+
throw new IllegalStateException("Cloning a job with an ingredient buffer is illegal");
299+
}
206300
return new CraftingJob(
207301
identifierGenerator.getNext(),
208302
getChannel(),

src/main/java/org/cyclops/integratedcrafting/api/crafting/ICraftingInterface.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
44
import org.cyclops.commoncapabilities.api.ingredient.IPrototypedIngredient;
55
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
6+
import org.cyclops.commoncapabilities.api.ingredient.storage.IIngredientComponentStorage;
67
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
78
import org.cyclops.integrateddynamics.api.part.PrioritizedPartPos;
89

9-
import java.util.Collection;
10-
import java.util.Comparator;
11-
import java.util.Iterator;
12-
import java.util.List;
13-
import java.util.Map;
10+
import java.util.*;
11+
import java.util.function.Function;
1412

1513
/**
1614
* A handler for invoking crafting recipes.
@@ -34,6 +32,13 @@ public interface ICraftingInterface {
3432
*/
3533
public void scheduleCraftingJob(CraftingJob craftingJob);
3634

35+
/**
36+
* Extract the required ingredients from storage and store them in the job.
37+
* @param craftingJob The crafting job.
38+
* @param storageGetter The storage getter.
39+
*/
40+
public void fillCraftingJobBufferFromStorage(CraftingJob craftingJob, Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter);
41+
3742
/**
3843
* @return Get the number of scheduled and running crafting jobs in this interface.
3944
*/
@@ -80,5 +85,4 @@ public interface ICraftingInterface {
8085
public static Comparator<ICraftingInterface> createComparator() {
8186
return Comparator.comparing(ICraftingInterface::getPosition);
8287
}
83-
8488
}

src/main/java/org/cyclops/integratedcrafting/api/crafting/ICraftingProcessOverride.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ public interface ICraftingProcessOverride {
2828
* @param recipe The recipe from which the ingredients were derived.
2929
* @param resultsSink A sink where the ingredients can optionally be inserted into.
3030
* This should only be used if the processor does not have an internal storage.
31+
* @param craftingJob The running crafting job (or pending job if simulating).
3132
* @param simulate If insertion should be simulated.
3233
* @return If all instances could be inserted.
3334
*/
3435
public boolean craft(Function<IngredientComponent<?, ?>, PartPos> targetGetter, IMixedIngredients ingredients,
35-
IRecipeDefinition recipe, ICraftingResultsSink resultsSink, boolean simulate);
36+
IRecipeDefinition recipe, ICraftingResultsSink resultsSink, CraftingJob craftingJob, boolean simulate);
3637

3738
}

src/main/java/org/cyclops/integratedcrafting/api/network/ICraftingNetwork.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.google.common.collect.Multimap;
44
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
55
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
6+
import org.cyclops.commoncapabilities.api.ingredient.storage.IIngredientComponentStorage;
67
import org.cyclops.integratedcrafting.api.crafting.CraftingJob;
78
import org.cyclops.integratedcrafting.api.crafting.CraftingJobDependencyGraph;
89
import org.cyclops.integratedcrafting.api.crafting.ICraftingInterface;
@@ -12,6 +13,7 @@
1213
import javax.annotation.Nullable;
1314
import java.util.Iterator;
1415
import java.util.Set;
16+
import java.util.function.Function;
1517

1618
/**
1719
* A network capability for crafting.
@@ -89,11 +91,13 @@ public interface ICraftingNetwork {
8991

9092
/**
9193
* Add the given crafting job to the list of crafting jobs.
92-
* @param craftingJob The crafting job.
94+
*
95+
* @param craftingJob The crafting job.
9396
* @param allowDistribution If the crafting job is allowed to be split over multiple crafting interfaces.
97+
* @param storageGetter The storage getter.
9498
* @throws UnavailableCraftingInterfacesException If no crafting interfaces were available.
9599
*/
96-
public void scheduleCraftingJob(CraftingJob craftingJob, boolean allowDistribution) throws UnavailableCraftingInterfacesException;
100+
public void scheduleCraftingJob(CraftingJob craftingJob, boolean allowDistribution, Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter) throws UnavailableCraftingInterfacesException;
97101

98102
/**
99103
* Called by crafting interfaces when the crafting job is finished and should be removed from all lists.

0 commit comments

Comments
 (0)