• 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/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.
UNCOV
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

NEW
155
                            onPendingIngredientsEmpty(jobsEntryIt, jobsEntry, jobEntryIt, jobEntry, pendingIngredients, craftingJobId);
×
156
                        }
NEW
157
                    }
×
158
                }
NEW
159
            }
×
160
        }
NEW
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) {
NEW
172
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
×
NEW
173
        long instanceAmount = matcher.getQuantity(instanceWrapper.getInstance());
×
174

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

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

199
                            // Check if the instance matches the job's expected outputs.
NEW
200
                            if (matcher.matches(instanceWrapper.getInstance(), prototypedIngredient.getPrototype(),
×
NEW
201
                                    prototypedIngredient.getCondition())) {
×
NEW
202
                                long extractedQuantityToAssign = Math.min(remainingQuantity, instanceAmount);
×
NEW
203
                                remainingQuantity -= extractedQuantityToAssign;
×
NEW
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.
NEW
208
                                IntIterator dependentJobs = craftingJob.getDependentCraftingJobs().intIterator();
×
NEW
209
                                while (dependentJobs.hasNext()) {
×
NEW
210
                                    CraftingJob dependentJob = craftingNetwork.getCraftingJob(craftingJob.getChannel(), dependentJobs.nextInt());
×
NEW
211
                                    if (dependentJob != null) {
×
NEW
212
                                        long missingQuantity = dependentJob.getMissingIngredientQuantity(ingredientComponent, extracted);
×
NEW
213
                                        if (missingQuantity > 0) {
×
NEW
214
                                            long toExtractQuantity = Math.min(missingQuantity, extractedQuantityToAssign);
×
NEW
215
                                            T toExtract = matcher.withQuantity(extracted, toExtractQuantity);
×
NEW
216
                                            dependentJob.addToIngredientsStorageBuffer(ingredientComponent, toExtract);
×
NEW
217
                                            instanceAmount -= toExtractQuantity;
×
NEW
218
                                            extractedQuantityToAssign -= toExtractQuantity;
×
NEW
219
                                            if (extractedQuantityToAssign == 0) {
×
NEW
220
                                                break;
×
221
                                            }
222
                                        }
223
                                    }
UNCOV
224
                                }
×
225
                            }
226

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

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

NEW
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) {
NEW
254
        if (pendingIngredients.isEmpty()) {
×
255
            // Remove observer (in next tick) when all pending ingredients are resolved
NEW
256
            handler.getObserversPendingDeletion().add(ingredientComponent);
×
257

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

© 2026 Coveralls, Inc