• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CyclopsMC / IntegratedCrafting / #479011822

29 Dec 2025 01:56PM UTC coverage: 24.23% (-0.6%) from 24.876%
#479011822

push

github

rubensworks
Add dedicated storage per crafting job

When a crafting job is started, ingredients are immediately moved from
general storage to the new storage buffers per crafting job. This avoids
issues where ingredients can be consumed elsewhere (e.g. exporters or
other crafting jobs) before it is used by the crafting job.

Closes #112

3 of 104 new or added lines in 7 files covered. (2.88%)

3 existing lines in 3 files now uncovered.

755 of 3116 relevant lines covered (24.23%)

0.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/main/java/org/cyclops/integratedcrafting/core/CraftingJobHandler.java
1
package org.cyclops.integratedcrafting.core;
2

3
import com.google.common.collect.Lists;
4
import com.google.common.collect.Maps;
5
import it.unimi.dsi.fastutil.ints.Int2IntMap;
6
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
7
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
8
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
9
import it.unimi.dsi.fastutil.objects.Object2IntMap;
10
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
11
import net.minecraft.core.Direction;
12
import net.minecraft.nbt.CompoundTag;
13
import net.minecraft.nbt.ListTag;
14
import net.minecraft.nbt.Tag;
15
import net.minecraft.resources.ResourceLocation;
16
import org.apache.commons.lang3.tuple.Pair;
17
import org.apache.logging.log4j.Level;
18
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
19
import org.cyclops.commoncapabilities.api.ingredient.*;
20
import org.cyclops.commoncapabilities.api.ingredient.storage.IIngredientComponentStorage;
21
import org.cyclops.integratedcrafting.GeneralConfig;
22
import org.cyclops.integratedcrafting.IntegratedCrafting;
23
import org.cyclops.integratedcrafting.api.crafting.*;
24
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
25
import org.cyclops.integrateddynamics.api.ingredient.IIngredientComponentStorageObservable;
26
import org.cyclops.integrateddynamics.api.network.INetwork;
27
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients;
28
import org.cyclops.integrateddynamics.api.part.PartPos;
29

30
import javax.annotation.Nullable;
31
import java.util.Collection;
32
import java.util.List;
33
import java.util.Map;
34
import java.util.function.Function;
35

36
/**
37
 * A CraftingJobHandler maintains a list of processing and pending crafting job.
38
 *
39
 * Each time that {@link #update(INetwork, int, PartPos)} is called,
40
 * the handler will observe the target position for changes in the processing job.
41
 * Also, it will try initiating pending jobs into the target if none was running.
42
 *
43
 * If blockingJobsMode is true, then a multi-amount job will only be crafted one-by-one.
44
 * If false, then as much as possible of that job will be crafted at once.
45
 *
46
 * @author rubensworks
47
 */
