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

CyclopsMC / IntegratedCrafting / #479011822

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

push

github

rubensworks
Add dedicated storage per crafting job

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

Closes #112

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

3 existing lines in 3 files now uncovered.

755 of 3116 relevant lines covered (24.23%)

0.24 hits per line

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

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

3
import com.google.common.collect.Iterables;
4
import com.google.common.collect.Lists;
5
import com.google.common.collect.Maps;
6
import com.google.common.collect.Sets;
7
import net.minecraft.core.Direction;
8
import net.minecraft.world.level.block.entity.BlockEntity;
9
import net.minecraftforge.common.capabilities.ICapabilityProvider;
10
import net.minecraftforge.common.util.LazyOptional;
11
import org.apache.commons.lang3.tuple.Pair;
12
import org.apache.logging.log4j.Level;
13
import org.cyclops.commoncapabilities.api.capability.recipehandler.IPrototypedIngredientAlternatives;
14
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
15
import org.cyclops.commoncapabilities.api.ingredient.*;
16
import org.cyclops.commoncapabilities.api.ingredient.storage.IIngredientComponentStorage;
17
import org.cyclops.commoncapabilities.api.ingredient.storage.IngredientComponentStorageEmpty;
18
import org.cyclops.cyclopscore.helper.BlockEntityHelpers;
19
import org.cyclops.cyclopscore.ingredient.collection.*;
20
import org.cyclops.cyclopscore.ingredient.storage.IngredientComponentStorageSlottedCollectionWrapper;
21
import org.cyclops.integratedcrafting.Capabilities;
22
import org.cyclops.integratedcrafting.IntegratedCrafting;
23
import org.cyclops.integratedcrafting.api.crafting.*;
24
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
25
import org.cyclops.integratedcrafting.api.recipe.IRecipeIndex;
26
import org.cyclops.integratedcrafting.capability.network.CraftingNetworkConfig;
27
import org.cyclops.integrateddynamics.IntegratedDynamics;
28
import org.cyclops.integrateddynamics.api.PartStateException;
29
import org.cyclops.integrateddynamics.api.ingredient.capability.IPositionedAddonsNetworkIngredientsHandler;
30
import org.cyclops.integrateddynamics.api.network.INetwork;
31
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients;
32
import org.cyclops.integrateddynamics.api.part.PartPos;
33
import org.cyclops.integrateddynamics.core.helper.NetworkHelpers;
34
import org.cyclops.integrateddynamics.core.network.IngredientChannelAdapter;
35
import org.cyclops.integrateddynamics.core.network.IngredientChannelIndexed;
36

37
import javax.annotation.Nullable;
38
import java.util.*;
39
import java.util.function.Function;
40
import java.util.stream.Collectors;
41
import java.util.stream.IntStream;
42

43
/**
44
 * Helpers related to handling crafting jobs.
45
 * @author rubensworks
46
 */
47
public class CraftingHelpers {
×
48

49
    /**
50
     * Get the network at the given position,
51
     * or throw a PartStateException if it is null.
52
     * @param pos A position.
53
     * @return A network.
54
     * @throws PartStateException If the network could not be found.
55
     */
56
    public static INetwork getNetworkChecked(PartPos pos) throws PartStateException {
57
        INetwork network = NetworkHelpers.getNetwork(pos.getPos().getLevel(true), pos.getPos().getBlockPos(), pos.getSide()).orElse(null);
×
58
        if (network == null) {
×
59
            IntegratedDynamics.clog(Level.ERROR, "Could not get the network for transfer as no network was found.");
×
60
            throw new PartStateException(pos.getPos(), pos.getSide());
×
61
        }
62
        return network;
×
63
    }
64

65
    /**
66
     * Get the crafting network in the given network.
67
     * @param network A network.
68
     * @return The crafting network.
69
     */
70
    public static LazyOptional<ICraftingNetwork> getCraftingNetwork(@Nullable INetwork network) {
71
        if (network != null) {
×
72
            return network.getCapability(CraftingNetworkConfig.CAPABILITY);
×
73
        }
74
        return null;
×
75
    }
76

77
    /**
78
     * Get the crafting network in the given network.
79
     * @param network A network.
80
     * @return The crafting network.
81
     */
82
    public static ICraftingNetwork getCraftingNetworkChecked(@Nullable INetwork network) {
83
        return getCraftingNetwork(network)
×
84
                .orElseThrow(() -> new IllegalStateException("Could not find a crafting network"));
×
85
    }
86

87
    /**
88
     * Get the storage network of the given type in the given network.
89
     * @param network A network.
90
     * @param ingredientComponent The ingredient component type of the network.
91
     * @param <T> The instance type.
92
     * @param <M> The matching condition parameter.
93
     * @return The storage network.
94
     */
95
    public static <T, M> LazyOptional<IPositionedAddonsNetworkIngredients<T, M>> getIngredientsNetwork(INetwork network,
96
                                                                                                       IngredientComponent<T, M> ingredientComponent) {
97
        IPositionedAddonsNetworkIngredientsHandler<T, M> ingredientsHandler = ingredientComponent.getCapability(Capabilities.POSITIONED_ADDONS_NETWORK_INGREDIENTS_HANDLER).orElse(null);
×
98
        if (ingredientsHandler != null) {
×
99
            return ingredientsHandler.getStorage(network);
×
100
        }
101
        return LazyOptional.empty();
×
102
    }
103

104
    /**
105
     * Get the storage network of the given type in the given network.
106
     * @param network A network.
107
     * @param ingredientComponent The ingredient component type of the network.
108
     * @param <T> The instance type.
109
     * @param <M> The matching condition parameter.
110
     * @return The storage network.
111
     */
112
    public static <T, M> IPositionedAddonsNetworkIngredients<T, M> getIngredientsNetworkChecked(INetwork network,
113
                                                                                                IngredientComponent<T, M> ingredientComponent) {
114
        return getIngredientsNetwork(network, ingredientComponent)
×
115
                .orElseThrow(() -> new IllegalStateException("Could not find an ingredients network"));
×
116
    }
117

118
    /**
119
     * Get the storage of the given ingredient component type from the network.
120
     * @param network The network.
121
     * @param channel A network channel.
122
     * @param ingredientComponent The ingredient component type of the network.
123
     * @param scheduleObservation If an observation inside the ingredients network should be scheduled.
124
     * @param <T> The instance type.
125
     * @param <M> The matching condition parameter.
126
     * @return The storage.
127
     */
128
    public static <T, M> IIngredientComponentStorage<T, M> getNetworkStorage(INetwork network, int channel,
129
                                                                             IngredientComponent<T, M> ingredientComponent,
130
                                                                             boolean scheduleObservation) {
131
        IPositionedAddonsNetworkIngredients<T, M> ingredientsNetwork = getIngredientsNetwork(network, ingredientComponent).orElse(null);
×
132
        if (ingredientsNetwork != null) {
×
133
            if (scheduleObservation) {
×
134
                ingredientsNetwork.scheduleObservation();
×
135
            }
136
            return ingredientsNetwork.getChannel(channel);
×
137
        }
138
        return new IngredientComponentStorageEmpty<>(ingredientComponent);
×
139
    }
140

141
    /**
142
     * If the network is guaranteed to have uncommitted changes (such as the one in #48),
143
     * forcefully run observers synchronously, so that we can calculate the job in a consistent network state.
144
     * @param network The network.
145
     * @param channel A network channel.
146
     */
147
    public static void beforeCalculateCraftingJobs(INetwork network, int channel) {
148
        for (IngredientComponent<?, ?> ingredientComponent : IngredientComponent.REGISTRY.getValues()) {
×
149
            IPositionedAddonsNetworkIngredients<?, ?> ingredientsNetwork = getIngredientsNetwork(network, ingredientComponent).orElse(null);
×
150
            if (ingredientsNetwork != null && (ingredientsNetwork.isObservationForcedPending(channel))) {
×
151
                ingredientsNetwork.runObserverSync();
×
152
            }
153
        }
×
154
    }
×
155

156
    /**
157
     * Calculate the required crafting jobs and their dependencies for the given instance in the given network.
158
     * @param network The target network.
159
     * @param channel The target channel.
160
     * @param ingredientComponent The ingredient component type of the instance.
161
     * @param instance The instance to craft.
162
     * @param matchCondition The match condition of the instance.
163
     * @param craftMissing If the missing required ingredients should also be crafted.
164
     * @param identifierGenerator identifierGenerator An ID generator for crafting jobs.
165
     * @param craftingJobsGraph The target graph where all dependencies will be stored.
166
     * @param collectMissingRecipes If the missing recipes should be collected inside
167
     *                              {@link UnknownCraftingRecipeException}.
168
     *                              This may slow down calculation for deeply nested recipe graphs.
169
     * @param <T> The instance type.
170
     * @param <M> The matching condition parameter.
171
     * @return The crafting job for the given instance.
172
     * @throws UnknownCraftingRecipeException If the recipe for a (sub)ingredient is unavailable.
173
     * @throws RecursiveCraftingRecipeException If an infinite recursive recipe was detected.
174
     */
175
    public static <T, M> CraftingJob calculateCraftingJobs(INetwork network, int channel,
176
                                                           IngredientComponent<T, M> ingredientComponent,
177
                                                           T instance, M matchCondition, boolean craftMissing,
178
                                                           IIdentifierGenerator identifierGenerator,
179
                                                           CraftingJobDependencyGraph craftingJobsGraph,
180
                                                           boolean collectMissingRecipes)
181
            throws UnknownCraftingRecipeException, RecursiveCraftingRecipeException {
182
        ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
183
        IRecipeIndex recipeIndex = craftingNetwork.getRecipeIndex(channel);
×
184
        Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter = getNetworkStorageGetter(network, channel, true);
×
185
        beforeCalculateCraftingJobs(network, channel);
×
186

187
        CraftingJob craftingJob = calculateCraftingJobs(recipeIndex, channel, storageGetter, ingredientComponent, instance, matchCondition,
×
188
                craftMissing, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), identifierGenerator, craftingJobsGraph, Sets.newHashSet(),
×
189
                collectMissingRecipes);
190
        craftingJobsGraph.addCraftingJobId(craftingJob);
×
191
        return craftingJob;
×
192
    }
193

