Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #6892: Send processing pattern inputs in the encoded sparse order #7007

Merged
merged 1 commit into from
Apr 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions src/main/java/appeng/api/crafting/IPatternDetails.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.level.Level;

import appeng.api.implementations.blockentities.ICraftingMachine;
import appeng.api.stacks.AEItemKey;
import appeng.api.stacks.AEKey;
import appeng.api.stacks.GenericStack;
import appeng.api.stacks.KeyCounter;

/**
* Information about a pattern for use by the autocrafting system.
Expand Down Expand Up @@ -63,6 +65,22 @@ default GenericStack getPrimaryOutput() {
*/
GenericStack[] getOutputs();

/**
* Gives the pattern a chance to reorder its inputs for pushing to external inventories (i.e. NOT to
* {@link ICraftingMachine}s).
*
* @param inputHolder For each {@link IInput}, the relevant items. The ownership is given to the pattern, do
* whatever with the key counters as long as all of their contents end up in the input sink.
* @param inputSink Where to push the inputs to.
*/
default void pushInputsToExternalInventory(KeyCounter[] inputHolder, PatternInputSink inputSink) {
for (var inputList : inputHolder) {
for (var input : inputList) {
inputSink.pushInput(input.getKey(), input.getLongValue());
}
}
}

interface IInput {
/**
* A list of possible inputs for this pattern: the first input is the primary input, others are just substitutes
Expand Down Expand Up @@ -90,4 +108,8 @@ interface IInput {
@Nullable
AEKey getRemainingKey(AEKey template);
}

interface PatternInputSink {
void pushInput(AEKey key, long amount);
}
}
34 changes: 34 additions & 0 deletions src/main/java/appeng/crafting/pattern/AEProcessingPattern.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import appeng.api.stacks.AEItemKey;
import appeng.api.stacks.AEKey;
import appeng.api.stacks.GenericStack;
import appeng.api.stacks.KeyCounter;

public class AEProcessingPattern implements IPatternDetails {
public static final int MAX_INPUT_SLOTS = 9 * 9;
Expand Down Expand Up @@ -87,6 +88,39 @@ public GenericStack[] getSparseOutputs() {
return sparseOutputs;
}

@Override
public void pushInputsToExternalInventory(KeyCounter[] inputHolder, PatternInputSink inputSink) {
if (sparseInputs.length == inputs.length) {
// No compression -> no need to reorder
IPatternDetails.super.pushInputsToExternalInventory(inputHolder, inputSink);
return;
}

var allInputs = new KeyCounter();
for (var counter : inputHolder) {
allInputs.addAll(counter);
}

// Push according to sparse input order
for (var sparseInput : sparseInputs) {
if (sparseInput == null) {
continue;
}

var key = sparseInput.what();
var amount = sparseInput.amount();
long available = allInputs.get(key);

if (available < amount) {
throw new RuntimeException("Expected at least %d of %s when pushing pattern, but only %d available"
.formatted(amount, key, available));
}

inputSink.pushInput(key, amount);
allInputs.remove(key, amount);
}
}

private static class Input implements IInput {
private final GenericStack[] template;
private final long multiplier;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,16 +339,12 @@ record PushTarget(Direction direction, PatternProviderTarget target) {
}

if (this.adapterAcceptsAll(adapter, inputHolder)) {
for (var inputList : inputHolder) {
for (var input : inputList) {
var what = input.getKey();
long amount = input.getLongValue();
var inserted = adapter.insert(what, amount, Actionable.MODULATE);
if (inserted < amount) {
this.addToSendList(what, amount - inserted);
}
patternDetails.pushInputsToExternalInventory(inputHolder, (what, amount) -> {
var inserted = adapter.insert(what, amount, Actionable.MODULATE);
if (inserted < amount) {
this.addToSendList(what, amount - inserted);
}
}
});
onPushPatternSuccess(patternDetails);
this.sendDirection = direction;
this.sendStacksOut();
Expand Down
68 changes: 68 additions & 0 deletions src/main/java/appeng/server/testplots/AutoCraftingTestPlots.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import appeng.api.stacks.GenericStack;
import appeng.blockentity.crafting.PatternProviderBlockEntity;
import appeng.blockentity.misc.InscriberBlockEntity;
import appeng.blockentity.storage.SkyChestBlockEntity;
import appeng.core.definitions.AEBlocks;
import appeng.core.definitions.AEItems;
import appeng.core.definitions.AEParts;
Expand All @@ -31,6 +32,7 @@
import appeng.menu.AutoCraftingMenu;
import appeng.server.testworld.PlotBuilder;
import appeng.server.testworld.TestCraftingJob;
import appeng.util.inv.AppEngInternalInventory;

public final class AutoCraftingTestPlots {
private AutoCraftingTestPlots() {
Expand Down Expand Up @@ -316,4 +318,70 @@ public static void patternProviderFacesRoundRobin(PlotBuilder plot) {
.thenSucceed();
});
}

/**
* Tests that identical processing pattern inputs get "unstacked" when they are pushed. This is validated using a
* sky stone chest with a changed slot limit of 1.
*/
@TestPlot("processing_pattern_inputs_unstacking")
public static void processingPatternInputsUnstacking(PlotBuilder plot) {
var chestPos = new BlockPos(1, 0, -3);

craftingCube(plot);

plot.cable("0 0 -2");
plot.blockEntity("0 0 -3", AEBlocks.PATTERN_PROVIDER, provider -> {
var pattern = PatternDetailsHelper.encodeProcessingPattern(
new GenericStack[] {
GenericStack.fromItemStack(new ItemStack(Items.STONE)),
GenericStack.fromItemStack(new ItemStack(Items.STONE)),
GenericStack.fromItemStack(new ItemStack(Items.DIAMOND)),
GenericStack.fromItemStack(new ItemStack(Items.STONE)),
},
new GenericStack[] {
GenericStack.fromItemStack(AEItems.CERTUS_QUARTZ_DUST.stack())
});
provider.getLogic().getPatternInv().addItems(pattern);
});
plot.blockEntity(chestPos, AEBlocks.SKY_STONE_CHEST, skyChest -> {
var inv = (AppEngInternalInventory) skyChest.getInternalInventory();
for (int i = 0; i < inv.size(); i++) {
inv.setMaxStackSize(i, 1);
}
});
plot.cable("0 0 -4");
{
var db = plot.drive(new BlockPos(0, 0, -5));
db.addCreativeCell().add(Items.STONE).add(Items.DIAMOND);
db.addItemCell64k();
}
plot.cable("0 1 -5").part(Direction.NORTH, AEParts.CRAFTING_TERMINAL);
plot.creativeEnergyCell("0 -1 -5");

// Check item distribution in chest
plot.test(helper -> {
var craftingJob = new TestCraftingJob(helper, BlockPos.ZERO, AEItemKey.of(AEItems.CERTUS_QUARTZ_DUST), 1);
helper.startSequence()
.thenWaitUntil(craftingJob::tickUntilStarted)
.thenIdle(1) // give time to push out job
.thenExecute(() -> {
var chest = (SkyChestBlockEntity) helper.getBlockEntity(chestPos);
var inv = chest.getInternalInventory();
for (int i = 0; i < 4; ++i) {
helper.check(inv.getStackInSlot(i).getCount() == 1,
"Chest should have 1 item in slot " + i, chestPos);
}

helper.check(inv.getStackInSlot(0).is(Items.STONE), "Chest should have stone in slot 0",
chestPos);
helper.check(inv.getStackInSlot(1).is(Items.STONE), "Chest should have stone in slot 1",
chestPos);
helper.check(inv.getStackInSlot(2).is(Items.DIAMOND), "Chest should have diamond in slot 2",
chestPos);
helper.check(inv.getStackInSlot(3).is(Items.STONE), "Chest should have stone in slot 3",
chestPos);
})
.thenSucceed();
});
}
}