• 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/part/PartTypeInterfaceCraftingBase.java
1
package org.cyclops.integratedcrafting.core.part;
2

3
import com.google.common.collect.Lists;
4
import net.minecraft.core.Direction;
5
import net.minecraft.nbt.CompoundTag;
6
import net.minecraft.nbt.ListTag;
7
import net.minecraft.nbt.Tag;
8
import net.minecraft.resources.ResourceLocation;
9
import net.minecraft.world.entity.player.Player;
10
import net.minecraft.world.item.ItemStack;
11
import net.minecraftforge.common.capabilities.Capability;
12
import net.minecraftforge.common.util.LazyOptional;
13
import org.cyclops.commoncapabilities.api.ingredient.IIngredientMatcher;
14
import org.cyclops.commoncapabilities.api.ingredient.IPrototypedIngredient;
15
import org.cyclops.commoncapabilities.api.ingredient.IngredientComponent;
16
import org.cyclops.commoncapabilities.api.ingredient.IngredientInstanceWrapper;
17
import org.cyclops.commoncapabilities.api.ingredient.storage.IIngredientComponentStorage;
18
import org.cyclops.integratedcrafting.GeneralConfig;
19
import org.cyclops.integratedcrafting.api.crafting.*;
20
import org.cyclops.integratedcrafting.api.network.ICraftingNetwork;
21
import org.cyclops.integratedcrafting.capability.network.CraftingInterfaceConfig;
22
import org.cyclops.integratedcrafting.capability.network.CraftingNetworkConfig;
23
import org.cyclops.integratedcrafting.core.CraftingHelpers;
24
import org.cyclops.integratedcrafting.core.CraftingJobHandler;
25
import org.cyclops.integratedcrafting.core.CraftingProcessOverrides;
26
import org.cyclops.integratedcrafting.ingredient.storage.IngredientComponentStorageSlottedInsertProxy;
27
import org.cyclops.integrateddynamics.api.evaluate.variable.ValueDeseralizationContext;
28
import org.cyclops.integrateddynamics.api.network.INetwork;
29
import org.cyclops.integrateddynamics.api.network.INetworkIngredientsChannel;
30
import org.cyclops.integrateddynamics.api.network.IPartNetwork;
31
import org.cyclops.integrateddynamics.api.network.IPositionedAddonsNetworkIngredients;
32
import org.cyclops.integrateddynamics.api.part.PartPos;
33
import org.cyclops.integrateddynamics.api.part.PartTarget;
34
import org.cyclops.integrateddynamics.api.part.PrioritizedPartPos;
35
import org.cyclops.integrateddynamics.capability.network.PositionedAddonsNetworkIngredientsHandlerConfig;
36
import org.cyclops.integrateddynamics.core.helper.NetworkHelpers;
37
import org.cyclops.integrateddynamics.core.part.PartStateBase;
38

39
import javax.annotation.Nullable;
40
import java.util.Iterator;
41
import java.util.List;
42
import java.util.ListIterator;
43
import java.util.Map;
44
import java.util.function.Function;
45

46
/**
47
 * Base logic for parts that do crafting interfacing.
48
 * @author rubensworks
49
 */