194
    /**
195
     * Calculate the required crafting jobs and their dependencies for the given instance in the given network.
196
     * @param network The target network.
197
     * @param channel The target channel.
198
     * @param recipe The recipe to calculate a job for.
199
     * @param amount The amount of times the recipe should be crafted.
200
     * @param craftMissing If the missing required ingredients should also be crafted.
201
     * @param identifierGenerator identifierGenerator An ID generator for crafting jobs.
202
     * @param craftingJobsGraph The target graph where all dependencies will be stored.
203
     * @param collectMissingRecipes If the missing recipes should be collected inside
204
     *                              {@link FailedCraftingRecipeException}.
205
     *                              This may slow down calculation for deeply nested recipe graphs.
206
     * @return The crafting job for the given instance.
207
     * @throws FailedCraftingRecipeException If the recipe could not be crafted due to missing sub-dependencies.
208
     * @throws RecursiveCraftingRecipeException If an infinite recursive recipe was detected.
209
     */
210
    public static CraftingJob calculateCraftingJobs(INetwork network, int channel,
211
                                                    IRecipeDefinition recipe, int amount, boolean craftMissing,
212
                                                    IIdentifierGenerator identifierGenerator,
213
                                                    CraftingJobDependencyGraph craftingJobsGraph,
214
                                                    boolean collectMissingRecipes)
215
            throws FailedCraftingRecipeException, RecursiveCraftingRecipeException {
216
        ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
217
        IRecipeIndex recipeIndex = craftingNetwork.getRecipeIndex(channel);
×
218
        Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter = getNetworkStorageGetter(network, channel, true);
×
219
        beforeCalculateCraftingJobs(network, channel);
×
220

221
        PartialCraftingJobCalculation result = calculateCraftingJobs(recipeIndex, channel, storageGetter, recipe, amount,
×
222
                craftMissing, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), identifierGenerator, craftingJobsGraph, Sets.newHashSet(),
×
223
                collectMissingRecipes);
224
        if (result.getCraftingJob() == null) {
×
225
            throw new FailedCraftingRecipeException(recipe, amount, result.getMissingDependencies(),
×
226
                    compressMixedIngredients(new MixedIngredients(result.getIngredientsStorage())), result.getPartialCraftingJobs());
×
227
        } else {
228
            craftingJobsGraph.addCraftingJobId(result.getCraftingJob());
×
229
            return result.getCraftingJob();
×
230
        }
231
    }
232

233
    /**
234
     * @return An identifier generator for crafting jobs.
235
     */
236
    public static IIdentifierGenerator getGlobalCraftingJobIdentifier() {
237
        return () -> IntegratedCrafting.globalCounters.getNext("craftingJob");
×
238
    }
239

240
    /**
241
     * Calculate the effective quantity for the given instance in the output of the given recipe.
242
     * @param recipe A recipe.
243
     * @param ingredientComponent The ingredient component.
244
     * @param instance An instance.
245
     * @param matchCondition A match condition.
246
     * @param <T> The instance type.
247
     * @param <M> The matching condition parameter.
248
     * @return The effective quantity.
249
     */
250
    public static <T, M> long getOutputQuantityForRecipe(IRecipeDefinition recipe,
251
                                                         IngredientComponent<T, M> ingredientComponent,
252
                                                         T instance, M matchCondition) {
253
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
1✔
254
        return recipe.getOutput().getInstances(ingredientComponent)
1✔
255
                .stream()
1✔
256
                .filter(i -> matcher.matches(i, instance, matchCondition))
1✔
257
                .mapToLong(matcher::getQuantity)
1✔
258
                .sum();
1✔
259
    }
260

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

310
        // Loop over all available recipes, and return the first valid one.
311
        Iterator<IRecipeDefinition> recipes = recipeIndex.getRecipes(ingredientComponent, instance, quantifierlessCondition);
1✔
312
        List<UnknownCraftingRecipeException> firstMissingDependencies = Lists.newArrayList();
1✔
313
        Map<IngredientComponent<?, ?>, List<?>> firstIngredientsStorage = Collections.emptyMap();
1✔
314
        List<CraftingJob> firstPartialCraftingJobs = Lists.newArrayList();
1✔
315
        RecursiveCraftingRecipeException firstRecursiveException = null;
1✔
316
        while (recipes.hasNext()) {
1✔
317
            IRecipeDefinition recipe = recipes.next();
1✔
318

319
            // Calculate the quantity for the given instance that the recipe outputs
320
            long recipeOutputQuantity = getOutputQuantityForRecipe(recipe, ingredientComponent, instance, quantifierlessCondition);
1✔
321
            // Based on the quantity of the recipe output, calculate the amount of required recipe jobs.
322
            int amount = (int) Math.ceil(((float) instanceQuantity) / (float) recipeOutputQuantity);
1✔
323

324
            // Calculate jobs for the given recipe
325
            try {
326
                PartialCraftingJobCalculation result = calculateCraftingJobs(recipeIndex, channel,
1✔
327
                        storageGetter, recipe, amount, craftMissing,
328
                        simulatedExtractionMemory, extractionMemoryReusable, identifierGenerator, craftingJobsGraph, parentDependencies,
329
                        collectMissingRecipes && firstMissingDependencies.isEmpty());
1✔
330
                if (result.getCraftingJob() == null) {
1✔
331
                    firstMissingDependencies = result.getMissingDependencies();
1✔
332
                    firstIngredientsStorage = result.getIngredientsStorage();
1✔
333
                    if (result.getPartialCraftingJobs() != null) {
1✔
334
                        firstPartialCraftingJobs = result.getPartialCraftingJobs();
1✔
335
                    }
336
                } else {
337
                    return result.getCraftingJob();
1✔
338
                }
339
            } catch (RecursiveCraftingRecipeException e) {
1✔
340
                if (firstRecursiveException == null) {
1✔
341
                    firstRecursiveException = e;
1✔
342
                }
343
                continue;
1✔
344
            }
1✔
345
        }
1✔
346

347
        if (firstRecursiveException != null) {
1✔
348
            throw firstRecursiveException;
1✔
349
        }
350

351
        // No valid recipes were available, so we error or collect the missing instance.
352
        throw new UnknownCraftingRecipeException(new PrototypedIngredient<>(ingredientComponent, instance, matchCondition),
1✔
353
                matcher.getQuantity(instance), firstMissingDependencies, compressMixedIngredients(new MixedIngredients(firstIngredientsStorage)),
1✔
354
                firstPartialCraftingJobs);
355
    }
356

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

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

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

426
                        missingDependencies.add(new UnknownCraftingRecipeException(
1✔
427
                                alternative.getRequestedPrototype(), alternative.getQuantityMissing(),
1✔
428
                                Collections.emptyList(), compressMixedIngredients(new MixedIngredients(storageMap)), Lists.newArrayList()));
1✔
429
                    }
1✔
430
                }
1✔
431
            }
432
            return new PartialCraftingJobCalculation(null, missingDependencies, simulation.getLeft(), null);
1✔
433
        }
434

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

477
        // If at least one of our dependencies does not have a valid recipe or is not available,
478
        // go check the next recipe.
479
        if (!missingDependencies.isEmpty()) {
1✔
480
            return new PartialCraftingJobCalculation(null, missingDependencies, simulation.getLeft(), partialCraftingJobs);
1✔
481
        }
482

483
        CraftingJob craftingJob = new CraftingJob(identifierGenerator.getNext(), channel, recipe, amount,
1✔
484
                compressMixedIngredients(new MixedIngredients(simulation.getLeft())));
1✔
485
        for (CraftingJob dependency : dependencies.values()) {
1✔
486
            craftingJob.addDependency(dependency);
1✔
487
            craftingJobsGraph.addDependency(craftingJob, dependency);
1✔
488
        }
1✔
489
        return new PartialCraftingJobCalculation(craftingJob, null, simulation.getLeft(), null);
1✔
490
    }
491

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

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

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

573
                            // Nothing has to be crafted anymore, jump to next dependency
574
                            skipDependency = true;
×
575
                            break;
×
576
                        } else {
577
                            // Partial availability, other part needs to be crafted still.
578
                            prototype = new PrototypedIngredient<>(dependencyComponent,
1✔
579
                                    dependencyMatcher.withQuantity(prototype.getPrototype(), remainingQuantity),
1✔
580
                                    prototype.getCondition());
1✔
581
                        }
582
                    }
583
                }
584

585
                // Try to craft the given prototype
586
                try {
587
                    Set<IPrototypedIngredient> childDependencies = Sets.newHashSet(parentDependencies);
1✔
588
                    IPrototypedIngredient<T, M> dependencyPrototype = prototype;
1✔
589
                    if (dependencyMatcher.getQuantity(dependencyPrototype.getPrototype()) != 1) {
1✔
590
                        // Ensure 1-quantity is stored, for proper comparisons in future calls.
591
                        dependencyPrototype = new PrototypedIngredient<>(dependencyComponent,
1✔
592
                                dependencyMatcher.withQuantity(prototype.getPrototype(), 1),
1✔
593
                                prototype.getCondition());
1✔
594
                    }
595
                    if (!childDependencies.add(dependencyPrototype)) {
1✔
596
                        throw new RecursiveCraftingRecipeException(prototype);
1✔
597
                    }
598

599
                    dependency = calculateCraftingJobs(recipeIndex, channel, storageGetter,
1✔
600
                            dependencyComponent, prototype.getPrototype(),
1✔
601
                            prototype.getCondition(), true, simulatedExtractionMemory, extractionMemoryReusable,
1✔
602
                            identifierGenerator, craftingJobsGraph, childDependencies, collectMissingRecipes);
603
                    dependencyInstance = prototype.getPrototype();
1✔
604

605
                    // The prototype is valid,
606
                    // so we can finally store our temporary surplus
607
                    if (dependencyComponentSurplus != null) {
1✔
608
                        dependenciesOutputSurplus.put(dependencyComponent, dependencyComponentSurplus);
1✔
609
                    }
610

611
                    // Add the auxiliary recipe outputs that are not requested to the surplus
612
                    Object dependencyQuantifierlessCondition = dependencyMatcher.withoutCondition(prototype.getCondition(),
1✔
613
                            dependencyComponent.getPrimaryQuantifier().getMatchCondition());
1✔
614
                    long requestedQuantity = dependencyMatcher.getQuantity(prototype.getPrototype());
1✔
615
                    for (IngredientComponent outputComponent : dependency.getRecipe().getOutput().getComponents()) {
1✔
616
                        IngredientCollectionPrototypeMap<?, ?> componentSurplus = dependenciesOutputSurplus.get(outputComponent);
1✔
617
                        if (componentSurplus == null) {
1✔
618
                            componentSurplus = new IngredientCollectionPrototypeMap<>(outputComponent, true);
1✔
619
                            dependenciesOutputSurplus.put(outputComponent, componentSurplus);
1✔
620
                        }
621
                        List<Object> instances = dependency.getRecipe().getOutput().getInstances(outputComponent);
1✔
622
                        long recipeAmount = dependency.getAmount();
1✔
623
                        if (recipeAmount > 1) {
1✔
624
                            IIngredientMatcher matcher = outputComponent.getMatcher();
1✔
625
                            // If more than one recipe amount was crafted, correctly multiply the outputs to calculate the effective surplus.
626
                            instances = instances
1✔
627
                                    .stream()
1✔
628
                                    .map(instance -> matcher.withQuantity(instance, matcher.getQuantity(instance) * recipeAmount))
1✔
629
                                    .collect(Collectors.toList());
1✔
630
                        }
631
                        addRemainderAsSurplusForComponent(outputComponent, (List) instances, componentSurplus,
1✔
632
                                (IngredientComponent) prototype.getComponent(), prototype.getPrototype(), dependencyQuantifierlessCondition,
1✔
633
                                requestedQuantity);
634
                    }
1✔
635

636
                    break;
1✔
637
                } catch (UnknownCraftingRecipeException e) {
1✔
638
                    // Save the first error, and check the next prototype
639
                    if (firstError == null) {
1✔
640
                        // Modify the error so that the correct missing quantity is stored
641
                        firstError = new UnknownCraftingRecipeException(
1✔
642
                                e.getIngredient(), prototypedAlternative.getQuantityMissing(),
1✔
643
                                e.getMissingChildRecipes(), compressMixedIngredients(e.getIngredientsStorage()), e.getPartialCraftingJobs());
1✔
644
                    }
645
                }
