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

CyclopsMC / IntegratedCrafting / #479011789

10 May 2025 06:36AM UTC coverage: 25.162% (-0.5%) from 25.626%
#479011789

push

github

rubensworks
Add smithing table and stonecutter support, Closes #118

0 of 64 new or added lines in 6 files covered. (0.0%)

1 existing line in 1 file now uncovered.

737 of 2929 relevant lines covered (25.16%)

0.25 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.IIngredientSerializer;
20
import org.cyclops.commoncapabilities.api.ingredient.IMixedIngredients;
21
import org.cyclops.commoncapabilities.api.ingredient.IPrototypedIngredient;
22
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
23
import org.cyclops.commoncapabilities.api.ingredient.MixedIngredients;
24
import org.cyclops.commoncapabilities.api.ingredient.PrototypedIngredient;
25
import org.cyclops.integratedcrafting.GeneralConfig;
26
import org.cyclops.integratedcrafting.IntegratedCrafting;
27
import org.cyclops.integratedcrafting.api.crafting.CraftingJob;
28
import org.cyclops.integratedcrafting.api.crafting.CraftingJobDependencyGraph;
29
import org.cyclops.integratedcrafting.api.crafting.CraftingJobStatus;
30
import org.cyclops.integratedcrafting.api.crafting.ICraftingProcessOverride;
31
import org.cyclops.integratedcrafting.api.crafting.ICraftingResultsSink;
32
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
33
import org.cyclops.integrateddynamics.api.ingredient.IIngredientComponentStorageObservable;
34
import org.cyclops.integrateddynamics.api.network.INetwork;
35
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients;
36
import org.cyclops.integrateddynamics.api.part.PartPos;
37

38
import javax.annotation.Nullable;
39
import java.util.Collection;
40
import java.util.List;
41
import java.util.Map;
42
import java.util.function.Function;
43

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

58
    private final int maxProcessingJobs;
59
    private boolean blockingJobsMode;
60
    private final ICraftingResultsSink resultsSink;
61
    private final Collection<ICraftingProcessOverride> craftingProcessOverrides;
62

63
    private final Int2ObjectMap<CraftingJob> allCraftingJobs;
64
    private final Int2ObjectMap<CraftingJob> processingCraftingJobs;
65
    private final Int2ObjectMap<List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>>> processingCraftingJobsPendingIngredients;
66
    private final Int2ObjectMap<CraftingJob> pendingCraftingJobs;
67
    private final Object2IntMap<IngredientComponent<?, ?>> ingredientObserverCounters;
68
    private final Map<IngredientComponent<?, ?>, IIngredientComponentStorageObservable.IIndexChangeObserver<?, ?>> ingredientObservers;
69
    private final List<IngredientComponent<?, ?>> observersPendingCreation;
70
    private final List<IngredientComponent<?, ?>> observersPendingDeletion;
71
    private final Int2ObjectMap<CraftingJob> finishedCraftingJobs;
72
    private final Map<IngredientComponent<?, ?>, Direction> ingredientComponentTargetOverrides;
73
    private final Int2IntMap nonBlockingJobsRunningAmount;
74

75
    public CraftingJobHandler(int maxProcessingJobs, boolean blockingJobsMode,
76
                              Collection<ICraftingProcessOverride> craftingProcessOverrides,
77
                              ICraftingResultsSink resultsSink) {
×
78
        this.maxProcessingJobs = maxProcessingJobs;
×
79
        this.blockingJobsMode = blockingJobsMode;
×
80
        this.resultsSink = resultsSink;
×
81
        this.craftingProcessOverrides = craftingProcessOverrides;
×
82

83
        this.allCraftingJobs = new Int2ObjectOpenHashMap<>();
×
84
        this.processingCraftingJobs = new Int2ObjectOpenHashMap<>();
×
85
        this.pendingCraftingJobs = new Int2ObjectOpenHashMap<>();
×
86
        this.processingCraftingJobsPendingIngredients = new Int2ObjectOpenHashMap<>();
×
87
        this.ingredientObserverCounters = new Object2IntOpenHashMap<>();
×
88
        this.ingredientObservers = Maps.newIdentityHashMap();
×
89
        this.observersPendingCreation = Lists.newArrayList();
×
90
        this.observersPendingDeletion = Lists.newArrayList();
×
91
        this.finishedCraftingJobs = new Int2ObjectOpenHashMap<>();
×
92
        this.ingredientComponentTargetOverrides = Maps.newIdentityHashMap();
×
93
        this.nonBlockingJobsRunningAmount = new Int2IntOpenHashMap();
×
94
    }