48
public class CraftingJobHandler {
49

50
    private final int maxProcessingJobs;
51
    private boolean blockingJobsMode;
52
    private final ICraftingResultsSink resultsSink;
53
    private final Collection<ICraftingProcessOverride> craftingProcessOverrides;
54

55
    private final Int2ObjectMap<CraftingJob> allCraftingJobs;
56
    private final Int2ObjectMap<CraftingJob> processingCraftingJobs;
57
    private final Int2ObjectMap<List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>>> processingCraftingJobsPendingIngredients;
58
    private final Int2ObjectMap<CraftingJob> pendingCraftingJobs;
59
    private final Object2IntMap<IngredientComponent<?, ?>> ingredientObserverCounters;
60
    private final Map<IngredientComponent<?, ?>, IIngredientComponentStorageObservable.IIndexChangeObserver<?, ?>> ingredientObservers;
61
    private final List<IngredientComponent<?, ?>> observersPendingCreation;
62
    private final List<IngredientComponent<?, ?>> observersPendingDeletion;
63
    private final Int2ObjectMap<CraftingJob> finishedCraftingJobs;
64
    private final Map<IngredientComponent<?, ?>, Direction> ingredientComponentTargetOverrides;
65
    private final Int2IntMap nonBlockingJobsRunningAmount;
66

67
    public CraftingJobHandler(int maxProcessingJobs, boolean blockingJobsMode,
68
                              Collection<ICraftingProcessOverride> craftingProcessOverrides,
69
                              ICraftingResultsSink resultsSink) {
×
70
        this.maxProcessingJobs = maxProcessingJobs;
×
71
        this.blockingJobsMode = blockingJobsMode;
×
72
        this.resultsSink = resultsSink;
×
73
        this.craftingProcessOverrides = craftingProcessOverrides;
×
74

75
        this.allCraftingJobs = new Int2ObjectOpenHashMap<>();
×
76
        this.processingCraftingJobs = new Int2ObjectOpenHashMap<>();
×
77
        this.pendingCraftingJobs = new Int2ObjectOpenHashMap<>();
×
78
        this.processingCraftingJobsPendingIngredients = new Int2ObjectOpenHashMap<>();
×
79
        this.ingredientObserverCounters = new Object2IntOpenHashMap<>();
×
80
        this.ingredientObservers = Maps.newIdentityHashMap();
×
81
        this.observersPendingCreation = Lists.newArrayList();
×
82
        this.observersPendingDeletion = Lists.newArrayList();
×
83
        this.finishedCraftingJobs = new Int2ObjectOpenHashMap<>();
×
84
        this.ingredientComponentTargetOverrides = Maps.newIdentityHashMap();
×
85
        this.nonBlockingJobsRunningAmount = new Int2IntOpenHashMap();
×
86
    }
×
87

88
    public void writeToNBT(CompoundTag tag) {
89
        tag.putBoolean("blockingJobsMode", this.blockingJobsMode);
×
90

91
        ListTag processingCraftingJobs = new ListTag();
×
92
        for (CraftingJob processingCraftingJob : this.processingCraftingJobs.values()) {
×
93
            CompoundTag entriesTag = new CompoundTag();
×
94
            entriesTag.put("craftingJob", CraftingJob.serialize(processingCraftingJob));
×
95

96
            List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>> ingredientsEntries = this.processingCraftingJobsPendingIngredients.get(processingCraftingJob.getId());
×
97
            ListTag pendingEntries = new ListTag();
×
98
            for (Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> ingredients : ingredientsEntries) {
×
99
                ListTag pendingIngredientInstances = new ListTag();
×
100
                for (Map.Entry<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> ingredientComponentListEntry : ingredients.entrySet()) {
×
101
                    CompoundTag ingredientInstance = new CompoundTag();
×
102

103
                    IngredientComponent<?, ?> ingredientComponent = ingredientComponentListEntry.getKey();
×
104
                    ingredientInstance.putString("ingredientComponent", IngredientComponent.REGISTRY.getKey(ingredientComponent).toString());
×
105

106
                    ListTag instances = new ListTag();
×
107
                    IIngredientSerializer serializer = ingredientComponent.getSerializer();
×
108
                    for (IPrototypedIngredient<?, ?> prototypedIngredient : ingredientComponentListEntry.getValue()) {
×
109
                        CompoundTag instance = new CompoundTag();
×
110
                        instance.put("prototype", serializer.serializeInstance(prototypedIngredient.getPrototype()));
×
111
                        instance.put("condition", serializer.serializeCondition(prototypedIngredient.getCondition()));
×
112
                        instances.add(instance);
×
113
                    }
×
114
                    ingredientInstance.put("instances", instances);
×
115

116
                    pendingIngredientInstances.add(ingredientInstance);
×
117
                }
×
118
                pendingEntries.add(pendingIngredientInstances);
×
119
            }
×
120
            entriesTag.put("pendingIngredientInstanceEntries", pendingEntries);
×
121
            processingCraftingJobs.add(entriesTag);
×
122
        }
×
123
        tag.put("processingCraftingJobs", processingCraftingJobs);
×
124

125
        ListTag pendingCraftingJobs = new ListTag();
×
126
        for (CraftingJob craftingJob : this.pendingCraftingJobs.values()) {
×
127
            pendingCraftingJobs.add(CraftingJob.serialize(craftingJob));
×
128
        }
×
129
        tag.put("pendingCraftingJobs", pendingCraftingJobs);
×
130

NEW
131
        ListTag finishedCraftingJobs = new ListTag();
×
NEW
132
        for (CraftingJob craftingJob : this.finishedCraftingJobs.values()) {
×
NEW
133
            finishedCraftingJobs.add(CraftingJob.serialize(craftingJob));
×
NEW
134
        }
×
NEW
135
        tag.put("finishedCraftingJobs", finishedCraftingJobs);
×
136

137
        CompoundTag targetOverrides = new CompoundTag();
×
138
        for (Map.Entry<IngredientComponent<?, ?>, Direction> entry : this.ingredientComponentTargetOverrides.entrySet()) {
×
139
            targetOverrides.putInt(entry.getKey().getName().toString(), entry.getValue().ordinal());
×
140
        }
×
141
        tag.put("targetOverrides", targetOverrides);
×
142

143
        CompoundTag nonBlockingJobsRunningAmount = new CompoundTag();
×
144
        for (Int2IntMap.Entry entry : this.nonBlockingJobsRunningAmount.int2IntEntrySet()) {
×
145
            nonBlockingJobsRunningAmount.putInt(String.valueOf(entry.getIntKey()), entry.getIntValue());
×
146
        }
×
147
        tag.put("nonBlockingJobsRunningAmount", nonBlockingJobsRunningAmount);
×
148
    }
×
149

150
    public void readFromNBT(CompoundTag tag) {
151
        if (tag.contains("blockingJobsMode")) {
×
152
            this.blockingJobsMode = tag.getBoolean("blockingJobsMode");
×
153
        }
154

155
        ListTag processingCraftingJobs = tag.getList("processingCraftingJobs", Tag.TAG_COMPOUND);
×
156
        for (Tag entry : processingCraftingJobs) {
×
157
            CompoundTag entryTag = (CompoundTag) entry;
×
158

159
            List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>> pendingIngredientInstanceEntries = Lists.newArrayList();
×
160
            if (entryTag.contains("pendingIngredientInstances")) {
×
161
                // TODO: for backwards-compatibility, remove this in the next major update
162
                Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> pendingIngredientInstances = Maps.newIdentityHashMap();
×
163
                ListTag pendingIngredientsList = entryTag.getList("pendingIngredientInstances", Tag.TAG_COMPOUND);
×
164
                for (Tag pendingIngredient : pendingIngredientsList) {
×
165
                    CompoundTag pendingIngredientTag = (CompoundTag) pendingIngredient;
×
166
                    String componentName = pendingIngredientTag.getString("ingredientComponent");
×
167
                    IngredientComponent<?, ?> ingredientComponent = IngredientComponent.REGISTRY.getValue(new ResourceLocation(componentName));
×
168
                    if (ingredientComponent == null) {
×
169
                        throw new IllegalArgumentException("Could not find the ingredient component type " + componentName);
×
170
                    }
171
                    IIngredientSerializer serializer = ingredientComponent.getSerializer();
×
172

173
                    List<IPrototypedIngredient<?, ?>> pendingIngredients = Lists.newArrayList();
×
174
                    for (Tag instanceTagUnsafe : pendingIngredientTag.getList("instances", Tag.TAG_COMPOUND)) {
×
175
                        CompoundTag instanceTag = (CompoundTag) instanceTagUnsafe;
×
176
                        Object instance = serializer.deserializeInstance(instanceTag.get("prototype"));
×
177
                        Object condition = serializer.deserializeCondition(instanceTag.get("condition"));
×
178
                        pendingIngredients.add(new PrototypedIngredient(ingredientComponent, instance, condition));
×
179
                    }
×
180

181
                    pendingIngredientInstances.put(ingredientComponent, pendingIngredients);
×
182
                }
×
183
                pendingIngredientInstanceEntries.add(pendingIngredientInstances);
×
184
            } else {
×
185
                ListTag ingredientsEntries = entryTag.getList("pendingIngredientInstanceEntries", Tag.TAG_LIST);
×
186
                for (Tag ingredientEntry : ingredientsEntries) {
×
187
                    ListTag pendingIngredientsList = (ListTag) ingredientEntry;
×
188

189
                    Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> pendingIngredientInstances = Maps.newIdentityHashMap();
×
190
                    for (Tag pendingIngredient : pendingIngredientsList) {
×
191
                        CompoundTag pendingIngredientTag = (CompoundTag) pendingIngredient;
×
192
                        String componentName = pendingIngredientTag.getString("ingredientComponent");
×
193
                        IngredientComponent<?, ?> ingredientComponent = IngredientComponent.REGISTRY.getValue(new ResourceLocation(componentName));
×
194
                        if (ingredientComponent == null) {
×
195
                            throw new IllegalArgumentException("Could not find the ingredient component type " + componentName);
×
196
                        }
197
                        IIngredientSerializer serializer = ingredientComponent.getSerializer();
×
198

199
                        List<IPrototypedIngredient<?, ?>> pendingIngredients = Lists.newArrayList();
×
200
                        for (Tag instanceTagUnsafe : pendingIngredientTag.getList("instances", Tag.TAG_COMPOUND)) {
×
201
                            CompoundTag instanceTag = (CompoundTag) instanceTagUnsafe;
×
202
                            Object instance = serializer.deserializeInstance(instanceTag.get("prototype"));
×
203
                            Object condition = serializer.deserializeCondition(instanceTag.get("condition"));
×
204
                            pendingIngredients.add(new PrototypedIngredient(ingredientComponent, instance, condition));
×
205
                        }
×
206

207
                        pendingIngredientInstances.put(ingredientComponent, pendingIngredients);
×
208
                    }
×
209

210
                    pendingIngredientInstanceEntries.add(pendingIngredientInstances);
×
211
                }
×
212
            }
213

214
            CraftingJob craftingJob = CraftingJob.deserialize(entryTag.getCompound("craftingJob"));
×
215

216
            this.processingCraftingJobs.put(craftingJob.getId(), craftingJob);
×
217
            this.allCraftingJobs.put(craftingJob.getId(), craftingJob);
×
218
            this.processingCraftingJobsPendingIngredients.put(
×
219
                    craftingJob.getId(),
×
220
                    pendingIngredientInstanceEntries);
221

222
        }
×
223

224
        ListTag pendingCraftingJobs = tag.getList("pendingCraftingJobs", Tag.TAG_COMPOUND);
×
225
        for (Tag craftingJob : pendingCraftingJobs) {
×
226
            CraftingJob craftingJobInstance = CraftingJob.deserialize((CompoundTag) craftingJob);
×
227
            this.pendingCraftingJobs.put(craftingJobInstance.getId(), craftingJobInstance);
×
228
            this.allCraftingJobs.put(craftingJobInstance.getId(), craftingJobInstance);
×
229
        }
×
230

NEW
231
        ListTag finishedCraftingJobs = tag.getList("finishedCraftingJobs", Tag.TAG_COMPOUND);
×
NEW
232
        for (Tag craftingJob : finishedCraftingJobs) {
×
NEW
233
            CraftingJob craftingJobInstance = CraftingJob.deserialize((CompoundTag) craftingJob);
×
NEW
234
            this.finishedCraftingJobs.put(craftingJobInstance.getId(), craftingJobInstance);
×
NEW
235
            this.allCraftingJobs.put(craftingJobInstance.getId(), craftingJobInstance);
×
NEW
236
        }
×
237

238
        // Add required observers to a list so that they will be created in the next tick
239
        for (List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>> valueEntries : this.processingCraftingJobsPendingIngredients.values()) {
×
240
            for (Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> value : valueEntries) {
×
241
                // It's possible that the same component is added multiple times over different jobs,
242
                // this is because we want to make sure our counters are correct.
243
                observersPendingCreation.addAll(value.keySet());
×
244
            }
×
245
        }
×
246

247
        this.ingredientComponentTargetOverrides.clear();
×
248
        CompoundTag targetOverrides = tag.getCompound("targetOverrides");
×
249
        for (String componentName : targetOverrides.getAllKeys()) {
×
250
            IngredientComponent<?, ?> component = IngredientComponent.REGISTRY.getValue(new ResourceLocation(componentName));
×
251
            this.ingredientComponentTargetOverrides.put(component, Direction.values()[targetOverrides.getInt(componentName)]);
×
252
        }
×
253

254
        this.nonBlockingJobsRunningAmount.clear();
×
255
        CompoundTag nonBlockingJobsRunningAmount = tag.getCompound("nonBlockingJobsRunningAmount");
×
256
        for (String key : nonBlockingJobsRunningAmount.getAllKeys()) {
×
257
            int craftingJobId = Integer.parseInt(key);
×
258
            int amount = nonBlockingJobsRunningAmount.getInt(key);
×
259
            this.nonBlockingJobsRunningAmount.put(craftingJobId, amount);
×
260
        }
×
261
    }
×
262

263
    public boolean setBlockingJobsMode(boolean blockingJobsMode) {
264
        if (this.blockingJobsMode != blockingJobsMode) {
×
265
            this.blockingJobsMode = blockingJobsMode;
×
266
            return true;
×
267
        }
268
        return false;
×
269
    }
270

271
    public boolean isBlockingJobsMode() {
272
        return blockingJobsMode;
×
273
    }
274

275
    public boolean canScheduleCraftingJobs() {
276
        return this.pendingCraftingJobs.size() < GeneralConfig.maxPendingCraftingJobs;
×
277
    }
278

279
    public void scheduleCraftingJob(CraftingJob craftingJob) {
280
        this.pendingCraftingJobs.put(craftingJob.getId(), craftingJob);
×
281
        this.allCraftingJobs.put(craftingJob.getId(), craftingJob);
×
282
        if (!this.isBlockingJobsMode()) {
×
283
            this.nonBlockingJobsRunningAmount.put(craftingJob.getId(), 0);
×
284
        }
285
    }
×
286

287
    public void fillCraftingJobBufferFromStorage(CraftingJob craftingJob, Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter) throws StorageExtractionException {
NEW
288
        if (!craftingJob.getIngredientsStorageBuffer().isEmpty()) {
×
NEW
289
            throw new IllegalStateException("Re-filling a non-empty crafting job buffer is illegal");
×
290
        }
NEW
291
        IMixedIngredients toExtract = craftingJob.getIngredientsStorage();
×
NEW
292
        Map<IngredientComponent<?, ?>, List<?>> extracted = Maps.newIdentityHashMap();
×
NEW
293
        for (IngredientComponent<?, ?> component : toExtract.getComponents()) {
×
NEW
294
            IIngredientComponentStorage<?, ?> storage = storageGetter.apply(component);
×
NEW
295
            List<?> extractedList = Lists.newArrayList();
×
NEW
296
            extracted.put(component, extractedList);
×
NEW
297
            if (!extractIngredientsFromStorage((IIngredientComponentStorage) storage, (IngredientComponent) component, (List) toExtract.getInstances(component), (List) extractedList)) {
×
298
                // If extraction failed, re-insert ALL extracted ingredients so far to network, and throw an exception.
NEW
299
                CraftingHelpers.insertIngredientsGuaranteed(new MixedIngredients(extracted), storageGetter, this.resultsSink);
×
NEW
300
                throw new StorageExtractionException(craftingJob);
×
301
            }
NEW
302
        }
×
NEW
303
        craftingJob.setIngredientsStorageBuffer(new MixedIngredients(extracted));
×
NEW
304
    }
×
305

306
    protected <T, M> boolean extractIngredientsFromStorage(IIngredientComponentStorage<T, M> storage, IngredientComponent<T, M> ingredientComponent, List<T> toExtract, List<T> extracted) {
NEW
307
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
×
NEW
308
        M exactCondition = matcher.getExactMatchCondition();
×
NEW
309
        for (T ingredient : toExtract) {
×
NEW
310
            T extractedIngredient = storage.extract(ingredient, exactCondition, false);
×
NEW
311
            if (matcher.matchesExactly(ingredient, extractedIngredient)) {
×
NEW
312
                extracted.add(extractedIngredient);
×
313
            } else {
NEW
314
                return false;
×
315
            }
NEW
316
        }
×
NEW
317
        return true;
×
318
    }
319

320
    public Int2ObjectMap<List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>>> getProcessingCraftingJobsPendingIngredients() {
321
        return processingCraftingJobsPendingIngredients;
×
322
    }
323

324
    public Int2ObjectMap<CraftingJob> getProcessingCraftingJobsRaw() {
325
        return processingCraftingJobs;
×
326
    }
327

328
    public Collection<CraftingJob> getProcessingCraftingJobs() {
329
        return getProcessingCraftingJobsRaw().values();
×
330
    }
331

332
    public Collection<CraftingJob> getPendingCraftingJobs() {
333
        return pendingCraftingJobs.values();
×
334
    }
335

336
    public void unmarkCraftingJobProcessing(CraftingJob craftingJob) {
337
        if (this.processingCraftingJobs.remove(craftingJob.getId()) != null) {
×
338
            this.processingCraftingJobsPendingIngredients.remove(craftingJob.getId());
×
339
            this.pendingCraftingJobs.put(craftingJob.getId(), craftingJob);
×
340
        }
341
    }
×
342

343
    public void addCraftingJobProcessingPendingIngredientsEntry(CraftingJob craftingJob,
344
                                                                Map<IngredientComponent<?, ?>,
345
                                                                   List<IPrototypedIngredient<?, ?>>> pendingIngredients) {
346
        if (pendingIngredients.isEmpty()) {
×
347
            this.processingCraftingJobs.remove(craftingJob.getId());
×
348
            this.allCraftingJobs.remove(craftingJob.getId());
×
349
            this.nonBlockingJobsRunningAmount.remove(craftingJob.getId());
×
350
            this.processingCraftingJobsPendingIngredients.remove(craftingJob.getId());
×
351

352
        } else {
353
            this.processingCraftingJobs.put(craftingJob.getId(), craftingJob);
×
354
            this.allCraftingJobs.put(craftingJob.getId(), craftingJob);
×
355

356
            List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>> pendingIngredientsEntries = this.processingCraftingJobsPendingIngredients.get(craftingJob.getId());
×
357
            if (pendingIngredientsEntries == null) {
×
358
                pendingIngredientsEntries = Lists.newArrayList();
×
359
                this.processingCraftingJobsPendingIngredients.put(craftingJob.getId(), pendingIngredientsEntries);
×
360
            }
361
            pendingIngredientsEntries.add(pendingIngredients);
×
362
        }
363
    }
×
364

365
    public List<IngredientComponent<?, ?>> getObserversPendingDeletion() {
366
        return observersPendingDeletion;
×
367
    }
368

369
    protected <T, M> void registerIngredientObserver(IngredientComponent<T, M> ingredientComponent, INetwork network) {
370
        int count = ingredientObserverCounters.getInt(ingredientComponent);
×
371
        if (count == 0) {
×
372
            IPositionedAddonsNetworkIngredients<T, M> ingredientsNetwork = CraftingHelpers
×
373
                    .getIngredientsNetworkChecked(network, ingredientComponent);
×
374
            ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(network);
×
NEW
375
            PendingCraftingJobResultIndexObserver<T, M> observer = new PendingCraftingJobResultIndexObserver<>(ingredientComponent, this, craftingNetwork, ingredientsNetwork, network);
×
376
            ingredientsNetwork.addObserver(observer);
×
377
            ingredientsNetwork.scheduleObservation();
×
378
            ingredientObservers.put(ingredientComponent, observer);
×
379
        }
380
        ingredientObserverCounters.put(ingredientComponent, count + 1);
×
381
    }
×
382

383
    protected <T, M> void unregisterIngredientObserver(IngredientComponent<T, M> ingredientComponent, INetwork network) {
384
        int count = ingredientObserverCounters.getInt(ingredientComponent);
×
385
        count--;
×
386
        ingredientObserverCounters.put(ingredientComponent, count);
×
387
        if (count == 0) {
×
388
            IPositionedAddonsNetworkIngredients<T, M> ingredientsNetwork = CraftingHelpers
×
389
                    .getIngredientsNetworkChecked(network, ingredientComponent);
×
390
            IIngredientComponentStorageObservable.IIndexChangeObserver<T, M> observer =
×
391
                    (IIngredientComponentStorageObservable.IIndexChangeObserver<T, M>) ingredientObservers
392
                            .remove(ingredientComponent);
×
393
            ingredientsNetwork.removeObserver(observer);
×
394
        }
395
    }
×
396

397
    public void onCraftingJobFinished(CraftingJob craftingJob) {
398
        this.processingCraftingJobs.remove(craftingJob.getId());
×
399
        this.pendingCraftingJobs.remove(craftingJob.getId());
×
400
        this.finishedCraftingJobs.put(craftingJob.getId(), craftingJob);
×
401
        this.allCraftingJobs.put(craftingJob.getId(), craftingJob);
×
402
    }
×
403

404
    // This does the same as above, just based on crafting job id
405
    public void markCraftingJobFinished(int craftingJobId) {
406
        this.processingCraftingJobsPendingIngredients.remove(craftingJobId);
×
407
        this.processingCraftingJobs.remove(craftingJobId);
×
408
        this.pendingCraftingJobs.remove(craftingJobId);
×
409

410
        // Needed so that we remove the job in the next tick
411
        CraftingJob craftingJob = this.allCraftingJobs.get(craftingJobId);
×
412
        this.finishedCraftingJobs.put(craftingJobId, craftingJob);
×
413
        craftingJob.setAmount(0);
×
414
    }
×
415

416
    public void reRegisterObservers(INetwork network) {
417
        for (Map.Entry<IngredientComponent<?, ?>, IIngredientComponentStorageObservable.IIndexChangeObserver<?, ?>> entry : ingredientObservers.entrySet()) {
×
418
            IPositionedAddonsNetworkIngredients ingredientsNetwork = CraftingHelpers
×
419
                    .getIngredientsNetworkChecked(network, entry.getKey());
×
420
            ingredientsNetwork.addObserver(entry.getValue());
×
421
        }
×
422
    }
×
423

424
    public void onCraftingJobEntryFinished(ICraftingNetwork craftingNetwork, int craftingJobId) {
425
        CraftingJob craftingJob = this.allCraftingJobs.get(craftingJobId);
×
426
        craftingJob.setAmount(craftingJob.getAmount() - 1);
×
427

428
        if (this.nonBlockingJobsRunningAmount.containsKey(craftingJobId)) {
×
429
            this.nonBlockingJobsRunningAmount.put(craftingJobId, this.nonBlockingJobsRunningAmount.get(craftingJobId) - 1);
×
430
        }
431

432
        // We mark each dependent job that it may attempt to be started,
433
        // because its (partially) finished dependency may have produced ingredients to already start part of this job.
434
        for (CraftingJob dependent : craftingNetwork.getCraftingJobDependencyGraph().getDependents(craftingJob)) {
×
435
            dependent.setIgnoreDependencyCheck(true);
×
436
        }
×
437
    }
×
438

439
    public void update(INetwork network, int channel, PartPos targetPos) {
440
        // Create creation-pending observers
441
        if (observersPendingCreation.size() > 0) {
×
442
            for (IngredientComponent<?, ?> ingredientComponent : observersPendingCreation) {
×
443
                registerIngredientObserver(ingredientComponent, network);
×
444
            }
×
445
            observersPendingCreation.clear();
×
446
        }
447

448
        // Remove removal-pending observers
449
        if (observersPendingDeletion.size() > 0) {
×
450
            for (IngredientComponent<?, ?> ingredientComponent : observersPendingDeletion) {
×
451
                unregisterIngredientObserver(ingredientComponent, network);
×
452
            }
×
453
            observersPendingDeletion.clear();
×
454
        }
455

456
        // Notify the network of finalized crafting jobs
457
        if (finishedCraftingJobs.size() > 0) {
×
458
            for (CraftingJob finishedCraftingJob : finishedCraftingJobs.values()) {
×
459
                if (finishedCraftingJob.getAmount() == 0) {
×
460
                    // If the job is fully finished, remove it from the network
461
                    ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(network);
×
462
                    craftingNetwork.onCraftingJobFinished(finishedCraftingJob);
×
463
                    allCraftingJobs.remove(finishedCraftingJob.getId());
×
464
                    nonBlockingJobsRunningAmount.remove(finishedCraftingJob.getId());
×
465

NEW
466
                    if (!finishedCraftingJob.getIngredientsStorageBuffer().isEmpty()) {
×
NEW
467
                        CraftingHelpers.insertIngredientsGuaranteed(finishedCraftingJob.getIngredientsStorageBuffer(), CraftingHelpers.getNetworkStorageGetter(network, channel, false), this.resultsSink);
×
468
                    }
UNCOV
469
                } else {
×
470
                    // Re-add it to the pending jobs list if entries are remaining
471
                    pendingCraftingJobs.put(finishedCraftingJob.getId(), finishedCraftingJob);
×
472
                }
473
            }
×
474
            finishedCraftingJobs.clear();
×
475
        }
476

477
        // The actual output observation of processing jobs is done via the ingredient observers
478
        int processingJobs = getProcessingCraftingJobs().size();
×
479

480
        // Enable the observers for the next tick
481
        if (processingJobs > 0) {
×
482
            for (IngredientComponent<?, ?> ingredientComponent : ingredientObservers.keySet()) {
×
483
                IPositionedAddonsNetworkIngredients<?, ?> ingredientsNetwork = CraftingHelpers.getIngredientsNetworkChecked(network, ingredientComponent);
×
484
                ingredientsNetwork.scheduleObservation();
×
485
            }
×
486
        }
487

488
        // Process the jobs that are in non-blocking mode and still require amounts to be processed by re-trying insertion
489
        if (!this.nonBlockingJobsRunningAmount.isEmpty()) {
×
490
            for (Int2IntMap.Entry entry : this.nonBlockingJobsRunningAmount.int2IntEntrySet()) {
×
491
                int craftingJobId = entry.getIntKey();
×
492
                int runningAmount = entry.getIntValue();
×
493
                CraftingJob craftingJob = this.allCraftingJobs.get(craftingJobId); // Could be null, but not sure why: CyclopsMC/IntegratedCrafting#161
×
494
                if (runningAmount > 0 && craftingJob != null && runningAmount < craftingJob.getAmount()) {
×
495
                    insertLoopNonBlocking(network, channel, targetPos, craftingJob);
×
496
                }
497
            }
×
498
        }
499

500
        if (processingJobs < this.maxProcessingJobs) {
×
501
            // Handle crafting jobs
502
            CraftingJob startingCraftingJob = null;
×
503
            ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(network);
×
504
            CraftingJobDependencyGraph dependencyGraph = craftingNetwork.getCraftingJobDependencyGraph();
×
505
            for (CraftingJob pendingCraftingJob : getPendingCraftingJobs()) {
×
506
                // Make sure that this crafting job has no incomplete dependency jobs
507
                // This check can be overridden if the ignoreDependencyCheck flag is set
508
                // (which is done once a dependent finishes a job entry).
509
                // This override only applies for a single tick.
510
                if (dependencyGraph.hasDependencies(pendingCraftingJob) && !pendingCraftingJob.isIgnoreDependencyCheck()) {
×
511
                    continue;
×
512
                }
513
                if (pendingCraftingJob.isIgnoreDependencyCheck()) {
×
514
                    pendingCraftingJob.setIgnoreDependencyCheck(false);
×
515
                }
516

517
                // Check if pendingCraftingJob can start and set as startingCraftingJob
518
                // This requires checking the available ingredients AND if the crafting handler can accept it.
519
                IRecipeDefinition recipe = pendingCraftingJob.getRecipe();
×
520
                Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>> inputs = CraftingHelpers.getRecipeInputs(
×
NEW
521
                        CraftingHelpers.getCraftingJobBufferStorageGetter(pendingCraftingJob),
×
522
                        recipe, true, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), true, 1);
×
523
                if (inputs.getRight().isEmpty()) { // If we have no missing ingredients
×
524
                    if (insertCrafting(targetPos, new MixedIngredients(inputs.getLeft()), recipe, network, channel, true)) {
×
525
                        startingCraftingJob = pendingCraftingJob;
×
526
                        startingCraftingJob.setInvalidInputs(false);
×
527
                        break;
×
528
                    } else {
529
                        pendingCraftingJob.setInvalidInputs(true);
×
530
                    }
531
                } else {
532
                    // Register listeners for pending ingredients
533
                    if (pendingCraftingJob.getLastMissingIngredients().isEmpty()) {
×
534
                        for (IngredientComponent<?, ?> component : inputs.getRight().keySet()) {
×
535
                            registerIngredientObserver(component, network);
×
536

537
                            // For the missing ingredients that are reusable,
538
                            // trigger a crafting job for them if no job is running yet.
539
                            // This special case is needed because reusable ingredients are usually durability-based,
540
                            // and may be consumed _during_ a bulk crafting job.
541
                            MissingIngredients<?, ?> missingIngredients = inputs.getRight().get(component);
×
542
                            for (MissingIngredients.Element<?, ?> element : missingIngredients.getElements()) {
×
543
                                if (element.isInputReusable()) {
×
544
                                    for (MissingIngredients.PrototypedWithRequested alternative : element.getAlternatives()) {
×
545
                                        // Try to start crafting jobs for each alternative until one of them succeeds.
546
                                        if (CraftingHelpers.isCrafting(craftingNetwork, channel,
×
547
                                                alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition())) {
×
548
                                            // Break loop if we have found an existing job for our dependency
549
                                            // This may occur if a crafting job was triggered in a parallelized job
550
                                            break;
×
551
                                        }
552
                                        CraftingJob craftingJob = CraftingHelpers.calculateAndScheduleCraftingJob(network, channel,
×
553
                                                alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition(), true, true,
×
554
                                                CraftingHelpers.getGlobalCraftingJobIdentifier(), null);
×
555
                                        if (craftingJob != null) {
×
556
                                            pendingCraftingJob.addDependency(craftingJob);
×
557
                                            // Break loop once we have found a valid job
558
                                            break;
×
559
                                        }
560
                                    }
×
561
                                }
562
                            }
×
563
                        }
×
564
                    }
565

566
                    pendingCraftingJob.setLastMissingIngredients(inputs.getRight());
×
567
                }
568
            }
×
569

570
            // Start the crafting job
571
            if (startingCraftingJob != null) {
×
572
                // If the job previously had missing in ingredients, unregister the observers that were previously created for it.
573
                if (!startingCraftingJob.getLastMissingIngredients().isEmpty()) {
×
574
                    for (IngredientComponent<?, ?> component : startingCraftingJob.getLastMissingIngredients().keySet()) {
×
575
                        unregisterIngredientObserver(component, network);
×
576
                    }
×
577
                    startingCraftingJob.setLastMissingIngredients(Maps.newIdentityHashMap());
×
578
                }
579

580
                // Check if the job was started while blocking mode was enabled in this handler
581
                boolean blockingMode = !nonBlockingJobsRunningAmount.containsKey(startingCraftingJob.getId()) || startingCraftingJob.getAmount() == 1;
×
582

583
                // Start the actual crafting
584
                boolean couldCraft = consumeAndInsertCrafting(blockingMode, network, channel, targetPos, startingCraftingJob);
×
585

586
                // Keep inserting as much as possible if non-blocking
587
                if (couldCraft && !blockingMode) {
×
588
                    nonBlockingJobsRunningAmount.put(startingCraftingJob.getId(), 1);
×
589
                    insertLoopNonBlocking(network, channel, targetPos, startingCraftingJob);
×
590
                }
591
            }
592
        }
593
    }
