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

CyclopsMC / IntegratedCrafting / #479011830

31 Dec 2025 08:35AM UTC coverage: 24.029% (-0.3%) from 24.3%
#479011830

push

github

rubensworks
Fix missing ingredients not taken into account when piping job results to dependents

0 of 51 new or added lines in 4 files covered. (0.0%)

4 existing lines in 3 files now uncovered.

755 of 3142 relevant lines covered (24.03%)

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/PendingCraftingJobResultIndexObserver.java
1
package org.cyclops.integratedcrafting.core;
2

3
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
4
import it.unimi.dsi.fastutil.ints.IntIterator;
5
import it.unimi.dsi.fastutil.objects.ObjectIterator;
6
import org.cyclops.commoncapabilities.api.ingredient.IIngredientMatcher;
7
import org.cyclops.commoncapabilities.api.ingredient.IPrototypedIngredient;
8
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
9
import org.cyclops.commoncapabilities.api.ingredient.PrototypedIngredient;
10
import org.cyclops.commoncapabilities.api.ingredient.storage.IIngredientComponentStorage;
11
import org.cyclops.cyclopscore.ingredient.collection.IIngredientCollection;
12
import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionPrototypeMap;
13
import org.cyclops.cyclopscore.ingredient.storage.IngredientComponentStorageCollectionWrapper;
14
import org.cyclops.integratedcrafting.IntegratedCrafting;
15
import org.cyclops.integratedcrafting.api.crafting.CraftingJob;
16
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
17
import org.cyclops.integrateddynamics.api.ingredient.IIngredientComponentStorageObservable;
18
import org.cyclops.integrateddynamics.api.network.INetwork;
19
import org.cyclops.integrateddynamics.api.network.INetworkIngredientsChannel;
20
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetwork;
21
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients;
22

23
import java.util.Iterator;
24
import java.util.List;
25
import java.util.ListIterator;
26
import java.util.Map;
27

28
/**
29
 * An ingredient index observer that tracks crafting job outputs for a certain ingredient component type.
30
 *
31
 * It will observe changes and (partially) resolve awaiting crafting job outputs when applicable.
32
 *
33
 * @author rubensworks
34
 */
