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

CyclopsMC / IntegratedCrafting / #479011834

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

push

github

rubensworks
Fix incorrect commoncapabilities version

755 of 3172 relevant lines covered (23.8%)

0.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/main/java/org/cyclops/integratedcrafting/core/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.*;
7
import org.cyclops.commoncapabilities.api.ingredient.storage.IIngredientComponentStorage;
8
import org.cyclops.cyclopscore.ingredient.collection.IIngredientCollection;
9
import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionPrototypeMap;
10
import org.cyclops.cyclopscore.ingredient.storage.IngredientComponentStorageCollectionWrapper;
11
import org.cyclops.integratedcrafting.IntegratedCrafting;
12
import org.cyclops.integratedcrafting.api.crafting.CraftingJob;
13
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
14
import org.cyclops.integrateddynamics.api.ingredient.IIngredientComponentStorageObservable;
15
import org.cyclops.integrateddynamics.api.network.INetwork;
16
import org.cyclops.integrateddynamics.api.network.INetworkIngredientsChannel;
17
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetwork;
18
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients;
19

20
import java.util.Iterator;
21
import java.util.List;
22
import java.util.ListIterator;
23
import java.util.Map;
24

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

35
    private final IngredientComponent<T, M> ingredientComponent;
36
    private final CraftingJobHandler handler;
37
    private final ICraftingNetwork craftingNetwork;
38
    private final IPositionedAddonsNetworkIngredients<T, M> ingredientsNetwork;
39
    private final INetwork network;
40

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

49
    @Override
50
    public void onChange(IIngredientComponentStorageObservable.StorageChangeEvent<T, M> event) { // If changes are made here, also change in method below!!! (only partially abstracted...)
51
        // This adds the given instance to the waiting crafting jobs if they have been detected in storage.
52
        // This only acts as a fallback to instances being detected once they are flushed from the crafting interface,
53
        // which will cause the method below to be invoked.
54
        // This will first attempt to complete running jobs that are awaiting results.
55
        // Then, it will try to give the instance to the dependent jobs based on their missing ingredients.
56
        if (event.getChangeType() == IIngredientComponentStorageObservable.Change.ADDITION
×
57
                // If we're still initializing the network, skip addition events.
58
                // Otherwise, we could incorrectly mark running crafting jobs as finished.
59
                && !event.isInitialChange()) {
×
60
            IIngredientCollection<T, M> addedIngredients = event.getInstances();
×
61
            IIngredientComponentStorage<T, M> ingredientsHayStack = null; // A mutable copy of addedIngredients (lazily created)
×
62
            IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
×
63

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

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

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

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

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

142
                                    remainingQuantity -= extractedQuantity;
×
143
                                } while (!matcher.isEmpty(extracted) && remainingQuantity > 0);
×
144

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

155
                            onPendingIngredientsEmpty(jobsEntryIt, jobsEntry, jobEntryIt, jobEntry, pendingIngredients, craftingJobId);
×
156
                        }
157
                    }
×
158
                }
159
            }
×
160
        }
161
    }
×
162

163
    /**
164
     * This adds the given instance to the waiting crafting jobs directly, without having to go through the network storage and its observers.
165
     * Like the method above, this will first attempt to complete running jobs that are awaiting results.
166
     * Then, it will try to give the instance to the dependent jobs based on their missing ingredients.
167
     * @param instanceWrapper The instance to add.
168
     * @param channel The channel.
169
     * @return The remaining instance that could not be given to any jobs that had missing ingredients.
170
     */