646
            }
1✔
647

648
            // Check if this dependency can be skipped
649
            if (skipDependency) {
1✔
650
                continue;
×
651
            }
652

653
            // If no valid crafting recipe was found for the current sub-instance, re-throw its error
654
            if (dependency == null) {
1✔
655
                missingDependencies.add(firstError);
1✔
656
                if (collectMissingRecipes) {
1✔
657
                    continue;
1✔
658
                } else {
659
                    break;
660
                }
661
            }
662

663
            // --- When we reach this point, a valid sub-recipe was found ---
664

665
            // Update our simulatedExtractionMemory to indicate that the dependency's
666
            // instance should not be extracted anymore.
667
            ((IngredientCollectionPrototypeMap<T, M>) simulatedExtractionMemory.get(dependencyComponent))
1✔
668
                    .remove(dependencyInstance);
1✔
669

670
            // If the dependency instance is reusable, mark it as available in our reusable extraction memory
671
            if (missingElement.isInputReusable()) {
1✔
672
                ((IIngredientCollectionMutable<T, M>) extractionMemoryReusable.get(dependencyComponent)).add(dependencyInstance);
1✔
673
            }
674

675
            // Add the valid sub-recipe it to our dependencies
676
            // If the recipe was already present at this level, just increment the amount of the existing job.
677
            CraftingJob existingJob = dependencies.get(dependency.getRecipe());
1✔
678
            if (existingJob == null) {
1✔
679
                dependencies.put(dependency.getRecipe(), dependency);
1✔
680
            } else {
681
                // Remove the standalone dependency job by merging it into the existing job
682
                craftingJobsGraph.mergeCraftingJobs(existingJob, dependency, true);
1✔
683
            }
684
        }
1✔
685

686
        return new PartialCraftingJobCalculationDependency(missingDependencies, dependencies.values());
1✔
687
    }
688

689
    // Helper function for calculateCraftingJobDependencyComponent
690
    protected static <T1, M1, T2, M2> void addRemainderAsSurplusForComponent(IngredientComponent<T1, M1> ingredientComponent,
691
                                                                             List<T1> instances,
692
                                                                             IngredientCollectionPrototypeMap<T1, M1> simulatedExtractionMemory,
693
                                                                             IngredientComponent<T2, M2> blackListComponent,
694
                                                                             T2 blacklistInstance, M2 blacklistCondition,
695
                                                                             long blacklistQuantity) {
696
        IIngredientMatcher<T2, M2> blacklistMatcher = blackListComponent.getMatcher();
1✔
697
        for (T1 instance : instances) {
1✔
698
            IIngredientMatcher<T1, M1> outputMatcher = ingredientComponent.getMatcher();
1✔
699
            long reduceQuantity = 0;
1✔
700
            if (blackListComponent == ingredientComponent
1✔
701
                    && blacklistMatcher.matches(blacklistInstance, (T2) instance, blacklistCondition)) {
1✔
702
                reduceQuantity = blacklistQuantity;
1✔
703
            }
704
            long quantity = simulatedExtractionMemory.getQuantity(instance) + (outputMatcher.getQuantity(instance) - reduceQuantity);
1✔
705
            if (quantity > 0) {
1✔
706
                simulatedExtractionMemory.setQuantity(instance, quantity);
1✔
707
            }
708
        }
1✔
709
    }
1✔
710

711
    /**
712
     * Schedule all crafting jobs in the given dependency graph in the given network.
713
     *
714
     * @param craftingNetwork            The target crafting network.
715
     * @param storageGetter              The storage getter.
716
     * @param craftingJobDependencyGraph The crafting job dependency graph.
717
     * @param allowDistribution          If the crafting jobs are allowed to be split over multiple crafting interfaces.
718
     * @param initiator                  Optional UUID of the initiator.
719
     * @throws UnavailableCraftingInterfacesException If no crafting interfaces were available.
720
     */
721
    public static void scheduleCraftingJobs(ICraftingNetwork craftingNetwork,
722
                                            Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
723
                                            CraftingJobDependencyGraph craftingJobDependencyGraph,
724
                                            boolean allowDistribution,
725
                                            @Nullable UUID initiator) throws UnavailableCraftingInterfacesException {
726
        List<CraftingJob> startedJobs = Lists.newArrayList();
×
727
        craftingNetwork.getCraftingJobDependencyGraph().importDependencies(craftingJobDependencyGraph);
×
728
        for (CraftingJob craftingJob : craftingJobDependencyGraph.getCraftingJobs()) {
×
729
            try {
NEW
730
                craftingNetwork.scheduleCraftingJob(craftingJob, allowDistribution, storageGetter);
×
NEW
731
            } catch (StorageExtractionException | UnavailableCraftingInterfacesException e) {
×
732
                // First, cancel all jobs that were already started
733
                for (CraftingJob startedJob : startedJobs) {
×
NEW
734
                    CraftingHelpers.insertIngredientsGuaranteed(startedJob.getIngredientsStorageBuffer(), storageGetter, (ICraftingResultsSink) Iterables.getFirst(craftingNetwork.getCraftingInterfaces(startedJob.getChannel()), null));
×
735
                    craftingNetwork.cancelCraftingJob(startedJob.getChannel(), startedJob.getId());
×
736
                }
×
737

738
                // Then, throw an exception for all jobs in this dependency graph
739
                throw new UnavailableCraftingInterfacesException(craftingJobDependencyGraph.getCraftingJobs());
×
740
            }
×
741
            startedJobs.add(craftingJob);
×
742
            if (initiator != null) {
×
743
                craftingJob.setInitiatorUuid(initiator.toString());
×
744
            }
745
        }
×
746
    }
×
747

748
    /**
749
     * Schedule the given crafting job  in the given network.
750
     *
751
     * @param craftingNetwork   The target crafting network.
752
     * @param storageGetter     The storage getter.
753
     * @param craftingJob       The crafting job to schedule.
754
     * @param allowDistribution If the crafting job is allowed to be split over multiple crafting interfaces.
755
     * @param initiator         Optional UUID of the initiator.
756
     * @return The scheduled crafting job.
757
     * @throws UnavailableCraftingInterfacesException If no crafting interfaces were available.
758
     * @throws StorageExtractionException             If storage extraction failed.
759
     */
760
    public static CraftingJob scheduleCraftingJob(ICraftingNetwork craftingNetwork,
761
                                                  Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
762
                                                  CraftingJob craftingJob,
763
                                                  boolean allowDistribution,
764
                                                  @Nullable UUID initiator) throws UnavailableCraftingInterfacesException, StorageExtractionException {
NEW
765
        craftingNetwork.scheduleCraftingJob(craftingJob, allowDistribution, storageGetter);
×
766
        if (initiator != null) {
×
767
            craftingJob.setInitiatorUuid(initiator.toString());
×
768
        }
769
        return craftingJob;
×
770
    }
771

772
    /**
773
     * Schedule a crafting job for the given instance in the given network.
774
     * @param network The target network.
775
     * @param channel The target channel.
776
     * @param ingredientComponent The ingredient component type of the instance.
777
     * @param instance The instance to craft.
778
     * @param matchCondition The match condition of the instance.
779
     * @param craftMissing If the missing required ingredients should also be crafted.
780
     * @param allowDistribution If the crafting job is allowed to be split over multiple crafting interfaces.
781
     * @param identifierGenerator An ID generator for crafting jobs.
782
     * @param initiator Optional UUID of the initiator.
783
     * @param <T> The instance type.
784
     * @param <M> The matching condition parameter.
785
     * @return The scheduled crafting job, or null if no recipe was found.
786
     */
787
    @Nullable
788
    public static <T, M> CraftingJob calculateAndScheduleCraftingJob(INetwork network, int channel,
789
                                                                     IngredientComponent<T, M> ingredientComponent,
790
                                                                     T instance, M matchCondition,
791
                                                                     boolean craftMissing, boolean allowDistribution,
792
                                                                     IIdentifierGenerator identifierGenerator,
793
                                                                     @Nullable UUID initiator) {
794
        try {
795
            CraftingJobDependencyGraph dependencyGraph = new CraftingJobDependencyGraph();
×
796
            CraftingJob craftingJob = calculateCraftingJobs(network, channel, ingredientComponent, instance,
×
797
                    matchCondition, craftMissing, identifierGenerator, dependencyGraph, false);
798

799
            ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
800

NEW
801
            scheduleCraftingJobs(craftingNetwork, getNetworkStorageGetter(network, channel, false), dependencyGraph, allowDistribution, initiator);
×
802

803
            return craftingJob;
×
804
        } catch (UnknownCraftingRecipeException | RecursiveCraftingRecipeException | UnavailableCraftingInterfacesException e) {
×
805
            return null;
×
806
        }
807
    }
808

809
    /**
810
     * Schedule a crafting job for the given recipe in the given network.
811
     * @param network The target network.
812
     * @param channel The target channel.
813
     * @param recipe The recipe to craft.
814
     * @param amount The amount to craft.
815
     * @param craftMissing If the missing required ingredients should also be crafted.
816
     * @param allowDistribution If the crafting job is allowed to be split over multiple crafting interfaces.
817
     * @param identifierGenerator An ID generator for crafting jobs.
818
     * @param initiator Optional UUID of the initiator.
819
     * @return The scheduled crafting job, or null if no recipe was found.
820
     */
821
    @Nullable
