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

CyclopsMC / IntegratedCrafting / #479011761

26 Oct 2024 01:51PM UTC coverage: 25.288% (+0.4%) from 24.903%
#479011761

push

github

rubensworks
Bump mod version

724 of 2863 relevant lines covered (25.29%)

0.25 hits per line

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

72.64
/src/main/java/org/cyclops/integratedcrafting/core/CraftingHelpers.java
1
package org.cyclops.integratedcrafting.core;
2

3
import com.google.common.collect.Lists;
4
import com.google.common.collect.Maps;
5
import com.google.common.collect.Sets;
6
import net.minecraft.core.Direction;
7
import net.minecraft.world.level.block.entity.BlockEntity;
8
import net.minecraftforge.common.capabilities.ICapabilityProvider;
9
import net.minecraftforge.common.util.LazyOptional;
10
import org.apache.commons.lang3.tuple.Pair;
11
import org.apache.logging.log4j.Level;
12
import org.cyclops.commoncapabilities.api.capability.recipehandler.IPrototypedIngredientAlternatives;
13
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
14
import org.cyclops.commoncapabilities.api.ingredient.IIngredientMatcher;
15
import org.cyclops.commoncapabilities.api.ingredient.IMixedIngredients;
16
import org.cyclops.commoncapabilities.api.ingredient.IPrototypedIngredient;
17
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
18
import org.cyclops.commoncapabilities.api.ingredient.MixedIngredients;
19
import org.cyclops.commoncapabilities.api.ingredient.PrototypedIngredient;
20
import org.cyclops.commoncapabilities.api.ingredient.storage.IIngredientComponentStorage;
21
import org.cyclops.commoncapabilities.api.ingredient.storage.IngredientComponentStorageEmpty;
22
import org.cyclops.cyclopscore.helper.BlockEntityHelpers;
23
import org.cyclops.cyclopscore.ingredient.collection.IIngredientCollectionMutable;
24
import org.cyclops.cyclopscore.ingredient.collection.IngredientArrayList;
25
import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionPrototypeMap;
26
import org.cyclops.cyclopscore.ingredient.collection.IngredientCollectionQuantitativeGrouper;
27
import org.cyclops.cyclopscore.ingredient.collection.IngredientHashSet;
28
import org.cyclops.integratedcrafting.Capabilities;
29
import org.cyclops.integratedcrafting.IntegratedCrafting;
30
import org.cyclops.integratedcrafting.api.crafting.CraftingJob;
31
import org.cyclops.integratedcrafting.api.crafting.CraftingJobDependencyGraph;
32
import org.cyclops.integratedcrafting.api.crafting.FailedCraftingRecipeException;
33
import org.cyclops.integratedcrafting.api.crafting.RecursiveCraftingRecipeException;
34
import org.cyclops.integratedcrafting.api.crafting.UnavailableCraftingInterfacesException;
35
import org.cyclops.integratedcrafting.api.crafting.UnknownCraftingRecipeException;
36
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
37
import org.cyclops.integratedcrafting.api.recipe.IRecipeIndex;
38
import org.cyclops.integratedcrafting.capability.network.CraftingNetworkConfig;
39
import org.cyclops.integrateddynamics.IntegratedDynamics;
40
import org.cyclops.integrateddynamics.api.PartStateException;
41
import org.cyclops.integrateddynamics.api.ingredient.capability.IPositionedAddonsNetworkIngredientsHandler;
42
import org.cyclops.integrateddynamics.api.network.INetwork;
43
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients;
44
import org.cyclops.integrateddynamics.api.part.PartPos;
45
import org.cyclops.integrateddynamics.core.helper.NetworkHelpers;
46
import org.cyclops.integrateddynamics.core.network.IngredientChannelAdapter;
47
import org.cyclops.integrateddynamics.core.network.IngredientChannelIndexed;
48

49
import javax.annotation.Nullable;
50
import java.util.Collection;
51
import java.util.Collections;
52
import java.util.Iterator;
53
import java.util.List;
54
import java.util.ListIterator;
55
import java.util.Map;
56
import java.util.Set;
57
import java.util.UUID;
58
import java.util.function.Function;
59
import java.util.stream.Collectors;
60
import java.util.stream.IntStream;
61

62
/**
63
 * Helpers related to handling crafting jobs.
64
 * @author rubensworks
65
 */
66
public class CraftingHelpers {
×
67

68
    /**
69
     * Get the network at the given position,
70
     * or throw a PartStateException if it is null.
71
     * @param pos A position.
72
     * @return A network.
73
     * @throws PartStateException If the network could not be found.
74
     */
75
    public static INetwork getNetworkChecked(PartPos pos) throws PartStateException {
76
        INetwork network = NetworkHelpers.getNetwork(pos.getPos().getLevel(true), pos.getPos().getBlockPos(), pos.getSide()).orElse(null);
×
77
        if (network == null) {
×
78
            IntegratedDynamics.clog(Level.ERROR, "Could not get the network for transfer as no network was found.");
×
79
            throw new PartStateException(pos.getPos(), pos.getSide());
×
80
        }
81
        return network;
×
82
    }
83

84
    /**
85
     * Get the crafting network in the given network.
86
     * @param network A network.
87
     * @return The crafting network.
88
     */
89
    public static LazyOptional<ICraftingNetwork> getCraftingNetwork(@Nullable INetwork network) {
90
        if (network != null) {
×
91
            return network.getCapability(CraftingNetworkConfig.CAPABILITY);
×
92
        }
93
        return null;
×
94
    }
95

96
    /**
97
     * Get the crafting network in the given network.
98
     * @param network A network.
99
     * @return The crafting network.
100
     */
101
    public static ICraftingNetwork getCraftingNetworkChecked(@Nullable INetwork network) {
102
        return getCraftingNetwork(network)
×
103
                .orElseThrow(() -> new IllegalStateException("Could not find a crafting network"));
×
104
    }
105

106
    /**
107
     * Get the storage network of the given type in the given network.
108
     * @param network A network.
109
     * @param ingredientComponent The ingredient component type of the network.
110
     * @param <T> The instance type.
111
     * @param <M> The matching condition parameter.
112
     * @return The storage network.
113
     */
114
    public static <T, M> LazyOptional<IPositionedAddonsNetworkIngredients<T, M>> getIngredientsNetwork(INetwork network,
115
                                                                                                       IngredientComponent<T, M> ingredientComponent) {
116
        IPositionedAddonsNetworkIngredientsHandler<T, M> ingredientsHandler = ingredientComponent.getCapability(Capabilities.POSITIONED_ADDONS_NETWORK_INGREDIENTS_HANDLER).orElse(null);
×
117
        if (ingredientsHandler != null) {
×
118
            return ingredientsHandler.getStorage(network);
×
119
        }
120
        return LazyOptional.empty();
×
121
    }
122

123
    /**
124
     * Get the storage network of the given type in the given network.
125
     * @param network A network.
126
     * @param ingredientComponent The ingredient component type of the network.
127
     * @param <T> The instance type.
128
     * @param <M> The matching condition parameter.
129
     * @return The storage network.
130
     */
131
    public static <T, M> IPositionedAddonsNetworkIngredients<T, M> getIngredientsNetworkChecked(INetwork network,
132
                                                                                                IngredientComponent<T, M> ingredientComponent) {
133
        return getIngredientsNetwork(network, ingredientComponent)
×
134
                .orElseThrow(() -> new IllegalStateException("Could not find an ingredients network"));
×
135
    }
136

137
    /**
138
     * Get the storage of the given ingredient component type from the network.
139
     * @param network The network.
140
     * @param channel A network channel.
141
     * @param ingredientComponent The ingredient component type of the network.
142
     * @param scheduleObservation If an observation inside the ingredients network should be scheduled.
143
     * @param <T> The instance type.
144
     * @param <M> The matching condition parameter.
145
     * @return The storage.
146
     */
147
    public static <T, M> IIngredientComponentStorage<T, M> getNetworkStorage(INetwork network, int channel,
148
                                                                             IngredientComponent<T, M> ingredientComponent,
149
                                                                             boolean scheduleObservation) {
150
        IPositionedAddonsNetworkIngredients<T, M> ingredientsNetwork = getIngredientsNetwork(network, ingredientComponent).orElse(null);
×
151
        if (ingredientsNetwork != null) {
×
152
            if (scheduleObservation) {
×
153
                ingredientsNetwork.scheduleObservation();
×
154
            }
155
            return ingredientsNetwork.getChannel(channel);
×
156
        }
157
        return new IngredientComponentStorageEmpty<>(ingredientComponent);
×
158
    }
159

160
    /**
161
     * If the network is guaranteed to have uncommitted changes (such as the one in #48),
162
     * forcefully run observers synchronously, so that we can calculate the job in a consistent network state.
163
     * @param network The network.
164
     * @param channel A network channel.
165
     */
166
    public static void beforeCalculateCraftingJobs(INetwork network, int channel) {
167
        for (IngredientComponent<?, ?> ingredientComponent : IngredientComponent.REGISTRY.getValues()) {
×
168
            IPositionedAddonsNetworkIngredients<?, ?> ingredientsNetwork = getIngredientsNetwork(network, ingredientComponent).orElse(null);
×
169
            if (ingredientsNetwork != null && (ingredientsNetwork.isObservationForcedPending(channel))) {
×
170
                ingredientsNetwork.runObserverSync();
×
171
            }
172
        }
×
173
    }
×
174

175
    /**
176
     * Calculate the required crafting jobs and their dependencies for the given instance in the given network.
177
     * @param network The target network.
178
     * @param channel The target channel.
179
     * @param ingredientComponent The ingredient component type of the instance.
180
     * @param instance The instance to craft.
181
     * @param matchCondition The match condition of the instance.
182
     * @param craftMissing If the missing required ingredients should also be crafted.
183
     * @param identifierGenerator identifierGenerator An ID generator for crafting jobs.
184
     * @param craftingJobsGraph The target graph where all dependencies will be stored.
185
     * @param collectMissingRecipes If the missing recipes should be collected inside
186
     *                              {@link UnknownCraftingRecipeException}.
187
     *                              This may slow down calculation for deeply nested recipe graphs.
188
     * @param <T> The instance type.
189
     * @param <M> The matching condition parameter.
190
     * @return The crafting job for the given instance.
191
     * @throws UnknownCraftingRecipeException If the recipe for a (sub)ingredient is unavailable.
192
     * @throws RecursiveCraftingRecipeException If an infinite recursive recipe was detected.
193
     */
194
    public static <T, M> CraftingJob calculateCraftingJobs(INetwork network, int channel,
195
                                                           IngredientComponent<T, M> ingredientComponent,
196
                                                           T instance, M matchCondition, boolean craftMissing,
197
                                                           IIdentifierGenerator identifierGenerator,
198
                                                           CraftingJobDependencyGraph craftingJobsGraph,
199
                                                           boolean collectMissingRecipes)
200
            throws UnknownCraftingRecipeException, RecursiveCraftingRecipeException {
201
        ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
202
        IRecipeIndex recipeIndex = craftingNetwork.getRecipeIndex(channel);
×
203
        Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter = getNetworkStorageGetter(network, channel, true);
×
204
        beforeCalculateCraftingJobs(network, channel);
×
205

206
        CraftingJob craftingJob = calculateCraftingJobs(recipeIndex, channel, storageGetter, ingredientComponent, instance, matchCondition,
×
207
                craftMissing, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), identifierGenerator, craftingJobsGraph, Sets.newHashSet(),
×
208
                collectMissingRecipes);
209
        craftingJobsGraph.addCraftingJobId(craftingJob);
×
210
        return craftingJob;
×
211
    }
212

213
    /**
214
     * Calculate the required crafting jobs and their dependencies for the given instance in the given network.
215
     * @param network The target network.
216
     * @param channel The target channel.
217
     * @param recipe The recipe to calculate a job for.
218
     * @param amount The amount of times the recipe should be crafted.
219
     * @param craftMissing If the missing required ingredients should also be crafted.
220
     * @param identifierGenerator identifierGenerator An ID generator for crafting jobs.
221
     * @param craftingJobsGraph The target graph where all dependencies will be stored.
222
     * @param collectMissingRecipes If the missing recipes should be collected inside
223
     *                              {@link FailedCraftingRecipeException}.
224
     *                              This may slow down calculation for deeply nested recipe graphs.
225
     * @return The crafting job for the given instance.
226
     * @throws FailedCraftingRecipeException If the recipe could not be crafted due to missing sub-dependencies.
227
     * @throws RecursiveCraftingRecipeException If an infinite recursive recipe was detected.
228
     */