171
    public IngredientInstanceWrapper<T, M> addIngredient(IngredientInstanceWrapper<T, M> instanceWrapper, int channel) {
172
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
×
173
        long instanceAmount = matcher.getQuantity(instanceWrapper.getInstance());
×
174

175
        Int2ObjectMap<List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>>> processingJobs = handler.getProcessingCraftingJobsPendingIngredients();
×
176
        ObjectIterator<Int2ObjectMap.Entry<List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>>>> jobsEntryIt = processingJobs.int2ObjectEntrySet().iterator();
×
177
        while (jobsEntryIt.hasNext() && instanceAmount > 0) {
×
178
            Int2ObjectMap.Entry<List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>>> jobsEntry = jobsEntryIt.next();
×
179
            int craftingJobId = jobsEntry.getIntKey();
×
180
            // Only check jobs that have a matching channel with the event
181
            CraftingJob craftingJob = handler.getAllCraftingJobs().get(jobsEntry.getIntKey());
×
182
            if (craftingJob != null
×
183
                    && (craftingJob.getChannel() == IPositionedAddonsNetwork.WILDCARD_CHANNEL || craftingJob.getChannel() == channel)) {
×
184
                Iterator<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>> jobEntryIt = jobsEntry.getValue().iterator();
×
185
                while (jobEntryIt.hasNext() && instanceAmount > 0) {
×
186
                    Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> jobEntry = jobEntryIt.next();
×
187
                    List<IPrototypedIngredient<?, ?>> pendingIngredientsUnsafe = jobEntry.get(ingredientComponent);
×
188
                    if (pendingIngredientsUnsafe != null) {
×
189
                        // Remove pending ingredients based on the given instance.
190
                        List<IPrototypedIngredient<T, M>> pendingIngredients = (List<IPrototypedIngredient<T, M>>) (Object) pendingIngredientsUnsafe;
×
191

192
                        // Iterate over all pending ingredients for this ingredient component
193
                        ListIterator<IPrototypedIngredient<T, M>> it = pendingIngredients.listIterator();
×
194
                        while (it.hasNext() && instanceAmount > 0) {
×
195
                            IPrototypedIngredient<T, M> prototypedIngredient = it.next();
×
196
                            final long initialQuantity = matcher.getQuantity(prototypedIngredient.getPrototype());
×
197
                            long remainingQuantity = initialQuantity;
×
198

199
                            // Check if the instance matches the job's expected outputs.
200
                            if (matcher.matches(instanceWrapper.getInstance(), prototypedIngredient.getPrototype(),
×
201
                                    prototypedIngredient.getCondition())) {
×
202
                                long extractedQuantityToAssign = Math.min(remainingQuantity, instanceAmount);
×
203
                                remainingQuantity -= extractedQuantityToAssign;
×
204
                                T extracted = matcher.withQuantity(instanceWrapper.getInstance(), extractedQuantityToAssign);
×
205

206
                                // Move this ingredient from storage to dependent crafting jobs.
207
                                // We only consider jobs that have this instance as missing ingredient.
208
                                IntIterator dependentJobs = craftingJob.getDependentCraftingJobs().intIterator();
×
209
                                while (dependentJobs.hasNext()) {
×
210
                                    CraftingJob dependentJob = craftingNetwork.getCraftingJob(craftingJob.getChannel(), dependentJobs.nextInt());
×
211
                                    if (dependentJob != null) {
×
212
                                        long missingQuantity = dependentJob.getMissingIngredientQuantity(ingredientComponent, extracted);
×
213
                                        if (missingQuantity > 0) {
×
214
                                            long toExtractQuantity = Math.min(missingQuantity, extractedQuantityToAssign);
×
215
                                            T toExtract = matcher.withQuantity(extracted, toExtractQuantity);
×
216
                                            dependentJob.addToIngredientsStorageBuffer(ingredientComponent, toExtract);
×
217
                                            instanceAmount -= toExtractQuantity;
×
218
                                            extractedQuantityToAssign -= toExtractQuantity;
×
219
                                            if (extractedQuantityToAssign == 0) {
×
220
                                                break;
×
221
                                            }
222
                                        }
223
                                    }
224
                                }
×
225
                            }
226

227
                            // Update the list if the prototype has changed.
228
                            if (remainingQuantity <= 0) {
×
229
                                it.remove();
×
230
                            } else if (initialQuantity != remainingQuantity) {
×
231
                                it.set(new PrototypedIngredient<>(ingredientComponent,
×
232
                                        matcher.withQuantity(prototypedIngredient.getPrototype(), remainingQuantity),
×
233
                                        prototypedIngredient.getCondition()));
×
234
                            }
235
                        }
×
236

237
                        onPendingIngredientsEmpty(jobsEntryIt, jobsEntry, jobEntryIt, jobEntry, pendingIngredients, craftingJobId);
×
238
                    }
239
                }
×
240
            }
241
        }
×
242

243
        return new IngredientInstanceWrapper<>(instanceWrapper.getComponent(), matcher.withQuantity(instanceWrapper.getInstance(), instanceAmount));
×
244
    }
245

246
    // If no prototypes for this component type for this crafting job for this job entry are pending.
247
    // If changes are made here, also change in method above!!! (only partially abstracted...)
248
    protected void onPendingIngredientsEmpty(ObjectIterator<Int2ObjectMap.Entry<List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>>>> jobsEntryIt,
249
                                             Int2ObjectMap.Entry<List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>>> jobsEntry,
250
                                             Iterator<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>> jobEntryIt,
251
                                             Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> jobEntry,
252
                                             List<IPrototypedIngredient<T, M>> pendingIngredients,
253
                                             int craftingJobId) {
254
        if (pendingIngredients.isEmpty()) {
×
255
            // Remove observer (in next tick) when all pending ingredients are resolved
256
            handler.getObserversPendingDeletion().add(ingredientComponent);
×
257

258
            // Remove crafting job if needed.
259
            // No ingredients of this ingredient component type are pending
260
            jobEntry.remove(ingredientComponent);
×
261
            if (jobEntry.isEmpty()) {
×
262
                // No ingredients are pending for this non-blocking-mode-entry are pending
263
                handler.onCraftingJobEntryFinished(this.craftingNetwork, craftingJobId);
×
264
                jobEntryIt.remove();
×
265
            }
266
            if (jobsEntry.getValue().isEmpty()) {
×
267
                // No more entries for this crafting job are pending
268
                handler.onCraftingJobFinished(handler.getAllCraftingJobs().get(craftingJobId));
×
269
                handler.getProcessingCraftingJobsRaw().remove(craftingJobId);
×
270
                jobsEntryIt.remove();
×
271
            }
272
        }
273
    }
×
274

275

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