From 52ec9bf04c6d0fcde2d20fafd437c06e85b64ed2 Mon Sep 17 00:00:00 2001 From: eragaxshim Date: Mon, 23 Sep 2024 10:57:11 +0200 Subject: [PATCH] more optimizations --- .../common/machine/electric/PumpMachine.java | 122 +++++++++++++----- 1 file changed, 88 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/gregtechceu/gtceu/common/machine/electric/PumpMachine.java b/src/main/java/com/gregtechceu/gtceu/common/machine/electric/PumpMachine.java index ef0ae36faa..06b9b644ee 100644 --- a/src/main/java/com/gregtechceu/gtceu/common/machine/electric/PumpMachine.java +++ b/src/main/java/com/gregtechceu/gtceu/common/machine/electric/PumpMachine.java @@ -79,7 +79,7 @@ public class PumpMachine extends TieredEnergyMachine implements IAutoOutputFluid private final Deque fluidSourceBlocks = new ArrayDeque<>(); private final Deque blocksToCheck = new ArrayDeque<>(); private PumpQueue pumpQueue = null; - private boolean initializedQueue = false; + private final boolean initializedQueue = false; @Getter @Persisted private int pumpHeadY; @@ -251,15 +251,42 @@ private SearchResult searchNext(Level level, BlockPos headPosBelow, BlockPos sea return null; } - record PumpPath(List path, FluidType fluidType) { } + /** + * Update the pump queue if it is empty. + * @param fluidType Use this if the pump queue must have the same fluid type because it was already decided in the + * pump cycle. + */ + private void updatePumpQueue(@Nullable FluidType fluidType) { + if (getLevel() == null) return; + + if (pumpQueue != null && !pumpQueue.queue().isEmpty()) { + return; + } + + BlockPos headPos = getPos().below(pumpHeadY); + + BlockPos downPos = headPos.below(1); + var downBlock = getLevel().getBlockState(downPos); + + if (!(downBlock.getBlock() instanceof LiquidBlock)) { + pumpQueue = null; + return; + } + + if (fluidType != null && downBlock.getFluidState().getFluidType() != fluidType) { + pumpQueue = null; + return; + } + + pumpQueue = buildPumpQueue(getLevel(), headPos, downBlock.getFluidState().getFluidType(), queueSize(), true); + } /** * Does a "depth-first"-ish search to find a path to a source. It prioritizes going up and away from the pump head. * If the path it finds only contains sources at the level below the pump head, it will keep looking until it finds * one that has a source at a higher location. If it cannot find one, it will return the original path. */ - @Nullable - private PumpQueue findSourcePath(Level level, BlockPos headPos, FluidType fluidType, int queueSourceAmount, boolean upSources) { + private PumpQueue buildPumpQueue(Level level, BlockPos headPos, FluidType fluidType, int queueSourceAmount, boolean upSources) { Set checked = new ObjectOpenHashSet<>(); BlockPos headPosBelow = headPos.below(); @@ -282,12 +309,12 @@ private PumpQueue findSourcePath(Level level, BlockPos headPos, FluidType fluidT int previousSources = 0; Queue> paths = new ArrayDeque<>(); List sources = new ArrayList<>(); + // We do at most 1000 iterations to try and find source blocks while (!pathStack.isEmpty() && iterations < 1000) { // Peeks at the tail BlockPos searchHead = pathStack.get(pathStack.size() - 1); - //BlockPos lastSource = sourceStack.peekLast(); - SearchResult next = searchNext(level, headPosBelow, searchHead, fluidType, maxPumpRange, upSources, checked); + SearchResult next = searchNext(level, headPosBelow, searchHead, fluidType, maxPumpRange, upSources, checked); iterations++; @@ -335,7 +362,7 @@ private PumpQueue findSourcePath(Level level, BlockPos headPos, FluidType fluidT pathToLastSource.addAll(nonSources); // Also add the source itself pathToLastSource.add(next.pos()); - // Clear it + // Reset non-sources because we just added them and found a source nonSources.clear(); sources.add(next.pos()); } else { @@ -348,15 +375,12 @@ private PumpQueue findSourcePath(Level level, BlockPos headPos, FluidType fluidT if (upSources) { // If we found none, we try again without the restriction if (paths.isEmpty()) { - return findSourcePath(level, headPos, fluidType, queueSourceAmount, false); + return buildPumpQueue(level, headPos, fluidType, queueSourceAmount, false); } - System.out.println("iterations up " + iterations); return new PumpQueue(paths, fluidType); } - System.out.println("iterations " + iterations); - // Only after everything except the block directly below the pipe is pumped, do we want to pump it // Otherwise we might advance the pump head prematurely if (paths.isEmpty() && level.getBlockState(headPosBelow).getFluidState().isSource()) { @@ -366,29 +390,30 @@ private PumpQueue findSourcePath(Level level, BlockPos headPos, FluidType fluidT return new PumpQueue(paths, fluidType); } - private void updateQueueState(int queueSourceAmount) { + /** + * Advances the pump head if the block below is air and the pump queue is empty. + */ + private boolean canAdvancePumpHead() { // position of the pump head, i.e. the position of the lowest mining pipe BlockPos headPos = getPos().below(pumpHeadY); if (pumpQueue == null || pumpQueue.queue.isEmpty()) { Level level; - // TODO set back to /4 - if(getOffsetTimer() % Math.max(getPumpingCycleLength()/8, 1) == 0 && ((level = getLevel()) != null)) { + if((level = getLevel()) != null) { BlockPos downPos = headPos.below(1); var downBlock = level.getBlockState(downPos); - if (downBlock.getBlock() instanceof LiquidBlock) { - FluidType fluidType = downBlock.getFluidState().getFluidType(); - pumpQueue = findSourcePath(getLevel(), headPos, fluidType, queueSourceAmount, true); - } else if (downBlock.isAir()) { + if (downBlock.isAir()) { this.pumpHeadY++; if (level instanceof ServerLevel serverLevel) { serverLevel.setBlockAndUpdate(downPos, GTBlocks.MINER_PIPE.getDefaultState()); } + return true; } } } + return false; } @Override @@ -404,10 +429,18 @@ public void onMachineRemoved() { record SourceState(BlockState state, BlockPos pos) {} + /** + * Does a full pump cycle, trying to do the required number of pumps. It will rebuild the queue if it becomes + * empty without having fulfilled its required number of pumps. All paths computed in the queue are checked + * if they are still valid and consist only of the right fluid. + */ private void pumpCycle() { - if (getLevel() == null) { + Level level; + if ((level = getLevel()) == null) { return; } + // Will only update if the queue is empty + updatePumpQueue(null); int pumps = pumpsPerCycle(); // We try to pump `pumps` amount of source blocks, using multiple paths if necessary @@ -419,7 +452,11 @@ private void pumpCycle() { // We iterate through the positions to check if it is still a valid path, saving the states for (BlockPos pos : pumpPath) { - BlockState state = getLevel().getBlockState(pos); + // Stop once an unloaded block is found + if (!level.isLoaded(pos)) { + break; + } + BlockState state = level.getBlockState(pos); if (state.getBlock() instanceof LiquidBlock liquidBlock && (liquidBlock.getFluidState(state)).getFluidType() == pumpQueue.fluidType()) { states.add(new SourceState(state, pos)); @@ -445,7 +482,6 @@ private void pumpCycle() { this.fluidSourceBlocks.remove(pos); pumped = true; pumps--; - System.out.println("Pumped: " + pos); } } } @@ -454,14 +490,19 @@ private void pumpCycle() { if (pumpPath.isEmpty()) { pumpQueue.queue().remove(); } + + // If we have pumps left over and there is still more to be pumped at the current level + // (But it wasn't in the queue because maybe it's the final source block below the pump head) + // We still want to be able to pump + if (pumps > 0 && pumpQueue.queue().isEmpty()) { + updatePumpQueue(pumpQueue.fluidType()); + } } // Use energy if any pumps happened at all if (pumped) { energyContainer.changeEnergy(-GTValues.V[getTier()] * 2); } - - } public void update() { @@ -471,30 +512,43 @@ public void update() { // do not do anything without enough energy supplied if (energyContainer.getEnergyStored() < GTValues.V[getTier()] * 2) { - System.out.println("pump not enough energy"); return; } - int pumps = pumpsPerCycle(); - // Try to put 5 times as many in the queue as there are pumps - updateQueueState(pumps*5); - if (getOffsetTimer() % getPumpingCycleLength() == 0) { - System.out.println("trying pump."); + // Try to put 5 times as many in the queue as there are pumps in the cycle + // In practice only EV tier has more than 1 pump per cycle + // The queue can contain at most the y-levels at the pump head or just the y-level below, so for many oil veins + // It will not be the ideal size + boolean advanced = false; + if (getOffsetTimer() % (getPumpingCycleLength() * 2L) == 0) { + advanced = canAdvancePumpHead(); + } + if (!advanced && getOffsetTimer() % getPumpingCycleLength() == 0) { pumpCycle(); } } - private float getPumpTierMultiplier() { - // For tier 1 + private int queueSize() { + return 5*pumpsPerCycle(); + } + + private float ticksPerPump() { + // How many ticks pass per pump. This is the ideal amount and thus can be less than 1 + // For LV this is 80/1 = 80 float tierMultiplier = (float) (1 << (getTier() - 1)); - return tierMultiplier / PUMP_SPEED_BASE; + return PUMP_SPEED_BASE / tierMultiplier; } private int pumpsPerCycle() { - return Math.max(1, (int) getPumpTierMultiplier()); + // The pumping cycle length can not be less than 20, so to ensure we still have the right amount of pumps + // We need to compensate with pumps per cycle + + return (int) (getPumpingCycleLength() / ticksPerPump()); } private int getPumpingCycleLength() { - return Math.max(1, (int) (1f / getPumpTierMultiplier())); + // For basic pumps this means once every 80 ticks + // It never pumps more than once every 20 ticks, but pumps more per cycle to compensate + return Math.max(20, (int) ticksPerPump()); } //////////////////////////////////////