×
95

96
    public void writeToNBT(CompoundTag tag) {
97
        tag.putBoolean("blockingJobsMode", this.blockingJobsMode);
×
98

99
        ListTag processingCraftingJobs = new ListTag();
×
100
        for (CraftingJob processingCraftingJob : this.processingCraftingJobs.values()) {
×
101
            CompoundTag entriesTag = new CompoundTag();
×
102
            entriesTag.put("craftingJob", CraftingJob.serialize(processingCraftingJob));
×
103

104
            List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>> ingredientsEntries = this.processingCraftingJobsPendingIngredients.get(processingCraftingJob.getId());
×
105
            ListTag pendingEntries = new ListTag();
×
106
            for (Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> ingredients : ingredientsEntries) {
×
107
                ListTag pendingIngredientInstances = new ListTag();
×
108
                for (Map.Entry<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> ingredientComponentListEntry : ingredients.entrySet()) {
×
109
                    CompoundTag ingredientInstance = new CompoundTag();
×
110

111
                    IngredientComponent<?, ?> ingredientComponent = ingredientComponentListEntry.getKey();
×
112
                    ingredientInstance.putString("ingredientComponent", IngredientComponent.REGISTRY.getKey(ingredientComponent).toString());
×
113

114
                    ListTag instances = new ListTag();
×
115
                    IIngredientSerializer serializer = ingredientComponent.getSerializer();
×
116
                    for (IPrototypedIngredient<?, ?> prototypedIngredient : ingredientComponentListEntry.getValue()) {
×
117
                        CompoundTag instance = new CompoundTag();
×
118
                        instance.put("prototype", serializer.serializeInstance(prototypedIngredient.getPrototype()));
×
119
                        instance.put("condition", serializer.serializeCondition(prototypedIngredient.getCondition()));
×
120
                        instances.add(instance);
×
121
                    }
×
122
                    ingredientInstance.put("instances", instances);
×
123

124
                    pendingIngredientInstances.add(ingredientInstance);
×
125
                }
×
126
                pendingEntries.add(pendingIngredientInstances);
×
127
            }
×
128
            entriesTag.put("pendingIngredientInstanceEntries", pendingEntries);
×
129
            processingCraftingJobs.add(entriesTag);
×
130
        }
×
131
        tag.put("processingCraftingJobs", processingCraftingJobs);
×
132

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

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

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

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

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

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

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

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

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

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

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

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

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

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

224
        }
×
225

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

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

242
        this.ingredientComponentTargetOverrides.clear();
×
243
        CompoundTag targetOverrides = tag.getCompound("targetOverrides");
×
244
        for (String componentName : targetOverrides.getAllKeys()) {
×
245
            IngredientComponent<?, ?> component = IngredientComponent.REGISTRY.getValue(new ResourceLocation(componentName));
×
246
            this.ingredientComponentTargetOverrides.put(component, Direction.values()[targetOverrides.getInt(componentName)]);
×
247
        }
×
248

249
        this.nonBlockingJobsRunningAmount.clear();
×
250
        CompoundTag nonBlockingJobsRunningAmount = tag.getCompound("nonBlockingJobsRunningAmount");
×
251
        for (String key : nonBlockingJobsRunningAmount.getAllKeys()) {
×
252
            int craftingJobId = Integer.parseInt(key);
×
253
            int amount = nonBlockingJobsRunningAmount.getInt(key);
×
254
            this.nonBlockingJobsRunningAmount.put(craftingJobId, amount);
×
255
        }
×
256
    }
×
257

258
    public boolean setBlockingJobsMode(boolean blockingJobsMode) {
259
        if (this.blockingJobsMode != blockingJobsMode) {
×
260
            this.blockingJobsMode = blockingJobsMode;
×
261
            return true;
×
262
        }
263
        return false;
×
264
    }
265

266
    public boolean isBlockingJobsMode() {
267
        return blockingJobsMode;
×
268
    }
269

270
    public boolean canScheduleCraftingJobs() {
271
        return this.pendingCraftingJobs.size() < GeneralConfig.maxPendingCraftingJobs;
×
272
    }
273

