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

CyclopsMC / IntegratedCrafting / #479011830

31 Dec 2025 08:35AM UTC coverage: 24.029% (-0.3%) from 24.3%
#479011830

push

github

rubensworks
Fix missing ingredients not taken into account when piping job results to dependents

0 of 51 new or added lines in 4 files covered. (0.0%)

4 existing lines in 3 files now uncovered.

755 of 3142 relevant lines covered (24.03%)

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

131
        ListTag finishedCraftingJobs = new ListTag();
×
132
        for (CraftingJob craftingJob : this.finishedCraftingJobs.values()) {
×
133
            finishedCraftingJobs.add(CraftingJob.serialize(craftingJob));
×
134
        }
×
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

231
        ListTag finishedCraftingJobs = tag.getList("finishedCraftingJobs", Tag.TAG_COMPOUND);
×
232
        for (Tag craftingJob : finishedCraftingJobs) {
×
233
            CraftingJob craftingJobInstance = CraftingJob.deserialize((CompoundTag) craftingJob);
×
234
            this.finishedCraftingJobs.put(craftingJobInstance.getId(), craftingJobInstance);
×
235
            this.allCraftingJobs.put(craftingJobInstance.getId(), craftingJobInstance);
×
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) {
288
        if (!craftingJob.getIngredientsStorageBuffer().isEmpty()) {
×
289
            throw new IllegalStateException("Re-filling a non-empty crafting job buffer is illegal");
×
290
        }
291
        // 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.
NEW
292
        Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>> inputResult = CraftingHelpers.getRecipeInputs(storageGetter, craftingJob.getRecipe(), false, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), true, craftingJob.getAmount());
×
NEW
293
        IMixedIngredients buffer = new MixedIngredients(inputResult.getLeft());
×
294
        craftingJob.setIngredientsStorageBuffer(CraftingHelpers.compressMixedIngredients(buffer));
×
NEW
295
        craftingJob.setLastMissingIngredients(inputResult.getRight());
×
UNCOV
296
    }
×
297

298
    public Int2ObjectMap<List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>>> getProcessingCraftingJobsPendingIngredients() {
299
        return processingCraftingJobsPendingIngredients;
×
300
    }
301

302
    public Int2ObjectMap<CraftingJob> getProcessingCraftingJobsRaw() {
303
        return processingCraftingJobs;
×
304
    }
305

306
    public Collection<CraftingJob> getProcessingCraftingJobs() {
307
        return getProcessingCraftingJobsRaw().values();
×
308
    }
309

310
    public Collection<CraftingJob> getPendingCraftingJobs() {
311
        return pendingCraftingJobs.values();
×
312
    }
313

314
    public void unmarkCraftingJobProcessing(CraftingJob craftingJob) {
315
        if (this.processingCraftingJobs.remove(craftingJob.getId()) != null) {
×
316
            this.processingCraftingJobsPendingIngredients.remove(craftingJob.getId());
×
317
            this.pendingCraftingJobs.put(craftingJob.getId(), craftingJob);
×
318
        }
319
    }
×
320

321
    public void addCraftingJobProcessingPendingIngredientsEntry(CraftingJob craftingJob,
322
                                                                Map<IngredientComponent<?, ?>,
323
                                                                   List<IPrototypedIngredient<?, ?>>> pendingIngredients) {
324
        if (pendingIngredients.isEmpty()) {
×
325
            this.processingCraftingJobs.remove(craftingJob.getId());
×
326
            this.allCraftingJobs.remove(craftingJob.getId());
×
327
            this.nonBlockingJobsRunningAmount.remove(craftingJob.getId());
×
328
            this.processingCraftingJobsPendingIngredients.remove(craftingJob.getId());
×
329

330
        } else {
331
            this.processingCraftingJobs.put(craftingJob.getId(), craftingJob);
×
332
            this.allCraftingJobs.put(craftingJob.getId(), craftingJob);
×
333

334
            List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>> pendingIngredientsEntries = this.processingCraftingJobsPendingIngredients.get(craftingJob.getId());
×
335
            if (pendingIngredientsEntries == null) {
×
336
                pendingIngredientsEntries = Lists.newArrayList();
×
337
                this.processingCraftingJobsPendingIngredients.put(craftingJob.getId(), pendingIngredientsEntries);
×
338
            }
339
            pendingIngredientsEntries.add(pendingIngredients);
×
340
        }
341
    }
