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

CyclopsMC / IntegratedCrafting / #479011826

30 Dec 2025 07:45AM UTC coverage: 24.363% (-0.04%) from 24.402%
#479011826

push

github

rubensworks
Fix buffer insertion failing if there are no empty slots

0 of 6 new or added lines in 1 file covered. (0.0%)

755 of 3099 relevant lines covered (24.36%)

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

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;
×
49
        this.ingredientsNetwork = ingredientsNetwork;
×
50
        this.network = network;
×
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.
114
                                        IntIterator dependentJobs = craftingJob.getDependentCraftingJobs().intIterator();
×
115
                                        while (dependentJobs.hasNext()) {
×
116
                                            CraftingJob dependentJob = craftingNetwork.getCraftingJob(craftingJob.getChannel(), dependentJobs.nextInt());
×
117
                                            if (dependentJob != null) {
×
118
                                                INetworkIngredientsChannel<T, M> storage = this.ingredientsNetwork.getChannelInternal(craftingJob.getChannel());
×
119
                                                T extractedFromStorage = storage.extract(extracted, matcher.getExactMatchCondition(), false);
×
120
                                                if (!matcher.matchesExactly(extracted, extractedFromStorage)) {
×
121
                                                    IntegratedCrafting.clog("Unable to extract ingredient from storage for pending crafting job: " + extracted);
×
122
                                                    storage.insert(extractedFromStorage, false);
×
123
                                                } else {
124
                                                    IMixedIngredients buffer = dependentJob.getIngredientsStorageBuffer();
×
125
                                                    if (!buffer.getComponents().contains(ingredientComponent)) {
×
126
                                                        Map<IngredientComponent<?, ?>, List<?>> mixedIngredientsRaw = Maps.newIdentityHashMap();
×
127
                                                        for (IngredientComponent<?, ?> component : buffer.getComponents()) {
×
128
                                                            mixedIngredientsRaw.put(component, buffer.getInstances(component));
×
129
                                                        }
×
130
                                                        List<T> list = Lists.newArrayList();
×
131
                                                        mixedIngredientsRaw.put(ingredientComponent, list);
×
132
                                                        list.add(extractedFromStorage);
×
133
                                                        buffer = new MixedIngredients(mixedIngredientsRaw);
×
134
                                                        dependentJob.setIngredientsStorageBuffer(buffer);
×
135
                                                    } else {
×
NEW
136
                                                        List<T> instances = buffer.getInstances(ingredientComponent);
×
NEW
137
                                                        if (!instances.stream().anyMatch(matcher::isEmpty)) {
×
138
                                                            // Make sure we have at least one empty slot available, to guarantee insertion can succeed.
NEW
139
                                                            instances.add(matcher.getEmptyInstance());
×
140
                                                        }
NEW
141
                                                        T remaining = new IngredientComponentStorageSlottedCollectionWrapper<>(new IngredientList<>(ingredientComponent, instances), Integer.MAX_VALUE, Integer.MAX_VALUE).insert(extractedFromStorage, false);
×
NEW
142
                                                        if (!matcher.isEmpty(remaining)) {
×
NEW
143
                                                            throw new IllegalStateException(String.format("Unable to insert %s into the crafting job buffer, remaining: ", extractedFromStorage, remaining));
×
144
                                                        }
145
                                                    }
146
                                                }
147
                                                break;
×
148
                                            }
149
                                        }
×
150
                                    }
151

152
                                    remainingQuantity -= matcher.getQuantity(extracted);
×
153
                                } while (!matcher.isEmpty(extracted) && remainingQuantity > 0);
×
154

155
                                // Update the list if the prototype has changed.
156
                                if (remainingQuantity <= 0) {
×
157
                                    it.remove();
×
158
                                } else if (initialQuantity != remainingQuantity) {
×
159
                                    it.set(new PrototypedIngredient<>(ingredientComponent,
×
160
                                            matcher.withQuantity(prototypedIngredient.getPrototype(), remainingQuantity),
×
161
                                            prototypedIngredient.getCondition()));
×
162
                                }
163
                            }
×
164

165
                            // If no prototypes for this component type for this crafting job for this job entry are pending.
166
                            if (pendingIngredients.isEmpty()) {
×
167
                                // Remove observer (in next tick) when all pending ingredients are resolved
168
                                handler.getObserversPendingDeletion().add(ingredientComponent);
×
169

170
                                // Remove crafting job if needed.
171
                                // No ingredients of this ingredient component type are pending
172
                                jobEntry.remove(ingredientComponent);
×
173
                                if (jobEntry.isEmpty()) {
×
174
                                    // No ingredients are pending for this non-blocking-mode-entry are pending
175
                                    handler.onCraftingJobEntryFinished(this.craftingNetwork, craftingJobId);
×
176
                                    jobEntryIt.remove();
×
177
                                }
178
                                if (jobsEntry.getValue().isEmpty()) {
×
179
                                    // No more entries for this crafting job are pending
180
                                    handler.onCraftingJobFinished(handler.getAllCraftingJobs().get(craftingJobId));
×
181
                                    handler.getProcessingCraftingJobsRaw().remove(craftingJobId);
×
182
                                    jobsEntryIt.remove();
×
183
                                }
184
                            }
185
                        }
186
                    }
×
187
                }
188
            }
×
189
        }
190
    }
×
191

192

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