229
    public static CraftingJob calculateCraftingJobs(INetwork network, int channel,
230
                                                    IRecipeDefinition recipe, int amount, boolean craftMissing,
231
                                                    IIdentifierGenerator identifierGenerator,
232
                                                    CraftingJobDependencyGraph craftingJobsGraph,
233
                                                    boolean collectMissingRecipes)
234
            throws FailedCraftingRecipeException, RecursiveCraftingRecipeException {
235
        ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
236
        IRecipeIndex recipeIndex = craftingNetwork.getRecipeIndex(channel);
×
237
        Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter = getNetworkStorageGetter(network, channel, true);
×
238
        beforeCalculateCraftingJobs(network, channel);
×
239

240
        PartialCraftingJobCalculation result = calculateCraftingJobs(recipeIndex, channel, storageGetter, recipe, amount,
×
241
                craftMissing, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), identifierGenerator, craftingJobsGraph, Sets.newHashSet(),
×
242
                collectMissingRecipes);
243
        if (result.getCraftingJob() == null) {
×
244
            throw new FailedCraftingRecipeException(recipe, amount, result.getMissingDependencies(),
×
245
                    compressMixedIngredients(new MixedIngredients(result.getIngredientsStorage())), result.getPartialCraftingJobs());
×
246
        } else {
247
            craftingJobsGraph.addCraftingJobId(result.getCraftingJob());
×
248
            return result.getCraftingJob();
×
249
        }
250
    }
251

252
    /**
253
     * @return An identifier generator for crafting jobs.
254
     */
255
    public static IIdentifierGenerator getGlobalCraftingJobIdentifier() {
256
        return () -> IntegratedCrafting.globalCounters.getNext("craftingJob");
×
257
    }
258

259
    /**
260
     * Calculate the effective quantity for the given instance in the output of the given recipe.
261
     * @param recipe A recipe.
262
     * @param ingredientComponent The ingredient component.
263
     * @param instance An instance.
264
     * @param matchCondition A match condition.
265
     * @param <T> The instance type.
266
     * @param <M> The matching condition parameter.
267
     * @return The effective quantity.
268
     */
269
    public static <T, M> long getOutputQuantityForRecipe(IRecipeDefinition recipe,
270
                                                         IngredientComponent<T, M> ingredientComponent,
271
                                                         T instance, M matchCondition) {
272
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
1✔
273
        return recipe.getOutput().getInstances(ingredientComponent)
1✔
274
                .stream()
1✔
275
                .filter(i -> matcher.matches(i, instance, matchCondition))
1✔
276
                .mapToLong(matcher::getQuantity)
1✔
277
                .sum();
1✔
278
    }
279

280
    /**
281
     * Calculate a crafting job for the given instance.
282
     *
283
     * This method is merely an easily-stubbable implementation for the method above,
284
     * to simplify unit testing.
285
     *
286
     * @param recipeIndex The recipe index.
287
     * @param channel The target channel that will be stored in created crafting jobs.
288
     * @param storageGetter A callback function to get a storage for the given ingredient component.
289
     * @param ingredientComponent The ingredient component type of the instance.
290
     * @param instance The instance to craft.
291
     * @param matchCondition The match condition of the instance.
292
     * @param craftMissing If the missing required ingredients should also be crafted.
293
     * @param simulatedExtractionMemory This map remembers all extracted instances in simulation mode.
294
     *                                  This is to make sure that instances can not be extracted multiple times
295
     *                                  when simulating.
296
     * @param extractionMemoryReusable Like simulatedExtractionMemory, but it stores the reusable ingredients.
297
     * @param identifierGenerator An ID generator for crafting jobs.
298
     * @param craftingJobsGraph The target graph where all dependencies will be stored.
299
     * @param parentDependencies A set of parent recipe dependencies that are pending.
300
     *                           This is used to check for infinite recursion in recipes.
301
     * @param collectMissingRecipes If the missing recipes should be collected inside
302
     *                              {@link UnknownCraftingRecipeException}.
303
     *                              This may slow down calculation for deeply nested recipe graphs.
304
     * @param <T> The instance type.
305
     * @param <M> The matching condition parameter.
306
     * @return The crafting job for the given instance.
307
     * @throws UnknownCraftingRecipeException If the recipe for a (sub)ingredient is unavailable.
308
     * @throws RecursiveCraftingRecipeException If an infinite recursive recipe was detected.
309
     */
310
    protected static <T, M> CraftingJob calculateCraftingJobs(IRecipeIndex recipeIndex, int channel,
311
                                                              Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
312
                                                              IngredientComponent<T, M> ingredientComponent,
313
                                                              T instance, M matchCondition, boolean craftMissing,
314
                                                              Map<IngredientComponent<?, ?>,
315
                                                                      IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemory,
316
                                                              Map<IngredientComponent<?, ?>,
317
                                                                      IIngredientCollectionMutable<?, ?>> extractionMemoryReusable,
318
                                                              IIdentifierGenerator identifierGenerator,
319
                                                              CraftingJobDependencyGraph craftingJobsGraph,
320
                                                              Set<IPrototypedIngredient> parentDependencies,
321
                                                              boolean collectMissingRecipes)
322
            throws UnknownCraftingRecipeException, RecursiveCraftingRecipeException {
323
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
1✔
324
        // This matching condition makes it so that the recipe output does not have to match with the requested input by quantity.
325
        M quantifierlessCondition = matcher.withoutCondition(matchCondition,
1✔
326
                ingredientComponent.getPrimaryQuantifier().getMatchCondition());
1✔
327
        long instanceQuantity = matcher.getQuantity(instance);
1✔
328

329
        // Loop over all available recipes, and return the first valid one.
330
        Iterator<IRecipeDefinition> recipes = recipeIndex.getRecipes(ingredientComponent, instance, quantifierlessCondition);
1✔
331
        List<UnknownCraftingRecipeException> firstMissingDependencies = Lists.newArrayList();
1✔
332
        Map<IngredientComponent<?, ?>, List<?>> firstIngredientsStorage = Collections.emptyMap();
1✔
333
        List<CraftingJob> firstPartialCraftingJobs = Lists.newArrayList();
1✔
334
        while (recipes.hasNext()) {
1✔
335
            IRecipeDefinition recipe = recipes.next();
1✔
336

337
            // Calculate the quantity for the given instance that the recipe outputs
338
            long recipeOutputQuantity = getOutputQuantityForRecipe(recipe, ingredientComponent, instance, quantifierlessCondition);
1✔
339
            // Based on the quantity of the recipe output, calculate the amount of required recipe jobs.
340
            int amount = (int) Math.ceil(((float) instanceQuantity) / (float) recipeOutputQuantity);
1✔
341

342
            // Calculate jobs for the given recipe
343
            PartialCraftingJobCalculation result = calculateCraftingJobs(recipeIndex, channel,
1✔
344
                    storageGetter, recipe, amount, craftMissing,
345
                    simulatedExtractionMemory, extractionMemoryReusable, identifierGenerator, craftingJobsGraph, parentDependencies,
346
                    collectMissingRecipes && firstMissingDependencies.isEmpty());
1✔
347
            if (result.getCraftingJob() == null) {
1✔
348
                firstMissingDependencies = result.getMissingDependencies();
1✔
349
                firstIngredientsStorage = result.getIngredientsStorage();
1✔
350
                if (result.getPartialCraftingJobs() != null) {
1✔
351
                    firstPartialCraftingJobs = result.getPartialCraftingJobs();
1✔
352
                }
353
            } else {
354
                return result.getCraftingJob();
1✔
355
            }
356
        }
1✔
357

358
        // No valid recipes were available, so we error or collect the missing instance.
359
        throw new UnknownCraftingRecipeException(new PrototypedIngredient<>(ingredientComponent, instance, matchCondition),
1✔
360
                matcher.getQuantity(instance), firstMissingDependencies, compressMixedIngredients(new MixedIngredients(firstIngredientsStorage)),
1✔
361
                firstPartialCraftingJobs);
362
    }
363

364
    /**
365
     * Calculate a crafting job for the given recipe.
366
     *
367
     * This method is merely an easily-stubbable implementation for the method above,
368
     * to simplify unit testing.
369
     *
370
     * @param recipeIndex The recipe index.
371
     * @param channel The target channel that will be stored in created crafting jobs.
372
     * @param storageGetter A callback function to get a storage for the given ingredient component.
373
     * @param recipe The recipe to calculate a job for.
374
     * @param amount The amount of times the recipe should be crafted.
375
     * @param craftMissing If the missing required ingredients should also be crafted.
376
     * @param simulatedExtractionMemory This map remembers all extracted instances in simulation mode.
377
     *                                  This is to make sure that instances can not be extracted multiple times
378
     *                                  when simulating.
379
     * @param extractionMemoryReusable Like simulatedExtractionMemory, but it stores the reusable ingredients.
380
     * @param identifierGenerator An ID generator for crafting jobs.
381
     * @param craftingJobsGraph The target graph where all dependencies will be stored.
382
     * @param parentDependencies A set of parent recipe dependencies that are pending.
383
     *                           This is used to check for infinite recursion in recipes.
384
     * @param collectMissingRecipes If the missing recipes should be collected inside
385
     *                              {@link UnknownCraftingRecipeException}.
386
     *                              This may slow down calculation for deeply nested recipe graphs.
387
     * @return The crafting job for the given instance.
388
     * @throws RecursiveCraftingRecipeException If an infinite recursive recipe was detected.
389
     */
390
    protected static PartialCraftingJobCalculation calculateCraftingJobs(
391
            IRecipeIndex recipeIndex, int channel,
392
            Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
393
            IRecipeDefinition recipe, int amount, boolean craftMissing,
394
            Map<IngredientComponent<?, ?>,
395
                    IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemory,
396
            Map<IngredientComponent<?, ?>,
397
                    IIngredientCollectionMutable<?, ?>> extractionMemoryReusable,
398
            IIdentifierGenerator identifierGenerator,
399
            CraftingJobDependencyGraph craftingJobsGraph,
400
            Set<IPrototypedIngredient> parentDependencies,
401
            boolean collectMissingRecipes)