274
    public void scheduleCraftingJob(CraftingJob craftingJob) {
275
        this.pendingCraftingJobs.put(craftingJob.getId(), craftingJob);
×
276
        this.allCraftingJobs.put(craftingJob.getId(), craftingJob);
×
277
        if (!this.isBlockingJobsMode()) {
×
278
            this.nonBlockingJobsRunningAmount.put(craftingJob.getId(), 0);
×
279
        }
280
    }
×
281

282
    public Int2ObjectMap<List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>>> getProcessingCraftingJobsPendingIngredients() {
283
        return processingCraftingJobsPendingIngredients;
×
284
    }
285

286
    public Int2ObjectMap<CraftingJob> getProcessingCraftingJobsRaw() {
287
        return processingCraftingJobs;
×
288
    }
289

290
    public Collection<CraftingJob> getProcessingCraftingJobs() {
291
        return getProcessingCraftingJobsRaw().values();
×
292
    }
293

294
    public Collection<CraftingJob> getPendingCraftingJobs() {
295
        return pendingCraftingJobs.values();
×
296
    }
297

298
    public void unmarkCraftingJobProcessing(CraftingJob craftingJob) {
299
        if (this.processingCraftingJobs.remove(craftingJob.getId()) != null) {
×
300
            this.processingCraftingJobsPendingIngredients.remove(craftingJob.getId());
×
301
            this.pendingCraftingJobs.put(craftingJob.getId(), craftingJob);
×
302
        }
303
    }
×
304

305
    public void addCraftingJobProcessingPendingIngredientsEntry(CraftingJob craftingJob,
306
                                                                Map<IngredientComponent<?, ?>,
307
                                                                   List<IPrototypedIngredient<?, ?>>> pendingIngredients) {
308
        if (pendingIngredients.isEmpty()) {
×
309
            this.processingCraftingJobs.remove(craftingJob.getId());
×
310
            this.allCraftingJobs.remove(craftingJob.getId());
×
311
            this.nonBlockingJobsRunningAmount.remove(craftingJob.getId());
×
312
            this.processingCraftingJobsPendingIngredients.remove(craftingJob.getId());
×
313

314
        } else {
315
            this.processingCraftingJobs.put(craftingJob.getId(), craftingJob);
×
316
            this.allCraftingJobs.put(craftingJob.getId(), craftingJob);
×
317

318
            List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>> pendingIngredientsEntries = this.processingCraftingJobsPendingIngredients.get(craftingJob.getId());
×
319
            if (pendingIngredientsEntries == null) {
×
320
                pendingIngredientsEntries = Lists.newArrayList();
×
321
                this.processingCraftingJobsPendingIngredients.put(craftingJob.getId(), pendingIngredientsEntries);
×
322
            }
323
            pendingIngredientsEntries.add(pendingIngredients);
×
324
        }
325
    }
×
326

327
    public List<IngredientComponent<?, ?>> getObserversPendingDeletion() {
328
        return observersPendingDeletion;
×
329
    }
330

331
    protected <T, M> void registerIngredientObserver(IngredientComponent<T, M> ingredientComponent, INetwork network) {
332
        int count = ingredientObserverCounters.getInt(ingredientComponent);
×
333
        if (count == 0) {
×
334
            IPositionedAddonsNetworkIngredients<T, M> ingredientsNetwork = CraftingHelpers
×
335
                    .getIngredientsNetworkChecked(network, ingredientComponent);
×
336
            ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(network);
×
337
            PendingCraftingJobResultIndexObserver<T, M> observer = new PendingCraftingJobResultIndexObserver<>(ingredientComponent, this, craftingNetwork);
×
338
            ingredientsNetwork.addObserver(observer);
×
339
            ingredientsNetwork.scheduleObservation();
×
340
            ingredientObservers.put(ingredientComponent, observer);
×
341
        }
342
        ingredientObserverCounters.put(ingredientComponent, count + 1);
×
343
    }
×
344

345
    protected <T, M> void unregisterIngredientObserver(IngredientComponent<T, M> ingredientComponent, INetwork network) {
346
        int count = ingredientObserverCounters.getInt(ingredientComponent);
×
347
        count--;
×
348
        ingredientObserverCounters.put(ingredientComponent, count);
×
349
        if (count == 0) {
×
350
            IPositionedAddonsNetworkIngredients<T, M> ingredientsNetwork = CraftingHelpers
×
351
                    .getIngredientsNetworkChecked(network, ingredientComponent);
×
352
            IIngredientComponentStorageObservable.IIndexChangeObserver<T, M> observer =
×
353
                    (IIngredientComponentStorageObservable.IIndexChangeObserver<T, M>) ingredientObservers
354
                            .remove(ingredientComponent);
×
355
            ingredientsNetwork.removeObserver(observer);
×
356
        }
357
    }