35
public class PendingCraftingJobResultIndexObserver<T, M>
36
        implements IIngredientComponentStorageObservable.IIndexChangeObserver<T, M> {
37

38
    private final IngredientComponent<T, M> ingredientComponent;
39
    private final CraftingJobHandler handler;
40
    private final ICraftingNetwork craftingNetwork;
41
    private final IPositionedAddonsNetworkIngredients<T, M> ingredientsNetwork;
42
    private final INetwork network;
43

44
    public PendingCraftingJobResultIndexObserver(IngredientComponent<T, M> ingredientComponent, CraftingJobHandler handler, ICraftingNetwork craftingNetwork, IPositionedAddonsNetworkIngredients<T, M> ingredientsNetwork, INetwork network) {
×
45
        this.ingredientComponent = ingredientComponent;
×
46
        this.handler = handler;
×
47
        this.craftingNetwork = craftingNetwork;
×
48
        this.ingredientsNetwork = ingredientsNetwork;
×
49
        this.network = network;
×
50
    }
×
51

52
    @Override
53
    public void onChange(IIngredientComponentStorageObservable.StorageChangeEvent<T, M> event) {
54
        if (event.getChangeType() == IIngredientComponentStorageObservable.Change.ADDITION
×
55
                // If we're still initializing the network, skip addition events.
56
                // Otherwise, we could incorrectly mark running crafting jobs as finished.
57
                && !event.isInitialChange()) {
×
58
            IIngredientCollection<T, M> addedIngredients = event.getInstances();
×
59
            IIngredientComponentStorage<T, M> ingredientsHayStack = null; // A mutable copy of addedIngredients (lazily created)
×
60
            IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
×
61

62
            Int2ObjectMap<List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>>> processingJobs = handler.getProcessingCraftingJobsPendingIngredients();
×
63
            ObjectIterator<Int2ObjectMap.Entry<List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>>>> jobsEntryIt = processingJobs.int2ObjectEntrySet().iterator();
×
64
            while (jobsEntryIt.hasNext()) {
×
65
                Int2ObjectMap.Entry<List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>>> jobsEntry = jobsEntryIt.next();
×
66
                int craftingJobId = jobsEntry.getIntKey();
×
67
                // Only check jobs that have a matching channel with the event
68
                CraftingJob craftingJob = handler.getAllCraftingJobs().get(jobsEntry.getIntKey());
×
69
                if (craftingJob != null
×
70
                        && (craftingJob.getChannel() == IPositionedAddonsNetwork.WILDCARD_CHANNEL || craftingJob.getChannel() == event.getChannel())) {
×
71
                    Iterator<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>> jobEntryIt = jobsEntry.getValue().iterator();
×
72
                    while (jobEntryIt.hasNext()) {
×
73
                        Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> jobEntry = jobEntryIt.next();
×
74
                        List<IPrototypedIngredient<?, ?>> pendingIngredientsUnsafe = jobEntry.get(ingredientComponent);
×
75
                        if (pendingIngredientsUnsafe != null) {
×
76
                            // Remove pending ingredients that were added in the event
77
                            List<IPrototypedIngredient<T, M>> pendingIngredients = (List<IPrototypedIngredient<T, M>>) (Object) pendingIngredientsUnsafe;
×
78

79
                            // Iterate over all pending ingredients for this ingredient component
80
                            ListIterator<IPrototypedIngredient<T, M>> it = pendingIngredients.listIterator();
×
81
                            while (it.hasNext()) {
×
82
                                IPrototypedIngredient<T, M> prototypedIngredient = it.next();
×
83
                                final long initialQuantity = matcher.getQuantity(prototypedIngredient.getPrototype());
×
84
                                long remainingQuantity = initialQuantity;
×
85

86
                                // Lazily create ingredientsHayStack only when needed,
87
                                // because we need to copy all ingredients from addedIngredients,
88
                                // which can get expensive
89
                                // We need to make a copy because multiple crafting jobs can have the same pending instances,
90
                                // so each instance may only be consumed by a single crafting job.
91
                                if (ingredientsHayStack == null) {
×
92
                                    if (addedIngredients.contains(prototypedIngredient.getPrototype(),
×
93
                                            prototypedIngredient.getCondition())) {
×
94
                                        IngredientCollectionPrototypeMap<T, M> prototypeMap = new IngredientCollectionPrototypeMap<>(ingredientComponent);
×
95
                                        ingredientsHayStack = new IngredientComponentStorageCollectionWrapper<>(prototypeMap);
×
96
                                        prototypeMap.addAll(addedIngredients);
×
97
                                    } else {
98
                                        continue;
99
                                    }
100
                                }
101

102
                                // Iteratively extract the pending ingredient from the hay stack.
103
                                T extracted;
104
                                do {
105
                                    extracted = ingredientsHayStack.extract(prototypedIngredient.getPrototype(),
×
106
                                            prototypedIngredient.getCondition(), false);
×
NEW
107
                                    long extractedQuantity = matcher.getQuantity(extracted);
×
108

109
                                    if (matcher.isEmpty(extracted)) {
×
110
                                        // Quickly break when no matches are available anymore
111
                                        break;
×
112
                                    } else {
NEW
113
                                        long extractedQuantityToAssign = extractedQuantity;
×
114
                                        // Move this ingredient from storage to dependent crafting jobs.
115
                                        // We only consider jobs that have this instance as missing ingredient.
116
                                        IntIterator dependentJobs = craftingJob.getDependentCraftingJobs().intIterator();
×
117
                                        while (dependentJobs.hasNext()) {
×
118
                                            CraftingJob dependentJob = craftingNetwork.getCraftingJob(craftingJob.getChannel(), dependentJobs.nextInt());
×
119
                                            if (dependentJob != null) {
×
NEW
120
                                                long missingQuantity = dependentJob.getMissingIngredientQuantity(ingredientComponent, extracted);
×
NEW
121
                                                if (missingQuantity > 0) {
×
NEW
122
                                                    INetworkIngredientsChannel<T, M> storage = this.ingredientsNetwork.getChannelInternal(craftingJob.getChannel());
×
NEW
123
                                                    T toExtract = matcher.withQuantity(extracted, Math.min(missingQuantity, extractedQuantityToAssign));
×
NEW
124
                                                    T extractedFromStorage = storage.extract(toExtract, matcher.getExactMatchCondition(), false);
×
NEW
125
                                                    if (!matcher.matchesExactly(toExtract, extractedFromStorage)) {
×
NEW
126
                                                        IntegratedCrafting.clog("Unable to extract ingredient from storage for pending crafting job: " + toExtract);
×
NEW
127
                                                        storage.insert(extractedFromStorage, false);
×
128
                                                    } else {
NEW
129
                                                        dependentJob.addToIngredientsStorageBuffer(ingredientComponent, extractedFromStorage);
×
130
                                                    }
NEW
131
                                                    extractedQuantityToAssign -= matcher.getQuantity(extractedFromStorage);
×
NEW
132
                                                    if (extractedQuantityToAssign == 0) {
×
NEW
133
                                                        break;
×
134
                                                    }
135
                                                }
136
                                            }
UNCOV
137
                                        }
×
138
                                    }
139

NEW
140
                                    remainingQuantity -= extractedQuantity;
×
141
                                } while (!matcher.isEmpty(extracted) && remainingQuantity > 0);
×
142

143
                                // Update the list if the prototype has changed.
144
                                if (remainingQuantity <= 0) {
×
145
                                    it.remove();
×
146
                                } else if (initialQuantity != remainingQuantity) {
×
147
                                    it.set(new PrototypedIngredient<>(ingredientComponent,
×
148
                                            matcher.withQuantity(prototypedIngredient.getPrototype(), remainingQuantity),
×
149
                                            prototypedIngredient.getCondition()));
×
150
                                }
151
                            }
×
152

153
                            // If no prototypes for this component type for this crafting job for this job entry are pending.
154
                            if (pendingIngredients.isEmpty()) {
×
155
                                // Remove observer (in next tick) when all pending ingredients are resolved
156
                                handler.getObserversPendingDeletion().add(ingredientComponent);
×
157

158
                                // Remove crafting job if needed.
159
                                // No ingredients of this ingredient component type are pending
160
                                jobEntry.remove(ingredientComponent);
×
161
                                if (jobEntry.isEmpty()) {
×
162
                                    // No ingredients are pending for this non-blocking-mode-entry are pending
163
                                    handler.onCraftingJobEntryFinished(this.craftingNetwork, craftingJobId);
×
164
                                    jobEntryIt.remove();
×
165
                                }
166
                                if (jobsEntry.getValue().isEmpty()) {
×
167
                                    // No more entries for this crafting job are pending
168
                                    handler.onCraftingJobFinished(handler.getAllCraftingJobs().get(craftingJobId));
×
169
                                    handler.getProcessingCraftingJobsRaw().remove(craftingJobId);
×
170
                                    jobsEntryIt.remove();
×
171
                                }
172
                            }
173
                        }
174
                    }
×
175
                }
176
            }
×
177
        }
178
    }
×
179

180

181
}
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