402
            throws RecursiveCraftingRecipeException {
403
        List<UnknownCraftingRecipeException> missingDependencies = Lists.newArrayList();
1✔
404
        List<CraftingJob> partialCraftingJobs = Lists.newArrayList();
1✔
405

406
        // Check if all requirements are met for this recipe, if so return directly (don't schedule yet)
407
        Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>> simulation =
1✔
408
                getRecipeInputs(storageGetter, recipe, true, simulatedExtractionMemory, extractionMemoryReusable,
1✔
409
                        true, amount);
410
        Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> missingIngredients = simulation.getRight();
1✔
411
        if (!craftMissing && !missingIngredients.isEmpty()) {
1✔
412
            if (collectMissingRecipes) {
1✔
413
                // Collect missing ingredients as missing recipes when we don't want to craft sub-components,
414
                // but they are missing.
415
                for (Map.Entry<IngredientComponent<?, ?>, MissingIngredients<?, ?>> entry : missingIngredients.entrySet()) {
1✔
416
                    for (MissingIngredients.Element<?, ?> element : entry.getValue().getElements()) {
1✔
417
                        MissingIngredients.PrototypedWithRequested<?, ?> alternative = element.getAlternatives().get(0);
1✔
418

419
                        // Calculate the instance that was available in storage
420
                        IngredientComponent<?, ?> component = alternative.getRequestedPrototype().getComponent();
1✔
421
                        IIngredientMatcher matcher = component.getMatcher();
1✔
422
                        long storedQuantity = matcher.getQuantity(alternative.getRequestedPrototype().getPrototype()) - alternative.getQuantityMissing();
1✔
423
                        Map<IngredientComponent<?, ?>, List<?>> storageMap;
424
                        if (storedQuantity > 0) {
1✔
425
                            storageMap = Maps.newIdentityHashMap();
×
426
                            storageMap.put(component, Collections.singletonList(
×
427
                                    matcher.withQuantity(alternative.getRequestedPrototype().getPrototype(), storedQuantity)
×
428
                            ));
429
                        } else {
430
                            storageMap = Collections.emptyMap();
1✔
431
                        }
432

433
                        missingDependencies.add(new UnknownCraftingRecipeException(
1✔
434
                                alternative.getRequestedPrototype(), alternative.getQuantityMissing(),
1✔
435
                                Collections.emptyList(), compressMixedIngredients(new MixedIngredients(storageMap)), Lists.newArrayList()));
1✔
436
                    }
1✔
437
                }
1✔
438
            }
439
            return new PartialCraftingJobCalculation(null, missingDependencies, simulation.getLeft(), null);
1✔
440
        }
441

442
        // For all missing ingredients, recursively call this method for all missing items, and add as dependencies
443
        // We store dependencies as a mapping from recipe to job,
444
        // so that only one job exist per unique recipe,
445
        // so that a job amount can be incremented once another equal recipe is found.
446
        Map<IRecipeDefinition, CraftingJob> dependencies = Maps.newHashMapWithExpectedSize(missingIngredients.size());
1✔
447
        Map<IngredientComponent<?, ?>, IngredientCollectionPrototypeMap<?, ?>> dependenciesOutputSurplus = Maps.newIdentityHashMap();
1✔
448
        // We must be able to find crafting jobs for all dependencies
449
        for (IngredientComponent dependencyComponent : missingIngredients.keySet()) {
1✔
450
            try {
451
                // TODO: if we run into weird simulated extraction bugs, we may have to scope simulatedExtractionMemory, but I'm not sure about this (yet)
452
                PartialCraftingJobCalculationDependency resultDependency = calculateCraftingJobDependencyComponent(
1✔
453
                        dependencyComponent, dependenciesOutputSurplus, missingIngredients.get(dependencyComponent), parentDependencies,
1✔
454
                        dependencies, recipeIndex, channel, storageGetter, simulatedExtractionMemory, extractionMemoryReusable,
455
                        identifierGenerator, craftingJobsGraph, collectMissingRecipes);
456
                // Don't check the other components once we have an invalid dependency.
457
                if (!resultDependency.isValid()) {
1✔
458
                    missingDependencies.addAll(resultDependency.getUnknownCrafingRecipes());
1✔
459
                    partialCraftingJobs.addAll(resultDependency.getPartialCraftingJobs());
1✔
460
                    if (!collectMissingRecipes) {
1✔
461
                        break;
1✔
462
                    }
463
                }
464
            } catch (RecursiveCraftingRecipeException e) {
1✔
465
                e.addRecipe(recipe);
1✔
466
                throw e;
1✔
467
            }
1✔
468
        }
1✔
469

470
        // If at least one of our dependencies does not have a valid recipe or is not available,
471
        // go check the next recipe.
472
        if (!missingDependencies.isEmpty()) {
1✔
473
            return new PartialCraftingJobCalculation(null, missingDependencies, simulation.getLeft(), partialCraftingJobs);
1✔
474
        }
475

476
        CraftingJob craftingJob = new CraftingJob(identifierGenerator.getNext(), channel, recipe, amount,
1✔
477
                compressMixedIngredients(new MixedIngredients(simulation.getLeft())));
1✔
478
        for (CraftingJob dependency : dependencies.values()) {
1✔
479
            craftingJob.addDependency(dependency);
1✔
480
            craftingJobsGraph.addDependency(craftingJob, dependency);
1✔
481
        }
1✔
482
        return new PartialCraftingJobCalculation(craftingJob, null, simulation.getLeft(), null);
1✔
483
    }
484

485
    // Helper function for calculateCraftingJobs, returns a list of non-craftable ingredients and craftable ingredients.
486
    protected static <T, M> PartialCraftingJobCalculationDependency calculateCraftingJobDependencyComponent(
487
            IngredientComponent<T, M> dependencyComponent,
488
            Map<IngredientComponent<?, ?>, IngredientCollectionPrototypeMap<?, ?>> dependenciesOutputSurplus,
489
            MissingIngredients<T, M> missingIngredients,
490
            Set<IPrototypedIngredient> parentDependencies,
491
            Map<IRecipeDefinition, CraftingJob> dependencies,
492
            IRecipeIndex recipeIndex,
493
            int channel,
494
            Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
495
            Map<IngredientComponent<?, ?>,
496
                    IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemory,
497
            Map<IngredientComponent<?, ?>,
498
                    IIngredientCollectionMutable<?, ?>> extractionMemoryReusable,
499
            IIdentifierGenerator identifierGenerator,
500
            CraftingJobDependencyGraph craftingJobsGraph,
501
            boolean collectMissingRecipes)
502
            throws RecursiveCraftingRecipeException {
503
        IIngredientMatcher<T, M> dependencyMatcher = dependencyComponent.getMatcher();
1✔
504
        List<UnknownCraftingRecipeException> missingDependencies = Lists.newArrayList();
1✔
505
        for (MissingIngredients.Element<T, M> missingElement : missingIngredients.getElements()) {
1✔
506
            CraftingJob dependency = null;
1✔
507
            T dependencyInstance = null;
1✔
508
            boolean skipDependency = false;
1✔
509
            UnknownCraftingRecipeException firstError = null;
1✔
510
            // Loop over all prototype alternatives, at least one has to match.
511
            for (MissingIngredients.PrototypedWithRequested<T, M> prototypedAlternative : missingElement.getAlternatives()) {
1✔
512
                // Check if the missing element is reusable, and was triggered for craft earlier.
513
                if (missingElement.isInputReusable() && ((IIngredientCollectionMutable<T, M>) extractionMemoryReusable.get(dependencyComponent))
1✔
514
                        .contains(prototypedAlternative.getRequestedPrototype().getPrototype())) {
1✔
515
                    // Nothing has to be crafted anymore, jump to next dependency
516
                    skipDependency = true;
×
517
                    break;
×
518
                }
519

520
                IPrototypedIngredient<T, M> prototype = new PrototypedIngredient<>(
1✔
521
                        dependencyComponent,
522
                        dependencyMatcher.withQuantity(prototypedAlternative.getRequestedPrototype().getPrototype(), prototypedAlternative.getQuantityMissing()),
1✔
523
                        prototypedAlternative.getRequestedPrototype().getCondition()
1✔
524
                );
525
                // First check if we can grab it from previous surplus
526
                IngredientCollectionPrototypeMap<T, M> dependencyComponentSurplusOld = (IngredientCollectionPrototypeMap<T, M>) dependenciesOutputSurplus.get(dependencyComponent);
1✔
527
                IngredientCollectionPrototypeMap<T, M> dependencyComponentSurplus = null;
1✔
528
                if (dependencyComponentSurplusOld != null) {
1✔
529
                    // First create a copy of the given surplus store,
530
                    // and only once we see that the prototype is valid,
531
                    // save this copy again.
532
                    // This is because if this prototype is invalid,
533
                    // then we don't want these invalid surpluses.
534
                    dependencyComponentSurplus = new IngredientCollectionPrototypeMap<>(dependencyComponentSurplusOld.getComponent(), true);
1✔
535
                    dependencyComponentSurplus.addAll(dependencyComponentSurplusOld);
1✔
536

537
                    long remainingQuantity = dependencyMatcher.getQuantity(prototype.getPrototype());
1✔
538
                    IIngredientMatcher<T, M> prototypeMatcher = prototype.getComponent().getMatcher();
1✔
539
                    // Check all instances in the surplus that match with the given prototype
540
                    // For each match, we subtract its quantity from the required quantity.
541
                    Iterator<T> surplusIt = dependencyComponentSurplus.iterator(prototype.getPrototype(),
1✔
542
                            prototypeMatcher.withoutCondition(prototype.getCondition(), prototype.getComponent().getPrimaryQuantifier().getMatchCondition()));
1✔
543
                    boolean updatedRemainingQuantity = false;
1✔
544
                    while (remainingQuantity > 0 && surplusIt.hasNext()) {
1✔
545
                        updatedRemainingQuantity = true;
1✔
546
                        T matchingInstance = surplusIt.next();
1✔
547
                        long matchingInstanceQuantity = dependencyMatcher.getQuantity(matchingInstance);
1✔
548
                        if (matchingInstanceQuantity <= remainingQuantity) {
1✔
549
                            // This whole surplus instance can be consumed
550
                            remainingQuantity -= matchingInstanceQuantity;
1✔
551
                            surplusIt.remove();
1✔
552
                        } else {
553
                            // Only part of this surplus instance can be consumed.
554
                            matchingInstanceQuantity -= remainingQuantity;
×
555
                            remainingQuantity = 0;
×
556
                            surplusIt.remove();
×
557
                            dependencyComponentSurplus.setQuantity(matchingInstance, matchingInstanceQuantity);
×
558
                        }
559
                    }
1✔
560
                    if (updatedRemainingQuantity) {
1✔
561
                        if (remainingQuantity == 0) {
1✔
562
                            // The prototype is valid,
563
                            // so we can finally store our temporary surplus
564
                            dependenciesOutputSurplus.put(dependencyComponent, dependencyComponentSurplus);
×
565

566
                            // Nothing has to be crafted anymore, jump to next dependency
567
                            skipDependency = true;
×
568
                            break;
×
569
                        } else {
570
                            // Partial availability, other part needs to be crafted still.
571
                            prototype = new PrototypedIngredient<>(dependencyComponent,
1✔
572
                                    dependencyMatcher.withQuantity(prototype.getPrototype(), remainingQuantity),
1✔
573
                                    prototype.getCondition());
1✔
574
                        }
575
                    }
576
                }
577

578
                // Try to craft the given prototype
579
                try {
580
                    Set<IPrototypedIngredient> childDependencies = Sets.newHashSet(parentDependencies);
1✔
581
                    if (!childDependencies.add(prototype)) {
1✔
582
                        throw new RecursiveCraftingRecipeException(prototype);
1✔
583
                    }
584

585
                    dependency = calculateCraftingJobs(recipeIndex, channel, storageGetter,
1✔
586
                            dependencyComponent, prototype.getPrototype(),
1✔
587
                            prototype.getCondition(), true, simulatedExtractionMemory, extractionMemoryReusable,
1✔
588
                            identifierGenerator, craftingJobsGraph, childDependencies, collectMissingRecipes);
589
                    dependencyInstance = prototype.getPrototype();
1✔
590

591
                    // The prototype is valid,
592
                    // so we can finally store our temporary surplus
593
                    if (dependencyComponentSurplus != null) {
1✔
594
                        dependenciesOutputSurplus.put(dependencyComponent, dependencyComponentSurplus);
1✔
595
                    }
596

597
                    // Add the auxiliary recipe outputs that are not requested to the surplus
598
                    Object dependencyQuantifierlessCondition = dependencyMatcher.withoutCondition(prototype.getCondition(),
1✔
599
                            dependencyComponent.getPrimaryQuantifier().getMatchCondition());
1✔
600
                    long requestedQuantity = dependencyMatcher.getQuantity(prototype.getPrototype());
1✔
601
                    for (IngredientComponent outputComponent : dependency.getRecipe().getOutput().getComponents()) {
1✔
602
                        IngredientCollectionPrototypeMap<?, ?> componentSurplus = dependenciesOutputSurplus.get(outputComponent);
1✔
603
                        if (componentSurplus == null) {
1✔
604
                            componentSurplus = new IngredientCollectionPrototypeMap<>(outputComponent, true);
1✔
605
                            dependenciesOutputSurplus.put(outputComponent, componentSurplus);
1✔
606
                        }
607
                        List<Object> instances = dependency.getRecipe().getOutput().getInstances(outputComponent);
1✔
608
                        long recipeAmount = dependency.getAmount();
1✔
609
                        if (recipeAmount > 1) {
1✔
610
                            IIngredientMatcher matcher = outputComponent.getMatcher();
1✔
611
                            // If more than one recipe amount was crafted, correctly multiply the outputs to calculate the effective surplus.
612
                            instances = instances
1✔
613
                                    .stream()
1✔
614
                                    .map(instance -> matcher.withQuantity(instance, matcher.getQuantity(instance) * recipeAmount))
1✔
615
                                    .collect(Collectors.toList());
1✔
616
                        }
617
                        addRemainderAsSurplusForComponent(outputComponent, (List) instances, componentSurplus,
1✔
618
                                (IngredientComponent) prototype.getComponent(), prototype.getPrototype(), dependencyQuantifierlessCondition,
1✔
619
                                requestedQuantity);
620
                    }
1✔
621

622
                    break;
1✔
623
                } catch (UnknownCraftingRecipeException e) {
1✔
624
                    // Save the first error, and check the next prototype
625
                    if (firstError == null) {
1✔
626
                        // Modify the error so that the correct missing quantity is stored
627
                        firstError = new UnknownCraftingRecipeException(
1✔
628
                                e.getIngredient(), prototypedAlternative.getQuantityMissing(),
1✔
629
                                e.getMissingChildRecipes(), compressMixedIngredients(e.getIngredientsStorage()), e.getPartialCraftingJobs());
1✔
630
                    }
631
                }
632
            }
1✔
633

634
            // Check if this dependency can be skipped
635
            if (skipDependency) {
1✔
636
                continue;
×
637
            }
638

639
            // If no valid crafting recipe was found for the current sub-instance, re-throw its error
640
            if (dependency == null) {
1✔
641
                missingDependencies.add(firstError);
1✔
642
                if (collectMissingRecipes) {
1✔
643
                    continue;
1✔
644
                } else {
645
                    break;
646
                }
647
            }
648

649
            // --- When we reach this point, a valid sub-recipe was found ---
650

651
            // Update our simulatedExtractionMemory to indicate that the dependency's
652
            // instance should not be extracted anymore.
653
            ((IngredientCollectionPrototypeMap<T, M>) simulatedExtractionMemory.get(dependencyComponent))
1✔
654
                    .remove(dependencyInstance);
1✔
655

656
            // If the dependency instance is reusable, mark it as available in our reusable extraction memory
657
            if (missingElement.isInputReusable()) {
1✔
658
                ((IIngredientCollectionMutable<T, M>) extractionMemoryReusable.get(dependencyComponent)).add(dependencyInstance);
1✔
659
            }
660

661
            // Add the valid sub-recipe it to our dependencies
662
            // If the recipe was already present at this level, just increment the amount of the existing job.
663
            CraftingJob existingJob = dependencies.get(dependency.getRecipe());
1✔
664
            if (existingJob == null) {
1✔
665
                dependencies.put(dependency.getRecipe(), dependency);
1✔
666
            } else {
667
                // Remove the standalone dependency job by merging it into the existing job
668
                craftingJobsGraph.mergeCraftingJobs(existingJob, dependency, true);
1✔
669
            }
670
        }
1✔
671

672
        return new PartialCraftingJobCalculationDependency(missingDependencies, dependencies.values());
1✔
673
    }