822
    public static CraftingJob calculateAndScheduleCraftingJob(INetwork network, int channel,
823
                                                              IRecipeDefinition recipe, int amount,
824
                                                              boolean craftMissing, boolean allowDistribution,
825
                                                              IIdentifierGenerator identifierGenerator,
826
                                                              @Nullable UUID initiator) {
827
        try {
828
            CraftingJobDependencyGraph dependencyGraph = new CraftingJobDependencyGraph();
×
829
            CraftingJob craftingJob = calculateCraftingJobs(network, channel, recipe, amount, craftMissing,
×
830
                    identifierGenerator, dependencyGraph, false);
831

832
            ICraftingNetwork craftingNetwork = getCraftingNetworkChecked(network);
×
833

NEW
834
            scheduleCraftingJobs(craftingNetwork, getNetworkStorageGetter(network, channel, false), dependencyGraph, allowDistribution, initiator);
×
835

836
            return craftingJob;
×
837
        } catch (RecursiveCraftingRecipeException | FailedCraftingRecipeException | UnavailableCraftingInterfacesException e) {
×
838
            return null;
×
839
        }
840
    }
841

842
    /**
843
     * Check if the given network contains the given instance in any of its storages.
844
     * @param network The target network.
845
     * @param channel The target channel.
846
     * @param ingredientComponent The ingredient component type of the instance.
847
     * @param instance The instance to check.
848
     * @param matchCondition The match condition of the instance.
849
     * @param <T> The instance type.
850
     * @param <M> The matching condition parameter.
851
     * @return If the instance is present in the network.
852
     */
853
    public static <T, M> boolean hasStorageInstance(INetwork network, int channel,
854
                                                    IngredientComponent<T, M> ingredientComponent,
855
                                                    T instance, M matchCondition) {
856
        IIngredientComponentStorage<T, M> storage = getNetworkStorage(network, channel, ingredientComponent, true);
×
857
        if (storage instanceof IngredientChannelAdapter) ((IngredientChannelAdapter) storage).disableLimits();
×
858
        boolean contains;
859
        if (storage instanceof IngredientChannelIndexed) {
×
860
            IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
×
861
            long quantityPresent = ((IngredientChannelIndexed<T, M>) storage).getIndex().getQuantity(instance);
×
862
            contains = matcher.hasCondition(matchCondition, ingredientComponent.getPrimaryQuantifier().getMatchCondition()) ? quantityPresent >= matcher.getQuantity(instance) : quantityPresent > 0;
×
863
        } else {
×
864
            contains = !ingredientComponent.getMatcher().isEmpty(storage.extract(instance, matchCondition, true));
×
865
        }
866
        if (storage instanceof IngredientChannelAdapter) ((IngredientChannelAdapter) storage).enableLimits();
×
867
        return contains;
×
868
    }
869

870
    /**
871
     * Check the quantity of the given instance in the network.
872
     * @param network The target network.
873
     * @param channel The target channel.
874
     * @param ingredientComponent The ingredient component type of the instance.
875
     * @param instance The instance to check.
876
     * @param matchCondition The match condition of the instance.
877
     * @param <T> The instance type.
878
     * @param <M> The matching condition parameter.
879
     * @return The quantity in the network.
880
     */
881
    public static <T, M> long getStorageInstanceQuantity(INetwork network, int channel,
882
                                                         IngredientComponent<T, M> ingredientComponent,
883
                                                         T instance, M matchCondition) {
884
        IIngredientComponentStorage<T, M> storage = getNetworkStorage(network, channel, ingredientComponent, true);
×
885
        if (storage instanceof IngredientChannelAdapter) ((IngredientChannelAdapter) storage).disableLimits();
×
886
        long quantityPresent;
887
        if (storage instanceof IngredientChannelIndexed) {
×
888
            quantityPresent = ((IngredientChannelIndexed<T, M>) storage).getIndex().getQuantity(instance);
×
889
        } else {
890
            quantityPresent = ingredientComponent.getMatcher().getQuantity(storage.extract(instance, matchCondition, true));
×
891
        }
892
        if (storage instanceof IngredientChannelAdapter) ((IngredientChannelAdapter) storage).enableLimits();
×
893
        return quantityPresent;
×
894
    }
895

896
    /**
897
     * Check if there is a scheduled crafting job for the given instance.
898
     * @param craftingNetwork The target crafting network.
899
     * @param channel The target channel.
900
     * @param ingredientComponent The ingredient component type of the instance.
901
     * @param instance The instance to check.
902
     * @param matchCondition The match condition of the instance.
903
     * @param <T> The instance type.
904
     * @param <M> The matching condition parameter.
905
     * @return If the instance has a crafting job.
906
     */
907
    public static <T, M> boolean isCrafting(ICraftingNetwork craftingNetwork, int channel,
908
                                            IngredientComponent<T, M> ingredientComponent,
909
                                            T instance, M matchCondition) {
910
        Iterator<CraftingJob> craftingJobs = craftingNetwork.getCraftingJobs(channel, ingredientComponent,
×
911
                instance, matchCondition);
912
        return craftingJobs.hasNext();
×
913
    }
914

915
    /**
916
     * Check if there is a scheduled crafting job for the given recipe.
917
     * @param craftingNetwork The target crafting network.
918
     * @param channel The target channel.
919
     * @param recipe The recipe to check.
920
     * @return If the instance has a crafting job.
921
     */
922
    public static boolean isCrafting(ICraftingNetwork craftingNetwork, int channel,
923
                                     IRecipeDefinition recipe) {
924
        Iterator<CraftingJob> it = craftingNetwork.getCraftingJobs(channel);
×
925
        while (it.hasNext()) {
×
926
            if (it.next().getRecipe().equals(recipe)) {
×
927
                return true;
×
928
            }
929
        }
930
        return false;
×
931
    }
932

933
    /**
934
     * Get all required recipe input ingredients from the network for the given ingredient component.
935
     *
936
     * If multiple alternative inputs are possible,
937
     * then only the first possible match will be taken.
938
     *
939
     * Note: Make sure that you first call in simulation-mode
940
     * to see if the ingredients are available.
941
     * If you immediately call this non-simulated,
942
     * then there might be a chance that ingredients are lost
943
     * from the network.
944
     *
945
     * @param storage The target storage.
946
     * @param ingredientComponent The ingredient component to get the ingredients for.
947
     * @param recipe The recipe to get the inputs from.
948
     * @param simulate If true, then the ingredients will effectively be removed from the network, not when false.
949
     * @param recipeOutputQuantity The number of times the given recipe should be applied.
950
     * @param <T> The instance type.
951
     * @param <M> The matching condition parameter, may be Void.
952
     * @return A list of slot-based ingredients, or null if no valid inputs could be found.
953
     */
954
    @Nullable
955
    public static <T, M> List<T> getIngredientRecipeInputs(IIngredientComponentStorage<T, M> storage,
956
                                                           IngredientComponent<T, M> ingredientComponent,
957
                                                           IRecipeDefinition recipe, boolean simulate,
958
                                                           long recipeOutputQuantity) {
959
        return getIngredientRecipeInputs(storage, ingredientComponent, recipe, simulate,
1✔
960
                simulate ? new IngredientCollectionPrototypeMap<>(ingredientComponent, true) : null,
1✔
961
                new IngredientHashSet<>(ingredientComponent),
962
                false, recipeOutputQuantity).getLeft();
1✔
963
    }
964

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

1008
        // Quickly return if the storage is empty
1009
        // We can't take this shortcut if we have a reusable ingredient AND extractionMemoryReusable is not empty
1010
        if (storage.getMaxQuantity() == 0 &&
1✔
1011
                extractionMemoryReusable.isEmpty() &&
1✔
1012
                IntStream.range(0, recipe.getInputs(ingredientComponent).size())
1✔
1013
                        .noneMatch(i -> recipe.isInputReusable(ingredientComponent, i))) {
1✔
1014
            if (collectMissingIngredients) {
1✔
1015
                List<IPrototypedIngredientAlternatives<T, M>> recipeInputs = recipe.getInputs(ingredientComponent);
1✔
1016
                MissingIngredients<T, M> missing = new MissingIngredients<>(recipeInputs.stream().map(IPrototypedIngredientAlternatives::getAlternatives)
1✔
1017
                        .map(l -> multiplyPrototypedIngredients(l, recipeOutputQuantity)) // If the input is reusable, don't multiply the expected input quantity
1✔
1018
                        .map(ps -> new MissingIngredients.Element<>(ps
1✔
1019
                                .stream()
1✔
1020
                                .map(p -> new MissingIngredients.PrototypedWithRequested<>(p, matcher.getQuantity(p.getPrototype())))
1✔
1021
                                .collect(Collectors.toList()), false)
1✔
1022
                        )
1023
                        .collect(Collectors.toList()));
1✔
1024
                return Pair.of(
1✔
1025
                        Lists.newArrayList(Collections.nCopies(recipe.getInputs(ingredientComponent).size(),
1✔
1026
                                ingredientComponent.getMatcher().getEmptyInstance())),
1✔
1027
                        missing);
1028
            } else {
1029
                return Pair.of(null, null);
1✔
1030
            }
1031
        }
1032

1033
        // Iterate over all input slots
1034
        List<IPrototypedIngredientAlternatives<T, M>> inputAlternativePrototypes = recipe.getInputs(ingredientComponent);
1✔
1035
        List<T> inputInstances = Lists.newArrayList();
1✔
1036
        List<MissingIngredients.Element<T, M>> missingElements =
1037
                collectMissingIngredients ? Lists.newArrayList() : null;