×
358

359
    public void onCraftingJobFinished(CraftingJob craftingJob) {
360
        this.processingCraftingJobs.remove(craftingJob.getId());
×
361
        this.pendingCraftingJobs.remove(craftingJob.getId());
×
362
        this.finishedCraftingJobs.put(craftingJob.getId(), craftingJob);
×
363
        this.allCraftingJobs.put(craftingJob.getId(), craftingJob);
×
364
    }
×
365

366
    // This does the same as above, just based on crafting job id
367
    public void markCraftingJobFinished(int craftingJobId) {
368
        this.processingCraftingJobsPendingIngredients.remove(craftingJobId);
×
369
        this.processingCraftingJobs.remove(craftingJobId);
×
370
        this.pendingCraftingJobs.remove(craftingJobId);
×
371

372
        // Needed so that we remove the job in the next tick
373
        CraftingJob craftingJob = this.allCraftingJobs.get(craftingJobId);
×
374
        this.finishedCraftingJobs.put(craftingJobId, craftingJob);
×
375
        craftingJob.setAmount(0);
×
376
    }
×
377

378
    public void reRegisterObservers(INetwork network) {
379
        for (Map.Entry<IngredientComponent<?, ?>, IIngredientComponentStorageObservable.IIndexChangeObserver<?, ?>> entry : ingredientObservers.entrySet()) {
×
380
            IPositionedAddonsNetworkIngredients ingredientsNetwork = CraftingHelpers
×
381
                    .getIngredientsNetworkChecked(network, entry.getKey());
×
382
            ingredientsNetwork.addObserver(entry.getValue());
×
383
        }
×
384
    }
×
385

386
    public void onCraftingJobEntryFinished(ICraftingNetwork craftingNetwork, int craftingJobId) {
387
        CraftingJob craftingJob = this.allCraftingJobs.get(craftingJobId);
×
388
        craftingJob.setAmount(craftingJob.getAmount() - 1);
×
389

390
        if (this.nonBlockingJobsRunningAmount.containsKey(craftingJobId)) {
×
391
            this.nonBlockingJobsRunningAmount.put(craftingJobId, this.nonBlockingJobsRunningAmount.get(craftingJobId) - 1);
×
392
        }
393

394
        // We mark each dependent job that it may attempt to be started,
395
        // because its (partially) finished dependency may have produced ingredients to already start part of this job.
396
        for (CraftingJob dependent : craftingNetwork.getCraftingJobDependencyGraph().getDependents(craftingJob)) {
×
397
            dependent.setIgnoreDependencyCheck(true);
×
398
        }
×
399
    }
×
400

