Skip to content

Commit

Permalink
fix parallel issues (#1201)
Browse files Browse the repository at this point in the history
* remake parallel logic in hopes of it being faster this way

* fix multi smelter not overclocking (fixes #1191)

* move EU/t logic into EURecipeCapability, also check for tick inputs in ParallelLogic
  • Loading branch information
screret committed May 9, 2024
1 parent 7eee35c commit 4066e7e
Show file tree
Hide file tree
Showing 16 changed files with 626 additions and 86 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package com.gregtechceu.gtceu.api.capability.recipe;

import com.gregtechceu.gtceu.api.GTValues;
import com.gregtechceu.gtceu.api.capability.IEnergyContainer;
import com.gregtechceu.gtceu.api.machine.feature.IOverclockMachine;
import com.gregtechceu.gtceu.api.machine.feature.ITieredMachine;
import com.gregtechceu.gtceu.api.recipe.GTRecipe;
import com.gregtechceu.gtceu.api.recipe.RecipeHelper;
import com.gregtechceu.gtceu.api.recipe.content.ContentModifier;
import com.gregtechceu.gtceu.api.recipe.content.SerializerLong;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
* @author KilaBash
Expand Down Expand Up @@ -33,4 +41,32 @@ public Long copyWithModifier(Long content, ContentModifier modifier) {
public List<Object> compressIngredients(Collection<Object> ingredients) {
return List.of(ingredients.stream().map(Long.class::cast).reduce(0L, Long::sum));
}

@Override
public int limitParallel(GTRecipe recipe, IRecipeCapabilityHolder holder, int multiplier) {
long maxVoltage = Long.MAX_VALUE;
if (holder instanceof IOverclockMachine overclockMachine) {
maxVoltage = overclockMachine.getOverclockVoltage();
} else if (holder instanceof ITieredMachine tieredMachine) {
maxVoltage = GTValues.V[tieredMachine.getTier()];
}

long recipeEUt = RecipeHelper.getInputEUt(recipe);
if (recipeEUt == 0) {
recipeEUt = RecipeHelper.getOutputEUt(recipe);
}
return Math.abs((int) (maxVoltage / recipeEUt));
}

@Override
public int getMaxParallelRatio(IRecipeCapabilityHolder holder, GTRecipe recipe, int parallelAmount) {
long needed = RecipeHelper.getInputEUt(recipe);
long available = Objects.requireNonNullElseGet(holder.getCapabilitiesProxy().get(IO.IN, EURecipeCapability.CAP), Collections::emptyList)
.stream()
.filter(IEnergyContainer.class::isInstance)
.map(IEnergyContainer.class::cast)
.map(IEnergyContainer::getEnergyStored)
.reduce(0L, Long::sum);
return (int) Math.min(parallelAmount, available / needed);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
import com.gregtechceu.gtceu.api.recipe.lookup.AbstractMapIngredient;
import com.gregtechceu.gtceu.api.recipe.lookup.MapFluidIngredient;
import com.gregtechceu.gtceu.api.recipe.lookup.MapFluidTagIngredient;
import com.gregtechceu.gtceu.api.recipe.modifier.ParallelLogic;
import com.gregtechceu.gtceu.api.recipe.ui.GTRecipeTypeUI;
import com.gregtechceu.gtceu.integration.GTRecipeWidget;
import com.gregtechceu.gtceu.utils.FluidKey;
import com.gregtechceu.gtceu.utils.GTHashMaps;
import com.gregtechceu.gtceu.utils.OverlayedFluidHandler;
import com.gregtechceu.gtceu.utils.OverlayingFluidStorage;
import com.lowdragmc.lowdraglib.gui.texture.ProgressTexture;
import com.lowdragmc.lowdraglib.gui.widget.TankWidget;
Expand All @@ -23,6 +27,7 @@
import net.minecraft.core.Holder;
import net.minecraft.core.component.PatchedDataComponentMap;
import net.neoforged.neoforge.fluids.FluidStack;
import it.unimi.dsi.fastutil.objects.Object2LongLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
Expand All @@ -32,9 +37,7 @@
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

/**
Expand Down Expand Up @@ -79,6 +82,7 @@ public List<AbstractMapIngredient> convertToMapIngredient(Object obj) {
}
} else if (obj instanceof FluidStack stack) {
ingredients.add(new MapFluidIngredient(stack));
//noinspection deprecation
stack.getFluid().builtInRegistryHolder().tags().forEach(tag -> ingredients.add(new MapFluidTagIngredient(tag)));
}

Expand Down Expand Up @@ -133,6 +137,139 @@ public boolean isRecipeSearchFilter() {
return true;
}

@Override
public int limitParallel(GTRecipe recipe, IRecipeCapabilityHolder holder, int multiplier) {
int minMultiplier = 0;
int maxMultiplier = multiplier;

OverlayedFluidHandler overlayedFluidHandler = new OverlayedFluidHandler(new FluidTransferList(
Objects.requireNonNullElseGet(holder.getCapabilitiesProxy().get(IO.OUT, FluidRecipeCapability.CAP), Collections::emptyList)
.stream()
.filter(IFluidHandlerModifiable.class::isInstance)
.map(IFluidHandlerModifiable.class::cast)
.toList()
));

while (minMultiplier != maxMultiplier) {
overlayedFluidHandler.reset();

long amountLeft = 0;

for (FluidStack fluidStack : recipe.getOutputContents(FluidRecipeCapability.CAP).stream().map(FluidRecipeCapability.CAP::of).map(ingredient -> ingredient.getStacks()[0]).toList()) {
if (fluidStack.getAmount() <= 0) continue;
// Since multiplier starts at Int.MAX, check here for integer overflow
if (multiplier > Integer.MAX_VALUE / fluidStack.getAmount()) {
amountLeft = Integer.MAX_VALUE;
} else {
amountLeft = fluidStack.getAmount() * multiplier;
}
long inserted = overlayedFluidHandler.insertFluid(fluidStack, amountLeft);
if (inserted > 0) {
amountLeft -= inserted;
}
if (amountLeft > 0) {
break;
}
}

int[] bin = ParallelLogic.adjustMultiplier(amountLeft == 0, minMultiplier, multiplier, maxMultiplier);
minMultiplier = bin[0];
multiplier = bin[1];
maxMultiplier = bin[2];

}
return multiplier;
}

@Override
public int getMaxParallelRatio(IRecipeCapabilityHolder holder, GTRecipe recipe, int parallelAmount) {
// Find all the fluids in the combined Fluid Input inventories and create oversized FluidStacks
Map<FluidKey, Long> fluidStacks = Objects.requireNonNullElseGet(holder.getCapabilitiesProxy().get(IO.IN, ItemRecipeCapability.CAP), Collections::emptyList)
.stream()
.flatMap(container -> GTHashMaps.fromFluidHandler((IFluidHandlerModifiable) container).entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Long::sum, Object2LongLinkedOpenHashMap::new));

int minMultiplier = Integer.MAX_VALUE;
// map the recipe input fluids to account for duplicated fluids,
// so their sum is counted against the total of fluids available in the input
Map<FluidKey, Long> fluidCountMap = new HashMap<>();
Map<FluidKey, Long> notConsumableMap = new HashMap<>();
for (Content content : recipe.getInputContents(FluidRecipeCapability.CAP)) {
FluidIngredient fluidInput = FluidRecipeCapability.CAP.of(content.content);
long fluidAmount = fluidInput.getAmount();
if (content.chance == 0.0f) {
notConsumableMap.computeIfPresent(new FluidKey(fluidInput.getStacks()[0]),
(k, v) -> v + fluidAmount);
notConsumableMap.putIfAbsent(new FluidKey(fluidInput.getStacks()[0]), fluidAmount);
} else {
fluidCountMap.computeIfPresent(new FluidKey(fluidInput.getStacks()[0]),
(k, v) -> v + fluidAmount);
fluidCountMap.putIfAbsent(new FluidKey(fluidInput.getStacks()[0]), fluidAmount);
}
}

// Iterate through the recipe inputs, excluding the not consumable fluids from the fluid inventory map
for (Map.Entry<FluidKey, Long> notConsumableFluid : notConsumableMap.entrySet()) {
long needed = notConsumableFluid.getValue();
long available = 0;
// For every fluid gathered from the fluid inputs.
for (Map.Entry<FluidKey, Long> inputFluid : fluidStacks.entrySet()) {
// Strip the Non-consumable tags here, as FluidKey compares the tags, which causes finding matching
// fluids
// in the input tanks to fail, because there is nothing in those hatches with a non-consumable tag
if (notConsumableFluid.getKey().equals(inputFluid.getKey())) {
available = inputFluid.getValue();
if (available > needed) {
inputFluid.setValue(available - needed);
needed -= available;
break;
} else {
inputFluid.setValue(0L);
notConsumableFluid.setValue(needed - available);
needed -= available;
}
}
}
// We need to check >= available here because of Non-Consumable inputs with stack size. If there is a NC
// input
// with size 1000, and only 500 in the input, needed will be equal to available, but this situation should
// still fail
// as not all inputs are present
if (needed >= available) {
return 0;
}
}

// Return the maximum parallel limit here if there are only non-consumed inputs, which are all found in the
// input bus
// At this point, we would have already returned 0 if we were missing any non-consumable inputs, so we can omit
// that check
if (fluidCountMap.isEmpty() && !notConsumableMap.isEmpty()) {
return parallelAmount;
}

// Iterate through the fluid inputs in the recipe
for (Map.Entry<FluidKey, Long> fs : fluidCountMap.entrySet()) {
long needed = fs.getValue();
long available = 0;
// For every fluid gathered from the fluid inputs.
for (Map.Entry<FluidKey, Long> inputFluid : fluidStacks.entrySet()) {
if (fs.getKey().equals(inputFluid.getKey())) {
available += inputFluid.getValue();
}
}
if (available >= needed) {
int ratio = (int) Math.min(parallelAmount, available / needed);
if (ratio < minMultiplier) {
minMultiplier = ratio;
}
} else {
return 0;
}
}
return minMultiplier;
}

@Override
public @NotNull List<Object> createXEIContainerContents(List<Content> contents, GTRecipe recipe, IO io) {
return contents.stream().map(content -> content.content)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@
import com.gregtechceu.gtceu.api.recipe.ingredient.IntCircuitIngredient;
import net.neoforged.neoforge.common.crafting.SizedIngredient;
import com.gregtechceu.gtceu.api.recipe.lookup.*;
import com.gregtechceu.gtceu.api.recipe.modifier.ParallelLogic;
import com.gregtechceu.gtceu.api.recipe.ui.GTRecipeTypeUI;
import com.gregtechceu.gtceu.common.recipe.ResearchCondition;
import com.gregtechceu.gtceu.config.ConfigHolder;
import com.gregtechceu.gtceu.integration.GTRecipeWidget;
import com.gregtechceu.gtceu.utils.ResearchManager;
import com.gregtechceu.gtceu.utils.*;
import com.lowdragmc.lowdraglib.gui.widget.SlotWidget;
import com.lowdragmc.lowdraglib.gui.widget.Widget;
import com.lowdragmc.lowdraglib.jei.IngredientIO;
import com.lowdragmc.lowdraglib.misc.ItemTransferList;
import com.lowdragmc.lowdraglib.utils.CycleItemStackHandler;
import com.lowdragmc.lowdraglib.utils.TagOrCycleItemStackTransfer;
import com.mojang.datafixers.util.Either;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenCustomHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component;
Expand Down Expand Up @@ -180,6 +185,141 @@ public boolean isRecipeSearchFilter() {
return true;
}

@Override
public int limitParallel(GTRecipe recipe, IRecipeCapabilityHolder holder, int multiplier) {
int minMultiplier = 0;
int maxMultiplier = multiplier;

OverlayedItemHandler itemHandler = new OverlayedItemHandler(new ItemTransferList(
Objects.requireNonNullElseGet(holder.getCapabilitiesProxy().get(IO.OUT, ItemRecipeCapability.CAP), Collections::emptyList)
.stream()
.filter(IItemHandlerModifiable.class::isInstance)
.map(IItemHandlerModifiable.class::cast)
.toList()
));

Object2IntMap<ItemStack> recipeOutputs = GTHashMaps.fromItemStackCollection(recipe.getOutputContents(ItemRecipeCapability.CAP)
.stream()
.map(ItemRecipeCapability.CAP::of)
.filter(ingredient -> !ingredient.ingredient().isEmpty())
.map(ingredient -> ingredient.getItems()[0])
.toList()
);

while (minMultiplier != maxMultiplier) {
itemHandler.reset();

int returnedAmount = 0;
int amountToInsert;

for (Object2IntMap.Entry<ItemStack> entry : recipeOutputs.object2IntEntrySet()) {
// Since multiplier starts at Int.MAX, check here for integer overflow
if (entry.getIntValue() != 0 && multiplier > Integer.MAX_VALUE / entry.getIntValue()) {
amountToInsert = Integer.MAX_VALUE;
} else {
amountToInsert = entry.getIntValue() * multiplier;
}
returnedAmount = itemHandler.insertStackedItemStack(entry.getKey(), amountToInsert);
if (returnedAmount > 0) {
break;
}
}

int[] bin = ParallelLogic.adjustMultiplier(returnedAmount == 0, minMultiplier, multiplier, maxMultiplier);
minMultiplier = bin[0];
multiplier = bin[1];
maxMultiplier = bin[2];

}
return multiplier;
}

@Override
public int getMaxParallelRatio(IRecipeCapabilityHolder holder, GTRecipe recipe, int parallelAmount) {
// Find all the items in the combined Item Input inventories and create oversized ItemStacks
Object2IntMap<ItemStack> ingredientStacks = Objects.requireNonNullElseGet(holder.getCapabilitiesProxy().get(IO.IN, ItemRecipeCapability.CAP), Collections::emptyList)
.stream()
.filter(IItemHandlerModifiable.class::isInstance)
.map(IItemHandlerModifiable.class::cast)
.flatMap(container -> GTHashMaps.fromItemHandler(container).object2IntEntrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Integer::sum, () -> new Object2IntOpenCustomHashMap<>(ItemStackHashStrategy.comparingAllButCount())));

int minMultiplier = Integer.MAX_VALUE;
// map the recipe ingredients to account for duplicated and notConsumable ingredients.
// notConsumable ingredients are not counted towards the max ratio
Object2IntOpenHashMap<Ingredient> notConsumableMap = new Object2IntOpenHashMap<>();
Object2IntOpenHashMap<Ingredient> countableMap = new Object2IntOpenHashMap<>();
for (Content content : recipe.getInputContents(ItemRecipeCapability.CAP)) {
SizedIngredient recipeIngredient = ItemRecipeCapability.CAP.of(content.content);
int ingredientCount = recipeIngredient.count();
if (content.chance == 0.0f) {
notConsumableMap.computeIfPresent(recipeIngredient.ingredient(), (k, v) -> v + ingredientCount);
notConsumableMap.putIfAbsent(recipeIngredient.ingredient(), ingredientCount);
} else {
countableMap.computeIfPresent(recipeIngredient.ingredient(), (k, v) -> v + ingredientCount);
countableMap.putIfAbsent(recipeIngredient.ingredient(), ingredientCount);
}
}

// Iterate through the recipe inputs, excluding the not consumable ingredients from the inventory map
for (Object2IntMap.Entry<Ingredient> recipeInputEntry : notConsumableMap.object2IntEntrySet()) {
int needed = recipeInputEntry.getIntValue();
int available = 0;
// For every stack in the ingredients gathered from the input bus.
for (Object2IntMap.Entry<ItemStack> inventoryEntry : ingredientStacks.object2IntEntrySet()) {
if (recipeInputEntry.getKey().test(inventoryEntry.getKey())) {
available = inventoryEntry.getIntValue();
if (available > needed) {
inventoryEntry.setValue(available - needed);
needed -= available;
break;
} else {
inventoryEntry.setValue(0);
recipeInputEntry.setValue(needed - available);
needed -= available;
}
}
}
// We need to check >= available here because of Non-Consumable inputs with stack size. If there is a NC
// input
// with size 2, and only 1 in the input, needed will be equal to available, but this situation should still
// fail
// as not all inputs are present
if (needed >= available) {
return 0;
}
}

// Return the maximum parallel limit here if there are only non-consumed inputs, which are all found in the
// input bus
// At this point, we would have already returned 0 if we were missing any non-consumable inputs, so we can omit
// that check
if (countableMap.isEmpty() && !notConsumableMap.isEmpty()) {
return parallelAmount;
}

// Iterate through the recipe inputs
for (Object2IntMap.Entry<Ingredient> recipeInputEntry : countableMap.object2IntEntrySet()) {
int needed = recipeInputEntry.getIntValue();
int available = 0;
// For every stack in the ingredients gathered from the input bus.
for (Object2IntMap.Entry<ItemStack> inventoryEntry : ingredientStacks.object2IntEntrySet()) {
if (recipeInputEntry.getKey().test(inventoryEntry.getKey())) {
available += inventoryEntry.getIntValue();
}
}
if (available >= needed) {
int ratio = Math.min(parallelAmount, available / needed);
if (ratio < minMultiplier) {
minMultiplier = ratio;
}
} else {
return 0;
}
}
return minMultiplier;
}

@Override
public @NotNull List<Object> createXEIContainerContents(List<Content> contents, GTRecipe recipe, IO io) {
var outputStacks = contents.stream().map(content -> content.content)
Expand Down
Loading

0 comments on commit 4066e7e

Please sign in to comment.