1✔
1038
        for (int inputIndex = 0; inputIndex < inputAlternativePrototypes.size(); inputIndex++) {
1✔
1039
            IPrototypedIngredientAlternatives<T, M> inputPrototypes = inputAlternativePrototypes.get(inputIndex);
1✔
1040
            T firstInputInstance = null;
1✔
1041
            boolean setFirstInputInstance = false;
1✔
1042
            T inputInstance = null;
1✔
1043
            boolean hasInputInstance = false;
1✔
1044
            IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemoryBufferFirst = null;
1✔
1045
            IIngredientCollectionMutable<T, M> extractionMemoryReusableBufferFirst = null;
1✔
1046

1047
            // Iterate over all alternatives for this input slot, and take the first matching ingredient.
1048
            List<MissingIngredients.PrototypedWithRequested<T, M>> missingAlternatives = Lists.newArrayList();
1✔
1049
            IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemoryAlternative = simulate ? new IngredientCollectionPrototypeMap<>(ingredientComponent, true) : null;
1✔
1050
            if (simulate) {
1✔
1051
                simulatedExtractionMemoryAlternative.addAll(simulatedExtractionMemory);
1✔
1052
            }
1053
            for (IPrototypedIngredient<T, M> inputPrototype : inputPrototypes.getAlternatives()) {
1✔
1054
                boolean inputReusable = recipe.isInputReusable(ingredientComponent, inputIndex);
1✔
1055
                IngredientCollectionPrototypeMap<T, M> simulatedExtractionMemoryBuffer = simulate ? new IngredientCollectionPrototypeMap<>(ingredientComponent, true) : null;
1✔
1056
                IIngredientCollectionMutable<T, M> extractionMemoryReusableBuffer = inputReusable ? new IngredientHashSet<>(ingredientComponent) : null;
1✔
1057
                boolean shouldBreak = false;
1✔
1058

1059
                // Multiply required prototype if recipe quantity is higher than one, AND if the input is NOT reusable.
1060
                if (recipeOutputQuantity > 1 && !inputReusable) {
1✔
1061
                    inputPrototype = multiplyPrototypedIngredient(inputPrototype, recipeOutputQuantity);
1✔
1062
                }
1063

1064
                // If the prototype is empty, we can skip network extraction
1065
                if (matcher.isEmpty(inputPrototype.getPrototype())) {
1✔
1066
                    inputInstance = inputPrototype.getPrototype();
1✔
1067
                    hasInputInstance = true;
1✔
1068
                    break;
1✔
1069
                }
1070

1071
                long prototypeQuantity = matcher.getQuantity(inputPrototype.getPrototype());
1✔
1072
                if (inputReusable && extractionMemoryReusable.contains(inputPrototype.getPrototype())) {
1✔
1073
                    // If the reusable item has been extracted before, mark as valid, and don't extract again.
1074
                    inputInstance = inputPrototype.getComponent().getMatcher().getEmptyInstance();
1✔
1075
                    hasInputInstance = true;
1✔
1076
                    shouldBreak = true;
1✔
1077
                } else {
1078
                    long memoryQuantity;
1079
                    if (simulate && (memoryQuantity = simulatedExtractionMemoryAlternative
1✔
1080
                            .getQuantity(inputPrototype.getPrototype())) != 0) {
1✔
1081
                        long newQuantity = memoryQuantity + prototypeQuantity;
1✔
1082
                        if (newQuantity > 0) {
1✔
1083
                            // Part of our quantity can be provided via simulatedExtractionMemory,
1084
                            // but not all of it,
1085
                            // so we need to extract from storage as well.
1086
                            T newInstance = matcher.withQuantity(inputPrototype.getPrototype(), newQuantity);
1✔
1087
                            M matchCondition = matcher.withoutCondition(inputPrototype.getCondition(),
1✔
1088
                                    ingredientComponent.getPrimaryQuantifier().getMatchCondition());
1✔
1089
                            if (storage instanceof IngredientChannelAdapter)
1✔
1090
                                ((IngredientChannelAdapter) storage).disableLimits();
×
1091
                            T extracted = storage.extract(newInstance, matchCondition, true);
1✔
1092
                            if (storage instanceof IngredientChannelAdapter)
1✔
1093
                                ((IngredientChannelAdapter) storage).enableLimits();
×
1094
                            long quantityExtracted = matcher.getQuantity(extracted);
1✔
1095
                            if (quantityExtracted == newQuantity) {
1✔
1096
                                // All remaining could be extracted from storage, all is fine now
1097
                                inputInstance = inputPrototype.getPrototype();
1✔
1098
                                simulatedExtractionMemoryAlternative.add(inputInstance);
1✔
1099
                                simulatedExtractionMemoryBuffer.add(inputInstance);
1✔
1100
                                if (inputReusable) {
1✔
1101
                                    extractionMemoryReusableBuffer.add(inputInstance);
1✔
1102
                                }
1103
                                hasInputInstance = true;
1✔
1104
                                shouldBreak = true;
1✔
1105
                            } else if (collectMissingIngredients) {
1✔
1106
                                // Not everything could be extracted from storage, we *miss* the remaining ingredient.
1107
                                long quantityMissingPrevious = Math.max(0, memoryQuantity - quantityExtracted);
1✔
1108
                                long quantityMissingTotal = newQuantity - quantityExtracted;
1✔
1109
                                long quantityMissingRelative = quantityMissingTotal - quantityMissingPrevious;
1✔
1110

1111
                                missingAlternatives.add(new MissingIngredients.PrototypedWithRequested<>(inputPrototype, quantityMissingRelative));
1✔
1112
                                inputInstance = matcher.withQuantity(inputPrototype.getPrototype(), prototypeQuantity - quantityMissingRelative);
1✔
1113
                                simulatedExtractionMemoryAlternative.setQuantity(inputPrototype.getPrototype(), quantityMissingTotal);
1✔
1114
                                // Original prototype quantity because we what can be extracted from storage and what could NOT be extracted from storage should be added to the simulation extraction memory.
1115
                                // The part that could NOT be extracted will be removed later when crafting jobs are calculated for the missing elements. (not doing so would lead to over-estimation of what is in storage)
1116
                                simulatedExtractionMemoryBuffer.add(matcher.withQuantity(inputPrototype.getPrototype(), prototypeQuantity));
1✔
1117
                            }
1118
                        } else {
1✔
1119
                            // All of our quantity can be provided via our surplus in simulatedExtractionMemory
1120
                            simulatedExtractionMemoryAlternative.add(inputPrototype.getPrototype());
1✔
1121
                            simulatedExtractionMemoryBuffer.add(inputPrototype.getPrototype());
1✔
1122
                            if (inputReusable) {
1✔
1123
                                extractionMemoryReusableBuffer.add(inputPrototype.getPrototype());
1✔
1124
                            }
1125
                            inputInstance = inputPrototype.getComponent().getMatcher().getEmptyInstance();
1✔
1126
                            hasInputInstance = true;
1✔
1127
                            shouldBreak = true;
1✔
1128
                        }
1129
                    } else {
1✔
1130
                        M matchCondition = matcher.withoutCondition(inputPrototype.getCondition(),
1✔
1131
                                ingredientComponent.getPrimaryQuantifier().getMatchCondition());
1✔
1132
                        if (storage instanceof IngredientChannelAdapter)
1✔
1133
                            ((IngredientChannelAdapter) storage).disableLimits();
×
1134
                        T extracted = storage.extract(inputPrototype.getPrototype(), matchCondition, simulate);
1✔
1135
                        if (storage instanceof IngredientChannelAdapter)
1✔
1136
                            ((IngredientChannelAdapter) storage).enableLimits();
×
1137
                        long quantityExtracted = matcher.getQuantity(extracted);
1✔
1138
                        inputInstance = extracted;
1✔
1139
                        if (simulate) {
1✔
1140
                            simulatedExtractionMemoryAlternative.add(extracted);
1✔
1141
                            simulatedExtractionMemoryBuffer.add(inputPrototype.getPrototype());
1✔
1142
                        }
1143
                        if (prototypeQuantity == quantityExtracted) {
1✔
1144
                            hasInputInstance = true;
1✔
1145
                            shouldBreak = true;
1✔
1146
                            if (inputReusable) {
1✔
1147
                                extractionMemoryReusableBuffer.add(inputPrototype.getPrototype());
1✔
1148
                            }
1149
                        } else if (collectMissingIngredients) {
1✔
1150
                            long quantityMissing = prototypeQuantity - quantityExtracted;
1✔
1151
                            missingAlternatives.add(new MissingIngredients.PrototypedWithRequested<>(inputPrototype, quantityMissing));
1✔
1152
                        }
1153
                    }
1154
                }
1155

1156
                if (!setFirstInputInstance || shouldBreak) {
1✔
1157
                    setFirstInputInstance = true;
1✔
1158
                    firstInputInstance = inputInstance;
1✔
1159
                    simulatedExtractionMemoryBufferFirst = simulatedExtractionMemoryBuffer;
1✔
1160
                    if (inputReusable) {
1✔
1161
                        extractionMemoryReusableBufferFirst = extractionMemoryReusableBuffer;
1✔
1162
                    } else {
1163
                        extractionMemoryReusableBufferFirst = null;
1✔
1164
                    }
1165
                }
1166

1167
                if (shouldBreak) {
1✔
1168
                    break;
1✔
1169
                }
1170
            }
1✔
1171

1172
            if (simulatedExtractionMemoryBufferFirst != null) {
1✔
1173
                for (T instance : simulatedExtractionMemoryBufferFirst) {
1✔
1174
                    simulatedExtractionMemory.add(instance);
1✔
1175
                }
1✔
1176
            }
1177
            if (extractionMemoryReusableBufferFirst != null) {
1✔
1178
                for (T instance : extractionMemoryReusableBufferFirst) {
1✔
1179
                    extractionMemoryReusable.add(instance);
1✔
1180
                }
1✔
1181
            }
1182

1183
            // If none of the alternatives were found, fail immediately
1184
            if (!hasInputInstance) {
1✔
1185
                if (!simulate) {
1✔
1186
                    // But first, re-insert all already-extracted instances
1187
                    for (T instance : inputInstances) {
1✔
1188
                        T remaining = storage.insert(instance, false);
1✔
1189
                        if (!matcher.isEmpty(remaining)) {
1✔
1190
                            throw new IllegalStateException("Extraction for a crafting recipe failed" +
×
1191
                                    "due to inconsistent insertion behaviour by destination in simulation " +
1192
                                    "and non-simulation: " + storage + ". Lost: " + remaining);
1193
                        }
1194
                    }
1✔
1195
                }
1196

1197
                if (!collectMissingIngredients) {
1✔
1198
                    // This input failed, return immediately
1199
                    return Pair.of(null, null);
1✔
1200
                } else {
1201
                    // Multiply missing collection if recipe quantity is higher than one
1202
                    if (missingAlternatives.size() > 0) {
1✔
1203
                        missingElements.add(new MissingIngredients.Element<>(missingAlternatives, recipe.isInputReusable(ingredientComponent, inputIndex)));
1✔
1204
                    }
1205
                }
1206
            }
1207

1208
            // Otherwise, append it to the list and carry on.
1209
            // If none of the instances were valid, we add the first partially valid instance.
1210
            if (hasInputInstance) {
1✔
1211
                inputInstances.add(inputInstance);
1✔
1212
            } else if (setFirstInputInstance && !matcher.isEmpty(firstInputInstance)) {
1✔
1213
                inputInstances.add(firstInputInstance);
1✔
1214
            }
1215
        }
1216

1217
        return Pair.of(
1✔
1218
                inputInstances,
1219
                collectMissingIngredients ? new MissingIngredients<>(missingElements) : null
1✔
1220
        );
1221
    }
1222