674

675
    // Helper function for calculateCraftingJobDependencyComponent
676
    protected static <T1, M1, T2, M2> void addRemainderAsSurplusForComponent(IngredientComponent<T1, M1> ingredientComponent,
677
                                                                             List<T1> instances,
678
                                                                             IngredientCollectionPrototypeMap<T1, M1> simulatedExtractionMemory,
679
                                                                             IngredientComponent<T2, M2> blackListComponent,
680
                                                                             T2 blacklistInstance, M2 blacklistCondition,
681
                                                                             long blacklistQuantity) {
682
        IIngredientMatcher<T2, M2> blacklistMatcher = blackListComponent.getMatcher();
1✔
683
        for (T1 instance : instances) {
1✔
684
            IIngredientMatcher<T1, M1> outputMatcher = ingredientComponent.getMatcher();
1✔
685
            long reduceQuantity = 0;
1✔
686
            if (blackListComponent == ingredientComponent
1✔
687
                    && blacklistMatcher.matches(blacklistInstance, (T2) instance, blacklistCondition)) {
1✔
688
                reduceQuantity = blacklistQuantity;
1✔
689
            }
690
            long quantity = simulatedExtractionMemory.getQuantity(instance) + (outputMatcher.getQuantity(instance) - reduceQuantity);
1✔
691
            if (quantity > 0) {
1✔
692
                simulatedExtractionMemory.setQuantity(instance, quantity);
1✔
693
            }
694
        }
1✔
695
    }
1✔
696

697
    /**
698
     * Schedule all crafting jobs in the given dependency graph in the given network.
699
     * @param craftingNetwork The target crafting network.
700
     * @param craftingJobDependencyGraph The crafting job dependency graph.
701
     * @param allowDistribution If the crafting jobs are allowed to be split over multiple crafting interfaces.
702
     * @param initiator Optional UUID of the initiator.
703
     * @throws UnavailableCraftingInterfacesException If no crafting interfaces were available.
704
     */
705
    public static void scheduleCraftingJobs(ICraftingNetwork craftingNetwork,
706
                                            CraftingJobDependencyGraph craftingJobDependencyGraph,
707
                                            boolean allowDistribution,
708
                                            @Nullable UUID initiator) throws UnavailableCraftingInterfacesException {
709
        List<CraftingJob> startedJobs = Lists.newArrayList();
×
710
        craftingNetwork.getCraftingJobDependencyGraph().importDependencies(craftingJobDependencyGraph);
×
711
        for (CraftingJob craftingJob : craftingJobDependencyGraph.getCraftingJobs()) {
×
712
            try {
713
                craftingNetwork.scheduleCraftingJob(craftingJob, allowDistribution);
×
714
            } catch (UnavailableCraftingInterfacesException e) {
×
715
                // First, cancel all jobs that were already started
716
                for (CraftingJob startedJob : startedJobs) {
×
717
                    craftingNetwork.cancelCraftingJob(startedJob.getChannel(), startedJob.getId());
×
718
                }
×
719

720
                // Then, throw an exception for all jobs in this dependency graph
721
                throw new UnavailableCraftingInterfacesException(craftingJobDependencyGraph.getCraftingJobs());
×
722
            }
×
723
            startedJobs.add(craftingJob);
×
724
            if (initiator != null) {
×
725
                craftingJob.setInitiatorUuid(initiator.toString());
×
726
            }
727
        }
×
728
    }
×
729

730
    /**
731
     * Schedule the given crafting job  in the given network.
732
     * @param craftingNetwork The target crafting network.
733
     * @param craftingJob The crafting job to schedule.
734
     * @param allowDistribution If the crafting job is allowed to be split over multiple crafting interfaces.
735
     * @param initiator Optional UUID of the initiator.
736
     * @return The scheduled crafting job.
737
     * @throws UnavailableCraftingInterfacesException If no crafting interfaces were available.
738
     */
739
    public static CraftingJob scheduleCraftingJob(ICraftingNetwork craftingNetwork,
740
                                                  CraftingJob craftingJob,
741
                                                  boolean allowDistribution,
742
                                                  @Nullable UUID initiator) throws UnavailableCraftingInterfacesException {
743
        craftingNetwork.scheduleCraftingJob(craftingJob, allowDistribution);
×
744
        if (initiator != null) {
×
745
            craftingJob.setInitiatorUuid(initiator.toString());
×
746
        }
747
        return craftingJob;
×
748
    }
749

750
    /**
751
     * Schedule a crafting job for the given instance in the given network.
752
     * @param network The target network.
753
     * @param channel The target channel.
754
     * @param ingredientComponent The ingredient component type of the instance.
755
     * @param instance The instance to craft.
756
     * @param matchCondition The match condition of the instance.
757
     * @param craftMissing If the missing required ingredients should also be crafted.
758
     * @param allowDistribution If the crafting job is allowed to be split over multiple crafting interfaces.
759
     * @param identifierGenerator An ID generator for crafting jobs.
760
     * @param initiator Optional UUID of the initiator.
761
     * @param <T> The instance type.
762
     * @param <M> The matching condition parameter.
763
     * @return The scheduled crafting job, or null if no recipe was found.
764
     */
765
    @Nullable
766
    public static <T, M> CraftingJob calculateAndScheduleCraftingJob(INetwork network, int channel,
767
                                                                     IngredientComponent<T, M> ingredientComponent,
768
                                                                     T instance, M matchCondition,
769
                                                                     boolean craftMissing, boolean allowDistribution,
770
                                                                     IIdentifierGenerator identifierGenerator,
771
                                                                     @Nullable UUID initiator) {
772
        try {
773
            CraftingJobDependencyGraph dependencyGraph = new CraftingJobDependencyGraph();
×
774
            CraftingJob craftingJob = calculateCraftingJobs(network, channel, ingredientComponent, instance,
×
775
                    matchCondition, craftMissing, identifierGenerator, dependencyGraph, false);
776

777
            ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
778

779
            scheduleCraftingJobs(craftingNetwork, dependencyGraph, allowDistribution, initiator);
×
780

781
            return craftingJob;
×
782
        } catch (UnknownCraftingRecipeException | RecursiveCraftingRecipeException | UnavailableCraftingInterfacesException e) {
×
783
            return null;
×
784
        }
785
    }
786

787
    /**
788
     * Schedule a crafting job for the given recipe in the given network.
789
     * @param network The target network.
790
     * @param channel The target channel.
791
     * @param recipe The recipe to craft.
792
     * @param amount The amount to craft.
793
     * @param craftMissing If the missing required ingredients should also be crafted.
794
     * @param allowDistribution If the crafting job is allowed to be split over multiple crafting interfaces.
795
     * @param identifierGenerator An ID generator for crafting jobs.
796
     * @param initiator Optional UUID of the initiator.
797
     * @return The scheduled crafting job, or null if no recipe was found.
798
     */
799
    @Nullable
800
    public static CraftingJob calculateAndScheduleCraftingJob(INetwork network, int channel,
801
                                                              IRecipeDefinition recipe, int amount,
802
                                                              boolean craftMissing, boolean allowDistribution,
803
                                                              IIdentifierGenerator identifierGenerator,
804
                                                              @Nullable UUID initiator) {
805
        try {
806
            CraftingJobDependencyGraph dependencyGraph = new CraftingJobDependencyGraph();
×
807
            CraftingJob craftingJob = calculateCraftingJobs(network, channel, recipe, amount, craftMissing,
×
808
                    identifierGenerator, dependencyGraph, false);
809

810
            ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
811

812
            scheduleCraftingJobs(craftingNetwork, dependencyGraph, allowDistribution, initiator);
×
813

814
            return craftingJob;
×
815
        } catch (RecursiveCraftingRecipeException | FailedCraftingRecipeException | UnavailableCraftingInterfacesException e) {
×
816
            return null;
×
817
        }
818
    }
819

820
    /**
821
     * Check if the given network contains the given instance in any of its storages.
822
     * @param network The target network.
823
     * @param channel The target channel.
824
     * @param ingredientComponent The ingredient component type of the instance.
825
     * @param instance The instance to check.
826
     * @param matchCondition The match condition of the instance.
827
     * @param <T> The instance type.
828
     * @param <M> The matching condition parameter.
829
     * @return If the instance is present in the network.
830
     */
831
    public static <T, M> boolean hasStorageInstance(INetwork network, int channel,
832
                                                    IngredientComponent<T, M> ingredientComponent,
833
                                                    T instance, M matchCondition) {
834
        IIngredientComponentStorage<T, M> storage = getNetworkStorage(network, channel, ingredientComponent, true);
×
835
        if (storage instanceof IngredientChannelAdapter) ((IngredientChannelAdapter) storage).disableLimits();
×
836
        boolean contains;
837
        if (storage instanceof IngredientChannelIndexed) {
×
838
            IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
×
839
            long quantityPresent = ((IngredientChannelIndexed<T, M>) storage).getIndex().getQuantity(instance);
×
840
            contains = matcher.hasCondition(matchCondition, ingredientComponent.getPrimaryQuantifier().getMatchCondition()) ? quantityPresent >= matcher.getQuantity(instance) : quantityPresent > 0;
×
841
        } else {
×
842
            contains = !ingredientComponent.getMatcher().isEmpty(storage.extract(instance, matchCondition, true));
×
843
        }
844
        if (storage instanceof IngredientChannelAdapter) ((IngredientChannelAdapter) storage).enableLimits();
×
845
        return contains;
×
846
    }