401
    public void update(INetwork network, int channel, PartPos targetPos) {
402
        // Create creation-pending observers
403
        if (observersPendingCreation.size() > 0) {
×
404
            for (IngredientComponent<?, ?> ingredientComponent : observersPendingCreation) {
×
405
                registerIngredientObserver(ingredientComponent, network);
×
406
            }
×
407
            observersPendingCreation.clear();
×
408
        }
409

410
        // Remove removal-pending observers
411
        if (observersPendingDeletion.size() > 0) {
×
412
            for (IngredientComponent<?, ?> ingredientComponent : observersPendingDeletion) {
×
413
                unregisterIngredientObserver(ingredientComponent, network);
×
414
            }
×
415
            observersPendingDeletion.clear();
×
416
        }
417

418
        // Notify the network of finalized crafting jobs
419
        if (finishedCraftingJobs.size() > 0) {
×
420
            for (CraftingJob finishedCraftingJob : finishedCraftingJobs.values()) {
×
421
                if (finishedCraftingJob.getAmount() == 0) {
×
422
                    // If the job is fully finished, remove it from the network
423
                    ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(network);
×
424
                    craftingNetwork.onCraftingJobFinished(finishedCraftingJob);
×
425
                    allCraftingJobs.remove(finishedCraftingJob.getId());
×
426
                    nonBlockingJobsRunningAmount.remove(finishedCraftingJob.getId());
×
427
                } else {
×
428
                    // Re-add it to the pending jobs list if entries are remaining
429
                    pendingCraftingJobs.put(finishedCraftingJob.getId(), finishedCraftingJob);
×
430
                }
431
            }
×
432
            finishedCraftingJobs.clear();
×
433
        }
434

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

438
        // Enable the observers for the next tick
439
        if (processingJobs > 0) {
×
440
            for (IngredientComponent<?, ?> ingredientComponent : ingredientObservers.keySet()) {
×
441
                IPositionedAddonsNetworkIngredients<?, ?> ingredientsNetwork = CraftingHelpers.getIngredientsNetworkChecked(network, ingredientComponent);
×
442
                ingredientsNetwork.scheduleObservation();
×
443
            }
×
444
        }
445

446
        // Process the jobs that are in non-blocking mode and still require amounts to be processed by re-trying insertion
447
        if (!this.nonBlockingJobsRunningAmount.isEmpty()) {
×
448
            for (Int2IntMap.Entry entry : this.nonBlockingJobsRunningAmount.int2IntEntrySet()) {
×
449
                int craftingJobId = entry.getIntKey();
×
450
                int runningAmount = entry.getIntValue();
×
451
                CraftingJob craftingJob = this.allCraftingJobs.get(craftingJobId);
×
452
                if (runningAmount > 0 && runningAmount < craftingJob.getAmount()) {
×
453
                    insertLoopNonBlocking(network, channel, targetPos, craftingJob);
×
454
                }
455
            }
×
456
        }
457

458
        if (processingJobs < this.maxProcessingJobs) {
×
459
            // Handle crafting jobs
460
            CraftingJob startingCraftingJob = null;
×
461
            ICraftingNetwork craftingNetwork = CraftingHelpers.getCraftingNetworkChecked(network);
×
462
            CraftingJobDependencyGraph dependencyGraph = craftingNetwork.getCraftingJobDependencyGraph();
×
463
            for (CraftingJob pendingCraftingJob : getPendingCraftingJobs()) {
×
464
                // Make sure that this crafting job has no incomplete dependency jobs
465
                // This check can be overridden if the ignoreDependencyCheck flag is set
466
                // (which is done once a dependent finishes a job entry).
467
                // This override only applies for a single tick.
468
                if (dependencyGraph.hasDependencies(pendingCraftingJob) && !pendingCraftingJob.isIgnoreDependencyCheck()) {
×
469
                    continue;
×
470
                }
471
                if (pendingCraftingJob.isIgnoreDependencyCheck()) {
×
472
                    pendingCraftingJob.setIgnoreDependencyCheck(false);
×
473
                }
474

475
                // Check if pendingCraftingJob can start and set as startingCraftingJob
476
                // This requires checking the available ingredients AND if the crafting handler can accept it.
NEW
477
                IRecipeDefinition recipe = pendingCraftingJob.getRecipe();
×
478
                Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>> inputs = CraftingHelpers.getRecipeInputs(
×
479
                        CraftingHelpers.getNetworkStorageGetter(network, pendingCraftingJob.getChannel(), false),
×
NEW
480
                        recipe, true, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), true, 1);
×
481
                if (inputs.getRight().isEmpty()) { // If we have no missing ingredients
×
NEW
482
                    if (insertCrafting(targetPos, new MixedIngredients(inputs.getLeft()), recipe, network, channel, true)) {
×
483
                        startingCraftingJob = pendingCraftingJob;
×
484
                        startingCraftingJob.setInvalidInputs(false);
×
485
                        break;
×
486
                    } else {
487
                        pendingCraftingJob.setInvalidInputs(true);
×
488
                    }
489
                } else {
490
                    // Register listeners for pending ingredients
491
                    if (pendingCraftingJob.getLastMissingIngredients().isEmpty()) {
×
492
                        for (IngredientComponent<?, ?> component : inputs.getRight().keySet()) {
×
493
                            registerIngredientObserver(component, network);
×
494

495
                            // For the missing ingredients that are reusable,
496
                            // trigger a crafting job for them if no job is running yet.
497
                            // This special case is needed because reusable ingredients are usually durability-based,
498
                            // and may be consumed _during_ a bulk crafting job.
499
                            MissingIngredients<?, ?> missingIngredients = inputs.getRight().get(component);
×
500
                            for (MissingIngredients.Element<?, ?> element : missingIngredients.getElements()) {
×
501
                                if (element.isInputReusable()) {
×
502
                                    for (MissingIngredients.PrototypedWithRequested alternative : element.getAlternatives()) {
×
503
                                        // Try to start crafting jobs for each alternative until one of them succeeds.
504
                                        if (CraftingHelpers.isCrafting(craftingNetwork, channel,
×
505
                                                alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition())) {
×
506
                                            // Break loop if we have found an existing job for our dependency
507
                                            // This may occur if a crafting job was triggered in a parallelized job
508
                                            break;
×
509
                                        }
510
                                        CraftingJob craftingJob = CraftingHelpers.calculateAndScheduleCraftingJob(network, channel,
×
511
                                                alternative.getRequestedPrototype().getComponent(), alternative.getRequestedPrototype().getPrototype(), alternative.getRequestedPrototype().getCondition(), true, true,
×
512
                                                CraftingHelpers.getGlobalCraftingJobIdentifier(), null);
×
513
                                        if (craftingJob != null) {
×
514
                                            pendingCraftingJob.addDependency(craftingJob);
×
515
                                            // Break loop once we have found a valid job
516
                                            break;
×
517
                                        }
518
                                    }
×
519
                                }
520
                            }
×
521
                        }
×
522
                    }
523

524
                    pendingCraftingJob.setLastMissingIngredients(inputs.getRight());
×
525
                }
526
            }
×
527

528
            // Start the crafting job
529
            if (startingCraftingJob != null) {
×
530
                // If the job previously had missing in ingredients, unregister the observers that were previously created for it.
531
                if (!startingCraftingJob.getLastMissingIngredients().isEmpty()) {
×
532
                    for (IngredientComponent<?, ?> component : startingCraftingJob.getLastMissingIngredients().keySet()) {
×
533
                        unregisterIngredientObserver(component, network);
×
534
                    }
×
535
                    startingCraftingJob.setLastMissingIngredients(Maps.newIdentityHashMap());
×
536
                }
537

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

541
                // Start the actual crafting
542
                boolean couldCraft = consumeAndInsertCrafting(blockingMode, network, channel, targetPos, startingCraftingJob);
×
543

544
                // Keep inserting as much as possible if non-blocking
545
                if (couldCraft && !blockingMode) {
×
546
                    nonBlockingJobsRunningAmount.put(startingCraftingJob.getId(), 1);
×
547
                    insertLoopNonBlocking(network, channel, targetPos, startingCraftingJob);
×
548
                }
549
            }
550
        }