1223
    /**
1224
     * Get all required recipe input ingredients from the network.
1225
     *
1226
     * If multiple alternative inputs are possible,
1227
     * then only the first possible match will be taken.
1228
     *
1229
     * Note: Make sure that you first call in simulation-mode
1230
     * to see if the ingredients are available.
1231
     * If you immediately call this non-simulated,
1232
     * then there might be a chance that ingredients are lost
1233
     * from the network.
1234
     *
1235
     * @param network The target network.
1236
     * @param channel The target channel.
1237
     * @param recipe The recipe to get the inputs from.
1238
     * @param simulate If true, then the ingredients will effectively be removed from the network, not when false.
1239
     * @param recipeOutputQuantity The number of times the given recipe should be applied.
1240
     * @return The found ingredients or null.
1241
     */
1242
    @Nullable
1243
    public static IMixedIngredients getRecipeInputs(INetwork network, int channel,
1244
                                                    IRecipeDefinition recipe, boolean simulate,
1245
                                                    long recipeOutputQuantity) {
1246
        Map<IngredientComponent<?, ?>, List<?>> inputs = getRecipeInputs(getNetworkStorageGetter(network, channel, true),
×
1247
                recipe, simulate, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), false, recipeOutputQuantity).getLeft();
×
1248
        return inputs == null ? null : new MixedIngredients(inputs);
×
1249
    }
1250

1251
    /**
1252
     * Get all required recipe input ingredients from the crafting job's buffer.
1253
     *
1254
     * If multiple alternative inputs are possible,
1255
     * then only the first possible match will be taken.
1256
     *
1257
     * Note: Make sure that you first call in simulation-mode
1258
     * to see if the ingredients are available.
1259
     * If you immediately call this non-simulated,
1260
     * then there might be a chance that ingredients are lost
1261
     * from the buffer.
1262
     *
1263
     * @param craftingJob The crafting job.
1264
     * @param recipe The recipe to get the inputs from.
1265
     * @param simulate If true, then the ingredients will effectively be removed from the buffer, not when false.
1266
     * @param recipeOutputQuantity The number of times the given recipe should be applied.
1267
     * @return The found ingredients or null.
1268
     */
1269
    @Nullable
1270
    public static IMixedIngredients getRecipeInputsFromCraftingJobBuffer(CraftingJob craftingJob,
1271
                                                    IRecipeDefinition recipe, boolean simulate,
1272
                                                    long recipeOutputQuantity) {
NEW
1273
        Map<IngredientComponent<?, ?>, List<?>> inputs = getRecipeInputs(getCraftingJobBufferStorageGetter(craftingJob),
×
NEW
1274
                recipe, simulate, Maps.newIdentityHashMap(), Maps.newIdentityHashMap(), false, recipeOutputQuantity).getLeft();
×
NEW
1275
        return inputs == null ? null : new MixedIngredients(inputs);
×
1276
    }
1277

1278
    /**
1279
     * Create a callback function for getting a storage for an ingredient component from the given network channel.
1280
     * @param network The target network.
1281
     * @param channel The target channel.
1282
     * @param scheduleObservation If an observation inside the ingredients network should be scheduled.
1283
     * @return A callback function for getting a storage for an ingredient component.
1284
     */
1285
    public static Function<IngredientComponent<?, ?>, IIngredientComponentStorage> getNetworkStorageGetter(INetwork network, int channel, boolean scheduleObservation) {
1286
        return ingredientComponent -> getNetworkStorage(network, channel, ingredientComponent, scheduleObservation);
×
1287
    }
1288

1289
    /**
1290
     * Create a callback function for getting a storage for an ingredient component from the given crafting job buffer.
1291
     * @param craftingJob The crafting job.
1292
     * @return A callback function for getting a storage for an ingredient component.
1293
     */
1294
    public static Function<IngredientComponent<?, ?>, IIngredientComponentStorage> getCraftingJobBufferStorageGetter(CraftingJob craftingJob) {
NEW
1295
        return ingredientComponent -> {
×
NEW
1296
            List<?> list = craftingJob.getIngredientsStorageBuffer().getInstances(ingredientComponent);
×
NEW
1297
            return new IngredientComponentStorageSlottedCollectionWrapper<>(new IngredientList(ingredientComponent, list), Long.MAX_VALUE, Long.MAX_VALUE);
×
1298
        };
1299
    }
1300

1301
    /**
1302
     * Get all required recipe input ingredients based on a given storage callback.
1303
     *
1304
     * If multiple alternative inputs are possible,
1305
     * then only the first possible match will be taken.
1306
     *
1307
     * Note: Make sure that you first call in simulation-mode
1308
     * to see if the ingredients are available.
1309
     * If you immediately call this non-simulated,
1310
     * then there might be a chance that ingredients are lost
1311
     * from the network.
1312
     *
1313
     * @param storageGetter A callback function to get a storage for the given ingredient component.
1314
     * @param recipe The recipe to get the inputs from.
1315
     * @param simulate If true, then the ingredients will effectively be removed from the network, not when false.
1316
     * @param simulatedExtractionMemories This map remembers all extracted instances in simulation mode.
1317
     *                                    This is to make sure that instances can not be extracted multiple times
1318
     *                                    when simulating.
1319
     * @param extractionMemoriesReusable Like simulatedExtractionMemories, but it stores the reusable ingredients.
1320
     * @param collectMissingIngredients If missing ingredients should be collected.
1321
     *                                  If false, then the first returned mixed ingredients may be null
1322
     *                                  if no valid matches can be found,
1323
     *                                  and the second returned list is always null,
1324
     * @param recipeOutputQuantity The number of times the given recipe should be applied.
1325
     * @return A pair with two objects:
1326
     *           1. The found ingredients or null.
1327
     *           2. A mapping from ingredient component to missing ingredients (non-slot-based).
1328
     */
1329
    public static Pair<Map<IngredientComponent<?, ?>, List<?>>, Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>>>
1330
    getRecipeInputs(Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter, IRecipeDefinition recipe, boolean simulate,
1331
                    Map<IngredientComponent<?, ?>, IngredientCollectionPrototypeMap<?, ?>> simulatedExtractionMemories,
1332
                    Map<IngredientComponent<?, ?>, IIngredientCollectionMutable<?, ?>> extractionMemoriesReusable,
1333
                    boolean collectMissingIngredients, long recipeOutputQuantity) {
1334
        // Determine available and missing ingredients
1335
        Map<IngredientComponent<?, ?>, List<?>> ingredientsAvailable = Maps.newIdentityHashMap();
1✔
1336
        Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> ingredientsMissing = Maps.newIdentityHashMap();
1✔
1337
        for (IngredientComponent<?, ?> ingredientComponent : recipe.getInputComponents()) {
1✔
1338
            IIngredientComponentStorage storage = storageGetter.apply(ingredientComponent);
1✔
1339
            IngredientCollectionPrototypeMap<?, ?> simulatedExtractionMemory = simulatedExtractionMemories.get(ingredientComponent);
1✔
1340
            if (simulatedExtractionMemory == null) {
1✔
1341
                simulatedExtractionMemory = new IngredientCollectionPrototypeMap<>(ingredientComponent, true);
1✔
1342
                simulatedExtractionMemories.put(ingredientComponent, simulatedExtractionMemory);
1✔
1343
            }
1344
            IIngredientCollectionMutable extractionMemoryReusable = extractionMemoriesReusable.get(ingredientComponent);
1✔
1345
            if (extractionMemoryReusable == null) {
1✔
1346
                extractionMemoryReusable = new IngredientHashSet<>(ingredientComponent);
1✔
1347
                extractionMemoriesReusable.put(ingredientComponent, extractionMemoryReusable);
1✔
1348
            }
1349
            Pair<List<?>, MissingIngredients<?, ?>> subIngredients = getIngredientRecipeInputs(storage,
1✔
1350
                    (IngredientComponent) ingredientComponent, recipe, simulate, simulatedExtractionMemory, extractionMemoryReusable,
1351
                    collectMissingIngredients, recipeOutputQuantity);
1352
            List<?> subIngredientAvailable = subIngredients.getLeft();
1✔
1353
            MissingIngredients<?, ?> subIngredientsMissing = subIngredients.getRight();
1✔
1354
            if (subIngredientAvailable == null && !collectMissingIngredients) {
1✔
1355
                return Pair.of(null, null);
×
1356
            } else {
1357
                if (subIngredientAvailable != null && !subIngredientAvailable.isEmpty()) {
1✔
1358
                    ingredientsAvailable.put(ingredientComponent, subIngredientAvailable);
1✔
1359
                }
1360
                if (collectMissingIngredients && !subIngredientsMissing.getElements().isEmpty()) {
1✔
1361
                    ingredientsMissing.put(ingredientComponent, subIngredientsMissing);
1✔
1362
                }
1363
            }
1364
        }
1✔
1365

1366
        // Compress missing ingredients
1367
        // We do this to ensure that instances missing multiple times can be easily combined
1368
        // when triggering a crafting job for them.
1369
        Map<IngredientComponent<?, ?>, MissingIngredients<?, ?>> ingredientsMissingCompressed = Maps.newIdentityHashMap();
1✔
1370
        for (IngredientComponent<?, ?> ingredientComponent : ingredientsMissing.keySet()) {
1✔
1371
            ingredientsMissingCompressed.put(ingredientComponent, compressMissingIngredients(ingredientsMissing.get(ingredientComponent)));
1✔
1372
        }
1✔
1373

1374
        return Pair.of(ingredientsAvailable, ingredientsMissingCompressed);
1✔
1375
    }
1376

1377
    /**
1378
     * Create a list of prototyped ingredients from the instances
1379
     * of the given ingredient component type in the given mixed ingredients.
1380
     *
1381
     * Equal prototypes will be stacked.
1382
     *
1383
     * @param ingredientComponent The ingredient component type.
1384
     * @param mixedIngredients The mixed ingredients.
1385
     * @param <T> The instance type.
1386
     * @param <M> The matching condition parameter.
1387
     * @return A list of prototypes.
1388
     */
