mirror of
https://github.com/GTNewHorizons/Applied-Energistics-2-Unofficial.git
synced 2026-10-26 14:25:20 +00:00
Crafting calculator v2 (#221)
* Update NEI * Update buildscript * Crafting Calculator V2 [WIP] * Implement refunds * Add a config option to switch crafting calculators, default to v2 * Initial AE2 glue code * Fix scheduling as a Future * The simplest of crafting now works * Fix crafting cpu picking up the wrong pattern fuzzy matches * Revert "use as substitute" changes in the old crafting calculator.
This commit is contained in:
1 parent
2eaef34300
commit
406c256832
27 files changed
+1514
-96
No files matched your search
+11
-6
@@ -1,4 +1,4 @@
|
||||
//version: 1671313514
|
||||
//version: 1673027205
|
||||
/*
|
||||
DO NOT CHANGE THIS FILE!
|
||||
Also, you may replace this file at any time if there is an update available.
|
||||
@@ -46,7 +46,7 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
//Overwrite the current ASM version to fix shading newer than java 8 applicatations.
|
||||
classpath 'org.ow2.asm:asm-debug-all-custom:5.0.3'
|
||||
classpath 'org.ow2.asm:asm-debug-all-custom:5.0.3'
|
||||
classpath 'net.minecraftforge.gradle:ForgeGradle:1.2.13'
|
||||
}
|
||||
}
|
||||
@@ -319,9 +319,13 @@ if (file('addon.gradle').exists()) {
|
||||
apply from: 'repositories.gradle'
|
||||
|
||||
configurations {
|
||||
implementation.extendsFrom(shadowImplementation) // TODO: remove after all uses are refactored
|
||||
implementation.extendsFrom(shadowCompile)
|
||||
implementation.extendsFrom(shadeCompile)
|
||||
// TODO: remove Compile after all uses are refactored to Implementation
|
||||
for (config in [shadowImplementation, shadowCompile, shadeCompile]) {
|
||||
compileClasspath.extendsFrom(config)
|
||||
runtimeClasspath.extendsFrom(config)
|
||||
testCompileClasspath.extendsFrom(config)
|
||||
testRuntimeClasspath.extendsFrom(config)
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -350,7 +354,8 @@ dependencies {
|
||||
annotationProcessor('com.google.code.gson:gson:2.8.6')
|
||||
annotationProcessor('com.gtnewhorizon:gtnhmixins:2.1.3:processor')
|
||||
if (usesMixinDebug.toBoolean()) {
|
||||
runtimeOnly('org.jetbrains:intellij-fernflower:1.2.1.16')
|
||||
runtimeClasspath('org.jetbrains:intellij-fernflower:1.2.1.16')
|
||||
testRuntimeClasspath('org.jetbrains:intellij-fernflower:1.2.1.16')
|
||||
}
|
||||
}
|
||||
if (usesMixins.toBoolean() || forceEnableMixins.toBoolean()) {
|
||||
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
// Add your dependencies here
|
||||
|
||||
dependencies {
|
||||
compile('com.github.GTNewHorizons:CodeChickenLib:1.1.5.5:dev')
|
||||
compile('com.github.GTNewHorizons:NotEnoughItems:2.2.33-GTNH:dev')
|
||||
compile('com.github.GTNewHorizons:CodeChickenLib:1.1.5.6:dev')
|
||||
compile('com.github.GTNewHorizons:NotEnoughItems:2.3.20-GTNH:dev')
|
||||
compile('curse.maven:cofh-core-69162:2388751')
|
||||
|
||||
compile('com.github.GTNewHorizons:BuildCraft:7.1.27:dev') {transitive = false}
|
||||
compile('com.github.GTNewHorizons:BuildCraft:7.1.28:dev') {transitive = false}
|
||||
compileOnly('com.github.GTNewHorizons:ForgeMultipart:1.2.8:dev') {transitive = false}
|
||||
compileOnly('com.github.GTNewHorizons:GT5-Unofficial:5.09.41.18:dev') {transitive = false}
|
||||
compileOnly('com.github.GTNewHorizons:Jabba:1.2.21:dev') {transitive = false}
|
||||
|
||||
@@ -28,6 +28,8 @@ import appeng.api.networking.IGridCache;
|
||||
import appeng.api.networking.security.BaseActionSource;
|
||||
import appeng.api.storage.data.IAEItemStack;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import java.util.concurrent.Future;
|
||||
import net.minecraft.world.World;
|
||||
@@ -44,6 +46,11 @@ public interface ICraftingGrid extends IGridCache {
|
||||
ImmutableCollection<ICraftingPatternDetails> getCraftingFor(
|
||||
IAEItemStack whatToCraft, ICraftingPatternDetails details, int slot, World world);
|
||||
|
||||
/**
|
||||
* @return a collection of all the crafting patterns in the system
|
||||
*/
|
||||
ImmutableMap<IAEItemStack, ImmutableList<ICraftingPatternDetails>> getCraftingPatterns();
|
||||
|
||||
/**
|
||||
* Begin calculating a crafting job.
|
||||
*
|
||||
|
||||
@@ -23,8 +23,11 @@
|
||||
|
||||
package appeng.api.networking.crafting;
|
||||
|
||||
import appeng.api.networking.security.BaseActionSource;
|
||||
import appeng.api.storage.data.IAEItemStack;
|
||||
import appeng.api.storage.data.IItemList;
|
||||
import appeng.crafting.MECraftingInventory;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public interface ICraftingJob {
|
||||
|
||||
@@ -51,4 +54,27 @@ public interface ICraftingJob {
|
||||
* @return the final output of the job.
|
||||
*/
|
||||
IAEItemStack getOutput();
|
||||
|
||||
/**
|
||||
* returns true if this needs more simulation.
|
||||
*
|
||||
* @param milli milliseconds of simulation
|
||||
* @return true if this needs more simulation
|
||||
*/
|
||||
boolean simulateFor(final int milli);
|
||||
|
||||
Future<ICraftingJob> schedule();
|
||||
|
||||
/**
|
||||
* @return whether this job can run on the given cluster
|
||||
*/
|
||||
default boolean supportsCPUCluster(final ICraftingCPU cluster) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins crafting on a CPU cluster
|
||||
*/
|
||||
default void startCrafting(
|
||||
final MECraftingInventory storage, final ICraftingCPU craftingCPUCluster, final BaseActionSource src) {}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
package appeng.api.storage.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
@@ -77,4 +78,12 @@ public interface IItemList<StackType extends IAEStack> extends IItemContainer<St
|
||||
* resets stack sizes to 0.
|
||||
*/
|
||||
void resetStatus();
|
||||
|
||||
default StackType[] toArray(StackType[] zeroSizedArray) {
|
||||
ArrayList<StackType> output = new ArrayList<>(size());
|
||||
for (StackType stack : this) {
|
||||
output.add(stack);
|
||||
}
|
||||
return output.toArray(zeroSizedArray);
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,7 @@ public final class AEConfig extends Configuration implements IConfigurableObject
|
||||
private double WirelessBoosterRangeMultiplier = 1;
|
||||
private double WirelessBoosterExp = 1.5;
|
||||
public int levelEmitterDelay = 40;
|
||||
public int craftingCalculatorVersion = 2;
|
||||
|
||||
public AEConfig(final File configFile) {
|
||||
super(configFile);
|
||||
@@ -235,6 +236,9 @@ public final class AEConfig extends Configuration implements IConfigurableObject
|
||||
this.quantumBridgeBackboneTransfer = this.get(
|
||||
"debug", "EnableQuantumBridgeBackboneTransfer", this.quantumBridgeBackboneTransfer)
|
||||
.getBoolean(this.quantumBridgeBackboneTransfer);
|
||||
this.craftingCalculatorVersion = this.get("debug", "CraftingCalculatorVersion", this.craftingCalculatorVersion)
|
||||
.getInt(this.craftingCalculatorVersion);
|
||||
this.craftingCalculatorVersion = Math.max(1, Math.min(this.craftingCalculatorVersion, 2));
|
||||
this.clientSync();
|
||||
|
||||
for (final AEFeature feature : AEFeature.values()) {
|
||||
|
||||
@@ -20,7 +20,7 @@ package appeng.crafting;
|
||||
|
||||
import appeng.api.storage.data.IAEItemStack;
|
||||
|
||||
public class CraftBranchFailure extends Exception {
|
||||
public class CraftBranchFailure extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 654603652836724823L;
|
||||
|
||||
|
||||
@@ -23,10 +23,7 @@ import appeng.api.config.Actionable;
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.api.networking.IGridHost;
|
||||
import appeng.api.networking.IGridNode;
|
||||
import appeng.api.networking.crafting.ICraftingCallback;
|
||||
import appeng.api.networking.crafting.ICraftingGrid;
|
||||
import appeng.api.networking.crafting.ICraftingJob;
|
||||
import appeng.api.networking.crafting.ICraftingPatternDetails;
|
||||
import appeng.api.networking.crafting.*;
|
||||
import appeng.api.networking.security.BaseActionSource;
|
||||
import appeng.api.networking.security.IActionHost;
|
||||
import appeng.api.networking.security.MachineSource;
|
||||
@@ -37,14 +34,17 @@ import appeng.api.storage.data.IItemList;
|
||||
import appeng.api.util.DimensionalCoord;
|
||||
import appeng.core.AELog;
|
||||
import appeng.hooks.TickHandler;
|
||||
import appeng.me.cache.CraftingGridCache;
|
||||
import appeng.me.cluster.implementations.CraftingCPUCluster;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class CraftingJob implements Runnable, ICraftingJob {
|
||||
public class CraftingJob implements ICraftingJob, Runnable {
|
||||
private static final String LOG_CRAFTING_JOB =
|
||||
"CraftingJob (%s) issued by %s requesting [%s] using %s bytes took %s ms";
|
||||
private static final String LOG_MACHINE_SOURCE_DETAILS = "Machine[object=%s, %s]";
|
||||
@@ -118,6 +118,11 @@ public class CraftingJob implements Runnable, ICraftingJob {
|
||||
this.missing.add(what);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<ICraftingJob> schedule() {
|
||||
return CraftingGridCache.getCraftingPool().submit(this, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
@@ -262,12 +267,6 @@ public class CraftingJob implements Runnable, ICraftingJob {
|
||||
return this.world;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if this needs more simulation.
|
||||
*
|
||||
* @param milli milliseconds of simulation
|
||||
* @return true if this needs more simulation
|
||||
*/
|
||||
public boolean simulateFor(final int milli) {
|
||||
this.time = milli;
|
||||
|
||||
@@ -335,6 +334,17 @@ public class CraftingJob implements Runnable, ICraftingJob {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCPUCluster(ICraftingCPU cluster) {
|
||||
return cluster instanceof CraftingCPUCluster;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCrafting(MECraftingInventory storage, ICraftingCPU craftingCPUCluster, BaseActionSource src) {
|
||||
CraftingCPUCluster cluster = (CraftingCPUCluster) craftingCPUCluster;
|
||||
this.tree.setJob(storage, cluster, src);
|
||||
}
|
||||
|
||||
private static class TwoIntegers {
|
||||
private final long perOp = 0;
|
||||
private final long times = 0;
|
||||
|
||||
@@ -309,8 +309,9 @@ public class CraftingTreeNode {
|
||||
}
|
||||
|
||||
public void setJob(
|
||||
final MECraftingInventory storage, final CraftingCPUCluster craftingCPUCluster, final BaseActionSource src)
|
||||
throws CraftBranchFailure {
|
||||
final MECraftingInventory storage,
|
||||
final CraftingCPUCluster craftingCPUCluster,
|
||||
final BaseActionSource src) {
|
||||
for (final IAEItemStack i : this.used) {
|
||||
final IAEItemStack ex = storage.extractItems(i, Actionable.MODULATE, src);
|
||||
|
||||
|
||||
@@ -237,13 +237,8 @@ public class CraftingTreeProcess {
|
||||
|
||||
// more fuzzy!
|
||||
for (final IAEItemStack is : this.details.getCondensedOutputs()) {
|
||||
// there are now two ways this craft is triggered via fuzzy
|
||||
// perfectMatch: done due to fuzziness in damage value
|
||||
// oredict: done through substitutes
|
||||
// catch both of them here
|
||||
final boolean perfectMatch = is.getItem() == what2.getItem()
|
||||
&& (is.getItem().isDamageable() || is.getItemDamage() == what2.getItemDamage());
|
||||
if (perfectMatch || this.details.canBeSubstitute() && is.sameOre(what2)) {
|
||||
if (is.getItem() == what2.getItem()
|
||||
&& (is.getItem().isDamageable() || is.getItemDamage() == what2.getItemDamage())) {
|
||||
what2 = is.copy();
|
||||
what2.setStackSize(is.getStackSize());
|
||||
return what2;
|
||||
|
||||
@@ -285,7 +285,7 @@ public class MECraftingInventory implements IMEInventory<IAEItemStack> {
|
||||
this.missingCache.add(extra);
|
||||
}
|
||||
|
||||
void ignore(final IAEItemStack what) {
|
||||
public void ignore(final IAEItemStack what) {
|
||||
final IAEItemStack list = this.localCache.findPrecise(what);
|
||||
if (list != null) {
|
||||
list.setStackSize(0);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package appeng.crafting.v2;
|
||||
|
||||
import appeng.api.storage.data.IAEFluidStack;
|
||||
import appeng.api.storage.data.IAEItemStack;
|
||||
import appeng.api.storage.data.IAEStack;
|
||||
import appeng.core.AELog;
|
||||
import appeng.crafting.v2.resolvers.*;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.apache.logging.log4j.Level;
|
||||
|
||||
/**
|
||||
* You can register additional crafting handlers here
|
||||
*/
|
||||
public class CraftingCalculations {
|
||||
private static final ListMultimap<Class<? extends IAEStack<?>>, CraftingRequestResolver<?>> providers =
|
||||
ArrayListMultimap.create(2, 8);
|
||||
|
||||
public static <StackType extends IAEStack<StackType>> void registerProvider(
|
||||
CraftingRequestResolver<StackType> provider, Class<StackType> stackTypeClass) {
|
||||
providers.put(stackTypeClass, provider);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <StackType extends IAEStack<StackType>> List<CraftingTask> tryResolveCraftingRequest(
|
||||
CraftingRequest<StackType> request, CraftingContext context) {
|
||||
final ArrayList<CraftingTask> allTasks = new ArrayList<>(4);
|
||||
for (final CraftingRequestResolver<?> unsafeProvider : Multimaps.filterKeys(
|
||||
providers, key -> key.isAssignableFrom(request.stackTypeClass))
|
||||
.values()) {
|
||||
try {
|
||||
// Safety: Filtered by type using Multimaps.filterKeys
|
||||
final CraftingRequestResolver<StackType> provider = (CraftingRequestResolver<StackType>) unsafeProvider;
|
||||
final List<CraftingTask> tasks = provider.provideCraftingRequestResolvers(request, context);
|
||||
allTasks.addAll(tasks);
|
||||
} catch (Exception t) {
|
||||
AELog.log(
|
||||
Level.DEBUG,
|
||||
t,
|
||||
"Error encountered when trying to generate the list of CraftingTasks for crafting {}",
|
||||
request.toString());
|
||||
}
|
||||
}
|
||||
allTasks.sort(CraftingTask.PRIORITY_COMPARATOR);
|
||||
return Collections.unmodifiableList(allTasks);
|
||||
}
|
||||
|
||||
static {
|
||||
registerProvider(new ExtractItemResolver(), IAEItemStack.class);
|
||||
registerProvider(new SimulateMissingItemResolver<>(), IAEItemStack.class);
|
||||
registerProvider(new SimulateMissingItemResolver<>(), IAEFluidStack.class);
|
||||
registerProvider(new EmitableItemResolver(), IAEItemStack.class);
|
||||
registerProvider(new CraftableItemResolver(), IAEItemStack.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
package appeng.crafting.v2;
|
||||
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.api.networking.crafting.ICraftingGrid;
|
||||
import appeng.api.networking.crafting.ICraftingPatternDetails;
|
||||
import appeng.api.networking.security.BaseActionSource;
|
||||
import appeng.api.networking.storage.IStorageGrid;
|
||||
import appeng.api.storage.data.IAEItemStack;
|
||||
import appeng.api.storage.data.IAEStack;
|
||||
import appeng.api.storage.data.IItemList;
|
||||
import appeng.crafting.MECraftingInventory;
|
||||
import appeng.crafting.v2.resolvers.CraftingTask;
|
||||
import appeng.me.cluster.implementations.CraftingCPUCluster;
|
||||
import appeng.util.item.OreListMultiMap;
|
||||
import com.google.common.collect.ClassToInstanceMap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.MutableClassToInstanceMap;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import javax.annotation.Nonnull;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/**
|
||||
* A bundle of state for the crafting operation like the ME grid, who requested crafting, etc.
|
||||
*/
|
||||
public final class CraftingContext {
|
||||
public final World world;
|
||||
public final IGrid meGrid;
|
||||
public final ICraftingGrid craftingGrid;
|
||||
public BaseActionSource actionSource;
|
||||
|
||||
/**
|
||||
* A working copy of the AE system's item list used for modelling what happens as crafting requests get resolved
|
||||
*/
|
||||
public final MECraftingInventory itemModel;
|
||||
/**
|
||||
* A cache of how many items were present at the beginning of the crafting request, do not modify
|
||||
*/
|
||||
public final MECraftingInventory availableCache;
|
||||
|
||||
public boolean wasSimulated = false;
|
||||
|
||||
public static final class RequestInProcessing<StackType extends IAEStack<StackType>> {
|
||||
public final CraftingRequest<StackType> request;
|
||||
/**
|
||||
* Ordered by priority
|
||||
*/
|
||||
public final Deque<CraftingTask> resolvers = new ArrayDeque<>(4);
|
||||
|
||||
public RequestInProcessing(CraftingRequest<StackType> request) {
|
||||
this.request = request;
|
||||
}
|
||||
}
|
||||
|
||||
private final List<RequestInProcessing<?>> liveRequests = new ArrayList<>(32);
|
||||
private final List<CraftingTask> resolvedTasks = new ArrayList<>();
|
||||
private final ArrayDeque<CraftingTask> tasksToProcess = new ArrayDeque<>(64);
|
||||
private boolean doingWork = false;
|
||||
// State at the point when the last task executed.
|
||||
private CraftingTask.State finishedState = CraftingTask.State.FAILURE;
|
||||
private final ImmutableMap<IAEItemStack, ImmutableList<ICraftingPatternDetails>> availablePatterns;
|
||||
private final Map<IAEItemStack, List<ICraftingPatternDetails>> precisePatternCache = new HashMap<>();
|
||||
private final OreListMultiMap<ICraftingPatternDetails> fuzzyPatternCache = new OreListMultiMap<>();
|
||||
private final ClassToInstanceMap<Object> userCaches = MutableClassToInstanceMap.create();
|
||||
|
||||
public CraftingContext(@Nonnull World world, @Nonnull IGrid meGrid, @Nonnull BaseActionSource actionSource) {
|
||||
this.world = world;
|
||||
this.meGrid = meGrid;
|
||||
this.craftingGrid = meGrid.getCache(ICraftingGrid.class);
|
||||
this.actionSource = actionSource;
|
||||
final IStorageGrid sg = meGrid.getCache(IStorageGrid.class);
|
||||
this.itemModel = new MECraftingInventory(sg.getItemInventory(), this.actionSource, true, false, true);
|
||||
this.availableCache = new MECraftingInventory(sg.getItemInventory(), this.actionSource, false, false, false);
|
||||
this.availablePatterns = craftingGrid.getCraftingPatterns();
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used for custom caching in plugins
|
||||
*/
|
||||
public <T> T getUserCache(Class<T> cacheType, Supplier<T> constructor) {
|
||||
// Can't use compute with generic types safely here
|
||||
T instance = userCaches.getInstance(cacheType);
|
||||
if (instance == null) {
|
||||
instance = constructor.get();
|
||||
userCaches.putInstance(cacheType, instance);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void addRequest(@Nonnull CraftingRequest<?> request) {
|
||||
if (doingWork) {
|
||||
throw new IllegalStateException(
|
||||
"Trying to add requests while inside a CraftingTask handler, return requests in the StepOutput instead");
|
||||
}
|
||||
final RequestInProcessing<?> processing = new RequestInProcessing<>(request);
|
||||
processing.resolvers.addAll(CraftingCalculations.tryResolveCraftingRequest(request, this));
|
||||
liveRequests.add(processing);
|
||||
if (processing.resolvers.isEmpty()) {
|
||||
throw new IllegalStateException("No resolvers available for request " + request.toString());
|
||||
}
|
||||
queueNextTaskOf(processing, true);
|
||||
}
|
||||
|
||||
public List<ICraftingPatternDetails> getPrecisePatternsFor(@Nonnull IAEItemStack stack) {
|
||||
return precisePatternCache.compute(stack, (key, value) -> {
|
||||
if (value == null) {
|
||||
return availablePatterns.getOrDefault(stack, ImmutableList.of());
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public List<ICraftingPatternDetails> getFuzzyPatternsFor(@Nonnull IAEItemStack stack) {
|
||||
if (!fuzzyPatternCache.isPopulated()) {
|
||||
for (final ImmutableList<ICraftingPatternDetails> patternSet : availablePatterns.values()) {
|
||||
for (final ICraftingPatternDetails pattern : patternSet) {
|
||||
if (pattern.canBeSubstitute()) {
|
||||
for (final IAEItemStack output : pattern.getOutputs()) {
|
||||
fuzzyPatternCache.put(output.copy(), pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fuzzyPatternCache.freeze();
|
||||
}
|
||||
return fuzzyPatternCache.get(stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does one unit of work towards solving the crafting problem.
|
||||
*
|
||||
* @return Is more work needed?
|
||||
*/
|
||||
public CraftingTask.State doWork() {
|
||||
if (tasksToProcess.isEmpty()) {
|
||||
return finishedState;
|
||||
}
|
||||
final CraftingTask frontTask = tasksToProcess.getFirst();
|
||||
if (frontTask.getState() == CraftingTask.State.SUCCESS || frontTask.getState() == CraftingTask.State.FAILURE) {
|
||||
resolvedTasks.add(frontTask);
|
||||
tasksToProcess.removeFirst();
|
||||
return CraftingTask.State.NEEDS_MORE_WORK;
|
||||
}
|
||||
doingWork = true;
|
||||
CraftingTask.StepOutput out = frontTask.calculateOneStep(this);
|
||||
CraftingTask.State newState = frontTask.getState();
|
||||
doingWork = false;
|
||||
if (out.extraInputsRequired.size() > 0) {
|
||||
out.extraInputsRequired.forEach(this::addRequest);
|
||||
} else if (newState == CraftingTask.State.SUCCESS) {
|
||||
if (tasksToProcess.getFirst() != frontTask) {
|
||||
throw new IllegalStateException("A crafting task got added to the queue without requesting more work.");
|
||||
}
|
||||
resolvedTasks.add(frontTask);
|
||||
tasksToProcess.removeFirst();
|
||||
finishedState = CraftingTask.State.SUCCESS;
|
||||
} else if (newState == CraftingTask.State.FAILURE) {
|
||||
tasksToProcess.clear();
|
||||
finishedState = CraftingTask.State.FAILURE;
|
||||
return CraftingTask.State.FAILURE;
|
||||
}
|
||||
return tasksToProcess.isEmpty() ? CraftingTask.State.SUCCESS : CraftingTask.State.NEEDS_MORE_WORK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of tasks that have finished executing, sorted topologically (dependencies before the tasks that require them)
|
||||
* @return An unmodifiable list of resolved tasks.
|
||||
*/
|
||||
public List<CraftingTask> getResolvedTasks() {
|
||||
return Collections.unmodifiableList(resolvedTasks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all requests that have been added to the context.
|
||||
* @return An unmodifiable list of the requests.
|
||||
*/
|
||||
public List<RequestInProcessing<?>> getLiveRequests() {
|
||||
return Collections.unmodifiableList(liveRequests);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If a task was added
|
||||
*/
|
||||
private boolean queueNextTaskOf(RequestInProcessing<?> request, boolean addResolverTask) {
|
||||
if (request.request.remainingToProcess <= 0 || request.resolvers.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
CraftingTask nextResolver = request.resolvers.removeFirst();
|
||||
if (addResolverTask && !request.resolvers.isEmpty()) {
|
||||
tasksToProcess.addFirst(new CheckOtherResolversTask(request));
|
||||
}
|
||||
tasksToProcess.addFirst(nextResolver);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A task to call queueNextTaskOf after a resolver gets computed to check if more resolving is needed for the same request-in-processing.
|
||||
*/
|
||||
private final class CheckOtherResolversTask extends CraftingTask {
|
||||
private final RequestInProcessing<?> myRequest;
|
||||
|
||||
public CheckOtherResolversTask(RequestInProcessing<?> myRequest) {
|
||||
super(0); // priority doesn't matter as this task is never a resolver output
|
||||
this.myRequest = myRequest;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StepOutput calculateOneStep(CraftingContext context) {
|
||||
final boolean needsMoreWork = queueNextTaskOf(myRequest, false);
|
||||
if (needsMoreWork) {
|
||||
this.state = State.NEEDS_MORE_WORK;
|
||||
} else if (myRequest.request.remainingToProcess <= 0) {
|
||||
this.state = State.SUCCESS;
|
||||
} else {
|
||||
this.state = State.FAILURE;
|
||||
}
|
||||
return new StepOutput();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void partialRefund(CraftingContext context, long amount) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fullRefund(CraftingContext context) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populatePlan(IItemList<IAEItemStack> targetPlan) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startOnCpu(
|
||||
CraftingContext context, CraftingCPUCluster cpuCluster, MECraftingInventory craftingInv) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CheckOtherResolversTask{" + "myRequest="
|
||||
+ myRequest + ", priority="
|
||||
+ priority + ", state="
|
||||
+ state + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package appeng.crafting.v2;
|
||||
|
||||
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.security.BaseActionSource;
|
||||
import appeng.api.storage.data.IAEItemStack;
|
||||
import appeng.api.storage.data.IItemList;
|
||||
import appeng.crafting.MECraftingInventory;
|
||||
import appeng.crafting.v2.CraftingContext.RequestInProcessing;
|
||||
import appeng.crafting.v2.CraftingRequest.SubstitutionMode;
|
||||
import appeng.crafting.v2.resolvers.CraftingTask;
|
||||
import appeng.crafting.v2.resolvers.CraftingTask.State;
|
||||
import appeng.hooks.TickHandler;
|
||||
import appeng.me.cluster.implementations.CraftingCPUCluster;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/**
|
||||
* A new, self-contained implementation of the crafting calculator.
|
||||
* Does an iterative search on the crafting recipe tree.
|
||||
*/
|
||||
public class CraftingJobV2 implements ICraftingJob, Future<ICraftingJob> {
|
||||
|
||||
protected volatile long totalByteCost = -1; // -1 means it needs to be recalculated
|
||||
|
||||
protected CraftingContext context;
|
||||
protected final CraftingRequest<IAEItemStack> originalRequest;
|
||||
protected ICraftingCallback callback;
|
||||
|
||||
protected enum State {
|
||||
RUNNING,
|
||||
FINISHED,
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
protected State state = State.RUNNING;
|
||||
|
||||
public CraftingJobV2(
|
||||
final World world,
|
||||
final IGrid meGrid,
|
||||
final BaseActionSource actionSource,
|
||||
final IAEItemStack what,
|
||||
final ICraftingCallback callback) {
|
||||
this.context = new CraftingContext(world, meGrid, actionSource);
|
||||
this.callback = callback;
|
||||
this.originalRequest = new CraftingRequest<>(what, SubstitutionMode.PRECISE_FRESH, IAEItemStack.class, true);
|
||||
this.context.addRequest(this.originalRequest);
|
||||
this.context.itemModel.ignore(what);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSimulation() {
|
||||
return context.wasSimulated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getByteTotal() {
|
||||
long byteCost = totalByteCost;
|
||||
if (byteCost < 0) {
|
||||
byteCost = 0;
|
||||
for (RequestInProcessing<?> request : context.getLiveRequests()) {
|
||||
byteCost += request.request.byteCost;
|
||||
}
|
||||
totalByteCost = byteCost;
|
||||
}
|
||||
return byteCost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populatePlan(IItemList<IAEItemStack> plan) {
|
||||
for (CraftingTask task : context.getResolvedTasks()) {
|
||||
task.populatePlan(plan);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IAEItemStack getOutput() {
|
||||
return originalRequest.stack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean simulateFor(int milli) {
|
||||
if (this.state != State.RUNNING) {
|
||||
return false;
|
||||
}
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final long finishTime = startTime + milli;
|
||||
CraftingTask.State taskState = CraftingTask.State.NEEDS_MORE_WORK;
|
||||
do {
|
||||
taskState = context.doWork();
|
||||
totalByteCost = -1;
|
||||
} while (taskState.needsMoreWork && System.currentTimeMillis() < finishTime && (state == State.RUNNING));
|
||||
|
||||
if (!taskState.needsMoreWork) {
|
||||
getByteTotal();
|
||||
this.state = State.FINISHED;
|
||||
if (callback != null) {
|
||||
callback.calculationComplete(this);
|
||||
}
|
||||
}
|
||||
|
||||
return taskState.needsMoreWork;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<ICraftingJob> schedule() {
|
||||
TickHandler.INSTANCE.registerCraftingSimulation(this.context.world, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCPUCluster(ICraftingCPU cluster) {
|
||||
return cluster instanceof CraftingCPUCluster;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCrafting(MECraftingInventory storage, ICraftingCPU rawCluster, BaseActionSource src) {
|
||||
if (this.state == State.RUNNING) {
|
||||
throw new IllegalStateException(
|
||||
"Trying to start crafting a not fully calculated job for " + originalRequest.toString());
|
||||
}
|
||||
CraftingCPUCluster cluster = (CraftingCPUCluster) rawCluster;
|
||||
context.actionSource = src;
|
||||
List<CraftingTask> resolvedTasks = context.getResolvedTasks();
|
||||
for (CraftingTask task : resolvedTasks) {
|
||||
task.startOnCpu(context, cluster, storage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean cancel(boolean mayInterruptIfRunning) {
|
||||
if (this.state != State.RUNNING) {
|
||||
return false;
|
||||
} else {
|
||||
this.state = State.CANCELLED;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCancelled() {
|
||||
return state == State.CANCELLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return state != State.RUNNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CraftingJobV2 get() throws InterruptedException, ExecutionException {
|
||||
this.simulateFor(Integer.MAX_VALUE);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CraftingJobV2 get(long timeout, TimeUnit unit)
|
||||
throws InterruptedException, ExecutionException, TimeoutException {
|
||||
try {
|
||||
this.simulateFor((int) unit.convert(timeout, TimeUnit.MILLISECONDS));
|
||||
} catch (Exception e) {
|
||||
throw new ExecutionException(e);
|
||||
}
|
||||
switch (this.state) {
|
||||
case RUNNING:
|
||||
throw new TimeoutException();
|
||||
case CANCELLED:
|
||||
throw new InterruptedException();
|
||||
case FINISHED:
|
||||
return this;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package appeng.crafting.v2;
|
||||
|
||||
import appeng.api.config.Actionable;
|
||||
import appeng.api.storage.data.IAEFluidStack;
|
||||
import appeng.api.storage.data.IAEItemStack;
|
||||
import appeng.api.storage.data.IAEStack;
|
||||
import appeng.api.storage.data.IItemList;
|
||||
import appeng.crafting.v2.resolvers.CraftingTask;
|
||||
import appeng.util.item.FluidList;
|
||||
import appeng.util.item.ItemList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* A single requested stack (item or fluid) to craft, e.g. 32x Torches
|
||||
*
|
||||
* @param <StackType> Should be {@link IAEItemStack} or {@link appeng.api.storage.data.IAEFluidStack}
|
||||
*/
|
||||
public class CraftingRequest<StackType extends IAEStack<StackType>> {
|
||||
public enum SubstitutionMode {
|
||||
/**
|
||||
* No substitution, do not use items from the AE system - used for user-started requests
|
||||
*/
|
||||
PRECISE_FRESH,
|
||||
/**
|
||||
* Use precisely the item requested
|
||||
*/
|
||||
PRECISE,
|
||||
/**
|
||||
* Allow fuzzy matching of ingredients, the request will have a {@link CraftingRequest#acceptableSubstituteFn} predicate to determine if the given fuzzy match item is valid
|
||||
*/
|
||||
ACCEPT_FUZZY
|
||||
}
|
||||
|
||||
public final Class<StackType> stackTypeClass;
|
||||
/**
|
||||
* An item/fluid + count representing how many need to be crafted
|
||||
*/
|
||||
public final StackType stack;
|
||||
|
||||
public final SubstitutionMode substitutionMode;
|
||||
public final Predicate<StackType> acceptableSubstituteFn;
|
||||
public final IItemList<StackType> resolvedInputs;
|
||||
public final List<CraftingTask> usedResolvers = new ArrayList<>();
|
||||
/**
|
||||
* Whether this request and its children can be fulfilled by simulations
|
||||
*/
|
||||
public final boolean allowSimulation;
|
||||
/**
|
||||
* The number of yet-unresolved elements from the stack (items/mB) that need to be crafted. Can go into the negatives if more are crafted than needed.
|
||||
*/
|
||||
public volatile long remainingToProcess;
|
||||
/**
|
||||
* The cost in bytes to process this task so far
|
||||
*/
|
||||
public volatile long byteCost = 0;
|
||||
/**
|
||||
* If the item had to be simulated (there was not enough ingredients in the system to fulfill this request in any way)
|
||||
*/
|
||||
public volatile boolean wasSimulated = false;
|
||||
|
||||
/**
|
||||
* @param stack The item/fluid and stack to request
|
||||
* @param substitutionMode Whether and how to allow substitutions when resolving this request
|
||||
* @param stackTypeClass Pass in {@code StackType.class}, needed for resolving types at runtime
|
||||
* @param acceptableSubstituteFn A predicate testing if a given item (in fuzzy mode) can fulfill the request
|
||||
*/
|
||||
public CraftingRequest(
|
||||
StackType stack,
|
||||
SubstitutionMode substitutionMode,
|
||||
Class<StackType> stackTypeClass,
|
||||
boolean allowSimulation,
|
||||
Predicate<StackType> acceptableSubstituteFn) {
|
||||
this.stackTypeClass = stackTypeClass;
|
||||
this.stack = stack;
|
||||
this.substitutionMode = substitutionMode;
|
||||
this.acceptableSubstituteFn = acceptableSubstituteFn;
|
||||
this.remainingToProcess = stack.getStackSize();
|
||||
this.allowSimulation = allowSimulation;
|
||||
if (stackTypeClass == IAEItemStack.class) {
|
||||
this.resolvedInputs = (IItemList<StackType>) new ItemList();
|
||||
} else if (stackTypeClass == IAEFluidStack.class) {
|
||||
this.resolvedInputs = (IItemList<StackType>) new FluidList();
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid stack type for a crafting request: " + stackTypeClass.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param request The item/fluid and stack to request
|
||||
* @param substitutionMode Whether and how to allow substitutions when resolving this request
|
||||
* @param stackTypeClass Pass in {@code StackType.class}, needed for resolving types at runtime
|
||||
*/
|
||||
public CraftingRequest(
|
||||
StackType request,
|
||||
SubstitutionMode substitutionMode,
|
||||
Class<StackType> stackTypeClass,
|
||||
boolean allowSimulation) {
|
||||
this(request, substitutionMode, stackTypeClass, allowSimulation, stack -> true);
|
||||
if (substitutionMode == SubstitutionMode.ACCEPT_FUZZY) {
|
||||
throw new IllegalArgumentException("Fuzzy requests must have a substitution-valid predicate");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CraftingRequest{request=" + stack + ", substitutionMode=" + substitutionMode + ", remainingToProcess="
|
||||
+ remainingToProcess + ", byteCost=" + byteCost + ", wasSimulated=" + wasSimulated + '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the items needed to fulfill this request, and adds any leftovers into the item cache of the context.
|
||||
*/
|
||||
public void fulfill(CraftingTask origin, StackType input, CraftingContext context) {
|
||||
if (input.getStackSize() < 0) {
|
||||
throw new IllegalArgumentException("Can't fulfill crafting request with a negative amount of " + input);
|
||||
}
|
||||
final long consumed = Math.max(0L, this.remainingToProcess);
|
||||
final long remaining = input.getStackSize() - consumed;
|
||||
if (remaining > 0 && input instanceof IAEItemStack) {
|
||||
context.itemModel.injectItems((IAEItemStack) input, Actionable.MODULATE, context.actionSource);
|
||||
}
|
||||
this.byteCost += input.getStackSize();
|
||||
this.remainingToProcess -= input.getStackSize();
|
||||
this.resolvedInputs.add(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the amount of items needed by {@code amount}, propagating any necessary refunds via the resolver crafting tasks.
|
||||
*/
|
||||
public void partialRefund(CraftingContext context, long amount) {
|
||||
for (CraftingTask task : usedResolvers) {
|
||||
task.partialRefund(context, amount);
|
||||
}
|
||||
final long processed = this.stack.getStackSize() - this.remainingToProcess;
|
||||
this.stack.setStackSize(this.stack.getStackSize() - amount);
|
||||
this.remainingToProcess = this.stack.getStackSize() - processed;
|
||||
}
|
||||
|
||||
public void fullRefund(CraftingContext context) {
|
||||
for (CraftingTask task : usedResolvers) {
|
||||
task.fullRefund(context);
|
||||
}
|
||||
this.remainingToProcess = 0;
|
||||
this.stack.setStackSize(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
package appeng.crafting.v2.resolvers;
|
||||
|
||||
import appeng.api.config.Actionable;
|
||||
import appeng.api.config.FuzzyMode;
|
||||
import appeng.api.networking.crafting.ICraftingPatternDetails;
|
||||
import appeng.api.storage.data.IAEItemStack;
|
||||
import appeng.api.storage.data.IItemList;
|
||||
import appeng.crafting.MECraftingInventory;
|
||||
import appeng.crafting.v2.CraftingContext;
|
||||
import appeng.crafting.v2.CraftingRequest;
|
||||
import appeng.crafting.v2.CraftingRequest.SubstitutionMode;
|
||||
import appeng.me.cluster.implementations.CraftingCPUCluster;
|
||||
import appeng.util.Platform;
|
||||
import appeng.util.item.HashBasedItemList;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import javax.annotation.Nonnull;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class CraftableItemResolver implements CraftingRequestResolver<IAEItemStack> {
|
||||
public static class RequestAndPerCraftAmount {
|
||||
public final CraftingRequest<IAEItemStack> request;
|
||||
public final long perCraftAmount;
|
||||
|
||||
public RequestAndPerCraftAmount(CraftingRequest<IAEItemStack> request, long perCraftAmount) {
|
||||
this.request = request;
|
||||
this.perCraftAmount = perCraftAmount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CraftFromPatternTask extends CraftingTask {
|
||||
public final CraftingRequest<IAEItemStack> request;
|
||||
public final ICraftingPatternDetails pattern;
|
||||
public final boolean allowSimulation;
|
||||
// Inputs needed to kickstart recursive crafting
|
||||
protected final IAEItemStack[] patternRecursionInputs;
|
||||
// With the recursive part subtracted
|
||||
protected final IAEItemStack[] patternInputs;
|
||||
// With the recursive part subtracted
|
||||
protected final IAEItemStack[] patternOutputs;
|
||||
protected final IAEItemStack matchingOutput;
|
||||
protected final Map<IAEItemStack, RequestAndPerCraftAmount> childRequests = new HashMap<>();
|
||||
protected final Map<IAEItemStack, CraftingRequest<IAEItemStack>> childRecursionRequests = new HashMap<>();
|
||||
// byproduct injected -> amount per craft
|
||||
protected final IdentityHashMap<IAEItemStack, Long> byproducts = new IdentityHashMap<>();
|
||||
protected boolean requestedInputs = false;
|
||||
protected long totalCraftsDone = 0;
|
||||
|
||||
public CraftFromPatternTask(
|
||||
CraftingRequest<IAEItemStack> request,
|
||||
ICraftingPatternDetails pattern,
|
||||
int priority,
|
||||
boolean allowSimulation) {
|
||||
super(priority);
|
||||
this.request = request;
|
||||
this.pattern = pattern;
|
||||
this.allowSimulation = allowSimulation;
|
||||
|
||||
HashBasedItemList pInputs = new HashBasedItemList();
|
||||
HashBasedItemList pOutputs = new HashBasedItemList();
|
||||
HashBasedItemList pRecInputs = new HashBasedItemList();
|
||||
Arrays.stream(pattern.getInputs()).filter(Objects::nonNull).forEach(pInputs::add);
|
||||
Arrays.stream(pattern.getOutputs()).filter(Objects::nonNull).forEach(pOutputs::add);
|
||||
for (IAEItemStack output : pOutputs) {
|
||||
IAEItemStack input = pInputs.findPrecise(output);
|
||||
if (input != null) {
|
||||
final long netProduced = output.getStackSize() - input.getStackSize();
|
||||
if (netProduced > 0) {
|
||||
pRecInputs.add(input);
|
||||
input.setStackSize(0);
|
||||
output.setStackSize(netProduced);
|
||||
} else {
|
||||
// Ensure recInput.stackSize + input.stackSize == original input.stackSize
|
||||
pRecInputs.add(input.copy().setStackSize(input.getStackSize() + netProduced));
|
||||
input.setStackSize(-netProduced);
|
||||
output.setStackSize(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.patternInputs = pInputs.toArray(new IAEItemStack[0]);
|
||||
this.patternOutputs = pOutputs.toArray(new IAEItemStack[0]);
|
||||
this.patternRecursionInputs = pRecInputs.toArray(new IAEItemStack[0]);
|
||||
IAEItemStack foundMatchingOutput = null;
|
||||
for (final IAEItemStack patternOutput : patternOutputs) {
|
||||
if (isOutputSameAs(patternOutput)) {
|
||||
foundMatchingOutput = patternOutput;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundMatchingOutput == null) {
|
||||
state = State.FAILURE;
|
||||
throw new IllegalStateException("Invalid pattern crafting step for " + request);
|
||||
}
|
||||
this.matchingOutput = foundMatchingOutput;
|
||||
}
|
||||
|
||||
public boolean isOutputSameAs(IAEItemStack otherStack) {
|
||||
if (request.substitutionMode == SubstitutionMode.ACCEPT_FUZZY) {
|
||||
return this.request.stack.fuzzyComparison(otherStack, FuzzyMode.IGNORE_ALL);
|
||||
} else {
|
||||
return this.request.stack.isSameType(otherStack);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isValidSubstitute(IAEItemStack reference, IAEItemStack stack, World world) {
|
||||
if (!pattern.isCraftable()) {
|
||||
return true;
|
||||
}
|
||||
IAEItemStack[] rawInputs = pattern.getInputs();
|
||||
for (int slot = 0; slot < rawInputs.length; slot++) {
|
||||
if (rawInputs[slot] != null && rawInputs[slot].isSameType(reference)) {
|
||||
return pattern.isValidItemForSlot(slot, stack.getItemStack(), world);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StepOutput calculateOneStep(CraftingContext context) {
|
||||
if (request.remainingToProcess <= 0) {
|
||||
state = State.SUCCESS;
|
||||
return new StepOutput(Collections.emptyList());
|
||||
}
|
||||
final boolean canUseSubstitutes = pattern.canSubstitute();
|
||||
final SubstitutionMode childMode =
|
||||
canUseSubstitutes ? SubstitutionMode.ACCEPT_FUZZY : SubstitutionMode.PRECISE;
|
||||
final long toCraft = Platform.ceilDiv(request.remainingToProcess, matchingOutput.getStackSize());
|
||||
|
||||
if (requestedInputs) {
|
||||
// Calculate how many full recipes we could fulfill
|
||||
long maxCraftable = toCraft;
|
||||
for (CraftingRequest<IAEItemStack> recInputChild : childRecursionRequests.values()) {
|
||||
if (recInputChild.remainingToProcess > 0) {
|
||||
// If we can't resolve an input to the recursive process, we can't craft anything at all
|
||||
maxCraftable = 0;
|
||||
}
|
||||
}
|
||||
for (RequestAndPerCraftAmount inputChildPair : childRequests.values()) {
|
||||
final CraftingRequest<IAEItemStack> inputChild = inputChildPair.request;
|
||||
final long costPerRecipe = inputChild.stack.getStackSize() / toCraft;
|
||||
final long available = inputChild.stack.getStackSize() - inputChild.remainingToProcess;
|
||||
final long fullRecipes = available / costPerRecipe;
|
||||
maxCraftable = Math.min(maxCraftable, fullRecipes);
|
||||
}
|
||||
// Fulfill those recipes
|
||||
request.fulfill(
|
||||
this,
|
||||
matchingOutput
|
||||
.copy()
|
||||
.setStackSize(Math.multiplyExact(maxCraftable, matchingOutput.getStackSize())),
|
||||
context);
|
||||
for (IAEItemStack output : patternOutputs) {
|
||||
// add byproducts to the system
|
||||
if (output != matchingOutput) {
|
||||
final IAEItemStack injected =
|
||||
output.copy().setStackSize(Math.multiplyExact(maxCraftable, output.getStackSize()));
|
||||
context.itemModel.injectItems(injected, Actionable.MODULATE, context.actionSource);
|
||||
this.byproducts.put(injected.copy(), output.getStackSize());
|
||||
}
|
||||
}
|
||||
this.totalCraftsDone = maxCraftable;
|
||||
if (maxCraftable != toCraft) {
|
||||
// Need to refund some items as not everything could be crafted.
|
||||
for (RequestAndPerCraftAmount inputChildPair : childRequests.values()) {
|
||||
final CraftingRequest<IAEItemStack> inputChild = inputChildPair.request;
|
||||
final long actuallyNeeded = Math.multiplyExact(inputChild.stack.getStackSize(), maxCraftable);
|
||||
final long produced =
|
||||
inputChild.stack.getStackSize() - Math.max(inputChild.remainingToProcess, 0);
|
||||
if (produced > actuallyNeeded) {
|
||||
if (maxCraftable == 0) {
|
||||
inputChild.fullRefund(context);
|
||||
} else {
|
||||
inputChild.partialRefund(context, produced - actuallyNeeded);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we couldn't craft even a single recipe, refund recursive inputs too
|
||||
if (maxCraftable == 0) {
|
||||
for (CraftingRequest<IAEItemStack> recChild : childRecursionRequests.values()) {
|
||||
recChild.fullRefund(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
state = State.SUCCESS;
|
||||
return new StepOutput(Collections.emptyList());
|
||||
} else {
|
||||
ArrayList<CraftingRequest<IAEItemStack>> newChildren = new ArrayList<>(patternInputs.length);
|
||||
if (patternRecursionInputs.length > 0) {
|
||||
for (IAEItemStack recInput : patternRecursionInputs) {
|
||||
CraftingRequest<IAEItemStack> req = new CraftingRequest<>(
|
||||
recInput.copy(),
|
||||
childMode,
|
||||
IAEItemStack.class,
|
||||
allowSimulation,
|
||||
stack -> this.isValidSubstitute(recInput, stack, context.world));
|
||||
newChildren.add(req);
|
||||
childRecursionRequests.put(recInput, req);
|
||||
}
|
||||
state = State.NEEDS_MORE_WORK;
|
||||
}
|
||||
for (IAEItemStack input : patternInputs) {
|
||||
final long amount = Math.multiplyExact(input.getStackSize(), toCraft);
|
||||
CraftingRequest<IAEItemStack> req = new CraftingRequest<>(
|
||||
input.copy().setStackSize(amount),
|
||||
childMode,
|
||||
IAEItemStack.class,
|
||||
allowSimulation,
|
||||
stack -> this.isValidSubstitute(input, stack, context.world));
|
||||
newChildren.add(req);
|
||||
childRequests.put(input, new RequestAndPerCraftAmount(req, input.getStackSize()));
|
||||
}
|
||||
requestedInputs = true;
|
||||
state = State.NEEDS_MORE_WORK;
|
||||
return new StepOutput(Collections.unmodifiableList(newChildren));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void partialRefund(CraftingContext context, long amount) {
|
||||
if (amount >= totalCraftsDone) {
|
||||
fullRefund(context);
|
||||
return;
|
||||
}
|
||||
totalCraftsDone -= amount;
|
||||
amount = amount / matchingOutput.getStackSize();
|
||||
if (amount == 0) {
|
||||
return;
|
||||
}
|
||||
for (RequestAndPerCraftAmount subrequest : childRequests.values()) {
|
||||
subrequest.request.partialRefund(context, subrequest.perCraftAmount * amount);
|
||||
}
|
||||
for (Entry<IAEItemStack, Long> entry : byproducts.entrySet()) {
|
||||
final IAEItemStack byproductStack = entry.getKey();
|
||||
final long perCraft = entry.getValue();
|
||||
context.itemModel.extractItems(
|
||||
byproductStack.copy().setStackSize(perCraft * amount),
|
||||
Actionable.MODULATE,
|
||||
context.actionSource);
|
||||
entry.getKey().setStackSize(byproductStack.getStackSize() - amount * perCraft);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fullRefund(CraftingContext context) {
|
||||
totalCraftsDone = 0;
|
||||
childRequests.values().forEach(req -> req.request.fullRefund(context));
|
||||
childRequests.clear();
|
||||
childRecursionRequests.values().forEach(req -> req.fullRefund(context));
|
||||
childRecursionRequests.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populatePlan(IItemList<IAEItemStack> targetPlan) {
|
||||
if (totalCraftsDone == 0) {
|
||||
return;
|
||||
}
|
||||
for (IAEItemStack output : patternOutputs) {
|
||||
targetPlan.addRequestable(output.copy().setCountRequestable(output.getStackSize() * totalCraftsDone));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startOnCpu(
|
||||
CraftingContext context, CraftingCPUCluster cpuCluster, MECraftingInventory craftingInv) {
|
||||
cpuCluster.addCrafting(pattern, totalCraftsDone);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CraftFromPatternTask{" + "request="
|
||||
+ request + ", pattern="
|
||||
+ pattern + ", allowSimulation="
|
||||
+ allowSimulation + ", matchingOutput="
|
||||
+ matchingOutput + ", requestedInputs="
|
||||
+ requestedInputs + ", totalCraftsDone="
|
||||
+ totalCraftsDone + ", priority="
|
||||
+ priority + ", state="
|
||||
+ state + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<CraftingTask> provideCraftingRequestResolvers(
|
||||
@Nonnull CraftingRequest<IAEItemStack> request, @Nonnull CraftingContext context) {
|
||||
ImmutableList.Builder<CraftingTask> tasks = new ImmutableList.Builder<>();
|
||||
final List<ICraftingPatternDetails> patterns = new ArrayList<>(context.getPrecisePatternsFor(request.stack));
|
||||
patterns.sort(Comparator.comparing(ICraftingPatternDetails::getPriority).reversed());
|
||||
// If fuzzy patterns are allowed,
|
||||
if (request.substitutionMode == SubstitutionMode.ACCEPT_FUZZY) {
|
||||
final List<ICraftingPatternDetails> fuzzyPatterns =
|
||||
new ArrayList<>(context.getFuzzyPatternsFor(request.stack));
|
||||
fuzzyPatterns.sort(
|
||||
Comparator.comparing(ICraftingPatternDetails::getPriority).reversed());
|
||||
patterns.addAll(fuzzyPatterns);
|
||||
}
|
||||
int priority = CraftingTask.PRIORITY_CRAFT_OFFSET + patterns.size() - 1;
|
||||
for (ICraftingPatternDetails pattern : patterns) {
|
||||
tasks.add(new CraftFromPatternTask(request, pattern, priority, false));
|
||||
priority--;
|
||||
}
|
||||
// Fallback: use highest priority pattern to simulate if nothing else works
|
||||
if (!patterns.isEmpty()) {
|
||||
tasks.add(new CraftFromPatternTask(request, patterns.get(0), CraftingTask.PRIORITY_SIMULATE_CRAFT, true));
|
||||
}
|
||||
return tasks.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package appeng.crafting.v2.resolvers;
|
||||
|
||||
import appeng.api.storage.data.IAEStack;
|
||||
import appeng.crafting.v2.CraftingContext;
|
||||
import appeng.crafting.v2.CraftingRequest;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* See {@link CraftingRequestResolver#provideCraftingRequestResolvers(CraftingRequest, CraftingContext)}
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CraftingRequestResolver<StackType extends IAEStack<StackType>> {
|
||||
/**
|
||||
* Provides a list of potential solutions for doing one crafting step, the calculator will try the solutions ordered by priority in turn.
|
||||
* For example, given a plank item, it can give tasks for log->plank crafting
|
||||
* Higher priority = will be used first, Integer.MAX_VALUE priority is used for items already in the system. <p>
|
||||
* <p>
|
||||
* It should reduce the {@link CraftingRequest#remainingToProcess} value by the number of items immediately available.
|
||||
*
|
||||
* @param request The request being resolved
|
||||
* @param context The ME system, job queue and pattern caches used to calculate potential resolutions
|
||||
* @return The list of potential solutions - return an empty list if none are available
|
||||
*/
|
||||
@Nonnull
|
||||
List<CraftingTask> provideCraftingRequestResolvers(
|
||||
@Nonnull CraftingRequest<StackType> request, @Nonnull CraftingContext context);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package appeng.crafting.v2.resolvers;
|
||||
|
||||
import appeng.api.storage.data.IAEItemStack;
|
||||
import appeng.api.storage.data.IItemList;
|
||||
import appeng.crafting.MECraftingInventory;
|
||||
import appeng.crafting.v2.CraftingContext;
|
||||
import appeng.crafting.v2.CraftingRequest;
|
||||
import appeng.me.cluster.implementations.CraftingCPUCluster;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* A single action that can be performed to solve a {@link CraftingRequest}.
|
||||
* Can have multiple inputs and outputs, resolved at runtime during crafting resolution (e.g. for handling substitutions).
|
||||
*/
|
||||
public abstract class CraftingTask {
|
||||
public enum State {
|
||||
NEEDS_MORE_WORK(true),
|
||||
SUCCESS(false),
|
||||
/**
|
||||
* This aborts the entire crafting operation, use only if absolutely necessary
|
||||
*/
|
||||
FAILURE(false);
|
||||
public final boolean needsMoreWork;
|
||||
|
||||
State(boolean needsMoreWork) {
|
||||
this.needsMoreWork = needsMoreWork;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class StepOutput {
|
||||
@Nonnull
|
||||
public final List<CraftingRequest<?>> extraInputsRequired;
|
||||
|
||||
public StepOutput() {
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
public StepOutput(@Nonnull List<CraftingRequest<?>> extraInputsRequired) {
|
||||
this.extraInputsRequired = extraInputsRequired;
|
||||
}
|
||||
}
|
||||
|
||||
public static final int PRIORITY_EXTRACT = Integer.MAX_VALUE - 100;
|
||||
public static final int PRIORITY_CRAFTING_EMITTER = PRIORITY_EXTRACT - 200;
|
||||
/** Gets added to a priority determined by the priority of the crafting pattern */
|
||||
public static final int PRIORITY_CRAFT_OFFSET = 0;
|
||||
|
||||
public static final int PRIORITY_SIMULATE_CRAFT = Integer.MIN_VALUE + 200;
|
||||
public static final int PRIORITY_SIMULATE = Integer.MIN_VALUE + 100;
|
||||
|
||||
public final int priority;
|
||||
protected State state;
|
||||
|
||||
/**
|
||||
* Called when it's this task's turn for computation.
|
||||
*
|
||||
* @return A {@link StepOutput} instance describing progress made by the task in this call.
|
||||
* If success or failure, the task should have cleaned up after itself - cancel won't be called.
|
||||
*/
|
||||
public abstract StepOutput calculateOneStep(CraftingContext context);
|
||||
|
||||
public abstract void partialRefund(CraftingContext context, long amount);
|
||||
|
||||
public abstract void fullRefund(CraftingContext context);
|
||||
|
||||
public abstract void populatePlan(IItemList<IAEItemStack> targetPlan);
|
||||
|
||||
public abstract void startOnCpu(
|
||||
CraftingContext context, CraftingCPUCluster cpuCluster, MECraftingInventory craftingInv);
|
||||
|
||||
protected CraftingTask(int priority) {
|
||||
this.priority = priority;
|
||||
this.state = State.NEEDS_MORE_WORK;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares priorities - highest priority first
|
||||
*/
|
||||
public static final Comparator<CraftingTask> PRIORITY_COMPARATOR = Comparator.comparing(ct -> -ct.priority);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package appeng.crafting.v2.resolvers;
|
||||
|
||||
import appeng.api.storage.data.IAEItemStack;
|
||||
import appeng.api.storage.data.IItemList;
|
||||
import appeng.crafting.MECraftingInventory;
|
||||
import appeng.crafting.v2.CraftingContext;
|
||||
import appeng.crafting.v2.CraftingRequest;
|
||||
import appeng.me.cluster.implementations.CraftingCPUCluster;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class EmitableItemResolver implements CraftingRequestResolver<IAEItemStack> {
|
||||
public static class EmitItemTask extends CraftingTask {
|
||||
public final CraftingRequest<IAEItemStack> request;
|
||||
|
||||
public EmitItemTask(CraftingRequest<IAEItemStack> request) {
|
||||
super(CraftingTask.PRIORITY_CRAFTING_EMITTER); // conjure items for calculations out of thin air as a last
|
||||
// resort
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StepOutput calculateOneStep(CraftingContext context) {
|
||||
if (request.remainingToProcess <= 0) {
|
||||
state = State.SUCCESS;
|
||||
return new StepOutput(Collections.emptyList());
|
||||
}
|
||||
// Assume items will be generated, triggered by the emitter
|
||||
request.fulfill(this, request.stack.copy().setStackSize(request.remainingToProcess), context);
|
||||
state = State.SUCCESS;
|
||||
return new StepOutput(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void partialRefund(CraftingContext context, long amount) {
|
||||
// no-op: items were simulated to be emitted, so there's nothing to refund
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fullRefund(CraftingContext context) {
|
||||
// no-op: items were simulated to be emitted, so there's nothing to refund
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populatePlan(IItemList<IAEItemStack> targetPlan) {
|
||||
targetPlan.addRequestable(request.stack.copy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startOnCpu(
|
||||
CraftingContext context, CraftingCPUCluster cpuCluster, MECraftingInventory craftingInv) {
|
||||
cpuCluster.addEmitable(this.request.stack.copy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EmitItemTask{" + "request=" + request + ", priority=" + priority + ", state=" + state + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<CraftingTask> provideCraftingRequestResolvers(
|
||||
@Nonnull CraftingRequest<IAEItemStack> request, @Nonnull CraftingContext context) {
|
||||
if (context.craftingGrid.canEmitFor(request.stack)) {
|
||||
return Collections.singletonList(new EmitItemTask(request));
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package appeng.crafting.v2.resolvers;
|
||||
|
||||
import appeng.api.config.Actionable;
|
||||
import appeng.api.config.FuzzyMode;
|
||||
import appeng.api.storage.data.IAEItemStack;
|
||||
import appeng.api.storage.data.IItemList;
|
||||
import appeng.crafting.CraftBranchFailure;
|
||||
import appeng.crafting.MECraftingInventory;
|
||||
import appeng.crafting.v2.CraftingContext;
|
||||
import appeng.crafting.v2.CraftingRequest;
|
||||
import appeng.me.cluster.implementations.CraftingCPUCluster;
|
||||
import java.util.*;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class ExtractItemResolver implements CraftingRequestResolver<IAEItemStack> {
|
||||
public static class ExtractItemTask extends CraftingTask {
|
||||
public final CraftingRequest<IAEItemStack> request;
|
||||
public final List<IAEItemStack> removedFromSystem = new ArrayList<>();
|
||||
|
||||
public ExtractItemTask(CraftingRequest<IAEItemStack> request) {
|
||||
super(CraftingTask.PRIORITY_EXTRACT); // always try to extract items first
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StepOutput calculateOneStep(CraftingContext context) {
|
||||
state = State.SUCCESS;
|
||||
if (request.remainingToProcess <= 0) {
|
||||
return new StepOutput(Collections.emptyList());
|
||||
}
|
||||
// Extract exact
|
||||
IAEItemStack exactMatching = context.itemModel.getItemList().findPrecise(request.stack);
|
||||
if (exactMatching != null) {
|
||||
final long requestSize = Math.min(request.remainingToProcess, exactMatching.getStackSize());
|
||||
final IAEItemStack extracted = context.itemModel.extractItems(
|
||||
exactMatching.copy().setStackSize(requestSize), Actionable.MODULATE, context.actionSource);
|
||||
request.fulfill(this, extracted, context);
|
||||
removedFromSystem.add(extracted.copy());
|
||||
}
|
||||
// Extract fuzzy
|
||||
if (request.remainingToProcess > 0
|
||||
&& request.substitutionMode == CraftingRequest.SubstitutionMode.ACCEPT_FUZZY) {
|
||||
Collection<IAEItemStack> fuzzyMatching =
|
||||
context.itemModel.getItemList().findFuzzy(request.stack, FuzzyMode.IGNORE_ALL);
|
||||
for (final IAEItemStack candidate : fuzzyMatching) {
|
||||
if (candidate == null) {
|
||||
continue;
|
||||
}
|
||||
if (request.acceptableSubstituteFn.test(candidate)) {
|
||||
final long requestSize = Math.min(request.remainingToProcess, candidate.getStackSize());
|
||||
final IAEItemStack extracted = context.itemModel.extractItems(
|
||||
candidate.copy().setStackSize(requestSize), Actionable.MODULATE, context.actionSource);
|
||||
request.fulfill(this, extracted, context);
|
||||
removedFromSystem.add(extracted.copy());
|
||||
}
|
||||
}
|
||||
}
|
||||
return new StepOutput(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void partialRefund(CraftingContext context, long amount) {
|
||||
// Remove fuzzy things first
|
||||
Collections.reverse(removedFromSystem);
|
||||
final Iterator<IAEItemStack> removedIt = removedFromSystem.iterator();
|
||||
while (removedIt.hasNext() && amount > 0) {
|
||||
final IAEItemStack available = removedIt.next();
|
||||
final long availAmount = available.getStackSize();
|
||||
if (availAmount > amount) {
|
||||
context.itemModel.injectItems(
|
||||
available.copy().setStackSize(amount), Actionable.MODULATE, context.actionSource);
|
||||
available.setStackSize(availAmount - amount);
|
||||
amount = 0;
|
||||
} else {
|
||||
context.itemModel.injectItems(available, Actionable.MODULATE, context.actionSource);
|
||||
amount -= availAmount;
|
||||
removedIt.remove();
|
||||
}
|
||||
}
|
||||
Collections.reverse(removedFromSystem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fullRefund(CraftingContext context) {
|
||||
for (IAEItemStack removed : removedFromSystem) {
|
||||
context.itemModel.injectItems(removed, Actionable.MODULATE, context.actionSource);
|
||||
}
|
||||
removedFromSystem.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populatePlan(IItemList<IAEItemStack> targetPlan) {
|
||||
for (IAEItemStack removed : removedFromSystem) {
|
||||
targetPlan.add(removed.copy());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startOnCpu(
|
||||
CraftingContext context, CraftingCPUCluster cpuCluster, MECraftingInventory craftingInv) {
|
||||
for (IAEItemStack stack : removedFromSystem) {
|
||||
if (stack.getStackSize() > 0) {
|
||||
IAEItemStack extracted = craftingInv.extractItems(stack, Actionable.MODULATE, context.actionSource);
|
||||
if (extracted == null || extracted.getStackSize() != stack.getStackSize()) {
|
||||
throw new CraftBranchFailure(stack, stack.getStackSize());
|
||||
}
|
||||
cpuCluster.addStorage(extracted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ExtractItemTask{" + "request="
|
||||
+ request + ", removedFromSystem="
|
||||
+ removedFromSystem + ", priority="
|
||||
+ priority + ", state="
|
||||
+ state + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<CraftingTask> provideCraftingRequestResolvers(
|
||||
@Nonnull CraftingRequest<IAEItemStack> request, @Nonnull CraftingContext context) {
|
||||
if (request.substitutionMode == CraftingRequest.SubstitutionMode.PRECISE_FRESH) {
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
return Collections.singletonList(new ExtractItemTask(request));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package appeng.crafting.v2.resolvers;
|
||||
|
||||
import appeng.api.storage.data.IAEItemStack;
|
||||
import appeng.api.storage.data.IAEStack;
|
||||
import appeng.api.storage.data.IItemList;
|
||||
import appeng.crafting.MECraftingInventory;
|
||||
import appeng.crafting.v2.CraftingContext;
|
||||
import appeng.crafting.v2.CraftingRequest;
|
||||
import appeng.me.cluster.implementations.CraftingCPUCluster;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class SimulateMissingItemResolver<StackType extends IAEStack<StackType>>
|
||||
implements CraftingRequestResolver<StackType> {
|
||||
public static class ConjureItemTask<StackType extends IAEStack<StackType>> extends CraftingTask {
|
||||
public final CraftingRequest<StackType> request;
|
||||
|
||||
public ConjureItemTask(CraftingRequest<StackType> request) {
|
||||
super(CraftingTask.PRIORITY_SIMULATE); // conjure items for calculations out of thin air as a last resort
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StepOutput calculateOneStep(CraftingContext context) {
|
||||
state = State.SUCCESS;
|
||||
if (request.remainingToProcess <= 0) {
|
||||
return new StepOutput(Collections.emptyList());
|
||||
}
|
||||
// Simulate items existing
|
||||
request.wasSimulated = true;
|
||||
context.wasSimulated = true;
|
||||
request.fulfill(this, request.stack.copy().setStackSize(request.remainingToProcess), context);
|
||||
return new StepOutput(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void partialRefund(CraftingContext context, long amount) {
|
||||
// no-op: items were simulated, so there's nothing to refund
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fullRefund(CraftingContext context) {
|
||||
// no-op: items were simulated, so there's nothing to refund
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populatePlan(IItemList<IAEItemStack> targetPlan) {
|
||||
if (request.stack instanceof IAEItemStack) {
|
||||
targetPlan.add((IAEItemStack) request.stack.copy());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startOnCpu(
|
||||
CraftingContext context, CraftingCPUCluster cpuCluster, MECraftingInventory craftingInv) {
|
||||
throw new IllegalStateException("Trying to start crafting a schedule with simulated items");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConjureItemTask{" + "request=" + request + ", priority=" + priority + ", state=" + state + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<CraftingTask> provideCraftingRequestResolvers(
|
||||
@Nonnull CraftingRequest<StackType> request, @Nonnull CraftingContext context) {
|
||||
if (request.allowSimulation) {
|
||||
return Collections.singletonList(new ConjureItemTask<StackType>(request));
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,13 +20,13 @@ package appeng.hooks;
|
||||
|
||||
import appeng.api.AEApi;
|
||||
import appeng.api.networking.IGridNode;
|
||||
import appeng.api.networking.crafting.ICraftingJob;
|
||||
import appeng.api.parts.CableRenderMode;
|
||||
import appeng.api.util.AEColor;
|
||||
import appeng.core.AEConfig;
|
||||
import appeng.core.AELog;
|
||||
import appeng.core.CommonHelper;
|
||||
import appeng.core.sync.packets.PacketPaintedEntity;
|
||||
import appeng.crafting.CraftingJob;
|
||||
import appeng.entity.EntityFloatingItem;
|
||||
import appeng.me.Grid;
|
||||
import appeng.me.NetworkList;
|
||||
@@ -51,7 +51,7 @@ public class TickHandler {
|
||||
|
||||
public static final TickHandler INSTANCE = new TickHandler();
|
||||
private final Queue<IWorldCallable<?>> serverQueue = new LinkedList<IWorldCallable<?>>();
|
||||
private final Multimap<World, CraftingJob> craftingJobs = LinkedListMultimap.create();
|
||||
private final Multimap<World, ICraftingJob> craftingJobs = LinkedListMultimap.create();
|
||||
private final WeakHashMap<World, Queue<IWorldCallable<?>>> callQueue =
|
||||
new WeakHashMap<World, Queue<IWorldCallable<?>>>();
|
||||
private final HandlerRep server = new HandlerRep();
|
||||
@@ -163,12 +163,12 @@ public class TickHandler {
|
||||
if (ev.type == Type.WORLD && ev.phase == Phase.END) {
|
||||
final WorldTickEvent wte = (WorldTickEvent) ev;
|
||||
synchronized (this.craftingJobs) {
|
||||
final Collection<CraftingJob> jobSet = this.craftingJobs.get(wte.world);
|
||||
final Collection<ICraftingJob> jobSet = this.craftingJobs.get(wte.world);
|
||||
if (!jobSet.isEmpty()) {
|
||||
final int simTime = Math.max(1, AEConfig.instance.craftingCalculationTimePerTick / jobSet.size());
|
||||
final Iterator<CraftingJob> i = jobSet.iterator();
|
||||
final Iterator<ICraftingJob> i = jobSet.iterator();
|
||||
while (i.hasNext()) {
|
||||
final CraftingJob cj = i.next();
|
||||
final ICraftingJob cj = i.next();
|
||||
if (!cj.simulateFor(simTime)) {
|
||||
i.remove();
|
||||
}
|
||||
@@ -242,7 +242,7 @@ public class TickHandler {
|
||||
// AELog.info( "processQueue Time: " + time + "ms" );
|
||||
}
|
||||
|
||||
public void registerCraftingSimulation(final World world, final CraftingJob craftingJob) {
|
||||
public void registerCraftingSimulation(final World world, final ICraftingJob craftingJob) {
|
||||
synchronized (this.craftingJobs) {
|
||||
this.craftingJobs.put(world, craftingJob);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public class NEIGuiHandler extends INEIGuiAdapter {
|
||||
return false;
|
||||
}
|
||||
|
||||
private String formattingText(final String displayName) {
|
||||
protected String formattingText(final String displayName) {
|
||||
return SPECIAL_REGEX_CHARS
|
||||
.matcher(EnumChatFormatting.getTextWithoutFormattingCodes(displayName))
|
||||
.replaceAll("\\\\$0");
|
||||
|
||||
+30
-42
@@ -20,7 +20,6 @@ package appeng.me.cache;
|
||||
|
||||
import appeng.api.config.AccessRestriction;
|
||||
import appeng.api.config.Actionable;
|
||||
import appeng.api.config.FuzzyMode;
|
||||
import appeng.api.networking.IGrid;
|
||||
import appeng.api.networking.IGridHost;
|
||||
import appeng.api.networking.IGridNode;
|
||||
@@ -39,10 +38,12 @@ import appeng.api.storage.StorageChannel;
|
||||
import appeng.api.storage.data.IAEItemStack;
|
||||
import appeng.api.storage.data.IAEStack;
|
||||
import appeng.api.storage.data.IItemList;
|
||||
import appeng.core.AEConfig;
|
||||
import appeng.crafting.CraftingJob;
|
||||
import appeng.crafting.CraftingLink;
|
||||
import appeng.crafting.CraftingLinkNexus;
|
||||
import appeng.crafting.CraftingWatcher;
|
||||
import appeng.crafting.v2.CraftingJobV2;
|
||||
import appeng.me.cluster.implementations.CraftingCPUCluster;
|
||||
import appeng.me.helpers.GenericInterestManager;
|
||||
import appeng.tile.crafting.TileCraftingStorageTile;
|
||||
@@ -410,60 +411,30 @@ public class CraftingGridCache
|
||||
return StorageChannel.ITEMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableMap<IAEItemStack, ImmutableList<ICraftingPatternDetails>> getCraftingPatterns() {
|
||||
return ImmutableMap.copyOf(this.craftableItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableCollection<ICraftingPatternDetails> getCraftingFor(
|
||||
final IAEItemStack whatToCraft,
|
||||
final ICraftingPatternDetails details,
|
||||
final int slotIndex,
|
||||
final World world) {
|
||||
ImmutableList<ICraftingPatternDetails> res;
|
||||
boolean normalMode = false;
|
||||
if (details != null && details.canSubstitute()) {
|
||||
final ImmutableList<ICraftingPatternDetails> substitutions = this.craftableItemSubstitutes.get(whatToCraft);
|
||||
if (substitutions.isEmpty()) {
|
||||
res = this.craftableItems.get(whatToCraft);
|
||||
normalMode = true;
|
||||
} else {
|
||||
res = substitutions;
|
||||
}
|
||||
} else if (details == null) {
|
||||
final ImmutableList<ICraftingPatternDetails> substitutions =
|
||||
this.craftableItemSubstitutes.getBeSubstitutePattern(whatToCraft);
|
||||
if (substitutions.isEmpty()) {
|
||||
res = this.craftableItems.get(whatToCraft);
|
||||
normalMode = true;
|
||||
} else {
|
||||
res = this.craftableItems.get(whatToCraft);
|
||||
}
|
||||
} else {
|
||||
res = this.craftableItems.get(whatToCraft);
|
||||
}
|
||||
final ImmutableList<ICraftingPatternDetails> res = this.craftableItems.get(whatToCraft);
|
||||
|
||||
if (res == null) {
|
||||
if (details != null && details.isCraftable()) {
|
||||
for (final IAEItemStack ais : this.craftableItems.keySet()) {
|
||||
final boolean perfectMatch = ais.getItem() == whatToCraft.getItem()
|
||||
&& (!ais.getItem().getHasSubtypes() || ais.getItemDamage() == whatToCraft.getItemDamage());
|
||||
if (perfectMatch) {
|
||||
if (ais.getItem() == whatToCraft.getItem()
|
||||
&& (!ais.getItem().getHasSubtypes()
|
||||
|| ais.getItemDamage() == whatToCraft.getItemDamage())) {
|
||||
if (details.isValidItemForSlot(slotIndex, ais.getItemStack(), world)) {
|
||||
return this.craftableItems.get(ais);
|
||||
}
|
||||
}
|
||||
}
|
||||
// no perfect match found, look for substitutions
|
||||
if (details.canSubstitute() && !normalMode) {
|
||||
for (final Map.Entry<IAEItemStack, ImmutableList<ICraftingPatternDetails>> entry :
|
||||
this.craftableItems.entrySet()) {
|
||||
final boolean canBeASubstitute =
|
||||
entry.getValue().stream().anyMatch(cp -> (cp != null) && cp.canBeSubstitute());
|
||||
if (canBeASubstitute && entry.getKey().fuzzyComparison(whatToCraft, FuzzyMode.IGNORE_ALL)) {
|
||||
if (details.isValidItemForSlot(
|
||||
slotIndex, entry.getKey().getItemStack(), world)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ImmutableSet.of();
|
||||
@@ -472,6 +443,13 @@ public class CraftingGridCache
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The task pool for executing crafting calculations.
|
||||
*/
|
||||
public static ExecutorService getCraftingPool() {
|
||||
return CRAFTING_POOL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<ICraftingJob> beginCraftingJob(
|
||||
final World world,
|
||||
@@ -483,9 +461,19 @@ public class CraftingGridCache
|
||||
throw new IllegalArgumentException("Invalid Crafting Job Request");
|
||||
}
|
||||
|
||||
final CraftingJob job = new CraftingJob(world, grid, actionSrc, slotItem, cb);
|
||||
final ICraftingJob job;
|
||||
switch (AEConfig.instance.craftingCalculatorVersion) {
|
||||
case 1:
|
||||
job = new CraftingJob(world, grid, actionSrc, slotItem, cb);
|
||||
break;
|
||||
case 2:
|
||||
job = new CraftingJobV2(world, grid, actionSrc, slotItem, cb);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Invalid crafting calculator version");
|
||||
}
|
||||
|
||||
return CRAFTING_POOL.submit(job, (ICraftingJob) job);
|
||||
return job.schedule();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -54,6 +54,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.IntStream;
|
||||
import net.minecraft.inventory.InventoryCrafting;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
@@ -407,29 +408,46 @@ public final class CraftingCPUCluster implements IAECluster, ICraftingCPU {
|
||||
return null;
|
||||
}
|
||||
|
||||
private ArrayList<IAEItemStack> getExtractItems(IAEItemStack g, ICraftingPatternDetails details) {
|
||||
ArrayList<IAEItemStack> list = new ArrayList<IAEItemStack>();
|
||||
if (details.canBeSubstitute()) {
|
||||
for (IAEItemStack fuzz : this.inventory.getItemList().findFuzzy(g, FuzzyMode.IGNORE_ALL)) {
|
||||
if (!details.isCraftable() && fuzz.getStackSize() <= 0) continue;
|
||||
private ArrayList<IAEItemStack> getExtractItems(IAEItemStack ingredient, ICraftingPatternDetails patternDetails) {
|
||||
ArrayList<IAEItemStack> list = new ArrayList<>();
|
||||
if (patternDetails.canSubstitute()) {
|
||||
for (IAEItemStack fuzz : this.inventory.getItemList().findFuzzy(ingredient, FuzzyMode.IGNORE_ALL)) {
|
||||
if (!patternDetails.isCraftable() && fuzz.getStackSize() <= 0) continue;
|
||||
if (patternDetails.isCraftable()) {
|
||||
final IAEItemStack[] inputSlots = patternDetails.getInputs();
|
||||
final IAEItemStack finalIngredient =
|
||||
ingredient; // have to copy because of Java lambda capture rules here
|
||||
final int matchingSlot = IntStream.range(0, inputSlots.length)
|
||||
.filter(idx -> inputSlots[idx] != null && inputSlots[idx].equals(finalIngredient))
|
||||
.findFirst()
|
||||
.orElse(-1);
|
||||
if (matchingSlot < 0) {
|
||||
continue;
|
||||
}
|
||||
if (!patternDetails.isValidItemForSlot(matchingSlot, fuzz.getItemStack(), getWorld())) {
|
||||
// Skip invalid fuzzy matches
|
||||
continue;
|
||||
}
|
||||
}
|
||||
fuzz = fuzz.copy();
|
||||
fuzz.setStackSize(g.getStackSize());
|
||||
fuzz.setStackSize(ingredient.getStackSize());
|
||||
final IAEItemStack ais = this.inventory.extractItems(fuzz, Actionable.SIMULATE, this.machineSrc);
|
||||
final ItemStack is = ais == null ? null : ais.getItemStack();
|
||||
|
||||
if (is != null && is.stackSize == g.getStackSize()) {
|
||||
if (is != null && is.stackSize == ingredient.getStackSize()) {
|
||||
list.add(ais);
|
||||
return list;
|
||||
} else if (is != null && details.isCraftable()) {
|
||||
g = g.copy();
|
||||
g.decStackSize(is.stackSize);
|
||||
} else if (is != null && patternDetails.isCraftable()) {
|
||||
ingredient = ingredient.copy();
|
||||
ingredient.decStackSize(is.stackSize);
|
||||
list.add(ais);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final IAEItemStack extractItems = this.inventory.extractItems(g, Actionable.SIMULATE, this.machineSrc);
|
||||
final IAEItemStack extractItems =
|
||||
this.inventory.extractItems(ingredient, Actionable.SIMULATE, this.machineSrc);
|
||||
final ItemStack is = extractItems == null ? null : extractItems.getItemStack();
|
||||
if (is != null && is.stackSize == g.getStackSize()) {
|
||||
if (is != null && is.stackSize == ingredient.getStackSize()) {
|
||||
list.add(extractItems);
|
||||
return list;
|
||||
}
|
||||
@@ -704,11 +722,11 @@ public final class CraftingCPUCluster implements IAECluster, ICraftingCPU {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(job instanceof CraftingJob)) {
|
||||
if (this.isBusy() || !this.isActive() || this.availableStorage < job.getByteTotal()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.isBusy() || !this.isActive() || this.availableStorage < job.getByteTotal()) {
|
||||
if (!job.supportsCPUCluster(this)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -718,7 +736,7 @@ public final class CraftingCPUCluster implements IAECluster, ICraftingCPU {
|
||||
|
||||
try {
|
||||
this.waitingFor.resetStatus();
|
||||
((CraftingJob) job).getTree().setJob(ci, this, src);
|
||||
job.startCrafting(ci, this, src);
|
||||
if (ci.commit(src)) {
|
||||
this.finalOutput = job.getOutput();
|
||||
this.waiting = false;
|
||||
|
||||
@@ -1829,4 +1829,11 @@ public class Platform {
|
||||
|
||||
return AEItemStack.create(slot.getStack());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return (a divided by b) rounded up
|
||||
*/
|
||||
public static long ceilDiv(long a, long b) {
|
||||
return Math.addExact(Math.addExact(a, b), -1) / b;
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ public class OreListMultiMap<T> {
|
||||
private ImmutableListMultimap<Integer, T> map;
|
||||
private final Map<Integer, Collection<ICraftingPatternDetails>> patternHashMap = new HashMap<>();
|
||||
private ImmutableListMultimap<Integer, T> patternMap;
|
||||
private ImmutableListMultimap.Builder<Integer, T> builder;
|
||||
private ImmutableListMultimap.Builder<Integer, T> builder = new Builder<>();
|
||||
|
||||
private static Collection<Integer> getAEEquivalents(IAEItemStack stack) {
|
||||
AEItemStack s;
|
||||
@@ -28,6 +28,10 @@ public class OreListMultiMap<T> {
|
||||
return ids;
|
||||
}
|
||||
|
||||
public boolean isPopulated() {
|
||||
return patternMap != null;
|
||||
}
|
||||
|
||||
public void put(IAEItemStack key, ICraftingPatternDetails val) {
|
||||
if (((AEItemStack) key).getDefinition() != null) {
|
||||
Collection<ICraftingPatternDetails> tmp = patternHashMap.getOrDefault(
|
||||
|
||||
Reference in new issue
Block a user