847

848
    /**
849
     * Check the quantity of the given instance in the network.
850
     * @param network The target network.
851
     * @param channel The target channel.
852
     * @param ingredientComponent The ingredient component type of the instance.
853
     * @param instance The instance to check.
854
     * @param matchCondition The match condition of the instance.
855
     * @param <T> The instance type.
856
     * @param <M> The matching condition parameter.
857
     * @return The quantity in the network.
858
     */
859
    public static <T, M> long getStorageInstanceQuantity(INetwork network, int channel,
860
                                                         IngredientComponent<T, M> ingredientComponent,
861
                                                         T instance, M matchCondition) {
862
        IIngredientComponentStorage<T, M> storage = getNetworkStorage(network, channel, ingredientComponent, true);
×
863
        if (storage instanceof IngredientChannelAdapter) ((IngredientChannelAdapter) storage).disableLimits();
×
864
        long quantityPresent;
865
        if (storage instanceof IngredientChannelIndexed) {
×
866
            quantityPresent = ((IngredientChannelIndexed<T, M>) storage).getIndex().getQuantity(instance);
×
867
        } else {
868
            quantityPresent = ingredientComponent.getMatcher().getQuantity(storage.extract(instance, matchCondition, true));
×
869
        }
870
        if (storage instanceof IngredientChannelAdapter) ((IngredientChannelAdapter) storage).enableLimits();
×
871
        return quantityPresent;
×
872
    }
873

874
    /**
875
     * Check if there is a scheduled crafting job for the given instance.
876
     * @param craftingNetwork The target crafting network.
877
     * @param channel The target channel.
878
     * @param ingredientComponent The ingredient component type of the instance.
879
     * @param instance The instance to check.
880
     * @param matchCondition The match condition of the instance.
881
     * @param <T> The instance type.
882
     * @param <M> The matching condition parameter.
883
     * @return If the instance has a crafting job.
884
     */
885
    public static <T, M> boolean isCrafting(ICraftingNetwork craftingNetwork, int channel,
886
                                            IngredientComponent<T, M> ingredientComponent,
887
                                            T instance, M matchCondition) {
888
        Iterator<CraftingJob> craftingJobs = craftingNetwork.getCraftingJobs(channel, ingredientComponent,
×
889
                instance, matchCondition);
890
        return craftingJobs.hasNext();
×
891
    }
892

893
    /**
894
     * Check if there is a scheduled crafting job for the given recipe.
895
     * @param craftingNetwork The target crafting network.
896
     * @param channel The target channel.
897
     * @param recipe The recipe to check.
898
     * @return If the instance has a crafting job.
899
     */
900
    public static boolean isCrafting(ICraftingNetwork craftingNetwork, int channel,
901
                                     IRecipeDefinition recipe) {
902
        Iterator<CraftingJob> it = craftingNetwork.getCraftingJobs(channel);
×
903
        while (it.hasNext()) {
×
904
            if (it.next().getRecipe().equals(recipe)) {
×
905
                return true;
×
906
            }
907
        }
908
        return false;
×
909
    }
910

911
    /**
912
     * Get all required recipe input ingredients from the network for the given ingredient component.
913
     *
914
     * If multiple alternative inputs are possible,
915
     * then only the first possible match will be taken.
916
     *
917
     * Note: Make sure that you first call in simulation-mode
918
     * to see if the ingredients are available.
919
     * If you immediately call this non-simulated,
920
     * then there might be a chance that ingredients are lost
921
     * from the network.
922
     *
923
     * @param storage The target storage.
924
     * @param ingredientComponent The ingredient component to get the ingredients for.
925
     * @param recipe The recipe to get the inputs from.
926
     * @param simulate If true, then the ingredients will effectively be removed from the network, not when false.
927
     * @param recipeOutputQuantity The number of times the given recipe should be applied.
928
     * @param <T> The instance type.
929
     * @param <M> The matching condition parameter, may be Void.
930
     * @return A list of slot-based ingredients, or null if no valid inputs could be found.
931
     */
932
    @Nullable
933
    public static <T, M> List<T> getIngredientRecipeInputs(IIngredientComponentStorage<T, M> storage,
934
                                                           IngredientComponent<T, M> ingredientComponent,
935
                                                           IRecipeDefinition recipe, boolean simulate,
936
                                                           long recipeOutputQuantity) {
937
        return getIngredientRecipeInputs(storage, ingredientComponent, recipe, simulate,
1✔
938
                simulate ? new IngredientCollectionPrototypeMap<>(ingredientComponent, true) : null,
1✔
939
                new IngredientHashSet<>(ingredientComponent),
940
                false, recipeOutputQuantity).getLeft();
1✔
941
    }
942

943
    /**
944
     * Get all required recipe input ingredients from the network for the given ingredient component,
945
     * and optionally, explicitly calculate the missing ingredients.
946
     *
947
     * If multiple alternative inputs are possible,
948
     * then only the first possible match will be taken.
949
     *
950
     * Note: Make sure that you first call in simulation-mode
951
     * to see if the ingredients are available.
952
     * If you immediately call this non-simulated,
953
     * then there might be a chance that ingredients are lost
954
     * from the network.
955
     *
956
     * @param storage The target storage.
957
     * @param ingredientComponent The ingredient component to get the ingredients for.
958
     * @param recipe The recipe to get the inputs from.
959
     * @param simulate If true, then the ingredients will effectively be removed from the network, not when false.
960
     * @param simulatedExtractionMemory This map remembers all extracted instances in simulation mode.
961
     *                                  This is to make sure that instances can not be extracted multiple times
962
     *                                  when simulating.
963
     *                                  The quantities can also go negatives,
964
     *                                  which means that a surplus of the given instance is present,
965
     *                                  which will be used up first before a call to the storage.
966
     * @param extractionMemoryReusable Like simulatedExtractionMemory, but it stores the reusable ingredients.
967
     * @param collectMissingIngredients If missing ingredients should be collected.
968
     *                                  If false, then the first returned list may be null
969
     *                                  if no valid matches can be found,
970
     *                                  and the second returned list is always null,
971
     * @param recipeOutputQuantity The number of times the given recipe should be applied.
972
     * @param <T> The instance type.
973
     * @param <M> The matching condition parameter, may be Void.
974
     * @return A pair with two lists:
975
     *           1. A list of available slot-based ingredients.
976
     *           2. A missing ingredients object.
977
     */
978
    public static <T, M> Pair<List<T>, MissingIngredients<T, M>>
979
    getIngredientRecipeInputs(IIngredientComponentStorage<T, M> storage, IngredientComponent<T, M> ingredientComponent,
980
                              IRecipeDefinition recipe, boolean simulate,
981
                              IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemory,
982
                              IIngredientCollectionMutable<T, M> extractionMemoryReusable,
983
                              boolean collectMissingIngredients, long recipeOutputQuantity) {
984
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
1✔
985

986
        // Quickly return if the storage is empty
987
        // We can't take this shortcut if we have a reusable ingredient AND extractionMemoryReusable is not empty
988
        if (storage.getMaxQuantity() == 0 &&
1✔
989
                extractionMemoryReusable.isEmpty() &&
1✔
990
                IntStream.range(0, recipe.getInputs(ingredientComponent).size())
1✔
991
                        .noneMatch(i -> recipe.isInputReusable(ingredientComponent, i))) {
1✔
992
            if (collectMissingIngredients) {
1✔
993
                List<IPrototypedIngredientAlternatives<T, M>> recipeInputs = recipe.getInputs(ingredientComponent);
1✔
994
                MissingIngredients<T, M> missing = new MissingIngredients<>(recipeInputs.stream().map(IPrototypedIngredientAlternatives::getAlternatives)
1✔
995
                        .map(l -> multiplyPrototypedIngredients(l, recipeOutputQuantity)) // If the input is reusable, don't multiply the expected input quantity
1✔
996
                        .map(ps -> new MissingIngredients.Element<>(ps
1✔
997
                                .stream()
1✔
998
                                .map(p -> new MissingIngredients.PrototypedWithRequested<>(p, matcher.getQuantity(p.getPrototype())))
1✔
999
                                .collect(Collectors.toList()), false)
1✔
1000
                        )
1001
                        .collect(Collectors.toList()));
1✔
1002
                return Pair.of(
1✔
1003
                        Lists.newArrayList(Collections.nCopies(recipe.getInputs(ingredientComponent).size(),
1✔
1004
                                ingredientComponent.getMatcher().getEmptyInstance())),
1✔
1005
                        missing);
1006
            } else {
1007
                return Pair.of(null, null);
1✔
1008
            }
1009
        }
1010

1011
        // Iterate over all input slots
1012
        List<IPrototypedIngredientAlternatives<T, M>> inputAlternativePrototypes = recipe.getInputs(ingredientComponent);
1✔
1013
        List<T> inputInstances = Lists.newArrayList();
1✔
1014
        List<MissingIngredients.Element<T, M>> missingElements =
1015
                collectMissingIngredients ? Lists.newArrayList() : null;
1✔
1016
        for (int inputIndex = 0; inputIndex < inputAlternativePrototypes.size(); inputIndex++) {
1✔
1017
            IPrototypedIngredientAlternatives<T, M> inputPrototypes = inputAlternativePrototypes.get(inputIndex);
1✔
1018
            T firstInputInstance = null;
1✔
1019
            boolean setFirstInputInstance = false;
1✔
1020
            T inputInstance = null;
1✔
1021
            boolean hasInputInstance = false;
1✔
1022
            IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemoryBufferFirst = null;
1✔
1023
            IIngredientCollectionMutable<T, M> extractionMemoryReusableBufferFirst = null;
1✔
1024

1025
            // Iterate over all alternatives for this input slot, and take the first matching ingredient.
1026
            List<MissingIngredients.PrototypedWithRequested<T, M>> missingAlternatives = Lists.newArrayList();
1✔
1027
            IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemoryAlternative = simulate ? new IngredientCollectionPrototypeMap<>(ingredientComponent, true) : null;
1✔
1028
            if (simulate) {
1✔
1029
                simulatedExtractionMemoryAlternative.addAll(simulatedExtractionMemory);
1✔
1030
            }
1031
            for (IPrototypedIngredient<T, M> inputPrototype : inputPrototypes.getAlternatives()) {
1✔
1032
                boolean inputReusable = recipe.isInputReusable(ingredientComponent, inputIndex);
1✔
1033
                IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemoryBuffer = simulate ? new IngredientCollectionPrototypeMap<>(ingredientComponent, true) : null;
1✔
1034
                IIngredientCollectionMutable<T, M> extractionMemoryReusableBuffer = inputReusable ? new IngredientHashSet<>(ingredientComponent) : null;
1✔
1035
                boolean shouldBreak = false;
1✔
1036

1037
                // Multiply required prototype if recipe quantity is higher than one, AND if the input is NOT reusable.
1038
                if (recipeOutputQuantity > 1 && !inputReusable) {
1✔
1039
                    inputPrototype = multiplyPrototypedIngredient(inputPrototype, recipeOutputQuantity);
1✔
1040
                }
1041

1042
                // If the prototype is empty, we can skip network extraction
1043
                if (matcher.isEmpty(inputPrototype.getPrototype())) {
1✔
1044
                    inputInstance = inputPrototype.getPrototype();
1✔
1045
                    hasInputInstance = true;
1✔
1046
                    break;
1✔
1047
                }
1048

1049
                long prototypeQuantity = matcher.getQuantity(inputPrototype.getPrototype());
1✔
1050
                if (inputReusable && extractionMemoryReusable.contains(inputPrototype.getPrototype())) {
1✔
1051
                    // If the reusable item has been extracted before, mark as valid, and don't extract again.
1052
                    inputInstance = inputPrototype.getComponent().getMatcher().getEmptyInstance();
1✔
1053
                    hasInputInstance = true;
1✔
1054
                    shouldBreak = true;
1✔
1055
                } else {
1056
                    long memoryQuantity;
1057
                    if (simulate && (memoryQuantity = simulatedExtractionMemoryAlternative
1✔
1058
                            .getQuantity(inputPrototype.getPrototype())) != 0) {
1✔
1059
                        long newQuantity = memoryQuantity + prototypeQuantity;
1✔
1060
                        if (newQuantity > 0) {
1✔
1061
                            // Part of our quantity can be provided via simulatedExtractionMemory,
1062
                            // but not all of it,
1063
                            // so we need to extract from storage as well.
1064
                            T newInstance = matcher.withQuantity(inputPrototype.getPrototype(), newQuantity);
1✔
1065
                            M matchCondition = matcher.withoutCondition(inputPrototype.getCondition(),
1✔
1066
                                    ingredientComponent.getPrimaryQuantifier().getMatchCondition());
1✔
1067
                            if (storage instanceof IngredientChannelAdapter)
1✔
1068
                                ((IngredientChannelAdapter) storage).disableLimits();
×
1069
                            T extracted = storage.extract(newInstance, matchCondition, true);
1✔
1070
                            if (storage instanceof IngredientChannelAdapter)
1✔
1071
                                ((IngredientChannelAdapter) storage).enableLimits();
×
1072
                            long quantityExtracted = matcher.getQuantity(extracted);
1✔
1073
                            if (quantityExtracted == newQuantity) {
1✔
1074
                                // All remaining could be extracted from storage, all is fine now
1075
                                inputInstance = inputPrototype.getPrototype();
1✔
1076
                                simulatedExtractionMemoryAlternative.add(inputInstance);
1✔
1077
                                simulatedExtractionMemoryBuffer.add(inputInstance);
1✔
1078
                                if (inputReusable) {
1✔
1079
                                    extractionMemoryReusableBuffer.add(inputInstance);
1✔
1080
                                }
1081
                                hasInputInstance = true;
1✔
1082
                                shouldBreak = true;
1✔
1083
                            } else if (collectMissingIngredients) {
1✔
1084
                                // Not everything could be extracted from storage, we *miss* the remaining ingredient.
1085
                                long quantityMissingPrevious = Math.max(0, memoryQuantity - quantityExtracted);
1✔
1086
                                long quantityMissingTotal = newQuantity - quantityExtracted;
1✔
1087
                                long quantityMissingRelative = quantityMissingTotal - quantityMissingPrevious;
1✔
1088

1089
                                missingAlternatives.add(new MissingIngredients.PrototypedWithRequested<>(inputPrototype, quantityMissingRelative));
1✔
1090
                                inputInstance = matcher.withQuantity(inputPrototype.getPrototype(), prototypeQuantity - quantityMissingRelative);
1✔
1091
                                simulatedExtractionMemoryAlternative.setQuantity(inputPrototype.getPrototype(), quantityMissingTotal);
1✔
1092
                                simulatedExtractionMemoryBuffer.add(matcher.withQuantity(inputPrototype.getPrototype(), quantityMissingRelative));
1✔
1093
                            }
1094
                        } else {
1✔
1095
                            // All of our quantity can be provided via our surplus in simulatedExtractionMemory
1096
                            simulatedExtractionMemoryAlternative.add(inputPrototype.getPrototype());
1✔
1097
                            simulatedExtractionMemoryBuffer.add(inputPrototype.getPrototype());
1✔
1098
                            if (inputReusable) {
1✔
1099
                                extractionMemoryReusableBuffer.add(inputPrototype.getPrototype());
1✔
1100
                            }
1101
                            inputInstance = inputPrototype.getComponent().getMatcher().getEmptyInstance();
1✔
1102
                            hasInputInstance = true;
1✔
1103
                            shouldBreak = true;
1✔
1104
                        }
1105
                    } else {
1✔
1106
                        M matchCondition = matcher.withoutCondition(inputPrototype.getCondition(),
1✔
1107
                                ingredientComponent.getPrimaryQuantifier().getMatchCondition());
1✔
1108
                        if (storage instanceof IngredientChannelAdapter)
1✔
1109
                            ((IngredientChannelAdapter) storage).disableLimits();
×
1110
                        T extracted = storage.extract(inputPrototype.getPrototype(), matchCondition, simulate);
1✔
1111
                        if (storage instanceof IngredientChannelAdapter)
1✔
1112
                            ((IngredientChannelAdapter) storage).enableLimits();
×
1113
                        long quantityExtracted = matcher.getQuantity(extracted);
1✔
1114
                        inputInstance = extracted;
1✔
1115
                        if (simulate) {
1✔
1116
                            simulatedExtractionMemoryAlternative.add(extracted);
1✔
1117
                            simulatedExtractionMemoryBuffer.add(inputPrototype.getPrototype());
1✔
1118
                        }
1119
                        if (prototypeQuantity == quantityExtracted) {
1✔
1120
                            hasInputInstance = true;
1✔
1121
                            shouldBreak = true;
1✔
1122
                            if (inputReusable) {
1✔
1123
                                extractionMemoryReusableBuffer.add(inputPrototype.getPrototype());
1✔
1124
                            }
1125
                        } else if (collectMissingIngredients) {
1✔
1126
                            long quantityMissing = prototypeQuantity - quantityExtracted;
1✔
1127
                            missingAlternatives.add(new MissingIngredients.PrototypedWithRequested<>(inputPrototype, quantityMissing));
1✔
1128
                        }
1129
                    }
1130
                }
1131

1132
                if (!setFirstInputInstance || shouldBreak) {
1✔
1133
                    setFirstInputInstance = true;
1✔
1134
                    firstInputInstance = inputInstance;
1✔
1135
                    simulatedExtractionMemoryBufferFirst = simulatedExtractionMemoryBuffer;
1✔
1136
                    if (inputReusable) {
1✔
1137
                        extractionMemoryReusableBufferFirst = extractionMemoryReusableBuffer;
1✔
1138
                    } else {
1139
                        extractionMemoryReusableBufferFirst = null;
1✔
1140
                    }
1141
                }
1142

1143
                if (shouldBreak) {
1✔
1144
                    break;
1✔
1145
                }
1146
            }
1✔
1147

1148
            if (simulatedExtractionMemoryBufferFirst != null) {
1✔
1149
                for (T instance : simulatedExtractionMemoryBufferFirst) {
1✔
1150
                    simulatedExtractionMemory.add(instance);
1✔
1151
                }
1✔
1152
            }
1153
            if (extractionMemoryReusableBufferFirst != null) {
1✔
1154
                for (T instance : extractionMemoryReusableBufferFirst) {
1✔
1155
                    extractionMemoryReusable.add(instance);
1✔
1156
                }
1✔
1157
            }
1158

1159
            // If none of the alternatives were found, fail immediately
1160
            if (!hasInputInstance) {
1✔
1161
                if (!simulate) {
1✔
1162
                    // But first, re-insert all already-extracted instances
1163
                    for (T instance : inputInstances) {
1✔
1164
                        T remaining = storage.insert(instance, false);
1✔
1165
                        if (!matcher.isEmpty(remaining)) {
1✔
1166
                            throw new IllegalStateException("Extraction for a crafting recipe failed" +
×
1167
                                    "due to inconsistent insertion behaviour by destination in simulation " +
1168
                                    "and non-simulation: " + storage + ". Lost: " + remaining);
1169
                        }
1170
                    }
1✔
1171
                }
1172

1173
                if (!collectMissingIngredients) {
1✔
1174
                    // This input failed, return immediately
1175
                    return Pair.of(null, null);
1✔
1176
                } else {
1177
                    // Multiply missing collection if recipe quantity is higher than one
1178
                    if (missingAlternatives.size() > 0) {
1✔
1179
                        missingElements.add(new MissingIngredients.Element<>(missingAlternatives, recipe.isInputReusable(ingredientComponent, inputIndex)));
1✔
1180
                    }
1181
                }
1182
            }
1183

1184
            // Otherwise, append it to the list and carry on.
1185
            // If none of the instances were valid, we add the first partially valid instance.
1186
            if (hasInputInstance) {
1✔
1187
                inputInstances.add(inputInstance);
1✔
1188
            } else if (setFirstInputInstance && !matcher.isEmpty(firstInputInstance)) {
1✔
1189
                inputInstances.add(firstInputInstance);
1✔
1190
            }
1191
        }
1192

1193
        return Pair.of(
1✔
1194
                inputInstances,
1195
                collectMissingIngredients ? new MissingIngredients<>(missingElements) : null
1✔
1196
        );
1197
    }
1198

1199
    /**
1200
     * Get all required recipe input ingredients from the network.
1201
     *
1202
     * If multiple alternative inputs are possible,
1203
     * then only the first possible match will be taken.
1204
     *
1205
     * Note: Make sure that you first call in simulation-mode
1206
     * to see if the ingredients are available.
1207
     * If you immediately call this non-simulated,
1208
     * then there might be a chance that ingredients are lost
1209
     * from the network.
1210
     *
1211
     * @param network The target network.
1212
     * @param channel The target channel.
1213
     * @param recipe The recipe to get the inputs from.
1214
     * @param simulate If true, then the ingredients will effectively be removed from the network, not when false.
1215
     * @param recipeOutputQuantity The number of times the given recipe should be applied.
1216
     * @return The found ingredients or null.
1217
     */
1218
    @Nullable
1219
    public static IMixedIngredients getRecipeInputs(INetwork network, int channel,
1220
                                                    IRecipeDefinition recipe, boolean simulate,
1221
                                                    long recipeOutputQuantity) {
1222
        Map<IngredientComponent<?, ?>, List<?>> inputs = getRecipeInputs(getNetworkStorageGetter(network, channel, true),
×
1223
                recipe, simulate, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), false, recipeOutputQuantity).getLeft();
×
1224
        return inputs == null ? null : new MixedIngredients(inputs);
×
1225
    }
1226

1227
    /**
1228
     * Create a callback function for getting a storage for an ingredient component from the given network channel.
1229
     * @param network The target network.
1230
     * @param channel The target channel.
1231
     * @param scheduleObservation If an observation inside the ingredients network should be scheduled.
1232
     * @return A callback function for getting a storage for an ingredient component.
1233
     */
1234
    public static Function<IngredientComponent<?, ?>, IIngredientComponentStorage> getNetworkStorageGetter(INetwork network, int channel, boolean scheduleObservation) {
1235
        return ingredientComponent -> getNetworkStorage(network, channel, ingredientComponent, scheduleObservation);
×
1236
    }
1237

1238
    /**
1239
     * Get all required recipe input ingredients based on a given storage callback.
1240
     *
1241
     * If multiple alternative inputs are possible,
1242
     * then only the first possible match will be taken.
1243
     *
1244
     * Note: Make sure that you first call in simulation-mode
1245
     * to see if the ingredients are available.
1246
     * If you immediately call this non-simulated,
1247
     * then there might be a chance that ingredients are lost
1248
     * from the network.
1249
     *
1250
     * @param storageGetter A callback function to get a storage for the given ingredient component.
1251
     * @param recipe The recipe to get the inputs from.
1252
     * @param simulate If true, then the ingredients will effectively be removed from the network, not when false.
1253
     * @param simulatedExtractionMemories This map remembers all extracted instances in simulation mode.
1254
     *                                    This is to make sure that instances can not be extracted multiple times
1255
     *                                    when simulating.
1256
     * @param extractionMemoriesReusable Like simulatedExtractionMemories, but it stores the reusable ingredients.
1257
     * @param collectMissingIngredients If missing ingredients should be collected.
1258
     *                                  If false, then the first returned mixed ingredients may be null
1259
     *                                  if no valid matches can be found,
1260
     *                                  and the second returned list is always null,
1261
     * @param recipeOutputQuantity The number of times the given recipe should be applied.
1262
     * @return A pair with two objects:
1263
     *           1. The found ingredients or null.
1264
     *           2. A mapping from ingredient component to missing ingredients (non-slot-based).
1265
     */
1266
    public static Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>>