1389
    public static <T, M> List<IPrototypedIngredient<T, M>> getCompressedIngredients(IngredientComponent<T, M> ingredientComponent,
1390
                                                                                    IMixedIngredients mixedIngredients) {
1391
        List<IPrototypedIngredient<T, M>> outputs = Lists.newArrayList();
1✔
1392

1393
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
1✔
1394
        for (T instance : mixedIngredients.getInstances(ingredientComponent)) {
1✔
1395
            // Try to stack this instance with an existing prototype
1396
            boolean stacked = false;
1✔
1397
            ListIterator<IPrototypedIngredient<T, M>> existingIt = outputs.listIterator();
1✔
1398
            while(existingIt.hasNext()) {
1✔
1399
                IPrototypedIngredient<T, M> prototypedIngredient = existingIt.next();
1✔
1400
                if (matcher.matches(instance, prototypedIngredient.getPrototype(),
1✔
1401
                        prototypedIngredient.getCondition())) {
1✔
1402
                    T stackedInstance = matcher.withQuantity(prototypedIngredient.getPrototype(),
1✔
1403
                            matcher.getQuantity(prototypedIngredient.getPrototype())
1✔
1404
                                    + matcher.getQuantity(instance));
1✔
1405
                    existingIt.set(new PrototypedIngredient<>(ingredientComponent, stackedInstance,
1✔
1406
                            prototypedIngredient.getCondition()));
1✔
1407
                    stacked = true;
1✔
1408
                    break;
1✔
1409
                }
1410
            }
1✔
1411

1412
            // If not possible, just append it to the list
1413
            if (!stacked) {
1✔
1414
                outputs.add(new PrototypedIngredient<>(ingredientComponent, instance,
1✔
1415
                        matcher.getExactMatchNoQuantityCondition()));
1✔
1416
            }
1417
        }
1✔
1418

1419
        return outputs;
1✔
1420
    }
1421

1422
    /**
1423
     * Compress the given missing ingredients so that equal instances just have an incremented quantity.
1424
     *
1425
     * @param missingIngredients The missing ingredients.
1426
     * @param <T> The instance type.
1427
     * @param <M> The matching condition parameter.
1428
     * @return A new missing ingredients object.
1429
     */
1430
    public static <T, M> MissingIngredients<T, M> compressMissingIngredients(MissingIngredients<T, M> missingIngredients) {
1431
        // Index identical missing ingredients in a map, to group them by quantity
1432
        Map<MissingIngredients.Element<T, M>, Long> elementsCompressedMap = Maps.newLinkedHashMap(); // Must be a linked map to maintain our order!!!
1✔
1433
        for (MissingIngredients.Element<T, M> element : missingIngredients.getElements()) {
1✔
1434
            elementsCompressedMap.merge(element, 1L, Long::sum);
1✔
1435
        }
1✔
1436

1437
        // Create a new missing ingredients list where we multiply the missing quantities
1438
        List<MissingIngredients.Element<T, M>> elementsCompressed = Lists.newArrayList();
1✔
1439
        for (Map.Entry<MissingIngredients.Element<T, M>, Long> entry : elementsCompressedMap.entrySet()) {
1✔
1440
            Long quantity = entry.getValue();
1✔
1441
            if (quantity == 1L || entry.getKey().isInputReusable()) {
1✔
1442
                elementsCompressed.add(entry.getKey());
1✔
1443
            } else {
1444
                MissingIngredients.Element<T, M> elementOld = entry.getKey();
1✔
1445
                MissingIngredients.Element<T, M> elementNewQuantity = new MissingIngredients.Element<>(
1✔
1446
                        elementOld.getAlternatives().stream()
1✔
1447
                                .map(alt -> new MissingIngredients.PrototypedWithRequested<>(alt.getRequestedPrototype(), alt.getQuantityMissing() * quantity))
1✔
1448
                                .toList(),
1✔
1449
                        elementOld.isInputReusable()
1✔
1450
                );
1451
                elementsCompressed.add(elementNewQuantity);
1✔
1452
            }
1453
        }
1✔
1454
        return new MissingIngredients<>(elementsCompressed);
1✔
1455
    }
1456

1457
    /**
1458
     * Create a collection of prototypes from the given recipe's outputs.
1459
     *
1460
     * Equal prototypes will be stacked.
1461
     *
1462
     * @param recipe A recipe.
1463
     * @return A map from ingredient component types to their list of prototypes.
1464
     */
1465
    public static Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> getRecipeOutputs(IRecipeDefinition recipe) {
1466
        Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> outputs = Maps.newHashMap();
1✔
1467

1468
        IMixedIngredients mixedIngredients = recipe.getOutput();
1✔
1469
        for (IngredientComponent ingredientComponent : mixedIngredients.getComponents()) {
1✔
1470
            outputs.put(ingredientComponent, getCompressedIngredients(ingredientComponent, mixedIngredients));
1✔
1471
        }
1✔
1472

1473
        return outputs;
1✔
1474
    }
1475

1476
    /**
1477
     * Creates a new recipe outputs object with all ingredient quantities multiplied by the given amount.
1478
     * @param recipeOutputs A recipe objects holder.
1479
     * @param amount An amount to multiply all instances by.
1480
     * @return A new recipe objects holder.
1481
     */
1482
    public static Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> multiplyRecipeOutputs(
1483
            Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> recipeOutputs, int amount) {
1484
        if (amount == 1) {
1✔
1485
            return recipeOutputs;
1✔
1486
        }
1487

1488
        Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> newRecipeOutputs = Maps.newIdentityHashMap();
1✔
1489
        for (Map.Entry<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>> entry : recipeOutputs.entrySet()) {
1✔
1490
            newRecipeOutputs.put(entry.getKey(), multiplyPrototypedIngredients((List) entry.getValue(), amount));
1✔
1491
        }
1✔
1492
        return newRecipeOutputs;
1✔
1493
    }
1494

1495
    /**
1496
     * Multiply the quantity of a given prototyped ingredient list with the given amount.
1497
     * @param prototypedIngredients A prototyped ingredient list.
1498
     * @param amount An amount to multiply by.
1499
     * @param <T> The instance type.
1500
     * @param <M> The matching condition parameter.
1501
     * @return A multiplied prototyped ingredient list.
1502
     */
1503
    public static <T, M> List<IPrototypedIngredient<T, M>> multiplyPrototypedIngredients(Collection<IPrototypedIngredient<T, M>> prototypedIngredients,
1504
                                                                                         long amount) {
1505
        return prototypedIngredients
1✔
1506
                .stream()
1✔
1507
                .map(p -> multiplyPrototypedIngredient(p, amount))
1✔
1508
                .collect(Collectors.toList());
1✔
1509
    }
1510

1511
    /**
1512
     * Multiply the quantity of a given prototyped ingredient with the given amount.
1513
     * @param prototypedIngredient A prototyped ingredient.
1514
     * @param amount An amount to multiply by.
1515
     * @param <T> The instance type.
1516
     * @param <M> The matching condition parameter.
1517
     * @return A multiplied prototyped ingredient.
1518
     */
1519
    public static <T, M> IPrototypedIngredient<T, M> multiplyPrototypedIngredient(IPrototypedIngredient<T, M> prototypedIngredient,
1520
                                                                                  long amount) {
1521
        IIngredientMatcher<T, M> matcher = prototypedIngredient.getComponent().getMatcher();
1✔
1522
        return new PrototypedIngredient<>(prototypedIngredient.getComponent(),
1✔
1523
                matcher.withQuantity(prototypedIngredient.getPrototype(),
1✔
1524
                        matcher.getQuantity(prototypedIngredient.getPrototype()) * amount),
1✔
1525
                prototypedIngredient.getCondition());
1✔
1526
    }
1527

1528
    /**
1529
     * Merge two mixed ingredients in a new mixed ingredients object.
1530
     * Instances will be stacked.
1531
     * @param a A first mixed ingredients object.
1532
     * @param b A second mixed ingredients object.
1533
     * @return A merged mixed ingredients object.
1534
     */
1535
    public static IMixedIngredients mergeMixedIngredients(IMixedIngredients a, IMixedIngredients b) {
1536
        // Temporarily store instances in IngredientCollectionPrototypeMaps
1537
        Map<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> groupings = Maps.newIdentityHashMap();
1✔
1538
        for (IngredientComponent<?, ?> component : a.getComponents()) {
1✔
1539
            IngredientCollectionQuantitativeGrouper grouping = new IngredientCollectionQuantitativeGrouper<>(new IngredientArrayList<>(component));
1✔
1540
            groupings.put(component, grouping);
1✔
1541
            grouping.addAll(a.getInstances(component));
1✔
1542
        }
1✔
1543
        for (IngredientComponent<?, ?> component : b.getComponents()) {
1✔
1544
            IngredientCollectionQuantitativeGrouper grouping = groupings.get(component);
1✔
1545
            if (grouping == null) {
1✔
1546
                grouping = new IngredientCollectionQuantitativeGrouper<>(new IngredientArrayList<>(component));
1✔
1547
                groupings.put(component, grouping);
1✔
1548
            }
1549
            grouping.addAll(b.getInstances(component));
1✔
1550
        }
1✔
1551

1552
        // Convert IngredientCollectionPrototypeMaps to lists
1553
        Map<IngredientComponent<?, ?>, List<?>> ingredients = Maps.newIdentityHashMap();
1✔
1554
        for (Map.Entry<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> entry : groupings.entrySet()) {
1✔
1555
            ingredients.put(entry.getKey(), Lists.newArrayList(entry.getValue()));
1✔
1556
        }
1✔
1557
        return new MixedIngredients(ingredients);
1✔
1558
    }
1559

1560
    /**
1561
     * Stack all ingredients in the given mixed ingredients object.
1562
     * @param mixedIngredients A mixed ingredients object.
1563
     * @return A new mixed ingredients object.
1564
     */
1565
    protected static IMixedIngredients compressMixedIngredients(IMixedIngredients mixedIngredients) {
1566
        // Temporarily store instances in IngredientCollectionPrototypeMaps
1567
        Map<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> groupings = Maps.newIdentityHashMap();
1✔
1568
        for (IngredientComponent<?, ?> component : mixedIngredients.getComponents()) {
1✔
1569
            IngredientCollectionQuantitativeGrouper grouping = new IngredientCollectionQuantitativeGrouper<>(new IngredientArrayList<>(component));
1✔
1570
            groupings.put(component, grouping);
1✔
1571
            grouping.addAll(mixedIngredients.getInstances(component));
1✔
1572
        }
1✔
1573

1574
        // Convert IngredientCollectionPrototypeMaps to lists
1575
        Map<IngredientComponent<?, ?>, List<?>> ingredients = Maps.newIdentityHashMap();
1✔
1576
        for (Map.Entry<IngredientComponent<?, ?>, IngredientCollectionQuantitativeGrouper<?, ?, IngredientArrayList<?, ?>>> entry : groupings.entrySet()) {
1✔
1577
            IIngredientMatcher matcher = entry.getKey().getMatcher();
1✔
1578
            List<?> values = entry.getValue()
1✔
1579
                    .stream()
1✔
1580
                    .filter(i -> !matcher.isEmpty(i))
1✔
1581
                    .collect(Collectors.toList());
1✔
1582
            if (!values.isEmpty()) {
1✔
1583
                ingredients.put(entry.getKey(), values);
1✔
1584
            }
1585
        }
1✔
1586
        return new MixedIngredients(ingredients);
1✔
1587
    }
1588

