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

CyclopsMC / IntegratedCrafting / #479011824

30 Dec 2025 07:13AM UTC coverage: 24.402% (+0.2%) from 24.214%
#479011824

push

github

rubensworks
Fix split jobs having incorrect buffer storage

0 of 3 new or added lines in 2 files covered. (0.0%)

1 existing line in 1 file now uncovered.

755 of 3094 relevant lines covered (24.4%)

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
        IMixedIngredients buffer = new MixedIngredients(CraftingHelpers.getRecipeInputs(storageGetter, craftingJob.getRecipe(), false, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), true, craftingJob.getAmount()).getLeft());
×
NEW
293
        craftingJob.setIngredientsStorageBuffer(CraftingHelpers.compressMixedIngredients(buffer));
×
UNCOV
294
    }
×
295

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

493
                // Check if pendingCraftingJob can start and set as startingCraftingJob
494
                // This requires checking the available ingredients AND if the crafting handler can accept it.
495
                IRecipeDefinition recipe = pendingCraftingJob.getRecipe();
×
496
                Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>> inputs = CraftingHelpers.getRecipeInputs(
×
497
                        CraftingHelpers.getCraftingJobBufferStorageGetter(pendingCraftingJob),
×
498
                        recipe, true, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), true, 1);
×
499
                if (inputs.getRight().isEmpty()) { // If we have no missing ingredients
×
500
                    if (insertCrafting(targetPos, new MixedIngredients(inputs.getLeft()), recipe, network, channel, true)) {
×
501
                        startingCraftingJob = pendingCraftingJob;
×
502
                        startingCraftingJob.setInvalidInputs(false);
×
503
                        break;
×
504
                    } else {
505
                        pendingCraftingJob.setInvalidInputs(true);
×
506
                    }
507
                } else {
508
                    // Register listeners for pending ingredients
509
                    if (pendingCraftingJob.getLastMissingIngredients().isEmpty()) {
×
510
                        for (IngredientComponent<?, ?> component : inputs.getRight().keySet()) {
×
511
                            registerIngredientObserver(component, network);
×
512

513
                            // For the missing ingredients that are reusable,
514
                            // trigger a crafting job for them if no job is running yet.
515
                            // This special case is needed because reusable ingredients are usually durability-based,
516
                            // and may be consumed _during_ a bulk crafting job.
517
                            MissingIngredients<?, ?> missingIngredients = inputs.getRight().get(component);
×
518
                            for (MissingIngredients.Element<?, ?> element : missingIngredients.getElements()) {
×
519
                                if (element.isInputReusable()) {
×
520
                                    for (MissingIngredients.PrototypedWithRequested alternative : element.getAlternatives()) {
×
521
                                        // Try to start crafting jobs for each alternative until one of them succeeds.
522
                                        if (CraftingHelpers.isCrafting(craftingNetwork, channel,
×
523
                                                alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition())) {
×
524
                                            // Break loop if we have found an existing job for our dependency
525
                                            // This may occur if a crafting job was triggered in a parallelized job
526
                                            break;
×
527
                                        }
528
                                        CraftingJob craftingJob = CraftingHelpers.calculateAndScheduleCraftingJob(network, channel,
×
529
                                                alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition(), true, true,
×
530
                                                CraftingHelpers.getGlobalCraftingJobIdentifier(), null);
×
531
                                        if (craftingJob != null) {
×
532
                                            pendingCraftingJob.addDependency(craftingJob);
×
533
                                            // Break loop once we have found a valid job
534
                                            break;
×
535
                                        }
536
                                    }
×
537
                                }
538
                            }
×
539
                        }
×
540
                    }
541

542
                    pendingCraftingJob.setLastMissingIngredients(inputs.getRight());
×
543
                }
544
            }
×
545

546
            // Start the crafting job
547
            if (startingCraftingJob != null) {
×
548
                // If the job previously had missing in ingredients, unregister the observers that were previously created for it.
549
                if (!startingCraftingJob.getLastMissingIngredients().isEmpty()) {
×
550
                    for (IngredientComponent<?, ?> component : startingCraftingJob.getLastMissingIngredients().keySet()) {
×
551
                        unregisterIngredientObserver(component, network);
×
552
                    }
×
553
                    startingCraftingJob.setLastMissingIngredients(Maps.newIdentityHashMap());
×
554
                }
555

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

559
                // Start the actual crafting
560
                boolean couldCraft = consumeAndInsertCrafting(blockingMode, network, channel, targetPos, startingCraftingJob);
×
561

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

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

580
        // Fallback to default crafting insertion
581
        return CraftingHelpers.insertCrafting(targetGetter, ingredients, network, channel, simulate);
×
582
    }
583

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

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

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

610
            // Update state with expected outputs
611
            addCraftingJobProcessingPendingIngredientsEntry(startingCraftingJob,
×
612
                    CraftingHelpers.getRecipeOutputs(startingCraftingJob.getRecipe()));
×
613

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

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

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

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

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

667
    public Int2ObjectMap<CraftingJob> getAllCraftingJobs() {
668
        return allCraftingJobs;
×
669
    }
670

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

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

684
    public Function<IngredientComponent<?, ?>, PartPos> getTargetGetter(PartPos defaultPosition) {
685
        return ingredientComponent -> {
×
686
            Direction sideOverride = this.ingredientComponentTargetOverrides.get(ingredientComponent);
×
687
            if (sideOverride == null) {
×
688
                return defaultPosition;
×
689
            } else {
690
                return PartPos.of(defaultPosition.getPos(), sideOverride);
×
691
            }
692
        };
693
    }
694
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc