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

CyclopsMC / IntegratedCrafting / #479011850

09 Mar 2026 07:58AM UTC coverage: 24.357% (+0.06%) from 24.3%
#479011850

push

github

rubensworks
Bump mod version

758 of 3112 relevant lines covered (24.36%)

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,
70
                              ICraftingResultsSink resultsSink) {
×
71
        this.maxProcessingJobs = maxProcessingJobs;
×
72
        this.blockingJobsMode = blockingJobsMode;
×
73
        this.resultsSink = resultsSink;
×
74
        this.craftingProcessOverrides = craftingProcessOverrides;
×
75

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) {
90
        tag.putBoolean("blockingJobsMode", this.blockingJobsMode);
×
91

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

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

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

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

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

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

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

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

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) {
152
        if (tag.contains("blockingJobsMode")) {
×
153
            this.blockingJobsMode = tag.getBoolean("blockingJobsMode");
×
154
        }
155

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

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
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
                    }
172
                    IIngredientSerializer serializer = ingredientComponent.getSerializer();
×
173

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

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

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
                        }
198
                        IIngredientSerializer serializer = ingredientComponent.getSerializer();
×
199

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

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

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

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

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

223
        }
×
224

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

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
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.
244
                observersPendingCreation.addAll(value.keySet());
×
245
            }
×
246
        }
×
247

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

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) {
265
        if (this.blockingJobsMode != blockingJobsMode) {
×
266
            this.blockingJobsMode = blockingJobsMode;
×
267
            return true;
×
268
        }
269
        return false;
×
270
    }
271

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

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

280
    public void scheduleCraftingJob(CraftingJob craftingJob) {
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
        }
286
    }
×
287

288
    public void fillCraftingJobBufferFromStorage(CraftingJob craftingJob, Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter) {
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.
293
        // If this job has dependencies, skip reusable ingredients so that they remain available for other jobs.
294
        // They will be lazily extracted in the update loop once the dependencies have finished.
295
        boolean skipReusableIngredients = !craftingJob.getDependencyCraftingJobs().isEmpty();
×
296
        Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>> inputResult = CraftingHelpers.getRecipeInputs(storageGetter, craftingJob.getRecipe(), false, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), true, craftingJob.getAmount(), skipReusableIngredients);
×
297
        IMixedIngredients buffer = new MixedIngredients(inputResult.getLeft());
×
298
        craftingJob.setIngredientsStorageBuffer(CraftingHelpers.compressMixedIngredients(buffer));
×
299
        craftingJob.setLastMissingIngredients(inputResult.getRight());
×
300
    }
×
301

302
    public Int2ObjectMap<List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>>> getProcessingCraftingJobsPendingIngredients() {
303
        return processingCraftingJobsPendingIngredients;
×
304
    }
305

306
    public Int2ObjectMap<CraftingJob> getProcessingCraftingJobsRaw() {
307
        return processingCraftingJobs;
×
308
    }
309

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

314
    public Collection<CraftingJob> getPendingCraftingJobs() {
315
        return pendingCraftingJobs.values();
×
316
    }
317

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

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

334
        } else {
335
            this.processingCraftingJobs.put(craftingJob.getId(), craftingJob);
×
336
            this.allCraftingJobs.put(craftingJob.getId(), craftingJob);
×
337

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

347
    public List<IngredientComponent<?, ?>> getObserversPendingDeletion() {
348
        return observersPendingDeletion;
×
349
    }
350

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

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

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

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

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

397
    public void onCraftingJobEntryFinished(ICraftingNetwork craftingNetwork, int craftingJobId) {
398
        CraftingJob craftingJob = this.allCraftingJobs.get(craftingJobId);
×
399
        craftingJob.setAmount(craftingJob.getAmount() - 1);
×
400

401
        if (this.nonBlockingJobsRunningAmount.containsKey(craftingJobId)) {
×
402
            this.nonBlockingJobsRunningAmount.put(craftingJobId, this.nonBlockingJobsRunningAmount.get(craftingJobId) - 1);
×
403
        }
404

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

574
        // Fallback to default crafting insertion
575
        return CraftingHelpers.insertCrafting(targetGetter, ingredients, network, channel, simulate);
×
576
    }
577

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

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

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

604
            // Update state with expected outputs
605
            addCraftingJobProcessingPendingIngredientsEntry(startingCraftingJob,
×
606
                    CraftingHelpers.getRecipeOutputs(startingCraftingJob.getRecipe()));
×
607

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

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

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

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

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

661
    public Int2ObjectMap<CraftingJob> getAllCraftingJobs() {
662
        return allCraftingJobs;
×
663
    }
664

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

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

678
    public Function<IngredientComponent<?, ?>, PartPos> getTargetGetter(PartPos defaultPosition) {
679
        return ingredientComponent -> {
×
680
            Direction sideOverride = this.ingredientComponentTargetOverrides.get(ingredientComponent);
×
681
            if (sideOverride == null) {
×
682
                return defaultPosition;
×
683
            } else {
684
                return PartPos.of(defaultPosition.getPos(), sideOverride);
×
685
            }
686
        };
687
    }
688

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