From 84a62a199bba4be0b52a6dff7fbb2b865f39ffcf Mon Sep 17 00:00:00 2001 From: kross <135918757+krossgg@users.noreply.github.com> Date: Mon, 25 Nov 2024 09:40:26 +0200 Subject: [PATCH] Rework OverlayedFluidHandler to fix Fluid Parallel Limiting (#2423) --- .../recipe/FluidRecipeCapability.java | 17 +- .../machine/trait/NotifiableFluidTank.java | 6 +- .../api/transfer/fluid/CustomFluidTank.java | 8 - .../ae2/machine/MEOutputHatchPartMachine.java | 1 + .../gtceu/utils/GTTransferUtils.java | 35 +--- .../gtceu/utils/OverlayedFluidHandler.java | 177 ------------------ .../gtceu/utils/OverlayedTankHandler.java | 156 +++++++++++++++ 7 files changed, 171 insertions(+), 229 deletions(-) delete mode 100644 src/main/java/com/gregtechceu/gtceu/utils/OverlayedFluidHandler.java create mode 100644 src/main/java/com/gregtechceu/gtceu/utils/OverlayedTankHandler.java diff --git a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java index 486596bb12..5db4019a30 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java +++ b/src/main/java/com/gregtechceu/gtceu/api/capability/recipe/FluidRecipeCapability.java @@ -1,6 +1,7 @@ package com.gregtechceu.gtceu.api.capability.recipe; import com.gregtechceu.gtceu.api.gui.widget.TankWidget; +import com.gregtechceu.gtceu.api.machine.trait.NotifiableFluidTank; import com.gregtechceu.gtceu.api.recipe.GTRecipe; import com.gregtechceu.gtceu.api.recipe.GTRecipeType; import com.gregtechceu.gtceu.api.recipe.content.Content; @@ -10,14 +11,13 @@ import com.gregtechceu.gtceu.api.recipe.lookup.ingredient.fluid.*; import com.gregtechceu.gtceu.api.recipe.modifier.ParallelLogic; import com.gregtechceu.gtceu.api.recipe.ui.GTRecipeTypeUI; -import com.gregtechceu.gtceu.api.transfer.fluid.FluidHandlerList; import com.gregtechceu.gtceu.api.transfer.fluid.IFluidHandlerModifiable; import com.gregtechceu.gtceu.api.transfer.fluid.TagOrCycleFluidHandler; import com.gregtechceu.gtceu.client.TooltipsHandler; 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.OverlayedTankHandler; import com.gregtechceu.gtceu.utils.OverlayingFluidStorage; import com.lowdragmc.lowdraglib.gui.texture.ProgressTexture; @@ -160,13 +160,14 @@ public int limitParallel(GTRecipe recipe, IRecipeCapabilityHolder holder, int mu int minMultiplier = 0; int maxMultiplier = multiplier; - OverlayedFluidHandler overlayedFluidHandler = new OverlayedFluidHandler(new FluidHandlerList( - Objects.requireNonNullElseGet(holder.getCapabilitiesProxy().get(IO.OUT, FluidRecipeCapability.CAP), + OverlayedTankHandler overlayedFluidHandler = new OverlayedTankHandler( + Objects.requireNonNullElseGet( + holder.getCapabilitiesProxy().get(IO.OUT, FluidRecipeCapability.CAP), Collections::emptyList) .stream() - .filter(IFluidHandler.class::isInstance) - .map(IFluidHandler.class::cast) - .toList())); + .filter(NotifiableFluidTank.class::isInstance) + .map(NotifiableFluidTank.class::cast) + .toList()); List recipeOutputs = recipe.getOutputContents(FluidRecipeCapability.CAP) .stream() @@ -190,7 +191,7 @@ public int limitParallel(GTRecipe recipe, IRecipeCapabilityHolder holder, int mu } else { amountToInsert = fluidStack.getAmount() * multiplier; } - returnedAmount = amountToInsert - overlayedFluidHandler.insertFluid(fluidStack, amountToInsert); + returnedAmount = amountToInsert - overlayedFluidHandler.tryFill(fluidStack, amountToInsert); if (returnedAmount > 0) { break; } diff --git a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableFluidTank.java b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableFluidTank.java index 3150b6b9b7..e11667e1f6 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableFluidTank.java +++ b/src/main/java/com/gregtechceu/gtceu/api/machine/trait/NotifiableFluidTank.java @@ -20,7 +20,6 @@ import net.neoforged.neoforge.fluids.crafting.SizedFluidIngredient; import lombok.Getter; -import lombok.Setter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -44,7 +43,7 @@ public class NotifiableFluidTank extends NotifiableRecipeHandlerTrait filter = f -> true; public NotifiableFluidTank(MetaMachine machine, int slots, int capacity, IO io, IO capabilityIO) { super(machine); @@ -224,6 +225,7 @@ public void setLocked(boolean locked, FluidStack fluidStack) { } public NotifiableFluidTank setFilter(Predicate filter) { + this.filter = filter; for (CustomFluidTank storage : storages) { storage.setValidator(filter); } diff --git a/src/main/java/com/gregtechceu/gtceu/api/transfer/fluid/CustomFluidTank.java b/src/main/java/com/gregtechceu/gtceu/api/transfer/fluid/CustomFluidTank.java index fa5445c181..bdf1849fd8 100644 --- a/src/main/java/com/gregtechceu/gtceu/api/transfer/fluid/CustomFluidTank.java +++ b/src/main/java/com/gregtechceu/gtceu/api/transfer/fluid/CustomFluidTank.java @@ -53,14 +53,6 @@ public void setFluidInTank(int tank, FluidStack stack) { this.onContentsChanged(); } - public int fill(int tank, FluidStack resource, FluidAction action) { - return this.fill(resource, action); - } - - public FluidStack drain(int tank, FluidStack resource, FluidAction action) { - return this.drain(resource, action); - } - @Override public CompoundTag serializeNBT(HolderLookup.Provider provider) { return writeToNBT(provider, new CompoundTag()); diff --git a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEOutputHatchPartMachine.java b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEOutputHatchPartMachine.java index e5a0014e3a..adc92935ea 100644 --- a/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEOutputHatchPartMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/integration/ae2/machine/MEOutputHatchPartMachine.java @@ -123,6 +123,7 @@ public InaccessibleInfiniteTank(MetaMachine holder) { super(holder, List.of(new FluidStorageDelegate()), IO.OUT, IO.NONE); internalBuffer.setOnContentsChanged(this::onContentsChanged); storage = getStorages()[0]; + allowSameFluids = true; } @Override diff --git a/src/main/java/com/gregtechceu/gtceu/utils/GTTransferUtils.java b/src/main/java/com/gregtechceu/gtceu/utils/GTTransferUtils.java index 80b93827ba..fae133c967 100644 --- a/src/main/java/com/gregtechceu/gtceu/utils/GTTransferUtils.java +++ b/src/main/java/com/gregtechceu/gtceu/utils/GTTransferUtils.java @@ -12,6 +12,7 @@ import net.neoforged.neoforge.capabilities.Capabilities; import net.neoforged.neoforge.fluids.FluidStack; import net.neoforged.neoforge.fluids.capability.IFluidHandler; +import net.neoforged.neoforge.fluids.capability.IFluidHandler.FluidAction; import net.neoforged.neoforge.items.IItemHandler; import net.neoforged.neoforge.items.IItemHandlerModifiable; @@ -184,40 +185,6 @@ public static boolean addItemsToItemHandler(final IItemHandlerModifiable handler return true; } - /** - * Simulates the insertion of fluid into a target fluid handler, then optionally performs the insertion. - *
- *
- * Simulating will not modify any of the input parameters. Insertion will either succeed completely, or fail - * without modifying anything. - * This method should be called with {@code simulate} {@code true} first, then {@code simulate} {@code false}, - * only if it returned {@code true}. - * - * @param fluidHandler the target inventory - * @param simulate whether to simulate ({@code true}) or actually perform the insertion ({@code false}) - * @param fluidStacks the items to insert into {@code fluidHandler}. - * @return {@code true} if the insertion succeeded, {@code false} otherwise. - */ - public static boolean addFluidsToFluidHandler(FluidHandlerList fluidHandler, - boolean simulate, - List fluidStacks) { - if (simulate) { - OverlayedFluidHandler overlayedFluidHandler = new OverlayedFluidHandler(fluidHandler); - for (FluidStack fluidStack : fluidStacks) { - int inserted = overlayedFluidHandler.insertFluid(fluidStack, fluidStack.getAmount()); - if (inserted != fluidStack.getAmount()) { - return false; - } - } - return true; - } - - for (FluidStack fluidStack : fluidStacks) { - fillFluidAccountNotifiableList(fluidHandler, fluidStack, FluidAction.EXECUTE); - } - return true; - } - public static int fillFluidAccountNotifiableList(IFluidHandler fluidHandler, FluidStack stack, FluidAction action) { if (stack.isEmpty()) return 0; if (fluidHandler instanceof FluidHandlerList handlerList) { diff --git a/src/main/java/com/gregtechceu/gtceu/utils/OverlayedFluidHandler.java b/src/main/java/com/gregtechceu/gtceu/utils/OverlayedFluidHandler.java deleted file mode 100644 index 65dba73790..0000000000 --- a/src/main/java/com/gregtechceu/gtceu/utils/OverlayedFluidHandler.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.gregtechceu.gtceu.utils; - -import com.gregtechceu.gtceu.api.transfer.fluid.CustomFluidTank; - -import com.lowdragmc.lowdraglib.misc.FluidTransferList; -import com.lowdragmc.lowdraglib.side.fluid.FluidHelper; - -import net.neoforged.neoforge.fluids.FluidStack; -import net.neoforged.neoforge.fluids.IFluidTank; -import net.neoforged.neoforge.fluids.capability.IFluidHandler; - -import com.google.common.primitives.Ints; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.IntStream; - -/** - * Simulates consecutive fills to {@link IFluidTank} instances. - */ -public class OverlayedFluidHandler { - - private final List overlayedTanks; - - public OverlayedFluidHandler(@NotNull FluidHandlerList tank) { - this.overlayedTanks = new ArrayList<>(); - FluidStack[] entries = IntStream.range(0, tank.getTanks()).mapToObj(tank::getFluidInTank) - .toArray(FluidStack[]::new); - for (int i = 0; i < tank.getTanks(); ++i) { - CustomFluidTank storage = new CustomFluidTank(tank.getTankCapacity(i)); - storage.setFluid(entries[i]); - this.overlayedTanks.add(new OverlayedTank(storage, tank.isFluidValid(i, entries[i]))); - } - } - - /** - * Resets the internal state back to the state when the handler was - * first mirrored. - */ - public void reset() { - for (OverlayedTank overlayedTank : this.overlayedTanks) { - overlayedTank.reset(); - } - } - - /** - * Simulate fluid insertion to the fluid tanks. - * - * @param fluid Fluid - * @param amountToInsert Amount of the fluid to insert - * @return Amount of fluid inserted into tanks - */ - public int insertFluid(@NotNull FluidStack fluid, int amountToInsert) { - if (amountToInsert <= 0) { - return 0; - } - int totalInserted = 0; - // flag value indicating whether the fluid was stored in 'distinct' slot at least once - boolean distinctFillPerformed = false; - - // search for tanks with same fluid type first - for (OverlayedTank overlayedTank : this.overlayedTanks) { - // if the fluid to insert matches the tank, insert the fluid - if (!overlayedTank.isEmpty() && overlayedTank.fluid != null && - FluidStack.isSameFluidSameComponents(fluid, overlayedTank.fluid)) { - int inserted = overlayedTank.tryInsert(fluid, amountToInsert); - if (inserted > 0) { - totalInserted += inserted; - amountToInsert -= inserted; - if (amountToInsert <= 0) { - return totalInserted; - } - } - // regardless of whether the insertion succeeded, presence of identical fluid in - // a slot prevents distinct fill to other slots - if (!overlayedTank.allowSameFluidFill) { - distinctFillPerformed = true; - } - } - } - // if we still have fluid to insert, loop through empty tanks until we find one that can accept the fluid - for (OverlayedTank overlayedTank : this.overlayedTanks) { - // if the tank uses distinct fluid fill (allowSameFluidFill disabled) and another distinct tank had - // received the fluid, skip this tank - if ((!distinctFillPerformed || overlayedTank.allowSameFluidFill) && - overlayedTank.isEmpty() && - overlayedTank.property.isFluidValid(fluid)) { - int inserted = overlayedTank.tryInsert(fluid, amountToInsert); - if (inserted > 0) { - totalInserted += inserted; - amountToInsert -= inserted; - if (amountToInsert <= 0) { - return totalInserted; - } - if (!overlayedTank.allowSameFluidFill) { - distinctFillPerformed = true; - } - } - } - } - // return the amount of fluid that was inserted - return totalInserted; - } - - @Override - public String toString() { - return toString(false); - } - - public String toString(boolean lineBreak) { - StringBuilder stb = new StringBuilder("OverlayedFluidHandler[").append(this.overlayedTanks.size()).append(";"); - if (lineBreak) stb.append("\n "); - for (int i = 0; i < this.overlayedTanks.size(); i++) { - if (i != 0) stb.append(','); - if (lineBreak) stb.append("\n "); - - OverlayedTank overlayedTank = this.overlayedTanks.get(i); - FluidStack fluid = overlayedTank.fluid; - if (fluid.isEmpty()) { - stb.append("None 0 / ").append(overlayedTank.property.getCapacity()); - } else { - stb.append(fluid.getDisplayName()).append(' ').append(fluid.getAmount()) - .append(" / ").append(overlayedTank.property.getCapacity()); - } - } - if (lineBreak) stb.append('\n'); - return stb.append(']').toString(); - } - - private static class OverlayedTank { - - private final IFluidTank property; - private final boolean allowSameFluidFill; - - private FluidStack fluid; - - OverlayedTank(@NotNull IFluidTank property, boolean allowSameFluidFill) { - this.property = property; - this.allowSameFluidFill = allowSameFluidFill; - reset(); - } - - public boolean isEmpty() { - return fluid.isEmpty(); - } - - /** - * Tries to insert set amount of fluid into this tank. If operation succeeds, - * the content of this tank will be updated. - * - * Note that this method does not check preexisting fluids for insertion. - * - * @param fluid Fluid - * @param amount Amount of the fluid to insert - * @return Amount of fluid inserted into this tank - */ - public int tryInsert(@NotNull FluidStack fluid, int amount) { - if (this.fluid.isEmpty()) { - this.fluid = fluid.copy(); - this.fluid.setAmount(Math.min(this.property.getCapacity(), amount)); - return this.fluid.getAmount(); - } else { - int maxInsert = Math.min(this.property.getCapacity() - this.fluid.getAmount(), amount); - if (maxInsert > 0) { - this.fluid.setAmount(this.fluid.getAmount() + maxInsert); - return maxInsert; - } else return 0; - } - } - - public void reset() { - FluidStack fluid = this.property.getFluid(); - this.fluid = !fluid.isEmpty() ? fluid.copy() : FluidStack.EMPTY; - } - } -} diff --git a/src/main/java/com/gregtechceu/gtceu/utils/OverlayedTankHandler.java b/src/main/java/com/gregtechceu/gtceu/utils/OverlayedTankHandler.java new file mode 100644 index 0000000000..d9da70bfeb --- /dev/null +++ b/src/main/java/com/gregtechceu/gtceu/utils/OverlayedTankHandler.java @@ -0,0 +1,156 @@ +package com.gregtechceu.gtceu.utils; + +import com.gregtechceu.gtceu.api.capability.recipe.IRecipeHandler; +import com.gregtechceu.gtceu.api.machine.trait.NotifiableFluidTank; + +import net.neoforged.neoforge.fluids.FluidStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +/** + * Simulates consecutive fills to multiple {@link NotifiableFluidTank} instances + */ +public class OverlayedTankHandler { + + private final List overlayedTanks; + + public OverlayedTankHandler(List tanks) { + overlayedTanks = new ArrayList<>(tanks.size()); + var copy = new ArrayList<>(tanks); + copy.sort(IRecipeHandler.ENTRY_COMPARATOR); + for (var tank : copy) { + overlayedTanks.add(new OverlayedTank(tank)); + } + } + + /** + * Resets the internal state of the handler to when it was constructed + */ + public void reset() { + for (var tank : overlayedTanks) tank.reset(); + } + + /** + * Simulate fluid filling to the tanks + * + * @param fluid {@link FluidStack} with the fluid to attempt to 'fill' - stack amount does not matter + * @param amount Actual amount of fluid to attempt to 'fill' + * @return Amount of fluid that could potentially be filled into these tanks + */ + public int tryFill(FluidStack fluid, int amount) { + if (amount <= 0) return 0; + int total = 0; + for (var tank : overlayedTanks) { + int filled = tank.fill(fluid, amount); + total += filled; + amount -= filled; + if (amount <= 0) return total; + } + + return total; + } + + /** + * Represents a single {@link NotifiableFluidTank} and its storages + */ + private static class OverlayedTank { + + private final int size; + private final int capacity; + private final boolean sameFluidFill; + private final Predicate filter; + private final List originalStacks; + + private List stacks; + + /** + * Constructs the Overlayed Tank from the given NotifiableFluidTank
+ * Stores properties, such as: + *
    + *
  • The number of tanks via {@link NotifiableFluidTank#getTanks}
  • + *
  • The tank's capacity per storage via {@link NotifiableFluidTank#getTankCapacity}
  • + *
  • Whether the tank is allowed be filled with duplicate fluids
  • + *
  • The tank's filter
  • + *
  • The tank's currently stored FluidStacks
  • + *
+ */ + private OverlayedTank(NotifiableFluidTank tank) { + size = tank.getTanks(); + capacity = tank.getTankCapacity(0); + sameFluidFill = tank.isAllowSameFluids(); + filter = tank.getFilter(); + originalStacks = new ArrayList<>(tank.getStorages().length); + for (var storage : tank.getStorages()) { + if (!storage.getFluid().isEmpty()) + originalStacks.add(storage.getFluid()); + } + reset(); + } + + /** + * Resets the Overlayed Tank back to its original state + */ + public void reset() { + stacks = new ArrayList<>(size); + for (var stack : originalStacks) stacks.add(stack.copy()); + } + + /** + * 'Fill' this Overlayed Tank as much as is allowed + * + * @param fluid FluidStack with the fluid to attempt to 'fill'; stack amount does not matter + * @param amount Actual amount of fluid to attempt to 'fill' + * @return Amount of fluid that could potentially be filled into this tank + */ + public int fill(FluidStack fluid, int amount) { + if (!filter.test(fluid) || capacity <= 0) return 0; + int filled = tryFill(fluid, amount); + if (!sameFluidFill || filled >= amount) return filled; + + int total = filled; + amount -= filled; + for (int i = 1; i < size; ++i) { // Attempt to 'fill' tanks a total of (size) times + filled = tryFill(fluid, amount); + total += filled; + amount -= filled; + if (amount <= 0) return total; + } + return total; + } + + private int tryFill(FluidStack fluid, int amount) { + var existing = search(fluid); + if (existing.isEmpty() || existing.getAmount() >= capacity) { // Need to add new stack + if (!existing.isEmpty() && !sameFluidFill) return 0; // Not allowed to add new stack + if (stacks.size() >= size) return 0; // No space to add new stack + int canInsert = Math.min(capacity, amount); + stacks.add(new FluidStack(fluid, amount)); + return canInsert; + } else { // Stack (that can grow) exists + int canInsert = Math.min(capacity - existing.getAmount(), amount); + existing.grow(canInsert); + return canInsert; + } + } + + /** + * Searches {@link OverlayedTank#stacks} for a FluidStack equivalent to the passed {@code fluid}
+ * + * @param fluid A FluidStack with the fluid to search for + * @return If {@code sameFluidFill} is false, then the first matching stack found. Otherwise, the first non-full + * stack. If no matching stack is found, then {@link FluidStack#EMPTY} + */ + private FluidStack search(FluidStack fluid) { + FluidStack found = FluidStack.EMPTY; + for (var stack : stacks) { + if (FluidStack.isSameFluid(stack, fluid)) { + if (!sameFluidFill || stack.getAmount() < capacity) return stack; + else found = stack; + } + } + return found; + } + } +}