1589
    /**
1590
     * Insert the ingredients of the given ingredient component type into the target to make it start crafting.
1591
     *
1592
     * If insertion in non-simulation mode fails,
1593
     * ingredients will be re-inserted into the network.
1594
     *
1595
     * @param ingredientComponent The ingredient component type.
1596
     * @param capabilityProvider The target capability provider.
1597
     * @param side The target side.
1598
     * @param ingredients The ingredients to insert.
1599
     * @param storageFallback The storage to insert any failed ingredients back into. Can be null in simulation mode.
1600
     * @param simulate If insertion should be simulated.
1601
     * @param <T> The instance type.
1602
     * @param <M> The matching condition parameter.
1603
     * @return If all instances could be inserted.
1604
     */
1605
    public static <T, M> boolean insertIngredientCrafting(IngredientComponent<T, M> ingredientComponent,
1606
                                                          ICapabilityProvider capabilityProvider,
1607
                                                          @Nullable Direction side,
1608
                                                          IMixedIngredients ingredients,
1609
                                                          IIngredientComponentStorage<T, M> storageFallback,
1610
                                                          boolean simulate) {
1611
        IIngredientMatcher<T, M> matcher = ingredientComponent.getMatcher();
×
1612
        IIngredientComponentStorage<T, M> storage = ingredientComponent.getStorage(capabilityProvider, side);
×
1613
        List<T> instances = ingredients.getInstances(ingredientComponent);
×
1614
        List<T> failedInstances = Lists.newArrayList();
×
1615
        boolean ok = true;
×
1616
        for (T instance : instances) {
×
1617
            T remaining = storage.insert(instance, simulate);
×
1618
            if (!matcher.isEmpty(remaining)) {
×
1619
                ok = false;
×
1620
                if (!simulate) {
×
1621
                    failedInstances.add(remaining);
×
1622
                }
1623
            }
1624
        }
×
1625

1626
        // If we had failed insertions, try to insert them back into the network.
1627
        for (T instance : failedInstances) {
×
1628
            T remaining = storageFallback.insert(instance, false);
×
1629
            if (!matcher.isEmpty(remaining)) {
×
1630
                throw new IllegalStateException("Insertion for a crafting recipe failed" +
×
1631
                        "due to inconsistent insertion behaviour by destination in simulation " +
1632
                        "and non-simulation: " + capabilityProvider + ". Lost: " + instances);
1633
            }
1634
        }
×
1635

1636
        return ok;
×
1637
    }
1638

1639
    /**
1640
     * Insert the ingredients of all applicable ingredient component types into the target to make it start crafting.
1641
     *
1642
     * If insertion in non-simulation mode fails,
1643
     * ingredients will be re-inserted into the network.
1644
     *
1645
     * @param targetGetter A function to get the target position.
1646
     * @param ingredients The ingredients to insert.
1647
     * @param network The network.
1648
     * @param channel The channel.
1649
     * @param simulate If insertion should be simulated.
1650
     * @return If all instances could be inserted.
1651
     */
1652
    public static boolean insertCrafting(Function<IngredientComponent<?, ?>, PartPos> targetGetter,
1653
                                         IMixedIngredients ingredients,
1654
                                         INetwork network, int channel,
1655
                                         boolean simulate) {
1656
        Map<IngredientComponent<?, ?>, BlockEntity> tileMap = Maps.newIdentityHashMap();
×
1657

1658
        // First, check if we can find valid tiles for all ingredient components
1659
        for (IngredientComponent<?, ?> ingredientComponent : ingredients.getComponents()) {
×
1660
            BlockEntity tile = BlockEntityHelpers.get(targetGetter.apply(ingredientComponent).getPos(), BlockEntity.class).orElse(null);
×
1661
            if (tile != null) {
×
1662
                tileMap.put(ingredientComponent, tile);
×
1663
            } else {
1664
                return false;
×
1665
            }
1666
        }
×
1667

1668
        // Next, insert the instances into the respective tiles
1669
        boolean ok = true;
×
1670
        for (Map.Entry<IngredientComponent<?, ?>, BlockEntity> entry : tileMap.entrySet()) {
×
1671
            IIngredientComponentStorage<?, ?> storageNetwork = simulate ? null : getNetworkStorage(network, channel, entry.getKey(), false);
×
1672
            if (!insertIngredientCrafting((IngredientComponent) entry.getKey(), entry.getValue(),
×
1673
                    targetGetter.apply(entry.getKey()).getSide(), ingredients,
×
1674
                    storageNetwork, simulate)) {
1675
                ok = false;
×
1676
            }
1677
        }
×
1678

1679
        return ok;
×
1680
    }
1681

1682
    /**
1683
     * Split the given crafting job amount into new jobs with a given split factor.
1684
     * @param craftingJob A crafting job to split.
1685
     * @param splitFactor The number of jobs to split the job over.
1686
     * @param dependencyGraph The dependency graph that will be updated if there are dependencies in the original job.
1687
     * @param identifierGenerator An identifier generator for the crafting jobs ids.
1688
     * @return The newly created crafting jobs.
1689
     */
1690
    public static List<CraftingJob> splitCraftingJobs(CraftingJob craftingJob, int splitFactor,
1691
                                                      CraftingJobDependencyGraph dependencyGraph,
1692
                                                      IIdentifierGenerator identifierGenerator) {
1693
        splitFactor = Math.min(splitFactor, craftingJob.getAmount());
1✔
1694
        int division = craftingJob.getAmount() / splitFactor;
1✔
1695
        int modulus = craftingJob.getAmount() % splitFactor;
1✔
1696

1697
        // Clone original job into splitFactor jobs
1698
        List<CraftingJob> newCraftingJobs = Lists.newArrayList();
1✔
1699
        for (int i = 0; i < splitFactor; i++) {
1✔
1700
            CraftingJob clonedJob = craftingJob.clone(identifierGenerator);
1✔
1701
            newCraftingJobs.add(clonedJob);
1✔
1702

1703
            // Update amount
1704
            int newAmount = division;
1✔
1705
            if (modulus > 0) {
1✔
1706
                // No amounts will be lost, as modulus is guaranteed to be smaller than splitFactor
1707
                newAmount++;
1✔
1708
                modulus--;
1✔
1709
            }
1710
            clonedJob.setAmount(newAmount);
1✔
1711
        }
1712

1713
        // Collect dependency links
1714
        Collection<CraftingJob> originalDependencies = dependencyGraph.getDependencies(craftingJob);
1✔
1715
        Collection<CraftingJob> originalDependents = dependencyGraph.getDependents(craftingJob);
1✔
1716

1717
        // Remove dependency links to and from the original jobs
1718
        for (CraftingJob dependency : originalDependencies) {
1✔
1719
            craftingJob.removeDependency(dependency);
1✔
1720
            dependencyGraph.removeDependency(craftingJob.getId(), dependency.getId());
1✔
1721
        }
1✔
1722
        for (CraftingJob dependent : originalDependents) {
1✔
1723
            dependent.removeDependency(craftingJob);
1✔
1724
            dependencyGraph.removeDependency(dependent.getId(), craftingJob.getId());
1✔
1725
        }
1✔
1726

1727
        // Create dependency links to and from the new crafting jobs
1728
        for (CraftingJob dependency : originalDependencies) {
1✔
1729
            for (CraftingJob newCraftingJob : newCraftingJobs) {
1✔
1730
                newCraftingJob.addDependency(dependency);
1✔
1731
                dependencyGraph.addDependency(newCraftingJob, dependency);
1✔
1732
            }
1✔
1733
        }
1✔
1734
        for (CraftingJob originalDependent : originalDependents) {
1✔
1735
            for (CraftingJob newCraftingJob : newCraftingJobs) {
1✔
1736
                originalDependent.addDependency(newCraftingJob);
1✔
1737
                dependencyGraph.addDependency(originalDependent, newCraftingJob);
1✔
1738
            }
1✔
1739
        }
1✔
1740

1741
        return newCraftingJobs;
1✔
1742
    }
1743

1744
    /**
1745
     * Insert the given ingredients into the given storage networks.
1746
     * @param ingredients A collection of ingredients.
1747
     * @param storageGetter A storage network getter.
1748
     * @param simulate If insertion should be simulated.
1749
     * @return The remaining ingredients that were not inserted.
1750
     */
1751
    public static IMixedIngredients insertIngredients(IMixedIngredients ingredients,
1752
                                                      Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
1753
                                                      boolean simulate) {
1754
        Map<IngredientComponent<?, ?>, List<?>> remainingIngredients = Maps.newIdentityHashMap();
×
1755
        for (IngredientComponent<?, ?> component : ingredients.getComponents()) {
×
1756
            IIngredientComponentStorage storage = storageGetter.apply(component);
×
1757
            IIngredientMatcher matcher = component.getMatcher();
×
1758
            for (Object instance : ingredients.getInstances(component)) {
×
1759
                Object remainder = storage.insert(instance, simulate);
×
1760
                if (!matcher.isEmpty(remainder)) {
×
1761
                    List remainingInstances = remainingIngredients.get(component);
×
1762
                    if (remainingInstances == null) {
×
1763
                        remainingInstances = Lists.newArrayList();
×
1764
                        remainingIngredients.put(component, remainingInstances);
×
1765
                    }
1766
                    remainingInstances.add(instance);
×
1767
                }
1768
            }
×
1769
        }
×
1770
        return new MixedIngredients(remainingIngredients);
×
1771
    }
1772

1773
    /**
1774
     * Insert the given ingredients into the given storage networks.
1775
     * If something fails to be inserted, produce a warning.
1776
     * @param ingredients A collection of ingredients.
1777
     * @param storageGetter A storage network getter.
1778
     * @param failureSink Ingredients that failed to be inserted will be inserted to this sink.
1779
     */
1780
    public static void insertIngredientsGuaranteed(IMixedIngredients ingredients,
1781
                                                   Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter,
1782
                                                   ICraftingResultsSink failureSink) {
NEW
1783
        IMixedIngredients remaining = insertIngredients(ingredients, storageGetter, false);
×
1784
        // If re-insertion into the network fails, insert it into the buffer of the crafting interface,
1785
        // to avoid loss of ingredients.
NEW
1786
        if (!remaining.isEmpty()) {
×
NEW
1787
            for (IngredientComponent<?, ?> component : remaining.getComponents()) {
×
NEW
1788
                for (Object instance : remaining.getInstances(component)) {
×
NEW
1789
                    failureSink.addResult((IngredientComponent) component, instance);
×
NEW
1790
                }
×
NEW
1791
            }
×
1792
        }
NEW
1793
    }
×
1794

1795
    /**
1796
     * Generates semi-unique IDs.
1797
     */
1798
    public static interface IIdentifierGenerator {
1799
        public int getNext();
1800
    }
1801

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