Skip to content

Commit

Permalink
Tanks with I/O connections now increase the throughput of fluid pipe …
Browse files Browse the repository at this point in the history
…networks
  • Loading branch information
Technici4n committed Dec 26, 2024
1 parent 242a889 commit 8ee188e
Show file tree
Hide file tree
Showing 17 changed files with 309 additions and 44 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ dependencies {

implementation "me.shedaniel.cloth:cloth-config-neoforge:${project.cloth_config_version}"

/*localRuntimeOnly*/ compileOnly("curse.maven:jade-324717:${project.jade_file_id}")
localRuntimeOnly compileOnly("curse.maven:jade-324717:${project.jade_file_id}")

// TODO: compileOnly because implementation will cause datagen to crash... hopefully kubejs fixes it soon
/*localRuntimeOnly*/ compileOnly("dev.latvian.mods:kubejs-neoforge:${project.kubejs_version}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ private static void registerAdditionalModels(ModelEvent.RegisterAdditional event

private static final List<Runnable> blockEntityRendererRegistrations = new ArrayList<>();

public static <T extends BlockEntity, U extends T> void registerBlockEntityRenderer(Supplier<BlockEntityType<U>> bet,
public static <T extends BlockEntity, U extends T> void registerBlockEntityRenderer(Supplier<? extends BlockEntityType<? extends U>> bet,
BlockEntityRendererProvider<T> renderer) {
blockEntityRendererRegistrations.add(() -> BlockEntityRenderers.register(bet.get(), renderer));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public BlockEntityWithoutLevelRenderer getCustomRenderer() {
}

@Override
public void registerPartTankClient(Supplier<BlockEntityType<AbstractTankBlockEntity>> blockEntityType, int meanRgb) {
public void registerPartTankClient(Supplier<? extends BlockEntityType<? extends AbstractTankBlockEntity>> blockEntityType, int meanRgb) {
MIClient.registerBlockEntityRenderer(blockEntityType, context -> new TankRenderer(TextureHelper.getOverlayTextColor(meanRgb)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
package aztech.modern_industrialization.blocks.storage.tank;

import aztech.modern_industrialization.pipes.fluid.FluidNetworkExtensionTank;
import aztech.modern_industrialization.thirdparty.fabrictransfer.api.bridge.SlotFluidHandler;
import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionHand;
Expand All @@ -32,6 +33,7 @@
import net.neoforged.neoforge.fluids.FluidUtil;

public class TankBlockEntity extends AbstractTankBlockEntity {
public final FluidNetworkExtensionTank fluidHandler = new FluidNetworkExtensionTank(this);

public TankBlockEntity(BlockEntityType<?> bet, BlockPos pos, BlockState state) {
super(bet, pos, state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,7 @@ private static ShapeSelection.LineInfo createLineInfo(int[] sizes, MIText baseTe
public static void registerFluidAPI(BlockEntityType<?> bet) {
MICapabilities.onEvent(event -> {
event.registerBlockEntity(Capabilities.FluidHandler.BLOCK, bet, (be, direction) -> {
LargeTankMultiblockBlockEntity tank = ((LargeTankMultiblockBlockEntity) be);
if (tank.isShapeValid()) {
return tank.fluidStorage.getFluidHandler();
} else {
return EmptyFluidHandler.INSTANCE;
}
return ((LargeTankMultiblockBlockEntity) be).getExposedFluidHandler();
});
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

import aztech.modern_industrialization.api.machine.component.FluidAccess;
import aztech.modern_industrialization.machines.IComponent;
import aztech.modern_industrialization.thirdparty.fabrictransfer.api.bridge.SlotFluidHandler;
import aztech.modern_industrialization.pipes.fluid.FluidNetworkExtensionTank;
import aztech.modern_industrialization.thirdparty.fabrictransfer.api.fluid.FluidVariant;
import aztech.modern_industrialization.thirdparty.fabrictransfer.api.storage.base.SingleVariantStorage;
import com.google.common.base.Preconditions;
Expand All @@ -48,7 +48,7 @@ protected long getCapacity(FluidVariant variant) {
return capacity;
}
};
private final IFluidHandler fluidHandler = new SlotFluidHandler(singleStorageVariant);
private final IFluidHandler fluidHandler = new FluidNetworkExtensionTank(singleStorageVariant);

public SingleVariantStorage<FluidVariant> getFluidStorage() {
return singleStorageVariant;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import aztech.modern_industrialization.items.ContainerItem;
import aztech.modern_industrialization.items.SortOrder;
import aztech.modern_industrialization.proxy.CommonProxy;
import aztech.modern_industrialization.thirdparty.fabrictransfer.api.bridge.SlotFluidHandler;
import aztech.modern_industrialization.thirdparty.fabrictransfer.api.fluid.FluidVariant;
import java.util.function.BiConsumer;
import net.minecraft.world.level.block.Block;
Expand Down Expand Up @@ -71,7 +70,7 @@ public PartTemplate of(long bucketCapacity) {
}

public PartTemplate of(PartEnglishNameFormatter englishNameFormatter, long bucketCapacity, @Nullable String maybePathOverridden) {
MutableObject<BlockEntityType<AbstractTankBlockEntity>> bet = new MutableObject<>();
MutableObject<BlockEntityType<TankBlockEntity>> bet = new MutableObject<>();
long capacity = FluidType.BUCKET_VOLUME * bucketCapacity;

PartTemplate tank = new PartTemplate(englishNameFormatter, key())
Expand Down Expand Up @@ -103,7 +102,7 @@ public PartTemplate of(PartEnglishNameFormatter englishNameFormatter, long bucke
});

MICapabilities.onEvent(event -> {
event.registerBlockEntity(Capabilities.FluidHandler.BLOCK, bet.getValue(), (be, side) -> new SlotFluidHandler(be));
event.registerBlockEntity(Capabilities.FluidHandler.BLOCK, bet.getValue(), (be, side) -> be.fluidHandler);

var item = (TankItem) blockDefinition.asItem();
event.registerItem(Capabilities.FluidHandler.ITEM, (stack, ignored) -> new ContainerItem.FluidHandler(stack, item), item);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
package aztech.modern_industrialization.pipes;

public class PipeStatsCollector {
private static int REFRESH_RATE = 20;
private static final int REFRESH_RATE = 20;

private long lastStat = 0;
private long currentTot = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,21 @@
import aztech.modern_industrialization.pipes.api.PipeNetworkNode;
import aztech.modern_industrialization.thirdparty.fabrictransfer.api.fluid.FluidVariant;
import com.google.common.primitives.Ints;
import com.mojang.logging.LogUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import net.minecraft.server.level.ServerLevel;
import net.neoforged.neoforge.fluids.capability.IFluidHandler;
import org.slf4j.Logger;

public class FluidNetwork extends PipeNetwork {
private static final Logger LOGGER = LogUtils.getLogger();

final int nodeCapacity;
final PipeStatsCollector stats = new PipeStatsCollector();
final PipeStatsCollector capacityStats = new PipeStatsCollector();

public FluidNetwork(int id, PipeNetworkData data, int nodeCapacity) {
super(id, data == null ? new FluidNetworkData(FluidVariant.blank()) : data);
Expand All @@ -49,11 +54,12 @@ public FluidNetwork(int id, PipeNetworkData data, int nodeCapacity) {
public void tick(ServerLevel world) {
// Gather targets and hopefully set fluid
List<FluidTarget> targets = new ArrayList<>();
List<FluidNetworkExtensionTank> extensions = new ArrayList<>();
long networkAmount = 0;
int loadedNodeCount = 0;
for (var entry : iterateTickingNodes()) {
FluidNetworkNode fluidNode = (FluidNetworkNode) entry.getNode();
fluidNode.gatherTargetsAndPickFluid(world, entry.getPos(), targets);
fluidNode.gatherTargetsAndPickFluid(world, entry.getPos(), targets, extensions);
// Amount goes after the gather...() call because the gather...() call cleans
// invalid amounts.
networkAmount += fluidNode.amount;
Expand All @@ -65,15 +71,38 @@ public void tick(ServerLevel world) {
long extracted = 0, inserted = 0;

if (!fluid.isBlank()) {
var it = extensions.iterator();
while (it.hasNext()) {
var extension = it.next();
if (extension.tryClaimForNetwork(world, fluid)) {
networkAmount += extension.storage().getAmount();
networkCapacity += extension.getCapacity();
extension.clear();
} else {
it.remove();
}
}

// Extract from targets into the network
extracted = transferByPriority(TransferOperation.EXTRACT, targets, fluid, networkCapacity - networkAmount);
networkAmount += extracted;
// Insert into the targets from the network
inserted = transferByPriority(TransferOperation.INSERT, targets, fluid, networkAmount);
networkAmount -= inserted;

// Split fluid evenly across the nodes
// Rebalance fluid inside the nodes
// Rebalance fluid inside the extensions and nodes
var sortedExtensions = new ArrayList<>(extensions);
sortedExtensions.sort((ext1, ext2) -> -Long.compare(ext1.getCapacity(), ext2.getCapacity()));

long removedCapacity = 0;
for (var extension : sortedExtensions) {
var capacity = extension.getCapacity();
long toInsert = (long) Math.ceil((double) networkAmount * capacity / (networkCapacity - removedCapacity));
extension.releaseFromNetwork(fluid, toInsert);
networkAmount -= toInsert;
removedCapacity += capacity;
}

for (var entry : iterateTickingNodes()) {
FluidNetworkNode fluidNode = (FluidNetworkNode) entry.getNode();
fluidNode.amount = networkAmount / loadedNodeCount;
Expand All @@ -83,6 +112,7 @@ public void tick(ServerLevel world) {
}

stats.addValue(Math.max(extracted, inserted));
capacityStats.addValue(networkCapacity);

for (var entry : iterateTickingNodes()) {
((FluidNetworkNode) entry.getNode()).afterTick(world, entry.getPos());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* MIT License
*
* Copyright (c) 2020 Azercoco & Technici4n
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package aztech.modern_industrialization.pipes.fluid;

import aztech.modern_industrialization.thirdparty.fabrictransfer.api.bridge.SlotFluidHandler;
import aztech.modern_industrialization.thirdparty.fabrictransfer.api.fluid.FluidVariant;
import aztech.modern_industrialization.thirdparty.fabrictransfer.api.storage.base.SingleSlotStorage;
import aztech.modern_industrialization.thirdparty.fabrictransfer.api.transaction.Transaction;
import net.minecraft.world.level.Level;

/**
* Act as a pipe network extension when connected to a fluid pipe in I/O mode.
*/
public class FluidNetworkExtensionTank extends SlotFluidHandler {
private static final int NOT_CLAIMED = -1;
private int lastClaimTick = NOT_CLAIMED;

public FluidNetworkExtensionTank(SingleSlotStorage<FluidVariant> storage) {
super(storage);
}

public boolean tryClaimForNetwork(Level level, FluidVariant networkFluid) {
if (storage.getAmount() != 0 && !storage.getResource().equals(networkFluid)) {
return false;
}

try (var tx = Transaction.openOuter()) {
storage.extract(networkFluid, storage.getAmount(), tx);
long inserted = storage.insert(networkFluid, storage.getCapacity(), tx);
if (inserted != storage.getCapacity()) {
// Tank locked to a different fluid
return false;
}
}

int tick = level.getServer().getTickCount();
if (tick > lastClaimTick) {
lastClaimTick = tick;
return true;
} else {
return false;
}
}

public void clear() {
if (storage.getAmount() == 0) {
return;
}
try (var tx = Transaction.openOuter()) {
storage.extract(storage.getResource(), storage.getAmount(), tx);
tx.commit();
}
if (storage.getAmount() > 0) {
throw new IllegalStateException("Internal MI error: extension %s should be empty after clearing it.".formatted(this));
}
}

public void releaseFromNetwork(FluidVariant fluid, long amount) {
if (storage.getAmount() > 0) {
throw new IllegalStateException("Internal MI error: extension %s should be empty when being released.".formatted(this));
}
lastClaimTick = NOT_CLAIMED;
if (fluid.isBlank()) {
if (storage.getAmount() != 0) {
throw new IllegalStateException("Internal MI error: releasing extension %s from network with non-empty tank.".formatted(this));
}
}
try (var tx = Transaction.openOuter()) {
long inserted = storage.insert(fluid, amount, tx);
tx.commit();
if (inserted != amount) {
throw new IllegalStateException(
"Internal MI error: releasing extension %s, only inserted %d out of %d.".formatted(this, inserted, amount));
}
}
}

public long getCapacity() {
return storage.getCapacity();
}

@Override
protected boolean disallowIo() {
return lastClaimTick != NOT_CLAIMED;
}

// TODO: lock and unlock insertion?

@Override
public String toString() {
return "FluidNetworkExtensionTank{" + storage + '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public class FluidNetworkNode extends PipeNetworkNode {
* Add all valid targets to the target list, and pick the fluid for the network
* if no fluid is set.
*/
void gatherTargetsAndPickFluid(ServerLevel world, BlockPos pos, List<FluidTarget> targets) {
void gatherTargetsAndPickFluid(ServerLevel world, BlockPos pos, List<FluidTarget> targets, List<FluidNetworkExtensionTank> extensions) {
FluidNetworkData data = (FluidNetworkData) network.data;
FluidNetwork network = (FluidNetwork) this.network;

Expand All @@ -84,7 +84,11 @@ void gatherTargetsAndPickFluid(ServerLevel world, BlockPos pos, List<FluidTarget
// Try to set fluid, will return null if none could be found.
data.fluid = FluidVariant.of(storage.drain(Integer.MAX_VALUE, IFluidHandler.FluidAction.SIMULATE));
}
targets.add(new FluidTarget(connection.priority, new IOFluidHandler(storage, connection.canInsert(), connection.canExtract())));
if (connection.canInsert() && connection.canExtract() && storage instanceof FluidNetworkExtensionTank extension) {
extensions.add(extension);
} else {
targets.add(new FluidTarget(connection.priority, new IOFluidHandler(storage, connection.canInsert(), connection.canExtract())));
}
}
}

Expand Down Expand Up @@ -324,6 +328,10 @@ public void afterTick(ServerLevel world, BlockPos pos) {
}
}

public long getAmount() {
return amount;
}

// Used in the Waila plugin
private FluidVariant getFluid() {
return ((FluidNetworkData) network.data).fluid;
Expand All @@ -337,7 +345,7 @@ public InGameInfo collectNetworkInfo() {
stored += node.amount;
capacity += fluidNetwork.nodeCapacity;
}
return new InGameInfo(getFluid(), stored, capacity, fluidNetwork.stats.getValue(), capacity);
return new InGameInfo(getFluid(), stored, capacity, fluidNetwork.stats.getValue(), fluidNetwork.capacityStats.getValue());
}

public record InGameInfo(FluidVariant fluid, long stored, long capacity, long transfer, long maxTransfer) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public List<Component> getFluidTooltip(FluidVariant variant) {
public void withStandardItemRenderer(Consumer<?> stupidClientProperties) {
}

public void registerPartTankClient(Supplier<BlockEntityType<AbstractTankBlockEntity>> blockEntityType, int meanRgb) {
public void registerPartTankClient(Supplier<? extends BlockEntityType<? extends AbstractTankBlockEntity>> blockEntityType, int meanRgb) {
}

public void registerPartBarrelClient(Supplier<BlockEntityType<BarrelBlockEntity>> blockEntityType, int meanRgb) {
Expand Down
Loading

0 comments on commit 8ee188e

Please sign in to comment.