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

CyclopsMC / IntegratedCrafting / #479011822

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

push

github

rubensworks
Add dedicated storage per crafting job

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

Closes #112

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

3 existing lines in 3 files now uncovered.

755 of 3116 relevant lines covered (24.23%)

0.24 hits per line

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

0.0
/src/main/java/org/cyclops/integratedcrafting/core/network/CraftingNetwork.java
1
package org.cyclops.integratedcrafting.core.network;
2

3
import com.google.common.collect.Iterators;
4
import com.google.common.collect.Multimap;
5
import com.google.common.collect.MultimapBuilder;
6
import com.google.common.collect.Sets;
7
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
8
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
9
import it.unimi.dsi.fastutil.ints.IntListIterator;
10
import net.minecraft.world.level.Level;
11
import net.minecraftforge.server.ServerLifecycleHooks;
12
import org.cyclops.commoncapabilities.api.capability.recipehandler.IRecipeDefinition;
13
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
14
import org.cyclops.commoncapabilities.api.ingredient.storage.IIngredientComponentStorage;
15
import org.cyclops.cyclopscore.datastructure.MultitransformIterator;
16
import org.cyclops.integratedcrafting.api.crafting.*;
17
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
18
import org.cyclops.integratedcrafting.api.recipe.ICraftingJobIndexModifiable;
19
import org.cyclops.integratedcrafting.api.recipe.IRecipeIndexModifiable;
20
import org.cyclops.integratedcrafting.core.CraftingHelpers;
21
import org.cyclops.integratedcrafting.core.CraftingJobIndexDefault;
22
import org.cyclops.integratedcrafting.core.RecipeIndexDefault;
23
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetwork;
24

25
import javax.annotation.Nullable;
26
import java.util.Collection;
27
import java.util.Iterator;
28
import java.util.Set;
29
import java.util.function.Function;
30
import java.util.stream.Collectors;
31

32
/**
33
 * A crafting handler network with multiple channels.
34
 * @author rubensworks
35
 */
