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

CyclopsMC / IntegratedCrafting / #479011831

31 Dec 2025 10:58AM UTC coverage: 23.802% (-0.2%) from 24.029%
#479011831

push

github

rubensworks
Avoid need for running synchronous observers

Instead, we now run crafting results through crafting job completion
logic, before flushing it to the network and relying on observer logic.

This used to cause performance issues.

Related to #112

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

71 existing lines in 4 files now uncovered.

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) {
NEW
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) {
NEW
705
        PendingCraftingJobResultIndexObserver<T, M> observer = (PendingCraftingJobResultIndexObserver<T, M>) ingredientObservers.get(instanceWrapper.getComponent());
×
NEW
706
        if (observer != null) {
×
NEW
707
            IIngredientCollectionMutable<T, M> instances = new IngredientCollectionPrototypeMap<>(instanceWrapper.getComponent());
×
NEW
708
            instances.add(instanceWrapper.getInstance());
×
NEW
709
            return observer.addIngredient(instanceWrapper, channel);
×
710
        }
NEW
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

© 2026 Coveralls, Inc