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

CyclopsMC / IntegratedCrafting / #479011828

30 Dec 2025 09:05AM UTC coverage: 24.324% (-0.06%) from 24.387%
#479011828

push

github

rubensworks
Fix reusable ingredients not being extracted from storage if consumed once

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

1 existing line in 1 file now uncovered.

756 of 3108 relevant lines covered (24.32%)

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);
×
107

108
                                    if (matcher.isEmpty(extracted)) {
×
109
                                        // Quickly break when no matches are available anymore
110
                                        break;
×
111
                                    } else {
112
                                        // Move this ingredient from storage to the first dependent crafting job.
113
                                        IntIterator dependentJobs = craftingJob.getDependentCraftingJobs().intIterator();
×
114
                                        while (dependentJobs.hasNext()) {
×
115
                                            CraftingJob dependentJob = craftingNetwork.getCraftingJob(craftingJob.getChannel(), dependentJobs.nextInt());
×
116
                                            if (dependentJob != null) {
×
117
                                                INetworkIngredientsChannel<T, M> storage = this.ingredientsNetwork.getChannelInternal(craftingJob.getChannel());
×
118
                                                T extractedFromStorage = storage.extract(extracted, matcher.getExactMatchCondition(), false);
×
119
                                                if (!matcher.matchesExactly(extracted, extractedFromStorage)) {
×
120
                                                    IntegratedCrafting.clog("Unable to extract ingredient from storage for pending crafting job: " + extracted);
×
121
                                                    storage.insert(extractedFromStorage, false);
×
122
                                                } else {
NEW
123
                                                    dependentJob.addToIngredientsStorageBuffer(ingredientComponent, extractedFromStorage);
×
124
                                                }
125
                                                break;
×
126
                                            }
127
                                        }
×
128
                                    }
129

130
                                    remainingQuantity -= matcher.getQuantity(extracted);
×
131
                                } while (!matcher.isEmpty(extracted) && remainingQuantity > 0);
×
132

133
                                // Update the list if the prototype has changed.
134
                                if (remainingQuantity <= 0) {
×
135
                                    it.remove();
×
136
                                } else if (initialQuantity != remainingQuantity) {
×
137
                                    it.set(new PrototypedIngredient<>(ingredientComponent,
×
138
                                            matcher.withQuantity(prototypedIngredient.getPrototype(), remainingQuantity),
×
139
                                            prototypedIngredient.getCondition()));
×
140
                                }
141
                            }
×
142

143
                            // If no prototypes for this component type for this crafting job for this job entry are pending.
144
                            if (pendingIngredients.isEmpty()) {
×
145
                                // Remove observer (in next tick) when all pending ingredients are resolved
146
                                handler.getObserversPendingDeletion().add(ingredientComponent);
×
147

148
                                // Remove crafting job if needed.
149
                                // No ingredients of this ingredient component type are pending
150
                                jobEntry.remove(ingredientComponent);
×
151
                                if (jobEntry.isEmpty()) {
×
152
                                    // No ingredients are pending for this non-blocking-mode-entry are pending
153
                                    handler.onCraftingJobEntryFinished(this.craftingNetwork, craftingJobId);
×
154
                                    jobEntryIt.remove();
×
155
                                }
156
                                if (jobsEntry.getValue().isEmpty()) {
×
157
                                    // No more entries for this crafting job are pending
158
                                    handler.onCraftingJobFinished(handler.getAllCraftingJobs().get(craftingJobId));
×
159
                                    handler.getProcessingCraftingJobsRaw().remove(craftingJobId);
×
160
                                    jobsEntryIt.remove();
×
161
                                }
162
                            }
163
                        }
164
                    }
×
165
                }
166
            }
×
167
        }
168
    }
×
169

170

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