×
342

343
    public List<IngredientComponent<?, ?>> getObserversPendingDeletion() {
344
        return observersPendingDeletion;
×
345
    }
346

347
    protected <T, M> void registerIngredientObserver(IngredientComponent<T, M> ingredientComponent, INetwork network) {
348
        int count = ingredientObserverCounters.getInt(ingredientComponent);
×
349
        if (count == 0) {
×
350
            IPositionedAddonsNetworkIngredients<T, M> ingredientsNetwork = CraftingHelpers
×
351
                    .getIngredientsNetworkChecked(network, ingredientComponent);
×
352
            ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(network);
×
353
            PendingCraftingJobResultIndexObserver<T, M> observer = new PendingCraftingJobResultIndexObserver<>(ingredientComponent, this, craftingNetwork, ingredientsNetwork, network);
×
354
            ingredientsNetwork.addObserver(observer);
×
355
            ingredientsNetwork.scheduleObservation();
×
356
            ingredientObservers.put(ingredientComponent, observer);
×
357
        }
358
        ingredientObserverCounters.put(ingredientComponent, count + 1);
×
359
    }
×
360

361
    protected <T, M> void unregisterIngredientObserver(IngredientComponent<T, M> ingredientComponent, INetwork network) {
362
        int count = ingredientObserverCounters.getInt(ingredientComponent);
×
363
        count--;
×
364
        ingredientObserverCounters.put(ingredientComponent, count);
×
365
        if (count == 0) {
×
366
            IPositionedAddonsNetworkIngredients<T, M> ingredientsNetwork = CraftingHelpers
×
367
                    .getIngredientsNetworkChecked(network, ingredientComponent);
×
368
            IIngredientComponentStorageObservable.IIndexChangeObserver<T, M> observer =
×
369
                    (IIngredientComponentStorageObservable.IIndexChangeObserver<T, M>) ingredientObservers
370
                            .remove(ingredientComponent);
×
371
            ingredientsNetwork.removeObserver(observer);
×
372
        }
373
    }
×
374

375
    public void onCraftingJobFinished(CraftingJob craftingJob) {
376
        this.processingCraftingJobs.remove(craftingJob.getId());
×
377
        this.pendingCraftingJobs.remove(craftingJob.getId());
×
378
        this.finishedCraftingJobs.put(craftingJob.getId(), craftingJob);
×
379
        this.allCraftingJobs.put(craftingJob.getId(), craftingJob);
×
380
    }
×
381

382
    // This does the same as above, just based on crafting job id
383
    public void markCraftingJobFinished(int craftingJobId) {
384
        this.processingCraftingJobsPendingIngredients.remove(craftingJobId);
×
385
        this.processingCraftingJobs.remove(craftingJobId);
×
386
        this.pendingCraftingJobs.remove(craftingJobId);
×
387

388
        // Needed so that we remove the job in the next tick
389
        CraftingJob craftingJob = this.allCraftingJobs.get(craftingJobId);
×
390
        this.finishedCraftingJobs.put(craftingJobId, craftingJob);
×
391
        craftingJob.setAmount(0);
×
392
    }
×
393

394
    public void reRegisterObservers(INetwork network) {
395
        for (Map.Entry<IngredientComponent<?, ?>, IIngredientComponentStorageObservable.IIndexChangeObserver<?, ?>> entry : ingredientObservers.entrySet()) {
×
396
            IPositionedAddonsNetworkIngredients ingredientsNetwork = CraftingHelpers
×
397
                    .getIngredientsNetworkChecked(network, entry.getKey());
×
398
            ingredientsNetwork.addObserver(entry.getValue());
×
399
        }
×
400
    }
×
401