×
594

595
    protected boolean insertCrafting(PartPos target, IMixedIngredients ingredients, IRecipeDefinition recipe, INetwork network, int channel, boolean simulate) {
596
        Function<IngredientComponent<?, ?>, PartPos> targetGetter = getTargetGetter(target);
×
597
        // First check our crafting overrides
598
        for (ICraftingProcessOverride craftingProcessOverride : this.craftingProcessOverrides) {
×
599
            if (craftingProcessOverride.isApplicable(target)) {
×
600
                return craftingProcessOverride.craft(targetGetter, ingredients, recipe, this.resultsSink, simulate);
×
601
            }
602
        }
×
603

604
        // Fallback to default crafting insertion
605
        return CraftingHelpers.insertCrafting(targetGetter, ingredients, network, channel, simulate);
×
606
    }
607

608
    protected void insertLoopNonBlocking(INetwork network, int channel, PartPos targetPos, CraftingJob craftingJob) {
609
        // If in non-blocking mode, try to push as much as possible into the target
610
        while (nonBlockingJobsRunningAmount.get(craftingJob.getId()) < craftingJob.getAmount()) {
×
611
            IRecipeDefinition recipe = craftingJob.getRecipe();
×
NEW
612
            IMixedIngredients ingredientsSimulated = CraftingHelpers.getRecipeInputsFromCraftingJobBuffer(craftingJob,
×
613
                    recipe, true, 1);
614
            if (ingredientsSimulated == null ||!insertCrafting(targetPos, ingredientsSimulated, recipe, network, channel, true)) {
×
615
                break;
×
616
            }
617
            if (!consumeAndInsertCrafting(true, network, channel, targetPos, craftingJob)) {
×
618
                break;
×
619
            }
620
            nonBlockingJobsRunningAmount.put(craftingJob.getId(), nonBlockingJobsRunningAmount.get(craftingJob.getId()) + 1);
×
621
        }
×
622
    }
