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

CyclopsMC / IntegratedCrafting / #479011822

29 Dec 2025 01:56PM UTC coverage: 24.23% (-0.6%) from 24.876%
#479011822

push

github

rubensworks
Add dedicated storage per crafting job

When a crafting job is started, ingredients are immediately moved from
general storage to the new storage buffers per crafting job. This avoids
issues where ingredients can be consumed elsewhere (e.g. exporters or
other crafting jobs) before it is used by the crafting job.

Closes #112

3 of 104 new or added lines in 7 files covered. (2.88%)

3 existing lines in 3 files now uncovered.

755 of 3116 relevant lines covered (24.23%)

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 com.google.common.collect.Maps;
4
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
5
import it.unimi.dsi.fastutil.ints.IntIterator;
6
import it.unimi.dsi.fastutil.objects.ObjectIterator;
7
import org.apache.commons.compress.utils.Lists;
8
import org.cyclops.commoncapabilities.api.ingredient.*;
9
import org.cyclops.commoncapabilities.api.ingredient.storage.IIngredientComponentStorage;
10
import org.cyclops.cyclopscore.ingredient.collection.IIngredientCollection;
11
import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionPrototypeMap;
12
import org.cyclops.cyclopscore.ingredient.collection.IngredientList;
13
import org.cyclops.cyclopscore.ingredient.storage.IngredientComponentStorageCollectionWrapper;
14
import org.cyclops.cyclopscore.ingredient.storage.IngredientComponentStorageSlottedCollectionWrapper;
15
import org.cyclops.integratedcrafting.IntegratedCrafting;
16
import org.cyclops.integratedcrafting.api.crafting.CraftingJob;
17
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
18
import org.cyclops.integrateddynamics.api.ingredient.IIngredientComponentStorageObservable;
19
import org.cyclops.integrateddynamics.api.network.INetwork;
20
import org.cyclops.integrateddynamics.api.network.INetworkIngredientsChannel;
21
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetwork;
22
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients;
23

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

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

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

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

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

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

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

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

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

109
                                    if (matcher.isEmpty(extracted)) {
×
110
                                        // Quickly break when no matches are available anymore
111
                                        break;
×
112
                                    } else {
113
                                        // Move this ingredient from storage to the first dependent crafting job.
NEW
114
                                        IntIterator dependentJobs = craftingJob.getDependentCraftingJobs().intIterator();
×
NEW
115
                                        while (dependentJobs.hasNext()) {
×
NEW
116
                                            CraftingJob dependentJob = craftingNetwork.getCraftingJob(craftingJob.getChannel(), dependentJobs.nextInt());
×
NEW
117
                                            if (dependentJob != null) {
×
NEW
118
                                                INetworkIngredientsChannel<T, M> storage = this.ingredientsNetwork.getChannelInternal(craftingJob.getChannel());
×
NEW
119
                                                T extractedFromStorage = storage.extract(extracted, matcher.getExactMatchCondition(), false);
×
NEW
120
                                                if (!matcher.matchesExactly(extracted, extractedFromStorage)) {
×
NEW
121
                                                    IntegratedCrafting.clog("Unable to extract ingredient from storage for pending crafting job: " + extracted);
×
NEW
122
                                                    storage.insert(extractedFromStorage, false);
×
123
                                                } else {
NEW
124
                                                    IMixedIngredients buffer = dependentJob.getIngredientsStorageBuffer();
×
NEW
125
                                                    if (!buffer.getComponents().contains(ingredientComponent)) {
×
NEW
126
                                                        Map<IngredientComponent<?, ?>, List<?>> mixedIngredientsRaw = Maps.newIdentityHashMap();
×
NEW
127
                                                        for (IngredientComponent<?, ?> component : buffer.getComponents()) {
×
NEW
128
                                                            mixedIngredientsRaw.put(component, buffer.getInstances(component));
×
NEW
129
                                                        }
×
NEW
130
                                                        List<T> list = Lists.newArrayList();
×
NEW
131
                                                        mixedIngredientsRaw.put(ingredientComponent, list);
×
NEW
132
                                                        list.add(extractedFromStorage);
×
NEW
133
                                                        buffer = new MixedIngredients(mixedIngredientsRaw);
×
NEW
134
                                                        dependentJob.setIngredientsStorageBuffer(buffer);
×
NEW
135
                                                    } else {
×
NEW
136
                                                        new IngredientComponentStorageSlottedCollectionWrapper<>(new IngredientList<>(ingredientComponent, buffer.getInstances(ingredientComponent)), Long.MAX_VALUE, Long.MAX_VALUE).insert(extractedFromStorage, false);
×
137
                                                    }
138
                                                }
NEW
139
                                                break;
×
140
                                            }
NEW
141
                                        }
×
142
                                    }
143

144
                                    remainingQuantity -= matcher.getQuantity(extracted);
×
145
                                } while (!matcher.isEmpty(extracted) && remainingQuantity > 0);
×
146

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

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

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

184

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