Lite crafting mode (#1438)

Co-authored-by: Martin Robertz <[email protected]>
Co-authored-by: UltraProdigy <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-07-19 04:50:26 +00:00
1 parent c7b686bf58
commit 689b53ec4a
14 files changed
+562 -58

No files matched your search

@@ -81,4 +81,12 @@ public interface ICraftingJob<StackType extends IAEStack<StackType>> {
default MECraftingInventory getStorageAtBeginning() {
return new MECraftingInventory();
}
default boolean supportsOptimization() {
return false;
}
default String getErrorMessage() {
return "";
}
}
@@ -25,6 +25,7 @@ import appeng.api.storage.StorageName;
import appeng.api.storage.data.IAEStack;
import appeng.client.gui.slots.VirtualMESlotSingle;
import appeng.client.gui.widgets.GuiImgButton;
import appeng.client.gui.widgets.GuiToggleButton;
import appeng.client.gui.widgets.MEGuiTextField;
import appeng.container.implementations.ContainerCraftAmount;
import appeng.container.interfaces.IVirtualSlotHolder;
@@ -49,6 +50,7 @@ public class GuiCraftAmount extends GuiAmount implements IVirtualSlotHolder {
private static final int TOP_FIELDS_Y_OFFSET = 30;
private GuiImgButton craftingMode;
private GuiToggleButton liteMode;
private GuiImgButton controlButtonValues;
private GuiButton applyControlValuesButton;
private final MEGuiTextField[] controlValueFields = new MEGuiTextField[4];
@@ -79,6 +81,15 @@ public class GuiCraftAmount extends GuiAmount implements IVirtualSlotHolder {
this.guiTop + 84,
Settings.ACTIONS,
ActionItems.CONTROL_BUTTON_VALUES_OFF));
this.buttonList.add(
this.liteMode = new GuiToggleButton(
this.guiLeft - 18,
this.guiTop + 66,
178,
194,
GuiText.CraftingModeLite.getLocal(),
GuiText.CraftingModeLiteDesc.getLocal()));
this.liteMode.setState(AEConfig.instance.getUseLiteCraftingMode());
this.buttonList.add(
this.applyControlValuesButton = new GuiButton(
0,
@@ -155,6 +166,10 @@ public class GuiCraftAmount extends GuiAmount implements IVirtualSlotHolder {
this.toggleControlButtonPressed();
return;
}
if (btn == this.liteMode) {
this.liteMode.setState(AEConfig.instance.toggleUseLiteCraftingMode());
return;
}
if (btn == this.applyControlValuesButton) {
this.applyControlValuesAndExitEditMode();
return;
@@ -178,7 +193,8 @@ public class GuiCraftAmount extends GuiAmount implements IVirtualSlotHolder {
getAmountLong(),
isShiftKeyDown(),
isCtrlKeyDown(),
(CraftingMode) this.craftingMode.getCurrentValue()));
(CraftingMode) this.craftingMode.getCurrentValue(),
AEConfig.instance.getUseLiteCraftingMode()));
}
} catch (final NumberFormatException e) {
// nope..
@@ -227,6 +243,8 @@ public class GuiCraftAmount extends GuiAmount implements IVirtualSlotHolder {
private void setControlButtonPressed(final boolean pressed) {
this.isControlButtonPressed = pressed;
this.liteMode.visible = !pressed;
this.liteMode.enabled = !pressed;
this.controlButtonValues
.set(pressed ? ActionItems.CONTROL_BUTTON_VALUES_ON : ActionItems.CONTROL_BUTTON_VALUES_OFF);
this.controlButtonValues.visible = !pressed;
@@ -454,10 +454,11 @@ public class GuiCraftConfirm extends GuiSub implements ICraftingCPUTableHolder,
final long BytesUsed = this.ccc.getUsedBytes();
final String byteUsed = Platform.formatByteDouble(BytesUsed);
final String bannerText;
if (jobTree != null && !jobTree.getErrorMessage().isEmpty()) {
if (jobTree.getErrorMessage().equals("java.lang.ArithmeticException: long overflow")) {
final String errorMessage = this.ccc.getErrorMessage();
if (!errorMessage.isEmpty()) {
if (errorMessage.equals("java.lang.ArithmeticException: long overflow")) {
bannerText = GuiText.CraftingSizeLimitExceeded.getLocal();
} else bannerText = StatCollector.translateToLocal(jobTree.getErrorMessage());
} else bannerText = StatCollector.translateToLocal(errorMessage);
} else if (BytesUsed > 0) {
bannerText = byteUsed;
} else {
@@ -94,6 +94,9 @@ public class ContainerCraftConfirm extends ContainerSubGui implements ICraftingC
@GuiSync(10)
public String serializedItemToCraft = "";
@GuiSync(11)
public String errorMessage = "";
public ContainerCraftConfirm(final InventoryPlayer ip, final ITerminalHost te) {
super(ip, te);
this.cpuTable = new ContainerCPUTable(this, this::onCPUUpdate, false, this::cpuMatches);
@@ -174,6 +177,7 @@ public class ContainerCraftConfirm extends ContainerSubGui implements ICraftingC
this.result.populatePlan(plan);
this.setUsedBytes(this.result.getByteTotal());
this.setErrorMessage(this.result.getErrorMessage());
for (final IAEStack<?> plannedItem : plan) {
@@ -324,8 +328,7 @@ public class ContainerCraftConfirm extends ContainerSubGui implements ICraftingC
}
public void optimizePatterns() {
// only V2 supported
if (this.result instanceof CraftingJobV2 && !this.isSimulation()
if (this.result.supportsOptimization() && !this.isSimulation()
&& getGrid() != null
&& !getGrid().getMachines(TilePatternOptimizationMatrix.class).isEmpty()
&& this.getPlayerInv().player.openContainer instanceof AEBaseContainer baseContainer) {
@@ -458,4 +461,12 @@ public class ContainerCraftConfirm extends ContainerSubGui implements ICraftingC
Platform.writeStackByte(itemToCraft, serialized);
this.serializedItemToCraft = serialized.toString(StandardCharsets.ISO_8859_1);
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(final String errorMessage) {
this.errorMessage = errorMessage;
}
}
@@ -31,6 +31,7 @@ import appeng.core.AELog;
import appeng.core.sync.network.NetworkHandler;
import appeng.core.sync.packets.PacketMEInventoryUpdate;
import appeng.core.sync.packets.PacketOptimizePatterns;
import appeng.crafting.fast.CraftingJobFast;
import appeng.crafting.v2.CraftingContext;
import appeng.crafting.v2.CraftingJobV2;
import appeng.crafting.v2.resolvers.CraftableItemResolver.CraftFromPatternTask;
@@ -56,68 +57,82 @@ public class ContainerOptimizePatterns extends ContainerSubGui {
patterns.clear();
if (this.result instanceof CraftingJobV2 cj) {
// use context to reuse caches
CraftingContext context = cj.getContext();
// use context to reuse caches
CraftingContext context = null;
if (result instanceof CraftingJobV2 cj) {
context = cj.getContext();
} else if (result instanceof CraftingJobFast cj) {
context = cj.getContext();
}
// check blacklisted interfaces
ItemStackSet blacklistedPatterns = new ItemStackSet();
if (context == null) return;
var supported = AEApi.instance().registries().interfaceTerminal().getSupportedClasses();
// check blacklisted interfaces
ItemStackSet blacklistedPatterns = new ItemStackSet();
for (Class<? extends IInterfaceViewable> c : supported) {
for (IGridNode node : context.meGrid.getMachines(c)) {
IInterfaceViewable machine = (IInterfaceViewable) node.getMachine();
if (!machine.allowsPatternOptimization()) {
IInventory patternInv = machine.getPatterns();
var supported = AEApi.instance().registries().interfaceTerminal().getSupportedClasses();
for (int i = 0; i < patternInv.getSizeInventory(); i++) {
ItemStack stack = patternInv.getStackInSlot(i);
if (stack != null) blacklistedPatterns.add(stack);
}
for (Class<? extends IInterfaceViewable> c : supported) {
for (IGridNode node : context.meGrid.getMachines(c)) {
IInterfaceViewable machine = (IInterfaceViewable) node.getMachine();
if (!machine.allowsPatternOptimization()) {
IInventory patternInv = machine.getPatterns();
for (int i = 0; i < patternInv.getSizeInventory(); i++) {
ItemStack stack = patternInv.getStackInSlot(i);
if (stack != null) blacklistedPatterns.add(stack);
}
}
}
}
if (this.result instanceof CraftingJobV2 cj) {
for (CraftingTask resolvedTask : context.getResolvedTasks()) {
if (resolvedTask instanceof CraftFromPatternTask cfpt) {
if (!blacklistedPatterns.contains(cfpt.pattern.getPattern())) {
patterns.computeIfAbsent(cfpt.request.stack, i -> new Pattern()).addCraftingTask(cfpt);
patterns.computeIfAbsent(cfpt.request.stack, i -> new Pattern())
.addCraftingTask(cfpt.pattern, cfpt.getTotalCraftsDone());
}
}
}
this.patterns.entrySet().removeIf(entry -> entry.getValue().patternDetails.size() != 1);
this.patterns.entrySet().removeIf(entry -> entry.getValue().getPattern().isCraftable());
try {
final PacketMEInventoryUpdate patternsUpdate = new PacketMEInventoryUpdate((byte) 0);
for (Entry<IAEStack<?>, Pattern> entry : this.patterns.entrySet()) {
IAEStack<?> stack = entry.getKey().copy();
stack.setCountRequestableCrafts(entry.getValue().requestedCrafts);
long perCraft = entry.getValue().getCraftAmountForItem(stack);
long hash = entry.getKey().hashCode();
// max multi is 62, that's 6 bits MAX!!
long multiplier = entry.getValue().getMaxBitMultiplier()
& PacketOptimizePatterns.MULTIPLIER_BIT_MASK;
// lowest bit is sign, the rest bits are magnitude
long highbits = (Math.abs(hash) << 1) | ((hash < 0) ? 1 : 0);
// Then shift it left by 6 bits
highbits = highbits << PacketOptimizePatterns.MULTIPLIER_BITS;
stack.setStackSize(highbits | multiplier);
stack.setCountRequestable(perCraft);
patternsUpdate.appendItem(stack);
} else if (this.result instanceof CraftingJobFast<?>cj) {
cj.forEachPattern((pattern, count) -> {
if (!blacklistedPatterns.contains(pattern.getPattern())) {
patterns.computeIfAbsent(pattern.getCondensedAEOutputs()[0], i -> new Pattern())
.addCraftingTask(pattern, count);
}
});
}
this.patterns.entrySet().removeIf(entry -> entry.getValue().patternDetails.size() != 1);
this.patterns.entrySet().removeIf(entry -> entry.getValue().getPattern().isCraftable());
for (final Object player : this.crafters) {
if (player instanceof EntityPlayerMP playerMP) {
NetworkHandler.instance.sendTo(patternsUpdate, playerMP);
}
}
try {
final PacketMEInventoryUpdate patternsUpdate = new PacketMEInventoryUpdate((byte) 0);
} catch (IOException e) {
//
for (Entry<IAEStack<?>, Pattern> entry : this.patterns.entrySet()) {
IAEStack<?> stack = entry.getKey().copy();
stack.setCountRequestableCrafts(entry.getValue().requestedCrafts);
long perCraft = entry.getValue().getCraftAmountForItem(stack);
long hash = entry.getKey().hashCode();
// max multi is 62, that's 6 bits MAX!!
long multiplier = entry.getValue().getMaxBitMultiplier() & PacketOptimizePatterns.MULTIPLIER_BIT_MASK;
// lowest bit is sign, the rest bits are magnitude
long highbits = (Math.abs(hash) << 1) | ((hash < 0) ? 1 : 0);
// Then shift it left by 6 bits
highbits = highbits << PacketOptimizePatterns.MULTIPLIER_BITS;
stack.setStackSize(highbits | multiplier);
stack.setCountRequestable(perCraft);
patternsUpdate.appendItem(stack);
}
for (final Object player : this.crafters) {
if (player instanceof EntityPlayerMP playerMP) {
NetworkHandler.instance.sendTo(patternsUpdate, playerMP);
}
}
} catch (IOException e) {
//
}
}
@@ -205,9 +220,9 @@ public class ContainerOptimizePatterns extends ContainerSubGui {
// private long requestedAmount;
private long requestedCrafts = 0;
private void addCraftingTask(CraftFromPatternTask task) {
patternDetails.add(task.pattern);
requestedCrafts += task.getTotalCraftsDone();
private void addCraftingTask(ICraftingPatternDetails pattern, long totalCrafts) {
patternDetails.add(pattern);
requestedCrafts += totalCrafts;
}
private long getCraftAmountForItem(IAEStack<?> stack) {
+20
View File
@@ -97,6 +97,7 @@ public final class AEConfig extends Configuration implements IConfigurableObject
public boolean highlightWhenSomethingStuckInInterface = true;
public boolean highlightPatternTypeMismatchInGUI = true;
public int screenColor = 0xFFFFFF;
private boolean useLiteCraftingMode = false;
/** Max rows for crafting pins section (1-16). Caps the cycle button options. */
public int maxCraftingPinRows = 16;
@@ -404,6 +405,8 @@ public final class AEConfig extends Configuration implements IConfigurableObject
screenColorProperty.set(this.screenColor);
this.visualiserWidthDense = (float) this.get("Client", "visualiserWidthDense", 1.0f).getDouble(1.0f);
this.visualiserWidthNormal = (float) this.get("Client", "visualiserWidthNormal", 1.0f).getDouble(1.0f);
this.useLiteCraftingMode = this.get("Client", "isLiteCraftingEnabled", this.useLiteCraftingMode)
.getBoolean(this.useLiteCraftingMode);
// Pin options (under Client category)
Property pMaxCraft = this.get("Client", "maxCraftingPinRows", this.maxCraftingPinRows);
@@ -684,6 +687,23 @@ public final class AEConfig extends Configuration implements IConfigurableObject
return this.levelByStacks[i];
}
public boolean getUseLiteCraftingMode() {
return this.useLiteCraftingMode;
}
public void setUseLiteCraftingMode(final boolean value) {
this.useLiteCraftingMode = value;
this.get("Client", "isLiteCraftingEnabled", false).set(value);
this.save();
}
public boolean toggleUseLiteCraftingMode() {
this.useLiteCraftingMode = !this.useLiteCraftingMode;
this.get("Client", "isLiteCraftingEnabled", false).set(this.useLiteCraftingMode);
this.save();
return this.useLiteCraftingMode;
}
public Enum getSetting(final String category, final Class<? extends Enum> class1, final Enum myDefault) {
final String name = class1.getSimpleName();
final Property p = this.get(category, name, myDefault.name());
@@ -194,6 +194,8 @@ public enum GuiText implements Localization {
Merge,
Bytes,
Set,
CraftingModeLite,
CraftingModeLiteDesc,
CraftingCPU,
Automatic,
@@ -40,6 +40,7 @@ public class PacketCraftRequest extends AppEngPacket {
private final boolean heldCtrl;
private final CraftingMode craftingMode;
private final boolean liteMode;
// automatic.
public PacketCraftRequest(final ByteBuf stream) {
@@ -47,18 +48,20 @@ public class PacketCraftRequest extends AppEngPacket {
this.heldCtrl = stream.readBoolean();
this.amount = stream.readLong();
this.craftingMode = CraftingMode.values()[stream.readByte()];
this.liteMode = stream.readBoolean();
}
public PacketCraftRequest(final int craftAmt, final boolean shift, final boolean control) {
this(craftAmt, shift, control, CraftingMode.STANDARD);
this(craftAmt, shift, control, CraftingMode.STANDARD, false);
}
public PacketCraftRequest(final long craftAmt, final boolean shift, final boolean control,
final CraftingMode craftingMode) {
final CraftingMode craftingMode, final boolean liteMode) {
this.amount = craftAmt;
this.heldShift = shift;
this.heldCtrl = control;
this.craftingMode = craftingMode;
this.liteMode = liteMode;
final ByteBuf data = Unpooled.buffer();
@@ -67,6 +70,7 @@ public class PacketCraftRequest extends AppEngPacket {
data.writeBoolean(control);
data.writeLong(this.amount);
data.writeByte(craftingMode.ordinal());
data.writeBoolean(liteMode);
this.configureWrite(data);
}
@@ -99,6 +103,7 @@ public class PacketCraftRequest extends AppEngPacket {
cca.getActionSrc(),
cca.getItemToCraft(),
this.craftingMode,
liteMode,
null);
} else {
futureJob = cg.beginCraftingJob(
@@ -0,0 +1,298 @@
package appeng.crafting.fast;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import net.minecraft.world.World;
import org.jetbrains.annotations.Nullable;
import appeng.api.config.Actionable;
import appeng.api.config.CraftingMode;
import appeng.api.networking.IGrid;
import appeng.api.networking.crafting.ICraftingCPU;
import appeng.api.networking.crafting.ICraftingCallback;
import appeng.api.networking.crafting.ICraftingJob;
import appeng.api.networking.crafting.ICraftingPatternDetails;
import appeng.api.networking.security.BaseActionSource;
import appeng.api.storage.data.IAEStack;
import appeng.api.storage.data.IItemList;
import appeng.crafting.CraftBranchFailure;
import appeng.crafting.MECraftingInventory;
import appeng.crafting.v2.CraftingCalculations;
import appeng.crafting.v2.CraftingContext;
import appeng.crafting.v2.CraftingRequest;
import appeng.crafting.v2.CraftingRequest.SubstitutionMode;
import appeng.hooks.TickHandler;
import appeng.me.cluster.implementations.CraftingCPUCluster;
import appeng.util.Platform;
import it.unimi.dsi.fastutil.longs.LongObjectImmutablePair;
import it.unimi.dsi.fastutil.longs.LongObjectPair;
import it.unimi.dsi.fastutil.objects.AbstractObject2LongMap;
import it.unimi.dsi.fastutil.objects.AbstractObject2ObjectMap;
import it.unimi.dsi.fastutil.objects.AbstractObjectSet;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectLongBiConsumer;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
public final class CraftingJobFast<StackType extends IAEStack<StackType>> implements ICraftingJob<StackType> {
private final CraftingContext context;
private final CraftingRequest originalRequest;
private final ICraftingCallback callback;
private final AbstractObject2LongMap<ICraftingPatternDetails> tasks = new Object2LongOpenHashMap<>();
private final AbstractObject2LongMap<IAEStack<?>> ingredients = new Object2LongOpenHashMap<>();
private final AbstractObject2LongMap<IAEStack<?>> missingIngredients = new Object2LongOpenHashMap<>();
private boolean calculated = false;
private boolean isSimulated = false;
private long byteCost = 0;
private String errorMsg = "";
public CraftingJobFast(final World world, final IGrid meGrid, final BaseActionSource actionSource,
final StackType what, final CraftingMode craftingMode, final ICraftingCallback callback) {
this.context = new CraftingContext(world, meGrid, actionSource);
this.callback = callback;
this.originalRequest = new CraftingRequest(what, SubstitutionMode.PRECISE_FRESH, true, craftingMode);
this.context.addRequest(this.originalRequest);
this.context.itemModel.ignore(what);
}
public CraftingContext getContext() {
return this.context;
}
public void forEachPattern(ObjectLongBiConsumer<ICraftingPatternDetails> consumer) {
tasks.forEach(consumer);
}
/**
* No ore dict, no substitution, currently no multi-output too. Does not respect any priority, will only use one
* pattern. This ensures no backtracking is needed.
*/
private void calculateImpl() {
SccResolver.Result result = SccResolver.compute(originalRequest.stack, stack -> {
List<ICraftingPatternDetails> patterns = context.getPrecisePatternsFor(stack);
if (patterns.isEmpty()) {
return null;
}
for (ICraftingPatternDetails pattern : patterns) {
for (var out : pattern.getCondensedAEOutputs()) {
if (out.equals(stack)) {
return new LongObjectImmutablePair<>(out.getStackSize(), pattern);
}
}
}
return null;
});
AbstractObject2ObjectMap<IAEStack<?>, LongObjectPair<ICraftingPatternDetails>> resolved = result.patterns;
AbstractObject2LongMap<IAEStack<?>> inDegree = result.inDegree;
// Now calculates the tree, by traversing in topological order
AbstractObjectSet<IAEStack<?>> traversed = new ObjectOpenHashSet<>();
Queue<IAEStack<?>> loopCandidates = new ArrayDeque<>();
// Contains anything that has zero in-degree / search head currently
Queue<IAEStack<?>> toTraverse = new ArrayDeque<>();
missingIngredients.put(originalRequest.stack, originalRequest.stack.getStackSize());
toTraverse.add(originalRequest.stack);
while (!toTraverse.isEmpty() || !loopCandidates.isEmpty()) {
IAEStack<?> current = toTraverse.poll();
if (current == null) {
current = loopCandidates.poll();
}
if (current == null) {
continue;
}
if (traversed.contains(current)) {
// Some form of loop, do not expand again
continue;
}
traversed.add(current);
var currentPair = resolved.get(current);
if (currentPair != null) {
// Regardless of whether we actually need the pattern / output, need to expand so that in-degrees
// are updated correctly. We're doing topological sort on the fly here.
for (IAEStack<?> stack : currentPair.right().getCondensedAEInputs()) {
if (addCountToMap(inDegree, stack, -1) == 0) {
toTraverse.add(stack);
}
if (result.loopingPatterns.contains(stack)) {
loopCandidates.add(stack);
}
}
}
exploreItem(current, currentPair);
}
}
// currentPair is the output-pattern pair, where output contains the pattern output count
private void exploreItem(IAEStack<?> current, @Nullable LongObjectPair<ICraftingPatternDetails> currentPair) {
long count = moveMissingByExtract(current);
if (count == 0) return;
if (currentPair == null) return;
// Apply the pattern
ICraftingPatternDetails pattern = currentPair.right();
long patternMultiplier = Platform.ceilDiv(count, currentPair.leftLong());
for (IAEStack<?> stack : pattern.getCondensedAEInputs()) {
addCountToMap(missingIngredients, stack, Math.multiplyExact(stack.getStackSize(), patternMultiplier));
}
addCountToMap(tasks, pattern, patternMultiplier);
byteCost += CraftingCalculations
.adjustByteCost(current, Math.multiplyExact(currentPair.leftLong(), patternMultiplier));
addCountToMap(missingIngredients, current, -count);
}
// Returns the remainder count
private long moveMissingByExtract(IAEStack<?> stack) {
long count = missingIngredients.getLong(stack);
IAEStack<?> result = context.itemModel.extractItems(stack.copy().setStackSize(count), Actionable.MODULATE);
if (result != null) {
long extracted = result.getStackSize();
addCountToMap(ingredients, stack, extracted);
byteCost += CraftingCalculations.adjustByteCost(stack, extracted);
if (extracted >= count) {
missingIngredients.removeLong(stack);
return 0;
}
addCountToMap(missingIngredients, stack, -extracted);
return count - extracted;
}
return count;
}
private void calculate() {
if (calculated) return;
try {
calculateImpl();
} catch (Throwable t) {
errorMsg = t.toString();
isSimulated = true;
} finally {
calculated = true;
}
}
private <T> long addCountToMap(AbstractObject2LongMap<T> map, T key, long count) {
long oldValue = map.getLong(key);
long newValue = Math.addExact(oldValue, count);
if (newValue == 0) {
map.removeLong(key);
return 0;
}
map.put(key, newValue);
return newValue;
}
@Override
public boolean isSimulation() {
return isSimulated
|| (originalRequest.craftingMode != CraftingMode.IGNORE_MISSING && !missingIngredients.isEmpty());
}
@Override
public long getByteTotal() {
return byteCost;
}
@Override
public StackType getOutput() {
return (StackType) originalRequest.stack;
}
@Override
public boolean simulateFor(int milli) {
calculate();
if (callback != null) {
callback.calculationComplete(this);
}
return false;
}
@Override
public Future<ICraftingJob<StackType>> schedule() {
calculate();
TickHandler.INSTANCE.registerCraftingSimulation(this.context.world, this);
return CompletableFuture.completedFuture(this);
}
@Override
public boolean supportsCPUCluster(ICraftingCPU cluster) {
return cluster instanceof CraftingCPUCluster;
}
@Override
public CraftingMode getCraftingMode() {
return this.originalRequest.craftingMode;
}
@Override
public void startCrafting(MECraftingInventory storage, ICraftingCPU craftingCPUCluster, BaseActionSource src) {
calculate();
CraftingCPUCluster cluster = (CraftingCPUCluster) craftingCPUCluster;
for (var entry : tasks.object2LongEntrySet()) {
cluster.addCrafting(entry.getKey(), entry.getLongValue());
}
for (var entry : ingredients.object2LongEntrySet()) {
pullStack(storage, cluster, entry.getKey(), entry.getLongValue());
}
for (var entry : missingIngredients.object2LongEntrySet()) {
pullStack(storage, cluster, entry.getKey(), entry.getLongValue());
}
}
private void pullStack(MECraftingInventory storage, CraftingCPUCluster cluster, IAEStack<?> stack, long count) {
IAEStack<?> extracted = storage.extractItems(stack.copy().setStackSize(count), Actionable.MODULATE);
if (extracted == null || extracted.getStackSize() != count) {
if (cluster.isMissingMode()) {
if (extracted == null) {
cluster.addEmitable(stack.copy().setStackSize(count));
return;
} else if (extracted.getStackSize() != count) {
cluster.addEmitable(stack.copy().setStackSize(count - extracted.getStackSize()));
}
} else {
throw new CraftBranchFailure(stack, count);
}
}
cluster.addStorage(extracted);
}
@Override
public MECraftingInventory getStorageAtBeginning() {
return context.availableCache;
}
@Override
public void populatePlan(IItemList plan) {
// This is what gets shown in the crafting plan
for (var entry : tasks.object2LongEntrySet()) {
for (var output : entry.getKey().getCondensedAEOutputs()) {
plan.addRequestable(
output.copy().setStackSize(0).setCountRequestable(entry.getLongValue() * output.getStackSize())
.setCountRequestableCrafts(entry.getLongValue()));
}
}
for (var entry : ingredients.object2LongEntrySet()) {
plan.add(entry.getKey().copy().setStackSize(entry.getLongValue()));
}
for (var entry : missingIngredients.object2LongEntrySet()) {
if (originalRequest.craftingMode == CraftingMode.IGNORE_MISSING) {
plan.addRequestable(entry.getKey().copy().setStackSize(0).setCountRequestable(entry.getLongValue()));
} else {
plan.add(entry.getKey().copy().setStackSize(entry.getLongValue()));
}
}
}
@Override
public boolean supportsOptimization() {
return true;
}
@Override
public String getErrorMessage() {
return errorMsg;
}
}
@@ -0,0 +1,107 @@
package appeng.crafting.fast;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
import java.util.function.Function;
import javax.annotation.ParametersAreNonnullByDefault;
import org.jetbrains.annotations.Nullable;
import appeng.api.networking.crafting.ICraftingPatternDetails;
import appeng.api.storage.data.IAEStack;
import gregtech.api.util.FieldsAreNonnullByDefault;
import gregtech.api.util.MethodsReturnNonnullByDefault;
import it.unimi.dsi.fastutil.longs.LongObjectPair;
import it.unimi.dsi.fastutil.objects.AbstractObject2LongMap;
import it.unimi.dsi.fastutil.objects.AbstractObject2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
/**
* Implementation of Tarjan's algorithm.
*/
@FieldsAreNonnullByDefault
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
public final class SccResolver {
public static final class Result {
final AbstractObject2ObjectMap<IAEStack<?>, LongObjectPair<ICraftingPatternDetails>> patterns = new Object2ObjectOpenHashMap<>();
final AbstractObject2LongMap<IAEStack<?>> inDegree = new Object2LongOpenHashMap<>();
final Set<IAEStack<?>> loopingPatterns = new HashSet<>();
}
private static final class NodeInfo {
final IAEStack<?> stack;
final long discovered;
long sccGroup;
boolean inStack;
private NodeInfo(IAEStack<?> stack, long discovered) {
this.stack = stack;
this.discovered = discovered;
this.sccGroup = discovered;
}
}
private final Function<IAEStack<?>, @Nullable LongObjectPair<ICraftingPatternDetails>> resolver;
private final Result result = new Result();
private long timer = 0;
private final AbstractObject2ObjectMap<IAEStack<?>, NodeInfo> nodeInfo = new Object2ObjectOpenHashMap<>();
private final Stack<NodeInfo> stack = new Stack<>();
private SccResolver(Function<IAEStack<?>, LongObjectPair<ICraftingPatternDetails>> resolver) {
this.resolver = resolver;
}
public static Result compute(IAEStack<?> target,
Function<IAEStack<?>, @Nullable LongObjectPair<ICraftingPatternDetails>> resolver) {
SccResolver sccResolver = new SccResolver(resolver);
sccResolver.computeImpl(target);
return sccResolver.result;
}
/**
* target is always undiscovered
*/
private NodeInfo computeImpl(IAEStack<?> current) {
final NodeInfo currentInfo = new NodeInfo(current, ++timer);
nodeInfo.put(current, currentInfo);
LongObjectPair<ICraftingPatternDetails> pattern = resolver.apply(current);
stack.push(currentInfo);
currentInfo.inStack = true;
if (pattern != null) {
result.patterns.put(current, pattern);
for (IAEStack<?> child : pattern.right().getCondensedAEInputs()) {
final NodeInfo childInfo = nodeInfo.get(child);
if (childInfo == null) {
currentInfo.sccGroup = Math.min(currentInfo.sccGroup, computeImpl(child).sccGroup);
} else if (childInfo.inStack) {
currentInfo.sccGroup = Math.min(currentInfo.sccGroup, childInfo.discovered);
}
result.inDegree.put(child, result.inDegree.getLong(child) + 1);
}
}
if (currentInfo.sccGroup == currentInfo.discovered) {
if (stack.peek() == currentInfo) {
// self edge is fine for our calculator, ignore such edge
stack.pop();
currentInfo.inStack = false;
} else {
// Part of a loop
while (true) {
NodeInfo tmp = stack.pop();
tmp.inStack = false;
result.loopingPatterns.add(tmp.stack);
if (tmp == currentInfo) break;
}
}
}
return currentInfo;
}
}
@@ -6,6 +6,7 @@ import java.util.List;
import org.apache.logging.log4j.Level;
import appeng.api.storage.data.IAEStack;
import appeng.core.AELog;
import appeng.crafting.v2.resolvers.CraftableItemResolver;
import appeng.crafting.v2.resolvers.CraftingRequestResolver;
@@ -56,6 +57,10 @@ public class CraftingCalculations {
return Platform.ceilDiv(byteCost, request.stack.getAmountPerUnit());
}
public static long adjustByteCost(IAEStack<?> stack, long byteCost) {
return Platform.ceilDiv(byteCost, stack.getAmountPerUnit());
}
static {
registerProvider(new ExtractItemResolver());
registerProvider(new SimulateMissingItemResolver());
@@ -156,6 +156,7 @@ public class CraftingJobV2<StackType extends IAEStack<StackType>>
return byteCost;
}
@Override
public String getErrorMessage() {
return errorMessage;
}
@@ -304,4 +305,9 @@ public class CraftingJobV2<StackType extends IAEStack<StackType>>
public MECraftingInventory getStorageAtBeginning() {
return getContext().availableCache;
}
@Override
public boolean supportsOptimization() {
return true;
}
}
+9 -3
View File
@@ -92,6 +92,7 @@ import appeng.api.storage.data.IItemList;
import appeng.crafting.CraftingLink;
import appeng.crafting.CraftingLinkNexus;
import appeng.crafting.CraftingWatcher;
import appeng.crafting.fast.CraftingJobFast;
import appeng.crafting.v2.CraftingJobV2;
import appeng.me.cluster.implementations.CraftingCPUCluster;
import appeng.me.diagnostics.CraftingDiagnosticSessionId;
@@ -588,21 +589,26 @@ public class CraftingGridCache
@Override
public Future<ICraftingJob> beginCraftingJob(final World world, final IGrid grid, final BaseActionSource actionSrc,
final IAEItemStack slotItem, final ICraftingCallback cb) {
return beginCraftingJob(world, grid, actionSrc, convertStack(slotItem), CraftingMode.STANDARD, cb);
return beginCraftingJob(world, grid, actionSrc, convertStack(slotItem), CraftingMode.STANDARD, false, cb);
}
@Override
public Future<ICraftingJob> beginCraftingJob(final World world, final IGrid grid, final BaseActionSource actionSrc,
final IAEStack<?> stack, final ICraftingCallback cb) {
return beginCraftingJob(world, grid, actionSrc, stack, CraftingMode.STANDARD, cb);
return beginCraftingJob(world, grid, actionSrc, stack, CraftingMode.STANDARD, false, cb);
}
public Future<ICraftingJob> beginCraftingJob(final World world, final IGrid grid, final BaseActionSource actionSrc,
final IAEStack<?> stack, final CraftingMode craftingMode, final ICraftingCallback cb) {
final IAEStack<?> stack, final CraftingMode craftingMode, final boolean liteMode,
final ICraftingCallback cb) {
if (world == null || grid == null || actionSrc == null || stack == null) {
throw new IllegalArgumentException("Invalid Crafting Job Request");
}
if (liteMode) {
return new CraftingJobFast<>(world, grid, actionSrc, (IAEStack) stack, craftingMode, cb).schedule();
}
final ICraftingJob job = new CraftingJobV2<>(world, grid, actionSrc, (IAEStack) stack, craftingMode, cb);
return job.schedule();
@@ -755,6 +755,8 @@ gui.tooltips.appliedenergistics2.CraftingModeStandard=Standard Crafting Mode
gui.tooltips.appliedenergistics2.CraftingModeStandardDesc=Starts a craft only if all ingredients are available in the ME System.
gui.tooltips.appliedenergistics2.CraftingModeIgnoreMissing=Ignore Missing Crafting Mode
gui.tooltips.appliedenergistics2.CraftingModeIgnoreMissingDesc=Starts a craft even if ingredients are missing from the ME System.
gui.appliedenergistics2.CraftingModeLite=Lite Crafting Mode
gui.appliedenergistics2.CraftingModeLiteDesc=Disable various pattern features in exchange for a much faster calculation.
gui.tooltips.appliedenergistics2.ExtraOptions=Extra Options