50
public abstract class PartTypeInterfaceCraftingBase<P extends PartTypeInterfaceCraftingBase<P, S>, S extends PartTypeInterfaceCraftingBase.State<P, S>> extends PartTypeCraftingBase<P, S> {
51

52
    public PartTypeInterfaceCraftingBase(String name) {
53
        super(name);
×
54
    }
×
55

56
    @Override
57
    public void afterNetworkReAlive(INetwork network, IPartNetwork partNetwork, PartTarget target, S state) {
58
        super.afterNetworkReAlive(network, partNetwork, target, state);
×
59
        addTargetToNetwork(network, target, state, true);
×
60
    }
×
61

62
    @Override
63
    public void onNetworkRemoval(INetwork network, IPartNetwork partNetwork, PartTarget target, S state) {
64
        super.onNetworkRemoval(network, partNetwork, target, state);
×
65
        removeTargetFromNetwork(network, target.getTarget(), state);
×
66
    }
×
67

68
    @Override
69
    public void onNetworkAddition(INetwork network, IPartNetwork partNetwork, PartTarget target, S state) {
70
        super.onNetworkAddition(network, partNetwork, target, state);
×
71
        addTargetToNetwork(network, target, state, true);
×
72
    }
×
73

74
    @Override
75
    public void setPriorityAndChannel(INetwork network, IPartNetwork partNetwork, PartTarget target, S state, int priority, int channel) {
76
        // We need to do this because the crafting network is not automagically aware of the priority changes,
77
        // so we have to re-add it.
78
        removeTargetFromNetwork(network, target.getTarget(), state);
×
79
        super.setPriorityAndChannel(network, partNetwork, target, state, priority, channel);
×
80
        addTargetToNetwork(network, target, state, false);
×
81
    }
×
82

83
    protected Capability<ICraftingNetwork> getNetworkCapability() {
84
        return CraftingNetworkConfig.CAPABILITY;
×
85
    }
86

87
    protected void addTargetToNetwork(INetwork network, PartTarget pos, S state, boolean initialize) {
88
        network.getCapability(getNetworkCapability())
×
89
                .ifPresent(craftingNetwork -> {
×
90
                    int channelCrafting = state.getChannelCrafting();
×
91
                    state.setTarget(pos);
×
92
                    state.setNetworks(network, craftingNetwork, NetworkHelpers.getPartNetworkChecked(network), channelCrafting, ValueDeseralizationContext.of(pos.getCenter().getPos().getLevel(true)), initialize);
×
93
                    state.setShouldAddToCraftingNetwork(true);
×
94
                });
×
95
    }
×
96

97
    protected void removeTargetFromNetwork(INetwork network, PartPos pos, S state) {
98
        ICraftingNetwork craftingNetwork = state.getCraftingNetwork();
×
99
        if (craftingNetwork != null) {
×
100
            network.getCapability(getNetworkCapability())
×
101
                    .ifPresent(n -> n.removeCraftingInterface(state.getChannelCrafting(), state));
×
102
        }
103
        state.setNetworks(null, null, null, -1, null, false);
×
104
        state.setTarget(null);
×
105
    }
×
106

107
    @Override
108
    public boolean isUpdate(S state) {
109
        return true;
×
110
    }
111

112
    @Override
113
    public int getMinimumUpdateInterval(S state) {
114
        return state.getDefaultUpdateInterval();
×
115
    }
116

117
    @Nullable
118
    protected static <T, M> IngredientInstanceWrapper<T, M> insertIntoNetwork(IngredientInstanceWrapper<T, M> wrapper,
119
                                                                              INetwork network, int channel) {
120
        IPositionedAddonsNetworkIngredients<T, M> storageNetwork = wrapper.getComponent()
×
121
                .getCapability(PositionedAddonsNetworkIngredientsHandlerConfig.CAPABILITY)
×
122
                .map(n -> (IPositionedAddonsNetworkIngredients<T, M>) n.getStorage(network).orElse(null))
×
123
                .orElse(null);
×
124
        if (storageNetwork != null) {
×
125
            IIngredientComponentStorage<T, M> storage = storageNetwork.getChannel(channel);
×
126
            T remaining = storage.insert(wrapper.getInstance(), false);
×
127
            if (wrapper.getComponent().getMatcher().isEmpty(remaining)) {
×
128
                return null;
×
129
            } else {
130
                return new IngredientInstanceWrapper<>(wrapper.getComponent(), remaining);
×
131
            }
132
        }
133
        return wrapper;
×
134
    }
135

136
    @Override
137
    public void update(INetwork network, IPartNetwork partNetwork, PartTarget target, S state) {
138
        super.update(network, partNetwork, target, state);
×
139

140
        // Init network data in part state if it has not been done yet.
141
        // This can occur when the part chunk is being reloaded.
142
        if (state.getCraftingNetwork() == null) {
×
143
            addTargetToNetwork(network, target, state, false);
×
144
        }
145

146
        int channel = state.getChannelCrafting();
×
147

148
        // Update the network data in the part state
149
        if (state.shouldAddToCraftingNetwork()) {
×
150
            ICraftingNetwork craftingNetwork = network.getCapability(getNetworkCapability()).orElse(null);
×
151
            craftingNetwork.addCraftingInterface(channel, state);
×
152
            state.setShouldAddToCraftingNetwork(false);
×
153
        }
154

155
        // Push any pending output ingredients into the network
156
        state.flushInventoryOutputBuffer(network);
×
157

158
        // Block job ticking if there still are outputs in our crafting result buffer.
159
        if (state.getInventoryOutputBuffer().isEmpty()) {
×
160
            // Tick the job handler
161
            PartPos targetPos = state.getTarget().getTarget();
×
162
            state.getCraftingJobHandler().update(network, channel, targetPos);
×
163
        }
164
    }
×
165

166
    @Override
167
    public void addDrops(PartTarget target, S state, List<ItemStack> itemStacks, boolean dropMainElement, boolean saveState) {
168
        // Drop any remaining output ingredients (only items)
169
        for (IngredientInstanceWrapper<?, ?> ingredientInstanceWrapper : state.getInventoryOutputBuffer()) {
×
170
            if (ingredientInstanceWrapper.getComponent() == IngredientComponent.ITEMSTACK) {
×
171
                itemStacks.add((ItemStack) ingredientInstanceWrapper.getInstance());
×
172
            }
173
        }
×
174
        state.getInventoryOutputBuffer().clear();
×
175

176
        super.addDrops(target, state, itemStacks, dropMainElement, saveState);
×
177
    }
×
178

179
    public static abstract class State<P extends PartTypeInterfaceCraftingBase<P, S>, S extends PartTypeInterfaceCraftingBase.State<P, S>>
180
            extends PartStateBase<P> implements ICraftingInterface, ICraftingResultsSink {
181

182
        private final CraftingJobHandler craftingJobHandler;
183
        private final List<IngredientInstanceWrapper<?, ?>> inventoryOutputBuffer;
184

185
        private int channelCrafting = 0;
×
186
        private PartTarget target = null;
×
187
        protected INetwork network = null;
×
188
        protected IPartNetwork partNetwork = null;
×
189
        protected ICraftingNetwork craftingNetwork = null;
×
190
        private int channel = -1;
×
191
        protected ValueDeseralizationContext valueDeseralizationContext;
192
        private boolean shouldAddToCraftingNetwork = false;
×
193
        protected Player lastPlayer;
194

195
        public State() {
×
196
            this.craftingJobHandler = new CraftingJobHandler(1, true,
×
197
                    CraftingProcessOverrides.REGISTRY.getCraftingProcessOverrides(), this);
×
198
            this.inventoryOutputBuffer = Lists.newArrayList();
×
199
        }
×
200

201
        @Override
202
        public void writeToNBT(CompoundTag tag) {
203
            super.writeToNBT(tag);
×
204

205
            ListTag instanceTags = new ListTag();
×
206
            for (IngredientInstanceWrapper instanceWrapper : inventoryOutputBuffer) {
×
207
                CompoundTag instanceTag = new CompoundTag();
×
208
                instanceTag.putString("component", IngredientComponent.REGISTRY.getKey(instanceWrapper.getComponent()).toString());
×
209
                instanceTag.put("instance", instanceWrapper.getComponent().getSerializer().serializeInstance(instanceWrapper.getInstance()));
×
210
                instanceTags.add(instanceTag);
×
211
            }
×
212
            tag.put("inventoryOutputBuffer", instanceTags);
×
213

214
            this.craftingJobHandler.writeToNBT(tag);
×
215
            tag.putInt("channelCrafting", channelCrafting);
×
216
        }
×
217

218
        @Override
219
        public void readFromNBT(ValueDeseralizationContext valueDeseralizationContext, CompoundTag tag) {
220
            super.readFromNBT(valueDeseralizationContext, tag);
×
221

222
            this.inventoryOutputBuffer.clear();
×
223
            for (Tag instanceTagRaw : tag.getList("inventoryOutputBuffer", Tag.TAG_COMPOUND)) {
×
224
                CompoundTag instanceTag = (CompoundTag) instanceTagRaw;
×
225
                String componentName = instanceTag.getString("component");
×
226
                IngredientComponent<?, ?> component = IngredientComponent.REGISTRY.getValue(new ResourceLocation(componentName));
×
227
                this.inventoryOutputBuffer.add(new IngredientInstanceWrapper(component,
×
228
                        component.getSerializer().deserializeInstance(instanceTag.get("instance"))));
×
229
            }
×
230

231
            this.craftingJobHandler.readFromNBT(tag);
×
232
            this.channelCrafting = tag.getInt("channelCrafting");
×
233
        }
×
234

235
        @Override
236
        protected int getDefaultUpdateInterval() {
237
            return GeneralConfig.minCraftingInterfaceUpdateFreq;
×
238
        }
239

240
        public void setChannelCrafting(int channelCrafting) {
241
            if (this.channelCrafting != channelCrafting) {
×
242
                // Unregister from the network
243
                if (craftingNetwork != null) {
×
244
                    craftingNetwork.removeCraftingInterface(this.channelCrafting, this);
×
245
                }
246

247
                // Update the channel
248
                this.channelCrafting = channelCrafting;
×
249

250
                // Re-register to the network
251
                if (craftingNetwork != null) {
×
252
                    craftingNetwork.addCraftingInterface(this.channelCrafting, this);
×
253
                }
254

255
                sendUpdate();
×
256
            }
257
        }
×
258

259
        public int getChannelCrafting() {
260
            return channelCrafting;
×
261
        }
262

263
        public void setTarget(PartTarget target) {
264
            this.target = target;
×
265
        }
×
266

267
        public PartTarget getTarget() {
268
            return target;
×
269
        }
270

271
        public void setNetworks(@Nullable INetwork network, @Nullable ICraftingNetwork craftingNetwork,
272
                                @Nullable IPartNetwork partNetwork, int channel,
273
                                @Nullable ValueDeseralizationContext valueDeseralizationContext,
274
                                boolean initialize) {
275
            this.network = network;
×
276
            this.craftingNetwork = craftingNetwork;
×
277
            this.partNetwork = partNetwork;
×
278
            this.channel = channel;
×
279
            this.valueDeseralizationContext = valueDeseralizationContext;
×
280
            reloadRecipes(initialize);
×
281
            if (network != null) {
×
282
                this.getCraftingJobHandler().reRegisterObservers(network);
×
283
            }
284
        }
×
285

286
        public void reloadRecipes(boolean initialize) {
287
            // Do nothing
288
        }
×
289

290
        public void setLastPlayer(Player lastPlayer) {
291
            this.lastPlayer = lastPlayer;
×
292
        }
×
293

294
        public ICraftingNetwork getCraftingNetwork() {
295
            return craftingNetwork;
×
296
        }
297

298
        @Override
299
        public int getChannel() {
300
            return channel;
×
301
        }
302

303
        @Override
304
        public boolean canScheduleCraftingJobs() {
305
            return getCraftingJobHandler().canScheduleCraftingJobs();
×
306
        }
307

308
        @Override
309
        public void scheduleCraftingJob(CraftingJob craftingJob) {
310
            getCraftingJobHandler().scheduleCraftingJob(craftingJob);
×
311
        }
×
312

313
        @Override
314
        public void fillCraftingJobBufferFromStorage(CraftingJob craftingJob, Function<IngredientComponent<?, ?>, IIngredientComponentStorage> storageGetter) throws StorageExtractionException {
NEW
315
            getCraftingJobHandler().fillCraftingJobBufferFromStorage(craftingJob, storageGetter);
×
NEW
316
        }
×
317

318
        @Override
319
        public int getCraftingJobsCount() {
320
            return this.craftingJobHandler.getAllCraftingJobs().size();
×
321
        }
322

323
        @Override
324
        public Iterator<CraftingJob> getCraftingJobs() {
325
            return this.craftingJobHandler.getAllCraftingJobs().values().iterator();
×
326
        }
327

328
        @Override
329
        public List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>> getPendingCraftingJobOutputs(int craftingJobId) {
330
            List<Map<IngredientComponent<?, ?>, List<IPrototypedIngredient<?, ?>>>> pending = this.craftingJobHandler.getProcessingCraftingJobsPendingIngredients().get(craftingJobId);
×
331
            if (pending == null) {
×
332
                pending = Lists.newArrayList();
×
333
            }
334
            return pending;
×
335
        }
336

337
        @Override
338
        public CraftingJobStatus getCraftingJobStatus(ICraftingNetwork network, int channel, int craftingJobId) {
339
            return craftingJobHandler.getCraftingJobStatus(network, channel, craftingJobId);
×
340
        }
341

342
        @Override
343
        public void cancelCraftingJob(int channel, int craftingJobId) {
344
            craftingJobHandler.markCraftingJobFinished(craftingJobId);
×
345
        }
×
346

347
        @Override
348
        public PrioritizedPartPos getPosition() {
349
            return PrioritizedPartPos.of(getTarget().getCenter(), getPriority());
×
350
        }
351

352
        public CraftingJobHandler getCraftingJobHandler() {
353
            return craftingJobHandler;
×
354
        }
355

356
        public boolean shouldAddToCraftingNetwork() {
357
            return shouldAddToCraftingNetwork;
×
358
        }
359

360
        public void setShouldAddToCraftingNetwork(boolean shouldAddToCraftingNetwork) {
361
            this.shouldAddToCraftingNetwork = shouldAddToCraftingNetwork;
×
362
        }
×
363

364
        public List<IngredientInstanceWrapper<?, ?>> getInventoryOutputBuffer() {
365
            return inventoryOutputBuffer;
×
366
        }
367

368
        @Override
369
        public <T> LazyOptional<T> getCapability(Capability<T> capability, INetwork network, IPartNetwork partNetwork, PartTarget target) {
370
            if (capability == CraftingInterfaceConfig.CAPABILITY) {
×
371
                return LazyOptional.of(() -> this).cast();
×
372
            }
373

374
            // Expose the whole storage
375
            if (this.network != null) {
×
376
                IngredientComponent<?, ?> ingredientComponent = IngredientComponent.getIngredientComponentForStorageCapability(capability);
×
377
                if (ingredientComponent != null) {
×
378
                    T cap = wrapStorageCapability(capability, ingredientComponent);
×
379
                    if (cap != null) {
×
380
                        return LazyOptional.of(() -> cap);
×
381
                    }
382
                }
383
            }
384

385
            return super.getCapability(capability, network, partNetwork, target);
×
386
        }
387

388
        protected <C, T, M> C wrapStorageCapability(Capability<C> capability, IngredientComponent<T, M> ingredientComponent) {
389
            IIngredientComponentStorage<T, M> storage = CraftingHelpers.getNetworkStorage(this.network, this.channelCrafting,
×
390
                    ingredientComponent, false);
391

392
            // Don't allow extraction, only insertion
393
            storage = new IngredientComponentStorageSlottedInsertProxy<>(storage);
×
394

395
            return ingredientComponent.getStorageWrapperHandler(capability).wrapStorage(storage);
×
396
        }
397

398
        @Override
399
        public <T, M> void addResult(IngredientComponent<T, M> ingredientComponent, T instance) {
400
            this.getInventoryOutputBuffer().add(new IngredientInstanceWrapper<>(ingredientComponent, instance));
×
401

402
            // Try to flush buffer immediately
403
            if (this.network != null) {
×
404
                this.flushInventoryOutputBuffer(this.network);
×
405
            }
406
        }
×
407

408
        public void setIngredientComponentTargetSideOverride(IngredientComponent<?, ?> ingredientComponent, Direction side) {
409
            if (getTarget().getTarget().getSide() == side) {
×
410
                craftingJobHandler.setIngredientComponentTarget(ingredientComponent, null);
×
411
            } else {
412
                craftingJobHandler.setIngredientComponentTarget(ingredientComponent, side);
×
413
            }
414
            sendUpdate();
×
415
        }
×
416

417
        public Direction getIngredientComponentTargetSideOverride(IngredientComponent<?, ?> ingredientComponent) {
418
            Direction side = craftingJobHandler.getIngredientComponentTarget(ingredientComponent);
×
419
            if (side == null) {
×
420
                side = getTarget().getTarget().getSide();
×
421
            }
422
            return side;
×
423
        }
424

425
        public void flushInventoryOutputBuffer(INetwork network) {
426
            // Try to insert each ingredient in the buffer into the network.
427
            boolean changed = false;
×
428
            ListIterator<IngredientInstanceWrapper<?, ?>> outputBufferIt = this.getInventoryOutputBuffer().listIterator();
×
429
            while (outputBufferIt.hasNext()) {
×
430
                IngredientInstanceWrapper<?, ?> oldWrapper = outputBufferIt.next();
×
431

432
                // Force observation before insertion (see #98 on why this is necessary)
433
                this.forceObservationOnInsertable(oldWrapper);
×
434

435
                IngredientInstanceWrapper<?, ?> newWrapper = insertIntoNetwork(oldWrapper,
×
436
                        network, this.getChannelCrafting());
×
437
                if (newWrapper != oldWrapper) {
×
438
                    changed = true;
×
439
                }
440
                if (newWrapper == null) {
×
441
                    outputBufferIt.remove();
×
442
                } else {
443
                    outputBufferIt.set(newWrapper);
×
444
                }
445
            }
×
446

447
            // If at least one ingredient was inserted, force a sync observer update in the network.
448
            if (changed) {
×
449
                CraftingHelpers.beforeCalculateCraftingJobs(network, getChannelCrafting());
×
450
            }
451
        }
×
452

453
        /**
454
         * Iterate over all positions that *could* accept the given instance,
455
         * and force an observation over them.
456
         *
457
         * This is necessary to ensure that we have the latest state indexed right before insertion.
458
         * This allows us to force another observation right after the insertion,
459
         * which will guarantee that we will track the expected diff events as result.
460
         *
461
         * @param oldWrapper The ingredient to attempt to insert (simulated).
462
         * @param <T> Ingredient type.
463
         * @param <M> Match flags.
464
         */
465
        protected <T, M> void forceObservationOnInsertable(IngredientInstanceWrapper<T, M> oldWrapper) {
466
            IIngredientMatcher<T, M> matcher = oldWrapper.getComponent().getMatcher();
×
467
            IPositionedAddonsNetworkIngredients<T, M> ingredientsNetwork = CraftingHelpers.getIngredientsNetwork(network, oldWrapper.getComponent()).orElse(null);
×
468
            if (ingredientsNetwork != null) {
×
469
                boolean marked = false;
×
470
                INetworkIngredientsChannel<?, ?> ingredientsNetworkChannel = ingredientsNetwork.getChannelInternal(this.getChannelCrafting());
×
471
                T instance = oldWrapper.getInstance();
×
472
                for (PartPos position : ingredientsNetworkChannel.findNonFullPositions()) {
×
473
                    T instanceOut = ingredientsNetwork.getPositionedStorage(position).insert(instance, true);
×
474
                    if (!matcher.matchesExactly(instanceOut, instance)) {
×
475
                        marked = true;
×
476
                        instance = instanceOut;
×
477
                        ingredientsNetwork.scheduleObservationForced(this.getChannelCrafting(), position);
×
478
                        if (matcher.isEmpty(instance)) {
×
479
                            break;
×
480
                        }
481
                    }
482
                }
×
483

484
                if (marked || ingredientsNetwork.isObservationForcedPending(channel)) {
×
485
                    ingredientsNetwork.runObserverSync();
×
486
                }
487
            }
488
        }
×
489

490
    }
491

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