mirror of
https://github.com/GTNewHorizons/Applied-Energistics-2-Unofficial.git
synced 2026-11-14 01:14:11 +00:00
Lite crafting mode (#1438)
Co-authored-by: Martin Robertz <[email protected]> Co-authored-by: UltraProdigy <[email protected]>
This commit is contained in:
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) {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user