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

CyclopsMC / IntegratedCrafting / #479011834

31 Dec 2025 02:03PM UTC coverage: 23.802% (-1.1%) from 24.876%
#479011834

push

github

rubensworks
Fix incorrect commoncapabilities version

755 of 3172 relevant lines covered (23.8%)

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.ingredient.IIngredientComponentStorageObservable;
28
import org.cyclops.integrateddynamics.api.network.INetwork;
29
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients;
30
import org.cyclops.integrateddynamics.api.part.PartPos;
31

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

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

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

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

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

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

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

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

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

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

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

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

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

133
        ListTag finishedCraftingJobs = new ListTag();
×
134
        for (CraftingJob craftingJob : this.finishedCraftingJobs.values()) {
×
135
            finishedCraftingJobs.add(CraftingJob.serialize(craftingJob));
×
136
        }
×
137
        tag.put("finishedCraftingJobs", finishedCraftingJobs);
×
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
        ListTag finishedCraftingJobs = tag.getList("finishedCraftingJobs", Tag.TAG_COMPOUND);
×
234
        for (Tag craftingJob : finishedCraftingJobs) {
×
235
            CraftingJob craftingJobInstance = CraftingJob.deserialize((CompoundTag) craftingJob);
×
236
            this.finishedCraftingJobs.put(craftingJobInstance.getId(), craftingJobInstance);
×
237
            this.allCraftingJobs.put(craftingJobInstance.getId(), craftingJobInstance);
×
238
        }
×
239

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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