×
623

624
    protected boolean consumeAndInsertCrafting(boolean blockingMode, INetwork network, int channel, PartPos targetPos, CraftingJob startingCraftingJob) {
625
        // Remove ingredients from network
626
        IRecipeDefinition recipe = startingCraftingJob.getRecipe();
×
NEW
627
        IMixedIngredients ingredients = CraftingHelpers.getRecipeInputsFromCraftingJobBuffer(startingCraftingJob,
×
628
                recipe, false, 1);
629

630
        // This may not be null, error if it is null!
631
        if (ingredients != null) {
×
632
            this.pendingCraftingJobs.remove(startingCraftingJob.getId());
×
633

634
            // Update state with expected outputs
635
            addCraftingJobProcessingPendingIngredientsEntry(startingCraftingJob,
×
636
                    CraftingHelpers.getRecipeOutputs(startingCraftingJob.getRecipe()));
×
637

638
            // Register listeners for pending ingredients
639
            for (IngredientComponent<?, ?> component : startingCraftingJob.getRecipe().getOutput().getComponents()) {
×
640
                registerIngredientObserver(component, network);
×
641
            }
×
642

643
            // Push the ingredients to the crafting interface
644
            if (!insertCrafting(targetPos, ingredients, recipe, network, channel, false)) {
×
645
                // Unregister listeners again for pending ingredients
646
                for (IngredientComponent<?, ?> component : startingCraftingJob.getRecipe().getOutput().getComponents()) {
×
647
                    unregisterIngredientObserver(component, network);
×
648
                }
×
649

650
                // If we reach this point, the target does not accept the recipe inputs,
651
                // even though they were acceptable in simulation mode.
652
                // The failed ingredients were already re-inserted into the network at this point,
653
                // so we mark the job as failed, and add it again to the queue.
654
                startingCraftingJob.setInvalidInputs(true);
×
655
                unmarkCraftingJobProcessing(startingCraftingJob);
×
656
                return false;
×
657
            } else {
658
                return true;
×
659
            }
660
        } else {
661
            IntegratedCrafting.clog(Level.WARN, "Failed to extract ingredients for crafting job " + startingCraftingJob.getId());
×
662
            return false;
×
663
        }
664
    }