402
    public void onCraftingJobEntryFinished(ICraftingNetwork craftingNetwork, int craftingJobId) {
403
        CraftingJob craftingJob = this.allCraftingJobs.get(craftingJobId);
×
404
        craftingJob.setAmount(craftingJob.getAmount() - 1);
×
405

406
        if (this.nonBlockingJobsRunningAmount.containsKey(craftingJobId)) {
×
407
            this.nonBlockingJobsRunningAmount.put(craftingJobId, this.nonBlockingJobsRunningAmount.get(craftingJobId) - 1);
×
408
        }
409

410
        // We mark each dependent job that it may attempt to be started,
411
        // because its (partially) finished dependency may have produced ingredients to already start part of this job.
412
        for (CraftingJob dependent : craftingNetwork.getCraftingJobDependencyGraph().getDependents(craftingJob)) {
×
413
            dependent.setIgnoreDependencyCheck(true);
×
414
        }
×
415
    }
×
416

417
    public void update(INetwork network, int channel, PartPos targetPos) {
418
        // Create creation-pending observers
419
        if (observersPendingCreation.size() > 0) {
×
420
            for (IngredientComponent<?, ?> ingredientComponent : observersPendingCreation) {
×
421
                registerIngredientObserver(ingredientComponent, network);
×
422
            }
×
423
            observersPendingCreation.clear();
×
424
        }
425

426
        // Remove removal-pending observers
427
        if (observersPendingDeletion.size() > 0) {
×
428
            for (IngredientComponent<?, ?> ingredientComponent : observersPendingDeletion) {
×
429
                unregisterIngredientObserver(ingredientComponent, network);
×
430
            }
×
431
            observersPendingDeletion.clear();
×
432
        }
433

434
        // Notify the network of finalized crafting jobs
435
        if (finishedCraftingJobs.size() > 0) {
×
436
            for (CraftingJob finishedCraftingJob : finishedCraftingJobs.values()) {
×
437
                if (finishedCraftingJob.getAmount() == 0) {
×
438
                    // If the job is fully finished, remove it from the network
439
                    ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(network);
×
440
                    craftingNetwork.onCraftingJobFinished(finishedCraftingJob);
×
441
                    allCraftingJobs.remove(finishedCraftingJob.getId());
×
442
                    nonBlockingJobsRunningAmount.remove(finishedCraftingJob.getId());
×
443

444
                    if (!finishedCraftingJob.getIngredientsStorageBuffer().isEmpty()) {
×
445
                        CraftingHelpers.insertIngredientsGuaranteed(finishedCraftingJob.getIngredientsStorageBuffer(), CraftingHelpers.getNetworkStorageGetter(network, channel, false), this.resultsSink);
×
446
                    }
447
                } else {
×
448
                    // Re-add it to the pending jobs list if entries are remaining
449
                    pendingCraftingJobs.put(finishedCraftingJob.getId(), finishedCraftingJob);
×
450
                }
451
            }
×
452
            finishedCraftingJobs.clear();
×
453
        }
454

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

458
        // Enable the observers for the next tick
459
        if (processingJobs > 0) {
×
460
            for (IngredientComponent<?, ?> ingredientComponent : ingredientObservers.keySet()) {
×
461
                IPositionedAddonsNetworkIngredients<?, ?> ingredientsNetwork = CraftingHelpers.getIngredientsNetworkChecked(network, ingredientComponent);
×
462
                ingredientsNetwork.scheduleObservation();
×
463
            }
×
464
        }
465

466
        // Process the jobs that are in non-blocking mode and still require amounts to be processed by re-trying insertion
467
        if (!this.nonBlockingJobsRunningAmount.isEmpty()) {
×
468
            for (Int2IntMap.Entry entry : this.nonBlockingJobsRunningAmount.int2IntEntrySet()) {
×
469
                int craftingJobId = entry.getIntKey();
×
470
                int runningAmount = entry.getIntValue();
×
471
                CraftingJob craftingJob = this.allCraftingJobs.get(craftingJobId); // Could be null, but not sure why: CyclopsMC/IntegratedCrafting#161
×
472
                if (runningAmount > 0 && craftingJob != null && runningAmount < craftingJob.getAmount()) {
×
473
                    insertLoopNonBlocking(network, channel, targetPos, craftingJob);
×
474
                }
475
            }
×
476
        }
477

478
        if (processingJobs < this.maxProcessingJobs) {
×
479
            // Handle crafting jobs
480
            CraftingJob startingCraftingJob = null;
×
481
            ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(network);
×
482
            CraftingJobDependencyGraph dependencyGraph = craftingNetwork.getCraftingJobDependencyGraph();
×
483
            for (CraftingJob pendingCraftingJob : getPendingCraftingJobs()) {
×
484
                // Make sure that this crafting job has no incomplete dependency jobs
485
                // This check can be overridden if the ignoreDependencyCheck flag is set
486
                // (which is done once a dependent finishes a job entry).
487
                // This override only applies for a single tick.
488
                if (dependencyGraph.hasDependencies(pendingCraftingJob) && !pendingCraftingJob.isIgnoreDependencyCheck()) {
×
489
                    continue;
×
490
                }
491
                if (pendingCraftingJob.isIgnoreDependencyCheck()) {
×
492
                    pendingCraftingJob.setIgnoreDependencyCheck(false);
×
493
                }
494

495
                // Check if pendingCraftingJob can start and set as startingCraftingJob
496
                // This requires checking the available ingredients AND if the crafting handler can accept it.
497
                IRecipeDefinition recipe = pendingCraftingJob.getRecipe();
×
498
                Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>> inputs = CraftingHelpers.getRecipeInputs(
×
499
                        CraftingHelpers.getCraftingJobBufferStorageGetter(pendingCraftingJob),
×
500
                        recipe, true, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), true, 1);
×
501
                if (inputs.getRight().isEmpty()) { // If we have no missing ingredients
×
502
                    if (insertCrafting(targetPos, new MixedIngredients(inputs.getLeft()), recipe, pendingCraftingJob, network, channel, true)) {
×
503
                        startingCraftingJob = pendingCraftingJob;
×
504
                        startingCraftingJob.setInvalidInputs(false);
×
505
                        break;
×
506
                    } else {
507
                        pendingCraftingJob.setInvalidInputs(true);
×
508
                    }
509
                } else {
510
                    // For the missing ingredients that are reusable,
511
                    // trigger a crafting job for them if no job is running yet.
512
                    // This special case is needed because reusable ingredients are usually durability-based,
513
                    // and may be consumed _during_ a bulk crafting job.
514
                    if (pendingCraftingJob.getLastMissingIngredients().isEmpty()) {
×
515
                        for (IngredientComponent<?, ?> component : inputs.getRight().keySet()) {
×
516
                            MissingIngredients<?, ?> missingIngredients = inputs.getRight().get(component);
×
UNCOV
517
                            for (MissingIngredients.Element<?, ?> element : missingIngredients.getElements()) {
×
518
                                if (element.isInputReusable()) {
×
519
                                    IIngredientComponentStorage storage = CraftingHelpers.getNetworkStorage(network, channel, component, true);
×
520
                                    for (MissingIngredients.PrototypedWithRequested alternative : element.getAlternatives()) {
×
521
                                        // First check if we can extract it from storage.
522
                                        Object extractedFromStorage = storage.extract(alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition(), false);
×
523
                                        if (!((IIngredientMatcher) component.getMatcher()).isEmpty(extractedFromStorage)) {
×
524
                                            pendingCraftingJob.addToIngredientsStorageBuffer((IngredientComponent<? super Object, ? extends Object>) component, extractedFromStorage);
×
525
                                            break;
×
526
                                        }
527

528
                                        // Try to start crafting jobs for each alternative until one of them succeeds.
529
                                        if (CraftingHelpers.isCrafting(craftingNetwork, channel,
×
530
                                                alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition())) {
×
531
                                            // Break loop if we have found an existing job for our dependency
532
                                            // This may occur if a crafting job was triggered in a parallelized job
533
                                            break;
×
534
                                        }
535
                                        CraftingJob craftingJob = CraftingHelpers.calculateAndScheduleCraftingJob(network, channel,
×
536
                                                alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition(), true, true,
×
537
                                                CraftingHelpers.getGlobalCraftingJobIdentifier(), null);
×
538
                                        if (craftingJob != null) {
×
539
                                            pendingCraftingJob.addDependency(craftingJob);
×
540
                                            // Break loop once we have found a valid job
541
                                            break;
×
542
                                        }
543
                                    }
×
544
                                }
545
                            }
×
546
                        }
×
547
                    }
548
                }
549
            }
×
550

551
            // Start the crafting job
552
            if (startingCraftingJob != null) {
×
553
                // Check if the job was started while blocking mode was enabled in this handler
554
                boolean blockingMode = !nonBlockingJobsRunningAmount.containsKey(startingCraftingJob.getId()) || startingCraftingJob.getAmount() == 1;
×
555

556
                // Start the actual crafting
557
                boolean couldCraft = consumeAndInsertCrafting(blockingMode, network, channel, targetPos, startingCraftingJob);
×
558

559
                // Keep inserting as much as possible if non-blocking
560
                if (couldCraft && !blockingMode) {
×
561
                    nonBlockingJobsRunningAmount.put(startingCraftingJob.getId(), 1);
×
562
                    insertLoopNonBlocking(network, channel, targetPos, startingCraftingJob);
×
563
                }
564
            }
565
        }