1267
    getRecipeInputs(Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter, IRecipeDefinition recipe, boolean simulate,
1268
                    Map<IngredientComponent<?, ?>, IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemories,
1269
                    Map<IngredientComponent<?, ?>, IIngredientCollectionMutable<?, ?>> extractionMemoriesReusable,
1270
                    boolean collectMissingIngredients, long recipeOutputQuantity) {
1271
        // Determine available and missing ingredients
1272
        Map<IngredientComponent<?, ?>, List<?>> ingredientsAvailable = Maps.newIdentityHashMap();
1✔
1273
        Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> ingredientsMissing = Maps.newIdentityHashMap();
1✔
1274
        for (IngredientComponent<?, ?> ingredientComponent : recipe.getInputComponents()) {
1✔
1275
            IIngredientComponentStorage storage = storageGetter.apply(ingredientComponent);
1✔
1276
            IngredientCollectionPrototypeMap<?, ?> simulatedExtractionMemory = simulatedExtractionMemories.get(ingredientComponent);
1✔
1277
            if (simulatedExtractionMemory == null) {
1✔
1278
                simulatedExtractionMemory = new IngredientCollectionPrototypeMap<>(ingredientComponent, true);
1✔
1279
                simulatedExtractionMemories.put(ingredientComponent, simulatedExtractionMemory);
1✔
1280
            }
1281
            IIngredientCollectionMutable extractionMemoryReusable = extractionMemoriesReusable.get(ingredientComponent);
1✔
1282
            if (extractionMemoryReusable == null) {
1✔
1283
                extractionMemoryReusable = new IngredientHashSet<>(ingredientComponent);
1✔
1284
                extractionMemoriesReusable.put(ingredientComponent, extractionMemoryReusable);
1✔
1285
            }
1286
            Pair<List<?>, MissingIngredients<?, ?>> subIngredients = getIngredientRecipeInputs(storage,
1✔
1287
                    (IngredientComponent) ingredientComponent, recipe, simulate, simulatedExtractionMemory, extractionMemoryReusable,
1288
                    collectMissingIngredients, recipeOutputQuantity);
1289
            List<?> subIngredientAvailable = subIngredients.getLeft();
1✔
1290
            MissingIngredients<?, ?> subIngredientsMissing = subIngredients.getRight();
1✔
1291
            if (subIngredientAvailable == null && !collectMissingIngredients) {
1✔
1292
                return Pair.of(null, null);
×
1293
            } else {
1294
                if (subIngredientAvailable != null && !subIngredientAvailable.isEmpty()) {
1✔
1295
                    ingredientsAvailable.put(ingredientComponent, subIngredientAvailable);
1✔
1296
                }
1297
                if (collectMissingIngredients && !subIngredientsMissing.getElements().isEmpty()) {
1✔
1298
                    ingredientsMissing.put(ingredientComponent, subIngredientsMissing);
1✔
1299
                }
1300
            }
1301
        }
1✔
1302

1303
        // Compress missing ingredients
1304
        // We do this to ensure that instances missing multiple times can be easily combined
1305
        // when triggering a crafting job for them.
1306
        Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> ingredientsMissingCompressed = Maps.newIdentityHashMap();
1✔
1307
        for (IngredientComponent<?, ?> ingredientComponent : ingredientsMissing.keySet()) {
1✔
1308
            ingredientsMissingCompressed.put(ingredientComponent, compressMissingIngredients(ingredientsMissing.get(ingredientComponent)));
1✔
1309
        }
1✔
1310

1311
        return Pair.of(ingredientsAvailable, ingredientsMissingCompressed);
1✔
1312
    }
1313

1314
    /**
1315
     * Create a list of prototyped ingredients from the instances
1316
     * of the given ingredient component type in the given mixed ingredients.
1317
     *
1318
     * Equal prototypes will be stacked.
1319
     *
1320
     * @param ingredientComponent The ingredient component type.
1321
     * @param mixedIngredients The mixed ingredients.
1322
     * @param <T> The instance type.
1323
     * @param <M> The matching condition parameter.
1324
     * @return A list of prototypes.
1325
     */
1326
    public static <T, M> List<IPrototypedIngredient<T, M>> getCompressedIngredients(IngredientComponent<T, M> ingredientComponent,
1327
                                                                                    IMixedIngredients mixedIngredients) {
1328
        List<IPrototypedIngredient<T, M>> outputs = Lists.newArrayList();
1✔
1329

1330
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
1✔
1331
        for (T instance : mixedIngredients.getInstances(ingredientComponent)) {
1✔
1332
            // Try to stack this instance with an existing prototype
1333
            boolean stacked = false;
1✔
1334
            ListIterator<IPrototypedIngredient<T, M>> existingIt = outputs.listIterator();
1✔
1335
            while(existingIt.hasNext()) {
1✔
1336
                IPrototypedIngredient<T, M> prototypedIngredient = existingIt.next();
1✔
1337
                if (matcher.matches(instance, prototypedIngredient.getPrototype(),
1✔
1338
                        prototypedIngredient.getCondition())) {
1✔
1339
                    T stackedInstance = matcher.withQuantity(prototypedIngredient.getPrototype(),
1✔
1340
                            matcher.getQuantity(prototypedIngredient.getPrototype())
1✔
1341
                                    + matcher.getQuantity(instance));
1✔
1342
                    existingIt.set(new PrototypedIngredient<>(ingredientComponent, stackedInstance,
1✔
1343
                            prototypedIngredient.getCondition()));
1✔
1344
                    stacked = true;
1✔
1345
                    break;
1✔
1346
                }
1347
            }
1✔
1348

1349
            // If not possible, just append it to the list
1350
            if (!stacked) {
1✔
1351
                outputs.add(new PrototypedIngredient<>(ingredientComponent, instance,
1✔
1352
                        matcher.getExactMatchNoQuantityCondition()));
1✔
1353
            }
1354
        }
1✔
1355

1356
        return outputs;
1✔
1357
    }
1358

1359
    /**
1360
     * Compress the given missing ingredients so that equal instances just have an incremented quantity.
1361
     *
1362
     * @param missingIngredients The missing ingredients.
1363
     * @param <T> The instance type.
1364
     * @param <M> The matching condition parameter.
1365
     * @return A new missing ingredients object.
1366
     */
1367
    public static <T, M> MissingIngredients<T, M> compressMissingIngredients(MissingIngredients<T, M> missingIngredients) {
1368
        // Index identical missing ingredients in a map, to group them by quantity
1369
        Map<MissingIngredients.Element<T, M>, Long> elementsCompressedMap = Maps.newLinkedHashMap(); // Must be a linked map to maintain our order!!!
1✔
1370
        for (MissingIngredients.Element<T, M> element : missingIngredients.getElements()) {
1✔
1371
            elementsCompressedMap.merge(element, 1L, Long::sum);
1✔
1372
        }
1✔
1373

1374
        // Create a new missing ingredients list where we multiply the missing quantities
1375
        List<MissingIngredients.Element<T, M>> elementsCompressed = Lists.newArrayList();
1✔
1376
        for (Map.Entry<MissingIngredients.Element<T, M>, Long> entry : elementsCompressedMap.entrySet()) {
1✔
1377
            Long quantity = entry.getValue();
1✔
1378
            if (quantity == 1L || entry.getKey().isInputReusable()) {
1✔
1379
                elementsCompressed.add(entry.getKey());
1✔
1380
            } else {
1381
                MissingIngredients.Element<T, M> elementOld = entry.getKey();
1✔
1382
                MissingIngredients.Element<T, M> elementNewQuantity = new MissingIngredients.Element<>(
1✔
1383
                        elementOld.getAlternatives().stream()
1✔
1384
                                .map(alt -> new MissingIngredients.PrototypedWithRequested<>(alt.getRequestedPrototype(), alt.getQuantityMissing() * quantity))
1✔
1385
                                .toList(),
1✔
1386
                        elementOld.isInputReusable()
1✔
1387
                );
1388
                elementsCompressed.add(elementNewQuantity);
1✔
1389
            }
1390
        }
1✔
1391
        return new MissingIngredients<>(elementsCompressed);
1✔
1392
    }
1393

1394
    /**
1395
     * Create a collection of prototypes from the given recipe's outputs.
1396
     *
1397
     * Equal prototypes will be stacked.
1398
     *
1399
     * @param recipe A recipe.
1400
     * @return A map from ingredient component types to their list of prototypes.
1401
     */
1402
    public static Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> getRecipeOutputs(IRecipeDefinition recipe) {
1403
        Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> outputs = Maps.newHashMap();
1✔
1404

1405
        IMixedIngredients mixedIngredients = recipe.getOutput();
1✔
1406
        for (IngredientComponent ingredientComponent : mixedIngredients.getComponents()) {
1✔
1407
            outputs.put(ingredientComponent, getCompressedIngredients(ingredientComponent, mixedIngredients));
1✔
1408
        }
1✔
1409

1410
        return outputs;
1✔
1411
    }
1412

1413
    /**
1414
     * Creates a new recipe outputs object with all ingredient quantities multiplied by the given amount.
1415
     * @param recipeOutputs A recipe objects holder.
1416
     * @param amount An amount to multiply all instances by.
1417
     * @return A new recipe objects holder.
1418
     */
1419
    public static Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> multiplyRecipeOutputs(
1420
            Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> recipeOutputs, int amount) {
1421
        if (amount == 1) {
1✔
1422
            return recipeOutputs;
1✔
1423
        }
1424

1425
        Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> newRecipeOutputs = Maps.newIdentityHashMap();
1✔
1426
        for (Map.Entry<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> entry : recipeOutputs.entrySet()) {
1✔
1427
            newRecipeOutputs.put(entry.getKey(), multiplyPrototypedIngredients((List) entry.getValue(), amount));
1✔
1428
        }
1✔
1429
        return newRecipeOutputs;
1✔
1430
    }
1431

1432
    /**
1433
     * Multiply the quantity of a given prototyped ingredient list with the given amount.
1434
     * @param prototypedIngredients A prototyped ingredient list.
1435
     * @param amount An amount to multiply by.
1436
     * @param <T> The instance type.
1437
     * @param <M> The matching condition parameter.
1438
     * @return A multiplied prototyped ingredient list.
1439
     */
1440
    public static <T, M> List<IPrototypedIngredient<T, M>> multiplyPrototypedIngredients(Collection<IPrototypedIngredient<T, M>> prototypedIngredients,
1441
                                                                                         long amount) {
1442
        return prototypedIngredients
1✔
1443
                .stream()
1✔
1444
                .map(p -> multiplyPrototypedIngredient(p, amount))
1✔
1445
                .collect(Collectors.toList());
1✔
1446
    }
1447

1448
    /**
1449
     * Multiply the quantity of a given prototyped ingredient with the given amount.
1450
     * @param prototypedIngredient A prototyped ingredient.
1451
     * @param amount An amount to multiply by.
1452
     * @param <T> The instance type.
1453
     * @param <M> The matching condition parameter.
1454
     * @return A multiplied prototyped ingredient.
1455
     */
1456
    public static <T, M> IPrototypedIngredient<T, M> multiplyPrototypedIngredient(IPrototypedIngredient<T, M> prototypedIngredient,
1457
                                                                                  long amount) {
1458
        IIngredientMatcher<T, M> matcher = prototypedIngredient.getComponent().getMatcher();
1✔
1459
        return new PrototypedIngredient<>(prototypedIngredient.getComponent(),
1✔
1460
                matcher.withQuantity(prototypedIngredient.getPrototype(),
1✔
1461
                        matcher.getQuantity(prototypedIngredient.getPrototype()) * amount),
1✔
1462
                prototypedIngredient.getCondition());
1✔
1463
    }
1464

1465
    /**
1466
     * Merge two mixed ingredients in a new mixed ingredients object.
1467
     * Instances will be stacked.
1468
     * @param a A first mixed ingredients object.
1469
     * @param b A second mixed ingredients object.
1470
     * @return A merged mixed ingredients object.
1471
     */