36
public class CraftingNetwork implements ICraftingNetwork {
×
37

38
    private final Set<ICraftingInterface> allCraftingInterfaces = Sets.newHashSet();
×
39
    private final Int2ObjectMap<Set<ICraftingInterface>> craftingInterfaces = new Int2ObjectOpenHashMap<>();
×
40

41
    private final Multimap<IRecipeDefinition, ICraftingInterface> allRecipeCraftingInterfaces = newRecipeCraftingInterfacesMap();
×
42
    private final Int2ObjectMap<Multimap<IRecipeDefinition, ICraftingInterface>> recipeCraftingInterfaces = new Int2ObjectOpenHashMap<>();
×
43

44
    private final IRecipeIndexModifiable allRecipesIndex = new RecipeIndexDefault();
×
45
    private final Int2ObjectMap<IRecipeIndexModifiable> recipeIndexes = new Int2ObjectOpenHashMap<>();
×
46

47
    private final ICraftingJobIndexModifiable allIndexedCraftingJobs = new CraftingJobIndexDefault();
×
48
    private final Int2ObjectMap<ICraftingJobIndexModifiable> indexedCraftingJobs = new Int2ObjectOpenHashMap<>();
×
49

50
    private final Int2ObjectMap<ICraftingInterface> allCraftingJobsToInterface = new Int2ObjectOpenHashMap<>();
×
51
    private final Int2ObjectMap<Int2ObjectMap<ICraftingInterface>> channeledCraftingJobsToInterface = new Int2ObjectOpenHashMap<>();
×
52

53
    private final CraftingJobDependencyGraph craftingJobDependencyGraph = new CraftingJobDependencyGraph();
×
54

55
    protected static Multimap<IRecipeDefinition, ICraftingInterface> newRecipeCraftingInterfacesMap() {
56
        return MultimapBuilder.hashKeys().treeSetValues(ICraftingInterface.createComparator()).build();
×
57
    }
58

59
    @Override
60
    public int[] getChannels() {
61
        return craftingInterfaces.keySet().toIntArray();
×
62
    }
63

64
    @Override
65
    public Set<ICraftingInterface> getCraftingInterfaces(int channel) {
66
        if (channel == IPositionedAddonsNetwork.WILDCARD_CHANNEL) {
×
67
            return allCraftingInterfaces;
×
68
        }
69
        Set<ICraftingInterface> craftingInterfaces = this.craftingInterfaces.get(channel);
×
70
        if (craftingInterfaces == null) {
×
71
            craftingInterfaces = Sets.newTreeSet(ICraftingInterface.createComparator());
×
72
            this.craftingInterfaces.put(channel, craftingInterfaces);
×
73
        }
74
        return craftingInterfaces;
×
75
    }
76

77
    @Override
78
    public Multimap<IRecipeDefinition, ICraftingInterface> getRecipeCraftingInterfaces(int channel) {
79
        if (channel == IPositionedAddonsNetwork.WILDCARD_CHANNEL) {
×
80
            return allRecipeCraftingInterfaces;
×
81
        }
82
        Multimap<IRecipeDefinition, ICraftingInterface> recipeCraftingInterfaces = this.recipeCraftingInterfaces.get(channel);
×
83
        if (recipeCraftingInterfaces == null) {
×
84
            recipeCraftingInterfaces = newRecipeCraftingInterfacesMap();
×
85
            this.recipeCraftingInterfaces.put(channel, recipeCraftingInterfaces);
×
86
        }
87
        return recipeCraftingInterfaces;
×
88
    }
89

90
    @Override
91
    public IRecipeIndexModifiable getRecipeIndex(int channel) {
92
        if (channel == IPositionedAddonsNetwork.WILDCARD_CHANNEL) {
×
93
            return allRecipesIndex;
×
94
        }
95
        IRecipeIndexModifiable recipeIndex = this.recipeIndexes.get(channel);
×
96
        if (recipeIndex == null) {
×
97
            recipeIndex = new RecipeIndexDefault();
×
98
            this.recipeIndexes.put(channel, recipeIndex);
×
99
        }
100
        return recipeIndex;
×
101
    }
102

103
    @Override
104
    public boolean addCraftingInterface(int channel, ICraftingInterface craftingInterface) {
105
        // Only process deeper indexes if the interface was not yet present
106
        if (getCraftingInterfaces(channel).add(craftingInterface)) {
×
107
            allCraftingInterfaces.add(craftingInterface);
×
108
            IRecipeIndexModifiable recipeIndex = getRecipeIndex(channel);
×
109
            Multimap<IRecipeDefinition, ICraftingInterface> recipeCraftingInterfaces = getRecipeCraftingInterfaces(channel);
×
110
            for (IRecipeDefinition recipe : craftingInterface.getRecipes()) {
×
111
                // Save the recipes in the index
112
                recipeIndex.addRecipe(recipe);
×
113
                allRecipesIndex.addRecipe(recipe);
×
114
                // Save a mapping from each of the recipes to this crafting interface
115
                recipeCraftingInterfaces.put(recipe, craftingInterface);
×
116
                allRecipeCraftingInterfaces.put(recipe, craftingInterface);
×
117
            }
×
118

119
            // Loop over the crafting jobs owned by the interface
120
            Iterator<CraftingJob> craftingJobsIt = craftingInterface.getCraftingJobs();
×
121
            while (craftingJobsIt.hasNext()) {
×
122
                CraftingJob craftingJob = craftingJobsIt.next();
×
123

124
                // Store mapping between interface and job in the network
125
                addCraftingJob(craftingJob.getChannel(), craftingJob, craftingInterface);
×
126

127
                // Add the crafting job dependencies
128
                craftingJobDependencyGraph.addCraftingJobId(craftingJob);
×
129
                IntListIterator dependencyIt = craftingJob.getDependencyCraftingJobs().iterator();
×
130
                while (dependencyIt.hasNext()) {
×
131
                    craftingJobDependencyGraph.addDependency(craftingJob, dependencyIt.nextInt());
×
132
                }
133
            }
×
134

135
            return true;
×
136
        }
137
        return false;
×
138
    }
139

140
    @Override
141
    public boolean removeCraftingInterface(int channel, ICraftingInterface craftingInterface) {
142
        // Only process deeper indexes if the interface was present
143
        if (getCraftingInterfaces(channel).remove(craftingInterface)) {
×
144
            allCraftingInterfaces.remove(craftingInterface);
×
145
            IRecipeIndexModifiable recipeIndex = getRecipeIndex(channel);
×
146
            Multimap<IRecipeDefinition, ICraftingInterface> recipeCraftingInterfaces = getRecipeCraftingInterfaces(channel);
×
147
            for (IRecipeDefinition recipe : craftingInterface.getRecipes()) {
×
148
                // Remove the mappings from each of the recipes to this crafting interface
149
                recipeCraftingInterfaces.remove(recipe, craftingInterface);
×
150
                allRecipeCraftingInterfaces.remove(recipe, craftingInterface);
×
151

152
                // If the mapping from this recipe to crafting interfaces is empty, remove the recipe from the index
153
                if (!recipeCraftingInterfaces.containsKey(recipe)) {
×
154
                    recipeIndex.removeRecipe(recipe);
×
155
                }
156
                if (!allRecipeCraftingInterfaces.containsKey(recipe)) {
×
157
                    allRecipesIndex.removeRecipe(recipe);
×
158
                }
159
            }
×
160

161
            // Try cleaning up the channel
162
            cleanupChannelIfEmpty(channel);
×
163

164
            // Loop over the crafting jobs owned by the interface
165
            Iterator<CraftingJob> craftingJobsIt = craftingInterface.getCraftingJobs();
×
166
            while (craftingJobsIt.hasNext()) {
×
167
                CraftingJob craftingJob = craftingJobsIt.next();
×
168

169
                // Remove the mapping between interface and job in the network
170
                removeCraftingJob(channel, craftingJob);
×
171

172
                // Remove the crafting job dependencies
173
                craftingJobDependencyGraph.removeCraftingJobId(craftingJob);
×
174
            }
×
175

176
            return true;
×
177
        }
178
        return false;
×
179
    }
180

181
    @Override
182
    public boolean addCraftingInterfaceRecipe(int channel, ICraftingInterface craftingInterface, IRecipeDefinition recipe) {
183
        IRecipeIndexModifiable recipeIndex = getRecipeIndex(channel);
×
184
        Multimap<IRecipeDefinition, ICraftingInterface> recipeCraftingInterfaces = getRecipeCraftingInterfaces(channel);
×
185

186
        // Save the recipes in the index
187
        recipeIndex.addRecipe(recipe);
×
188
        allRecipesIndex.addRecipe(recipe);
×
189
        // Save a mapping from each of the recipes to this crafting interface
190
        boolean changed = recipeCraftingInterfaces.put(recipe, craftingInterface);
×
191
        allRecipeCraftingInterfaces.put(recipe, craftingInterface);
×
192

193
        return changed;
×
194
    }
195

196
    @Override
197
    public boolean removeCraftingInterfaceRecipe(int channel, ICraftingInterface craftingInterface, IRecipeDefinition recipe) {
198
        IRecipeIndexModifiable recipeIndex = getRecipeIndex(channel);
×
199
        Multimap<IRecipeDefinition, ICraftingInterface> recipeCraftingInterfaces = getRecipeCraftingInterfaces(channel);
×
200

201
        // Remove the mappings from each of the recipes to this crafting interface
202
        boolean changed = recipeCraftingInterfaces.remove(recipe, craftingInterface);
×
203
        allRecipeCraftingInterfaces.remove(recipe, craftingInterface);
×
204

205
        // If the mapping from this recipe to crafting interfaces is empty, remove the recipe from the index
206
        if (!recipeCraftingInterfaces.containsKey(recipe)) {
×
207
            recipeIndex.removeRecipe(recipe);
×
208
        }
209
        if (!allRecipeCraftingInterfaces.containsKey(recipe)) {
×
210
            allRecipesIndex.removeRecipe(recipe);
×
211
        }
212

213
        return changed;
×
214
    }
215

216
    @Override
217
    public void scheduleCraftingJob(CraftingJob craftingJob, boolean allowDistribution, Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter)
218
            throws UnavailableCraftingInterfacesException, StorageExtractionException {
219
        Multimap<IRecipeDefinition, ICraftingInterface> recipeInterfaces = getRecipeCraftingInterfaces(craftingJob.getChannel());
×
220
        Collection<ICraftingInterface> craftingInterfaces = recipeInterfaces.get(craftingJob.getRecipe())
×
221
                .stream()
×
222
                .filter(ICraftingInterface::canScheduleCraftingJobs)
×
223
                .collect(Collectors.toList());
×
224

225
        if (craftingInterfaces.size() == 0) {
×
226
            throw new UnavailableCraftingInterfacesException(craftingJob);
×
227
        }
228

229
        // If our crafting job amount is larger than 1,
230
        // and we have multiple crafting interfaces available,
231
        // split our crafting job so we can distribute
232
        if (allowDistribution && craftingInterfaces.size() > 1 && craftingJob.getAmount() > 1) {
×
233
            Collection<CraftingJob> splitCraftingJobs = CraftingHelpers.splitCraftingJobs(craftingJob,
×
234
                    craftingInterfaces.size(), getCraftingJobDependencyGraph(),
×
235
                    CraftingHelpers.getGlobalCraftingJobIdentifier());
×
236
            for (CraftingJob splitCraftingJob : splitCraftingJobs) {
×
NEW
237
                scheduleCraftingJob(splitCraftingJob, false, storageGetter);
×
238
            }
×
239
            return;
×
240
        }
241

242
        // Find the crafting interface that has the least number of crafting jobs.
243
        // This will achieve parallelized jobs.
244
        int bestCraftingInterfaceJobCount = 0;
×
245
        ICraftingInterface bestCraftingInterface = null;
×
246
        for (ICraftingInterface craftingInterface : craftingInterfaces) {
×
247
            int jobCount = craftingInterface.getCraftingJobsCount();
×
248
            if (bestCraftingInterface == null || jobCount < bestCraftingInterfaceJobCount) {
×
249
                bestCraftingInterfaceJobCount = jobCount;
×
250
                bestCraftingInterface = craftingInterface;
×
251
            }
252
        }
×
253

254
        // This should not be null, but let's check to be sure.
255
        if (bestCraftingInterface != null) {
×
256
            // Extract from storage
NEW
257
            bestCraftingInterface.fillCraftingJobBufferFromStorage(craftingJob, storageGetter);
×
258

259
            // Schedule the job in the interface
260
            bestCraftingInterface.scheduleCraftingJob(craftingJob);
×
261
            addCraftingJob(craftingJob.getChannel(), craftingJob, bestCraftingInterface);
×
262

263
            // Store the starting tick in the job
264
            craftingJob.setStartTick(getCurrentTick());
×
265
        }
266
    }
×
267

268
    protected long getCurrentTick() {
269
        return ServerLifecycleHooks.getCurrentServer().getLevel(Level.OVERWORLD).getGameTime();
×
270
    }
271

272
    @Override
273
    public void onCraftingJobFinished(CraftingJob craftingJob) {
274
        removeCraftingJob(craftingJob.getChannel(), craftingJob);
×
275
        getCraftingJobDependencyGraph().onCraftingJobFinished(craftingJob);
×
276
    }
×
277

278
    @Override
279
    public boolean cancelCraftingJob(int channel, int craftingJobId) {
280
        CraftingJob craftingJob = getCraftingJob(channel, craftingJobId);
×
281
        if (craftingJob != null) {
×
282
            cancelCraftingJob(craftingJob);
×
283
            return true;
×
284
        }
285
        return false;
×
286
    }
287

288
    protected void cancelCraftingJob(CraftingJob craftingJob) {
289
        // First cancel all dependencies
290
        for (CraftingJob dependency : getCraftingJobDependencyGraph().getDependencies(craftingJob)) {
×
291
            cancelCraftingJob(dependency);
×
292
        }
×
293

294
        // Remove all job references from the interface
295
        ICraftingInterface craftingInterface = getCraftingJobInterface(craftingJob.getChannel(), craftingJob.getId());
×
296
        if (craftingInterface != null) {
×
297
            craftingInterface.cancelCraftingJob(craftingJob.getChannel(), craftingJob.getId());
×
298
        }
299

300
        // Remove all job references from the network
301
        onCraftingJobFinished(craftingJob);
×
302
    }
×
303

304
    @Override
305
    public Iterator<CraftingJob> getCraftingJobs(int channel) {
306
        return new MultitransformIterator<>(getCraftingInterfaces(channel).iterator(),
×
307
                ICraftingInterface::getCraftingJobs);
308
    }
309

310
    @Nullable
311
    @Override
312
    public CraftingJob getCraftingJob(int channel, int craftingJobId) {
313
        if (channel == IPositionedAddonsNetwork.WILDCARD_CHANNEL) {
×
314
            return allIndexedCraftingJobs.getCraftingJob(craftingJobId);
×
315
        }
316

317
        // Check for the channel directly
318
        ICraftingJobIndexModifiable index = indexedCraftingJobs.get(channel);
×
319
        if (index != null) {
×
320
            CraftingJob craftingJob = index.getCraftingJob(craftingJobId);
×
321
            if (craftingJob != null) {
×
322
                return craftingJob;
×
323
            }
324
        }
325

326
        // Check for the case the crafting job was explicitly started on the wildcard channel
327
        ICraftingJobIndexModifiable wildcardIndex = indexedCraftingJobs.get(IPositionedAddonsNetwork.WILDCARD_CHANNEL);
×
328
        if (wildcardIndex != null) {
×
329
            return wildcardIndex.getCraftingJob(craftingJobId);
×
330
        }
331

332
        return null;
×
333
    }
334

335
    protected void addCraftingJob(int channel, CraftingJob craftingJob, ICraftingInterface craftingInterface) {
336
        // Prepare crafting job index
337
        ICraftingJobIndexModifiable craftingJobIndex = indexedCraftingJobs.get(channel);
×
338
        if (craftingJobIndex == null) {
×
339
            craftingJobIndex = new CraftingJobIndexDefault();
×
340
            indexedCraftingJobs.put(channel, craftingJobIndex);
×
341
        }
342

343
        // Prepare crafting job to interface mapping
344
        Int2ObjectMap<ICraftingInterface> craftingJobsToInterface = this.channeledCraftingJobsToInterface.get(channel);
×
345
        if (craftingJobsToInterface == null) {
×
346
            craftingJobsToInterface = new Int2ObjectOpenHashMap<>();
×
347
            this.channeledCraftingJobsToInterface.put(channel, craftingJobsToInterface);
×
348
        }
349

350
        // Insert into crafting job index
351
        allIndexedCraftingJobs.addCraftingJob(craftingJob);
×
352
        craftingJobIndex.addCraftingJob(craftingJob);
×
353

354
        // Insert into crafting job to interface mapping
355
        allCraftingJobsToInterface.put(craftingJob.getId(), craftingInterface);
×
356
        craftingJobsToInterface.put(craftingJob.getId(), craftingInterface);
×
357
    }
×
358

359
    protected void removeCraftingJob(int channel, CraftingJob craftingJob) {
360
        // Prepare crafting job index
361
        ICraftingJobIndexModifiable craftingJobIndex = indexedCraftingJobs.get(channel);
×
362

363
        // Prepare crafting job to interface mapping
364
        Int2ObjectMap<ICraftingInterface> craftingJobsToInterface = this.channeledCraftingJobsToInterface.get(channel);
×
365

366
        // Remove from crafting job index
367
        allIndexedCraftingJobs.removeCraftingJob(craftingJob);
×
368
        if (craftingJobIndex != null) {
×
369
            craftingJobIndex.removeCraftingJob(craftingJob);
×
370
        }
371

372
        // Remove from crafting job to interface mapping
373
        allCraftingJobsToInterface.remove(craftingJob.getId());
×
374
        if (craftingJobsToInterface != null) {
×
375
            craftingJobsToInterface.remove(craftingJob.getId());
×
376
        }
377
    }
×
378

379
    @Override
380
    public <T, M> Iterator<CraftingJob> getCraftingJobs(int channel, IngredientComponent<T, M> ingredientComponent,
381
                                                        T instance, M matchCondition) {
382
        if (channel == IPositionedAddonsNetwork.WILDCARD_CHANNEL) {
×
383
            return allIndexedCraftingJobs.getCraftingJobs(ingredientComponent, instance, matchCondition);
×
384
        }
385

386
        // Check for the specific channel
387
        ICraftingJobIndexModifiable craftingJobIndex = indexedCraftingJobs.get(channel);
×
388
        Iterator<CraftingJob> channelIterator;
389
        if (craftingJobIndex != null) {
×
390
            channelIterator = craftingJobIndex.getCraftingJobs(ingredientComponent, instance, matchCondition);
×
391
        } else {
392
            channelIterator = Iterators.forArray();
×
393
        }
394

395
        // Check for the case the crafting job was explicitly started on the wildcard channel
396
        ICraftingJobIndexModifiable wildcardCraftingJobIndex = indexedCraftingJobs.get(IPositionedAddonsNetwork.WILDCARD_CHANNEL);
×
397
        Iterator<CraftingJob> wildcardChannelIterator;
398
        if (wildcardCraftingJobIndex != null) {
×
399
            wildcardChannelIterator = wildcardCraftingJobIndex.getCraftingJobs(ingredientComponent, instance, matchCondition);
×
400
        } else {
401
            wildcardChannelIterator = Iterators.forArray();
×
402
        }
403

404
        // Concat both iterators
405
        return Iterators.concat(channelIterator, wildcardChannelIterator);
×
406
    }
407

408
    @Override
409
    public CraftingJobDependencyGraph getCraftingJobDependencyGraph() {
410
        return craftingJobDependencyGraph;
×
411
    }
412

413
    @Nullable
414
    @Override
415
    public ICraftingInterface getCraftingJobInterface(int channel, int craftingJobId) {
416
        if (channel == IPositionedAddonsNetwork.WILDCARD_CHANNEL) {
×
417
            return allCraftingJobsToInterface.get(craftingJobId);
×
418
        }
419

420
        // Check for the channel directly
421
        Int2ObjectMap<ICraftingInterface> craftingJobsToInterface = this.channeledCraftingJobsToInterface.get(channel);
×
422
        if (craftingJobsToInterface != null) {
×
423
            ICraftingInterface craftingInterface = craftingJobsToInterface.get(craftingJobId);
×
424
            if (craftingInterface != null) {
×
425
                return craftingInterface;
×
426
            }
427
        }
428

429
        // In case the crafting job was explicitly started on the wildcard channel
430
        Int2ObjectMap<ICraftingInterface> craftingJobsToInterfaceWildcard = this.channeledCraftingJobsToInterface
×
431
                .get(IPositionedAddonsNetwork.WILDCARD_CHANNEL);
×
432
        if (craftingJobsToInterfaceWildcard != null) {
×
433
            return craftingJobsToInterfaceWildcard.get(craftingJobId);
×
434
        }
435

436
        return null;
×
437
    }
438

439
    @Override
440
    public long getRunningTicks(CraftingJob craftingJob) {
441
        return getCurrentTick() - craftingJob.getStartTick();
×
442
    }
443

444
    protected void cleanupChannelIfEmpty(int channel) {
445
        Set<ICraftingInterface> craftingInterfaces = this.craftingInterfaces.get(channel);
×
446
        if (craftingInterfaces != null && craftingInterfaces.isEmpty()) {
×
447
            this.craftingInterfaces.remove(channel);
×
448
            this.recipeIndexes.remove(channel);
×
449
            this.recipeCraftingInterfaces.remove(channel);
×
450
        }
451
    }
×
452
}
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