566
    }
×
567

568
    protected boolean insertCrafting(PartPos target, IMixedIngredients ingredients, IRecipeDefinition recipe, CraftingJob craftingJob, INetwork network, int channel, boolean simulate) {
569
        Function<IngredientComponent<?, ?>, PartPos> targetGetter = getTargetGetter(target);
×
570
        // First check our crafting overrides
571
        for (ICraftingProcessOverride craftingProcessOverride : this.craftingProcessOverrides) {
×
572
            if (craftingProcessOverride.isApplicable(target)) {
×
573
                return craftingProcessOverride.craft(targetGetter, ingredients, recipe, this.resultsSink, craftingJob, simulate);
×
574
            }
575
        }
×
576

577
        // Fallback to default crafting insertion
578
        return CraftingHelpers.insertCrafting(targetGetter, ingredients, network, channel, simulate);
×
579
    }
580

581
    protected void insertLoopNonBlocking(INetwork network, int channel, PartPos targetPos, CraftingJob craftingJob) {
582
        // If in non-blocking mode, try to push as much as possible into the target
583
        while (nonBlockingJobsRunningAmount.get(craftingJob.getId()) < craftingJob.getAmount()) {
×
584
            IRecipeDefinition recipe = craftingJob.getRecipe();
×
585
            IMixedIngredients ingredientsSimulated = CraftingHelpers.getRecipeInputsFromCraftingJobBuffer(craftingJob,
×
586
                    recipe, true, 1);
587
            if (ingredientsSimulated == null ||!insertCrafting(targetPos, ingredientsSimulated, recipe, craftingJob, network, channel, true)) {
×
588
                break;
×
589
            }
590
            if (!consumeAndInsertCrafting(true, network, channel, targetPos, craftingJob)) {
×
591
                break;
×
592
            }
593
            nonBlockingJobsRunningAmount.put(craftingJob.getId(), nonBlockingJobsRunningAmount.get(craftingJob.getId()) + 1);
×
594
        }
×
595
    }