1472
    public static IMixedIngredients mergeMixedIngredients(IMixedIngredients a, IMixedIngredients b) {
1473
        // Temporarily store instances in IngredientCollectionPrototypeMaps
1474
        Map<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> groupings = Maps.newIdentityHashMap();
1✔
1475
        for (IngredientComponent<?, ?> component : a.getComponents()) {
1✔
1476
            IngredientCollectionQuantitativeGrouper grouping = new IngredientCollectionQuantitativeGrouper<>(new IngredientArrayList<>(component));
1✔
1477
            groupings.put(component, grouping);
1✔
1478
            grouping.addAll(a.getInstances(component));
1✔
1479
        }
1✔
1480
        for (IngredientComponent<?, ?> component : b.getComponents()) {
1✔
1481
            IngredientCollectionQuantitativeGrouper grouping = groupings.get(component);
1✔
1482
            if (grouping == null) {
1✔
1483
                grouping = new IngredientCollectionQuantitativeGrouper<>(new IngredientArrayList<>(component));
1✔
1484
                groupings.put(component, grouping);
1✔
1485
            }
1486
            grouping.addAll(b.getInstances(component));
1✔
1487
        }
1✔
1488

1489
        // Convert IngredientCollectionPrototypeMaps to lists
1490
        Map<IngredientComponent<?, ?>, List<?>> ingredients = Maps.newIdentityHashMap();
1✔
1491
        for (Map.Entry<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> entry : groupings.entrySet()) {
1✔
1492
            ingredients.put(entry.getKey(), Lists.newArrayList(entry.getValue()));
1✔
1493
        }
1✔
1494
        return new MixedIngredients(ingredients);
1✔
1495
    }
1496

1497
    /**
1498
     * Stack all ingredients in the given mixed ingredients object.
1499
     * @param mixedIngredients A mixed ingredients object.
1500
     * @return A new mixed ingredients object.
1501
     */
1502
    protected static IMixedIngredients compressMixedIngredients(IMixedIngredients mixedIngredients) {
1503
        // Temporarily store instances in IngredientCollectionPrototypeMaps
1504
        Map<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> groupings = Maps.newIdentityHashMap();
1✔
1505
        for (IngredientComponent<?, ?> component : mixedIngredients.getComponents()) {
1✔
1506
            IngredientCollectionQuantitativeGrouper grouping = new IngredientCollectionQuantitativeGrouper<>(new IngredientArrayList<>(component));
1✔
1507
            groupings.put(component, grouping);
1✔
1508
            grouping.addAll(mixedIngredients.getInstances(component));
1✔
1509
        }
1✔
1510

1511
        // Convert IngredientCollectionPrototypeMaps to lists
1512
        Map<IngredientComponent<?, ?>, List<?>> ingredients = Maps.newIdentityHashMap();
1✔
1513
        for (Map.Entry<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> entry : groupings.entrySet()) {
1✔
1514
            IIngredientMatcher matcher = entry.getKey().getMatcher();
1✔
1515
            List<?> values = entry.getValue()
1✔
1516
                    .stream()
1✔
1517
                    .filter(i -> !matcher.isEmpty(i))
1✔
1518
                    .collect(Collectors.toList());
1✔
1519
            if (!values.isEmpty()) {
1✔
1520
                ingredients.put(entry.getKey(), values);
1✔
1521
            }
1522
        }
1✔
1523
        return new MixedIngredients(ingredients);
1✔
1524
    }
1525

1526
    /**
1527
     * Insert the ingredients of the given ingredient component type into the target to make it start crafting.
1528
     *
1529
     * If insertion in non-simulation mode fails,
1530
     * ingredients will be re-inserted into the network.
1531
     *
1532
     * @param ingredientComponent The ingredient component type.
1533
     * @param capabilityProvider The target capability provider.
1534
     * @param side The target side.
1535
     * @param ingredients The ingredients to insert.
1536
     * @param storageFallback The storage to insert any failed ingredients back into. Can be null in simulation mode.
1537
     * @param simulate If insertion should be simulated.
1538
     * @param <T> The instance type.
1539
     * @param <M> The matching condition parameter.
1540
     * @return If all instances could be inserted.
1541
     */
1542
    public static <T, M> boolean insertIngredientCrafting(IngredientComponent<T, M> ingredientComponent,
1543
                                                          ICapabilityProvider capabilityProvider,
1544
                                                          @Nullable Direction side,
1545
                                                          IMixedIngredients ingredients,
1546
                                                          IIngredientComponentStorage<T, M> storageFallback,
1547
                                                          boolean simulate) {
1548
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
×
1549
        IIngredientComponentStorage<T, M> storage = ingredientComponent.getStorage(capabilityProvider, side);
×
1550
        List<T> instances = ingredients.getInstances(ingredientComponent);
×
1551
        List<T> failedInstances = Lists.newArrayList();
×
1552
        boolean ok = true;
×
1553
        for (T instance : instances) {
×
1554
            T remaining = storage.insert(instance, simulate);
×
1555
            if (!matcher.isEmpty(remaining)) {
×
1556
                ok = false;
×
1557
                if (!simulate) {
×
1558
                    failedInstances.add(remaining);
×
1559
                }
1560
            }
1561
        }
×
1562

1563
        // If we had failed insertions, try to insert them back into the network.
1564
        for (T instance : failedInstances) {
×
1565
            T remaining = storageFallback.insert(instance, false);
×
1566
            if (!matcher.isEmpty(remaining)) {
×
1567
                throw new IllegalStateException("Insertion for a crafting recipe failed" +
×
1568
                        "due to inconsistent insertion behaviour by destination in simulation " +
1569
                        "and non-simulation: " + capabilityProvider + ". Lost: " + instances);
1570
            }
1571
        }
×
1572

1573
        return ok;
×
1574
    }
1575

1576
    /**
1577
     * Insert the ingredients of all applicable ingredient component types into the target to make it start crafting.
1578
     *
1579
     * If insertion in non-simulation mode fails,
1580
     * ingredients will be re-inserted into the network.
1581
     *
1582
     * @param targetGetter A function to get the target position.
1583
     * @param ingredients The ingredients to insert.
1584
     * @param network The network.
1585
     * @param channel The channel.
1586
     * @param simulate If insertion should be simulated.
1587
     * @return If all instances could be inserted.
1588
     */
1589
    public static boolean insertCrafting(Function<IngredientComponent<?, ?>, PartPos> targetGetter,
1590
                                         IMixedIngredients ingredients,
1591
                                         INetwork network, int channel,
1592
                                         boolean simulate) {
1593
        Map<IngredientComponent<?, ?>, BlockEntity> tileMap = Maps.newIdentityHashMap();
×
1594

1595
        // First, check if we can find valid tiles for all ingredient components
1596
        for (IngredientComponent<?, ?> ingredientComponent : ingredients.getComponents()) {
×
1597
            BlockEntity tile = BlockEntityHelpers.get(targetGetter.apply(ingredientComponent).getPos(), BlockEntity.class).orElse(null);
×
1598
            if (tile != null) {
×
1599
                tileMap.put(ingredientComponent, tile);
×
1600
            } else {
1601
                return false;
×
1602
            }
1603
        }
×
1604

1605
        // Next, insert the instances into the respective tiles
1606
        boolean ok = true;
×
1607
        for (Map.Entry<IngredientComponent<?, ?>, BlockEntity> entry : tileMap.entrySet()) {
×
1608
            IIngredientComponentStorage<?, ?> storageNetwork = simulate ? null : getNetworkStorage(network, channel, entry.getKey(), false);
×
1609
            if (!insertIngredientCrafting((IngredientComponent) entry.getKey(), entry.getValue(),
×
1610
                    targetGetter.apply(entry.getKey()).getSide(), ingredients,
×
1611
                    storageNetwork, simulate)) {
1612
                ok = false;
×
1613
            }
1614
        }
×
1615

1616
        return ok;
×
1617
    }
1618

1619
    /**
1620
     * Split the given crafting job amount into new jobs with a given split factor.
1621
     * @param craftingJob A crafting job to split.
1622
     * @param splitFactor The number of jobs to split the job over.
1623
     * @param dependencyGraph The dependency graph that will be updated if there are dependencies in the original job.
1624
     * @param identifierGenerator An identifier generator for the crafting jobs ids.
1625
     * @return The newly created crafting jobs.
1626
     */
1627
    public static List<CraftingJob> splitCraftingJobs(CraftingJob craftingJob, int splitFactor,
1628
                                                      CraftingJobDependencyGraph dependencyGraph,
1629
                                                      IIdentifierGenerator identifierGenerator) {
1630
        splitFactor = Math.min(splitFactor, craftingJob.getAmount());
1✔
1631
        int division = craftingJob.getAmount() / splitFactor;
1✔
1632
        int modulus = craftingJob.getAmount() % splitFactor;
1✔
1633

1634
        // Clone original job into splitFactor jobs
1635
        List<CraftingJob> newCraftingJobs = Lists.newArrayList();
1✔
1636
        for (int i = 0; i < splitFactor; i++) {
1✔
1637
            CraftingJob clonedJob = craftingJob.clone(identifierGenerator);
1✔
1638
            newCraftingJobs.add(clonedJob);
1✔
1639

1640
            // Update amount
1641
            int newAmount = division;
1✔
1642
            if (modulus > 0) {
1✔
1643
                // No amounts will be lost, as modulus is guaranteed to be smaller than splitFactor
1644
                newAmount++;
1✔
1645
                modulus--;
1✔
1646
            }
1647
            clonedJob.setAmount(newAmount);
1✔
1648
        }
1649

1650
        // Collect dependency links
1651
        Collection<CraftingJob> originalDependencies = dependencyGraph.getDependencies(craftingJob);
1✔
1652
        Collection<CraftingJob> originalDependents = dependencyGraph.getDependents(craftingJob);
1✔
1653

1654
        // Remove dependency links to and from the original jobs
1655
        for (CraftingJob dependency : originalDependencies) {
1✔
1656
            craftingJob.removeDependency(dependency);
1✔
1657
            dependencyGraph.removeDependency(craftingJob.getId(), dependency.getId());
1✔
1658
        }
1✔
1659
        for (CraftingJob dependent : originalDependents) {
1✔
1660
            dependent.removeDependency(craftingJob);
1✔
1661
            dependencyGraph.removeDependency(dependent.getId(), craftingJob.getId());
1✔
1662
        }
1✔
1663

1664
        // Create dependency links to and from the new crafting jobs
1665
        for (CraftingJob dependency : originalDependencies) {
1✔
1666
            for (CraftingJob newCraftingJob : newCraftingJobs) {
1✔
1667
                newCraftingJob.addDependency(dependency);
1✔
1668
                dependencyGraph.addDependency(newCraftingJob, dependency);
1✔
1669
            }
1✔
1670
        }
1✔
1671
        for (CraftingJob originalDependent : originalDependents) {
1✔
1672
            for (CraftingJob newCraftingJob : newCraftingJobs) {
1✔
1673
                originalDependent.addDependency(newCraftingJob);
1✔
1674
                dependencyGraph.addDependency(originalDependent, newCraftingJob);
1✔
1675
            }
1✔
1676
        }
1✔
1677

1678
        return newCraftingJobs;
1✔
1679
    }
1680

1681
    /**
1682
     * Insert the given ingredients into the given storage networks.
1683
     * @param ingredients A collection of ingredients.
1684
     * @param storageGetter A storage network getter.
1685
     * @param simulate If insertion should be simulated.
1686
     * @return The remaining ingredients that were not inserted.
1687
     */
1688
    public static IMixedIngredients insertIngredients(IMixedIngredients ingredients,
1689
                                                      Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
1690
                                                      boolean simulate) {
1691
        Map<IngredientComponent<?, ?>, List<?>> remainingIngredients = Maps.newIdentityHashMap();
×
1692
        for (IngredientComponent<?, ?> component : ingredients.getComponents()) {
×
1693
            IIngredientComponentStorage storage = storageGetter.apply(component);
×
1694
            IIngredientMatcher matcher = component.getMatcher();
×
1695
            for (Object instance : ingredients.getInstances(component)) {
×
1696
                Object remainder = storage.insert(instance, simulate);
×
1697
                if (!matcher.isEmpty(remainder)) {
×
1698
                    List remainingInstances = remainingIngredients.get(component);
×
1699
                    if (remainingInstances == null) {
×
1700
                        remainingInstances = Lists.newArrayList();
×
1701
                        remainingIngredients.put(component, remainingInstances);
×
1702
                    }
1703
                    remainingInstances.add(instance);
×
1704
                }
1705
            }
×
1706
        }
×
1707
        return new MixedIngredients(remainingIngredients);
×
1708
    }
1709

1710
    /**
1711
     * Generates semi-unique IDs.
1712
     */
1713
    public static interface IIdentifierGenerator {
1714
        public int getNext();
1715
    }
1716

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