551
    }
×
552

553
    protected boolean insertCrafting(PartPos target, IMixedIngredients ingredients, IRecipeDefinition recipe, INetwork network, int channel, boolean simulate) {
554
        Function<IngredientComponent<?, ?>, PartPos> targetGetter = getTargetGetter(target);
×
555
        // First check our crafting overrides
556
        for (ICraftingProcessOverride craftingProcessOverride : this.craftingProcessOverrides) {
×
557
            if (craftingProcessOverride.isApplicable(target)) {
×
NEW
558
                return craftingProcessOverride.craft(targetGetter, ingredients, recipe, this.resultsSink, simulate);
×
559
            }
560
        }
×
561

562
        // Fallback to default crafting insertion
563
        return CraftingHelpers.insertCrafting(targetGetter, ingredients, network, channel, simulate);
×
564
    }
565

566
    protected void insertLoopNonBlocking(INetwork network, int channel, PartPos targetPos, CraftingJob craftingJob) {
567
        // If in non-blocking mode, try to push as much as possible into the target
568
        while (nonBlockingJobsRunningAmount.get(craftingJob.getId()) < craftingJob.getAmount()) {
×
NEW
569
            IRecipeDefinition recipe = craftingJob.getRecipe();
×
570
            IMixedIngredients ingredientsSimulated = CraftingHelpers.getRecipeInputs(network, craftingJob.getChannel(),
×
571
                    recipe, true, 1);
NEW
572
            if (ingredientsSimulated == null ||!insertCrafting(targetPos, ingredientsSimulated, recipe, network, channel, true)) {
×
UNCOV
573
                break;
×
574
            }
575
            if (!consumeAndInsertCrafting(true, network, channel, targetPos, craftingJob)) {
×
576
                break;
×
577
            }
578
            nonBlockingJobsRunningAmount.put(craftingJob.getId(), nonBlockingJobsRunningAmount.get(craftingJob.getId()) + 1);
×
579
        }
×
580
    }