×
596

597
    protected boolean consumeAndInsertCrafting(boolean blockingMode, INetwork network, int channel, PartPos targetPos, CraftingJob startingCraftingJob) {
598
        // Remove ingredients from network
599
        IRecipeDefinition recipe = startingCraftingJob.getRecipe();
×
600
        IMixedIngredients ingredients = CraftingHelpers.getRecipeInputsFromCraftingJobBuffer(startingCraftingJob,
×
601
                recipe, false, 1);
602

603
        // This may not be null, error if it is null!
604
        if (ingredients != null) {
×
605
            this.pendingCraftingJobs.remove(startingCraftingJob.getId());
×
606

607
            // Update state with expected outputs
608
            addCraftingJobProcessingPendingIngredientsEntry(startingCraftingJob,
×
609
                    CraftingHelpers.getRecipeOutputs(startingCraftingJob.getRecipe()));
×
610

611
            // Register listeners for pending ingredients
612
            for (IngredientComponent<?, ?> component : startingCraftingJob.getRecipe().getOutput().getComponents()) {
×
613
                registerIngredientObserver(component, network);
×
614
            }
×
615

616
            // Push the ingredients to the crafting interface
617
            if (!insertCrafting(targetPos, ingredients, recipe, startingCraftingJob, network, channel, false)) {
×
618
                // Unregister listeners again for pending ingredients
619
                for (IngredientComponent<?, ?> component : startingCraftingJob.getRecipe().getOutput().getComponents()) {
×
620
                    unregisterIngredientObserver(component, network);
×
621
                }
×
622

623
                // If we reach this point, the target does not accept the recipe inputs,
624
                // even though they were acceptable in simulation mode.
625
                // The failed ingredients were already re-inserted into the network at this point,
626
                // so we mark the job as failed, and add it again to the queue.
627
                startingCraftingJob.setInvalidInputs(true);
×
628
                unmarkCraftingJobProcessing(startingCraftingJob);
×
629
                return false;
×
630
            } else {
631
                return true;
×
632
            }
633
        } else {
634
            IntegratedCrafting.clog(Level.WARN, "Failed to extract ingredients for crafting job " + startingCraftingJob.getId());
×
635
            return false;
×
636
        }
637
    }
638

639
    public CraftingJobStatus getCraftingJobStatus(ICraftingNetwork network, int channel, int craftingJobId) {
640
        if (pendingCraftingJobs.containsKey(craftingJobId)) {
×
641
            CraftingJob craftingJob = allCraftingJobs.get(craftingJobId);
×
642
            if (craftingJob != null && craftingJob.isInvalidInputs()) {
×
643
                return CraftingJobStatus.INVALID_INPUTS;
×
644
            }
645

646
            CraftingJobDependencyGraph dependencyGraph = network.getCraftingJobDependencyGraph();
×
647
            if (dependencyGraph.hasDependencies(craftingJobId)) {
×
648
                return CraftingJobStatus.PENDING_DEPENDENCIES;
×
649
            } else {
650
                if (!craftingJob.getLastMissingIngredients().isEmpty()) {
×
651
                    return CraftingJobStatus.PENDING_INGREDIENTS;
×
652
                } else {
653
                    return CraftingJobStatus.PENDING_INTERFACE;
×
654
                }
655
            }
656
        } else if (processingCraftingJobs.containsKey(craftingJobId)) {
×
657
            return CraftingJobStatus.PROCESSING;
×
658
        } else if (finishedCraftingJobs.containsKey(craftingJobId)) {
×
659
            return CraftingJobStatus.FINISHED;
×
660
        }
661
        return CraftingJobStatus.UNKNOWN;
×
662
    }
663

664
    public Int2ObjectMap<CraftingJob> getAllCraftingJobs() {
665
        return allCraftingJobs;
×
666
    }
667

668
    public void setIngredientComponentTarget(IngredientComponent<?, ?> ingredientComponent, @Nullable Direction side) {
669
        if (side == null) {
×
670
            this.ingredientComponentTargetOverrides.remove(ingredientComponent);
×
671
        } else {
672
            this.ingredientComponentTargetOverrides.put(ingredientComponent, side);
×
673
        }
674
    }
×
675

676
    @Nullable
677
    public Direction getIngredientComponentTarget(IngredientComponent<?, ?> ingredientComponent) {
678
        return this.ingredientComponentTargetOverrides.get(ingredientComponent);
×
679
    }
680

681
    public Function<IngredientComponent<?, ?>, PartPos> getTargetGetter(PartPos defaultPosition) {
682
        return ingredientComponent -> {
×
683
            Direction sideOverride = this.ingredientComponentTargetOverrides.get(ingredientComponent);
×
684
            if (sideOverride == null) {
×
685
                return defaultPosition;
×
686
            } else {
687
                return PartPos.of(defaultPosition.getPos(), sideOverride);
×
688
            }
689
        };
690
    }
691
}
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

© 2026 Coveralls, Inc