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

CyclopsMC / IntegratedCrafting / #479011729

10 Jun 2024 03:02PM UTC coverage: 25.044% (+0.1%) from 24.947%
#479011729

push

github

rubensworks
Update to NeoForge 1.20.4

0 of 52 new or added lines in 14 files covered. (0.0%)

3 existing lines in 3 files now uncovered.

706 of 2819 relevant lines covered (25.04%)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

223
        }
×
224

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

522
                    pendingCraftingJob.setLastMissingIngredients(inputs.getRight());
×
523
                }
524
            }
×
525

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

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

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

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

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

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

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

579
    protected boolean consumeAndInsertCrafting(boolean blockingMode, INetwork network, int channel, PartPos targetPos, CraftingJob startingCraftingJob) {
580
        // Remove ingredients from network
581
        IMixedIngredients ingredients = CraftingHelpers.getRecipeInputs(network, startingCraftingJob.getChannel(),
×
582
                startingCraftingJob.getRecipe(), false, 1);
×
583

584
        // This may not be null, error if it is null!
585
        if (ingredients != null) {
×
586
            this.pendingCraftingJobs.remove(startingCraftingJob.getId());
×
587

588
            // Update state with expected outputs
589
            addCraftingJobProcessingPendingIngredientsEntry(startingCraftingJob,
×
590
                    CraftingHelpers.getRecipeOutputs(startingCraftingJob.getRecipe()));
×
591

592
            // Register listeners for pending ingredients
593
            for (IngredientComponent<?, ?> component : startingCraftingJob.getRecipe().getOutput().getComponents()) {
×
594
                registerIngredientObserver(component, network);
×
595
            }
×
596

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

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

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

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

645
    public Int2ObjectMap<CraftingJob> getAllCraftingJobs() {
646
        return allCraftingJobs;
×
647
    }
648

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

657
    @Nullable
658
    public Direction getIngredientComponentTarget(IngredientComponent<?, ?> ingredientComponent) {
659
        return this.ingredientComponentTargetOverrides.get(ingredientComponent);
×
660
    }
661

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