665

666
    public CraftingJobStatus getCraftingJobStatus(ICraftingNetwork network, int channel, int craftingJobId) {
667
        if (pendingCraftingJobs.containsKey(craftingJobId)) {
×
668
            CraftingJob craftingJob = allCraftingJobs.get(craftingJobId);
×
669
            if (craftingJob != null && craftingJob.isInvalidInputs()) {
×
670
                return CraftingJobStatus.INVALID_INPUTS;
×
671
            }
672

673
            CraftingJobDependencyGraph dependencyGraph = network.getCraftingJobDependencyGraph();
×
674
            if (dependencyGraph.hasDependencies(craftingJobId)) {
×
675
                return CraftingJobStatus.PENDING_DEPENDENCIES;
×
676
            } else {
677
                if (!craftingJob.getLastMissingIngredients().isEmpty()) {
×
678
                    return CraftingJobStatus.PENDING_INGREDIENTS;
×
679
                } else {
680
                    return CraftingJobStatus.PENDING_INTERFACE;
×
681
                }
682
            }
683
        } else if (processingCraftingJobs.containsKey(craftingJobId)) {
×
684
            return CraftingJobStatus.PROCESSING;
×
685
        } else if (finishedCraftingJobs.containsKey(craftingJobId)) {
×
686
            return CraftingJobStatus.FINISHED;
×
687
        }
688
        return CraftingJobStatus.UNKNOWN;
×
689
    }
690

691
    public Int2ObjectMap<CraftingJob> getAllCraftingJobs() {
692
        return allCraftingJobs;
×
693
    }
694

695
    public void setIngredientComponentTarget(IngredientComponent<?, ?> ingredientComponent, @Nullable Direction side) {
696
        if (side == null) {
×
697
            this.ingredientComponentTargetOverrides.remove(ingredientComponent);
×
698
        } else {
699
            this.ingredientComponentTargetOverrides.put(ingredientComponent, side);
×
700
        }
701
    }
×
702

703
    @Nullable
704
    public Direction getIngredientComponentTarget(IngredientComponent<?, ?> ingredientComponent) {
705
        return this.ingredientComponentTargetOverrides.get(ingredientComponent);
×
706
    }
707

708
    public Function<IngredientComponent<?, ?>, PartPos> getTargetGetter(PartPos defaultPosition) {
709
        return ingredientComponent -> {
×
710
            Direction sideOverride = this.ingredientComponentTargetOverrides.get(ingredientComponent);
×
711
            if (sideOverride == null) {
×
712
                return defaultPosition;
×
713
            } else {
714
                return PartPos.of(defaultPosition.getPos(), sideOverride);
×
715
            }
716
        };
717
    }
718
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc