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

CyclopsMC / IntegratedCrafting / #479011835

02 Jan 2026 09:46AM UTC coverage: 24.316% (+0.5%) from 23.802%
#479011835

push

github

rubensworks
Fix crafting storage not dropping when breaking crafting interfaces

0 of 5 new or added lines in 1 file covered. (0.0%)

221 existing lines in 3 files now uncovered.

755 of 3105 relevant lines covered (24.32%)

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.cyclopscore.ingredient.collection.IIngredientCollectionMutable;
22
import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionPrototypeMap;
23
import org.cyclops.integratedcrafting.GeneralConfig;
24
import org.cyclops.integratedcrafting.IntegratedCrafting;
25
import org.cyclops.integratedcrafting.api.crafting.*;
26
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
27
import org.cyclops.integrateddynamics.api.network.INetwork;
28
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients;
29
import org.cyclops.integrateddynamics.api.part.PartPos;
30

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
223
        }
×
224

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

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

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

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

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

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

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

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

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

288
    public void fillCraftingJobBufferFromStorage(CraftingJob craftingJob, Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter) {
UNCOV
289
        if (!craftingJob.getIngredientsStorageBuffer().isEmpty()) {
×
290
            throw new IllegalStateException("Re-filling a non-empty crafting job buffer is illegal");
×
291
        }
292
        // 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.
UNCOV
293
        Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>> inputResult = CraftingHelpers.getRecipeInputs(storageGetter, craftingJob.getRecipe(), false, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), true, craftingJob.getAmount());
×
294
        IMixedIngredients buffer = new MixedIngredients(inputResult.getLeft());
×
295
        craftingJob.setIngredientsStorageBuffer(CraftingHelpers.compressMixedIngredients(buffer));
×
296
        craftingJob.setLastMissingIngredients(inputResult.getRight());
×
297
    }
×
298

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

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

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

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

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

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

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

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

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

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

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

375
    public void onCraftingJobFinished(CraftingJob craftingJob) {
UNCOV
376
        this.processingCraftingJobs.remove(craftingJob.getId());
×
UNCOV
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) {
UNCOV
384
        this.processingCraftingJobsPendingIngredients.remove(craftingJobId);
×
UNCOV
385
        this.processingCraftingJobs.remove(craftingJobId);
×
386
        this.pendingCraftingJobs.remove(craftingJobId);
×
387

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

394
    public void onCraftingJobEntryFinished(ICraftingNetwork craftingNetwork, int craftingJobId) {
UNCOV
395
        CraftingJob craftingJob = this.allCraftingJobs.get(craftingJobId);
×
UNCOV
396
        craftingJob.setAmount(craftingJob.getAmount() - 1);
×
397

398
        if (this.nonBlockingJobsRunningAmount.containsKey(craftingJobId)) {
×
399
            this.nonBlockingJobsRunningAmount.put(craftingJobId, this.nonBlockingJobsRunningAmount.get(craftingJobId) - 1);
×
400
        }
401

402
        // We mark each dependent job that it may attempt to be started,
403
        // because its (partially) finished dependency may have produced ingredients to already start part of this job.
UNCOV
404
        for (CraftingJob dependent : craftingNetwork.getCraftingJobDependencyGraph().getDependents(craftingJob)) {
×
405
            dependent.setIgnoreDependencyCheck(true);
×
406
        }
×
UNCOV
407
    }
×
408

409
    public void update(INetwork network, int channel, PartPos targetPos) {
410
        // Create creation-pending observers
UNCOV
411
        if (observersPendingCreation.size() > 0) {
×
UNCOV
412
            for (IngredientComponent<?, ?> ingredientComponent : observersPendingCreation) {
×
UNCOV
413
                registerIngredientObserver(ingredientComponent, network);
×
414
            }
×
415
            observersPendingCreation.clear();
×
416
        }
417

418
        // Remove removal-pending observers
UNCOV
419
        if (observersPendingDeletion.size() > 0) {
×
UNCOV
420
            for (IngredientComponent<?, ?> ingredientComponent : observersPendingDeletion) {
×
421
                unregisterIngredientObserver(ingredientComponent, network);
×
422
            }
×
423
            observersPendingDeletion.clear();
×
424
        }
425

426
        // Notify the network of finalized crafting jobs
UNCOV
427
        if (finishedCraftingJobs.size() > 0) {
×
UNCOV
428
            for (CraftingJob finishedCraftingJob : finishedCraftingJobs.values()) {
×
429
                if (finishedCraftingJob.getAmount() == 0) {
×
430
                    // If the job is fully finished, remove it from the network
431
                    ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(network);
×
432
                    craftingNetwork.onCraftingJobFinished(finishedCraftingJob);
×
433
                    allCraftingJobs.remove(finishedCraftingJob.getId());
×
UNCOV
434
                    nonBlockingJobsRunningAmount.remove(finishedCraftingJob.getId());
×
435

UNCOV
436
                    if (!finishedCraftingJob.getIngredientsStorageBuffer().isEmpty()) {
×
437
                        CraftingHelpers.insertIngredientsGuaranteed(finishedCraftingJob.getIngredientsStorageBuffer(), CraftingHelpers.getNetworkStorageGetter(network, channel, false), this.resultsSink);
×
438
                    }
439
                } else {
×
440
                    // Re-add it to the pending jobs list if entries are remaining
441
                    pendingCraftingJobs.put(finishedCraftingJob.getId(), finishedCraftingJob);
×
442
                }
443
            }
×
444
            finishedCraftingJobs.clear();
×
445
        }
446

447
        // The actual output observation of processing jobs is done via the ingredient observers
UNCOV
448
        int processingJobs = getProcessingCraftingJobs().size();
×
449

450
        // Enable the observers for the next tick
451
        if (processingJobs > 0) {
×
UNCOV
452
            for (IngredientComponent<?, ?> ingredientComponent : ingredientObservers.keySet()) {
×
453
                IPositionedAddonsNetworkIngredients<?, ?> ingredientsNetwork = CraftingHelpers.getIngredientsNetworkChecked(network, ingredientComponent);
×
454
                ingredientsNetwork.scheduleObservation();
×
UNCOV
455
            }
×
456
        }
457

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

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

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

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

543
            // Start the crafting job
UNCOV
544
            if (startingCraftingJob != null) {
×
545
                // Check if the job was started while blocking mode was enabled in this handler
UNCOV
546
                boolean blockingMode = !nonBlockingJobsRunningAmount.containsKey(startingCraftingJob.getId()) || startingCraftingJob.getAmount() == 1;
×
547

548
                // Start the actual crafting
UNCOV
549
                boolean couldCraft = consumeAndInsertCrafting(blockingMode, network, channel, targetPos, startingCraftingJob);
×
550

551
                // Keep inserting as much as possible if non-blocking
UNCOV
552
                if (couldCraft && !blockingMode) {
×
UNCOV
553
                    nonBlockingJobsRunningAmount.put(startingCraftingJob.getId(), 1);
×
554
                    insertLoopNonBlocking(network, channel, targetPos, startingCraftingJob);
×
555
                }
556
            }
557
        }
UNCOV
558
    }
×
559

560
    protected boolean insertCrafting(PartPos target, IMixedIngredients ingredients, IRecipeDefinition recipe, CraftingJob craftingJob, INetwork network, int channel, boolean simulate) {
UNCOV
561
        Function<IngredientComponent<?, ?>, PartPos> targetGetter = getTargetGetter(target);
×
562
        // First check our crafting overrides
563
        for (ICraftingProcessOverride craftingProcessOverride : this.craftingProcessOverrides) {
×
564
            if (craftingProcessOverride.isApplicable(target)) {
×
UNCOV
565
                return craftingProcessOverride.craft(targetGetter, ingredients, recipe, this.resultsSink, craftingJob, simulate);
×
566
            }
UNCOV
567
        }
×
568

569
        // Fallback to default crafting insertion
UNCOV
570
        return CraftingHelpers.insertCrafting(targetGetter, ingredients, network, channel, simulate);
×
571
    }
572

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

589
    protected boolean consumeAndInsertCrafting(boolean blockingMode, INetwork network, int channel, PartPos targetPos, CraftingJob startingCraftingJob) {
590
        // Remove ingredients from network
UNCOV
591
        IRecipeDefinition recipe = startingCraftingJob.getRecipe();
×
592
        IMixedIngredients ingredients = CraftingHelpers.getRecipeInputsFromCraftingJobBuffer(startingCraftingJob,
×
593
                recipe, false, 1);
594

595
        // This may not be null, error if it is null!
596
        if (ingredients != null) {
×
597
            this.pendingCraftingJobs.remove(startingCraftingJob.getId());
×
598

599
            // Update state with expected outputs
UNCOV
600
            addCraftingJobProcessingPendingIngredientsEntry(startingCraftingJob,
×
601
                    CraftingHelpers.getRecipeOutputs(startingCraftingJob.getRecipe()));
×
602

603
            // Register listeners for pending ingredients
UNCOV
604
            for (IngredientComponent<?, ?> component : startingCraftingJob.getRecipe().getOutput().getComponents()) {
×
UNCOV
605
                registerIngredientObserver(component, network);
×
606
            }
×
607

608
            // Push the ingredients to the crafting interface
UNCOV
609
            if (!insertCrafting(targetPos, ingredients, recipe, startingCraftingJob, network, channel, false)) {
×
610
                // Unregister listeners again for pending ingredients
611
                for (IngredientComponent<?, ?> component : startingCraftingJob.getRecipe().getOutput().getComponents()) {
×
UNCOV
612
                    unregisterIngredientObserver(component, network);
×
UNCOV
613
                }
×
614

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

631
    public CraftingJobStatus getCraftingJobStatus(ICraftingNetwork network, int channel, int craftingJobId) {
UNCOV
632
        if (pendingCraftingJobs.containsKey(craftingJobId)) {
×
633
            CraftingJob craftingJob = allCraftingJobs.get(craftingJobId);
×
UNCOV
634
            if (craftingJob != null && craftingJob.isInvalidInputs()) {
×
UNCOV
635
                return CraftingJobStatus.INVALID_INPUTS;
×
636
            }
637

UNCOV
638
            CraftingJobDependencyGraph dependencyGraph = network.getCraftingJobDependencyGraph();
×
UNCOV
639
            if (dependencyGraph.hasDependencies(craftingJobId)) {
×
UNCOV
640
                return CraftingJobStatus.PENDING_DEPENDENCIES;
×
641
            } else {
642
                if (!craftingJob.getLastMissingIngredients().isEmpty()) {
×
643
                    return CraftingJobStatus.PENDING_INGREDIENTS;
×
644
                } else {
645
                    return CraftingJobStatus.PENDING_INTERFACE;
×
646
                }
647
            }
648
        } else if (processingCraftingJobs.containsKey(craftingJobId)) {
×
649
            return CraftingJobStatus.PROCESSING;
×
650
        } else if (finishedCraftingJobs.containsKey(craftingJobId)) {
×
UNCOV
651
            return CraftingJobStatus.FINISHED;
×
652
        }
653
        return CraftingJobStatus.UNKNOWN;
×
654
    }
655

656
    public Int2ObjectMap<CraftingJob> getAllCraftingJobs() {
UNCOV
657
        return allCraftingJobs;
×
658
    }
659

660
    public void setIngredientComponentTarget(IngredientComponent<?, ?> ingredientComponent, @Nullable Direction side) {
661
        if (side == null) {
×
UNCOV
662
            this.ingredientComponentTargetOverrides.remove(ingredientComponent);
×
663
        } else {
UNCOV
664
            this.ingredientComponentTargetOverrides.put(ingredientComponent, side);
×
665
        }
UNCOV
666
    }
×
667

668
    @Nullable
669
    public Direction getIngredientComponentTarget(IngredientComponent<?, ?> ingredientComponent) {
UNCOV
670
        return this.ingredientComponentTargetOverrides.get(ingredientComponent);
×
671
    }
672

673
    public Function<IngredientComponent<?, ?>, PartPos> getTargetGetter(PartPos defaultPosition) {
674
        return ingredientComponent -> {
×
UNCOV
675
            Direction sideOverride = this.ingredientComponentTargetOverrides.get(ingredientComponent);
×
676
            if (sideOverride == null) {
×
UNCOV
677
                return defaultPosition;
×
678
            } else {
UNCOV
679
                return PartPos.of(defaultPosition.getPos(), sideOverride);
×
680
            }
681
        };
682
    }
683

684
    /**
685
     * This method is called right before a crafting interface's result buffer is flushed to the network.
686
     * This will first try to pass the instance along to crafting jobs that have pending ingredients.
687
     * The remaining instance that could not be inserted into any of those crafting jobs is returned.
688
     * @param instanceWrapper The instance that would be inserted into the network.
689
     * @param channel The channel.
690
     * @return The remaining instance that was not consumed by observers.
691
     * @param <T> The ingredient type.
692
     * @param <M> The match condition.
693
     */
694
    public <T, M> IngredientInstanceWrapper<T, M> beforeFlushIngredientToNetwork(IngredientInstanceWrapper<T, M> instanceWrapper, int channel) {
UNCOV
695
        PendingCraftingJobResultIndexObserver<T, M> observer = (PendingCraftingJobResultIndexObserver<T, M>) ingredientObservers.get(instanceWrapper.getComponent());
×
UNCOV
696
        if (observer != null) {
×
UNCOV
697
            IIngredientCollectionMutable<T, M> instances = new IngredientCollectionPrototypeMap<>(instanceWrapper.getComponent());
×
UNCOV
698
            instances.add(instanceWrapper.getInstance());
×
UNCOV
699
            return observer.addIngredient(instanceWrapper, channel, false);
×
700
        }
UNCOV
701
        return instanceWrapper;
×
702
    }
703
}
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