×
581

582
    protected boolean consumeAndInsertCrafting(boolean blockingMode, INetwork network, int channel, PartPos targetPos, CraftingJob startingCraftingJob) {
583
        // Remove ingredients from network
NEW
584
        IRecipeDefinition recipe = startingCraftingJob.getRecipe();
×
585
        IMixedIngredients ingredients = CraftingHelpers.getRecipeInputs(network, startingCraftingJob.getChannel(),
×
586
                recipe, false, 1);
587

588
        // This may not be null, error if it is null!
589
        if (ingredients != null) {
×
590
            this.pendingCraftingJobs.remove(startingCraftingJob.getId());
×
591

592
            // Update state with expected outputs
593
            addCraftingJobProcessingPendingIngredientsEntry(startingCraftingJob,
×
594
                    CraftingHelpers.getRecipeOutputs(startingCraftingJob.getRecipe()));
×
595

596
            // Register listeners for pending ingredients
597
            for (IngredientComponent<?, ?> component : startingCraftingJob.getRecipe().getOutput().getComponents()) {
×
598
                registerIngredientObserver(component, network);
×
599
            }
×
600

601
            // Push the ingredients to the crafting interface
NEW
602
            if (!insertCrafting(targetPos, ingredients, recipe, network, channel, false)) {
×
603
                // Unregister listeners again for pending ingredients
604
                for (IngredientComponent<?, ?> component : startingCraftingJob.getRecipe().getOutput().getComponents()) {
×
605
                    unregisterIngredientObserver(component, network);
×
606
                }
×
607

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

624
    public CraftingJobStatus getCraftingJobStatus(ICraftingNetwork network, int channel, int craftingJobId) {
625
        if (pendingCraftingJobs.containsKey(craftingJobId)) {
×
626
            CraftingJob craftingJob = allCraftingJobs.get(craftingJobId);
×
627
            if (craftingJob != null && craftingJob.isInvalidInputs()) {
×
628
                return CraftingJobStatus.INVALID_INPUTS;
×
629
            }
630

631
            CraftingJobDependencyGraph dependencyGraph = network.getCraftingJobDependencyGraph();
×
632
            if (dependencyGraph.hasDependencies(craftingJobId)) {
×
633
                return CraftingJobStatus.PENDING_DEPENDENCIES;
×
634
            } else {
635
                if (!craftingJob.getLastMissingIngredients().isEmpty()) {
×
636
                    return CraftingJobStatus.PENDING_INGREDIENTS;
×
637
                } else {
638
                    return CraftingJobStatus.PENDING_INTERFACE;
×
639
                }
640
            }
641
        } else if (processingCraftingJobs.containsKey(craftingJobId)) {
×
642
            return CraftingJobStatus.PROCESSING;
×
643
        } else if (finishedCraftingJobs.containsKey(craftingJobId)) {
×
644
            return CraftingJobStatus.FINISHED;
×
645
        }
646
        return CraftingJobStatus.UNKNOWN;
×
647
    }
648

649
    public Int2ObjectMap<CraftingJob> getAllCraftingJobs() {
650
        return allCraftingJobs;
×
651
    }
652

653
    public void setIngredientComponentTarget(IngredientComponent<?, ?> ingredientComponent, @Nullable Direction side) {
654
        if (side == null) {
×
655
            this.ingredientComponentTargetOverrides.remove(ingredientComponent);
×
656
        } else {
657
            this.ingredientComponentTargetOverrides.put(ingredientComponent, side);
×
658
        }
659
    }
×
660

661
    @Nullable
662
    public Direction getIngredientComponentTarget(IngredientComponent<?, ?> ingredientComponent) {
663
        return this.ingredientComponentTargetOverrides.get(ingredientComponent);
×
664
    }
665

666
    public Function<IngredientComponent<?, ?>, PartPos> getTargetGetter(PartPos defaultPosition) {
667
        return ingredientComponent -> {
×
668
            Direction sideOverride = this.ingredientComponentTargetOverrides.get(ingredientComponent);
×
669
            if (sideOverride == null) {
×
670
                return defaultPosition;
×
671
            } else {
672
                return PartPos.of(defaultPosition.getPos(), sideOverride);
×
673
            }
674
        };
675
    }
676
}
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