diff --git a/src/main/java/uk/gemwire/engage/block/coolant/CoolantBaseBlock.java b/src/main/java/uk/gemwire/engage/block/coolant/CoolantBaseBlock.java index 86623c7..ee99b8c 100644 --- a/src/main/java/uk/gemwire/engage/block/coolant/CoolantBaseBlock.java +++ b/src/main/java/uk/gemwire/engage/block/coolant/CoolantBaseBlock.java @@ -28,135 +28,164 @@ public class CoolantBaseBlock extends Block { if (level.isClientSide) return; long startTime = System.currentTimeMillis(); - boolean encounteredMetering = false; - boolean encounteredCompressor = false; - boolean encounteredHeatSpreader = false; - Set visitedBlocks = new HashSet<>(); - // If we happened to choose the path that encounters the metering device before a heat spreader, we need to swap our detection system. - boolean prematureMetering = false; - Direction lastTransitionDirection; - LOGGER.info("Beginning network discovery"); + searchForValidNetwork(new BlockPos.MutableBlockPos(pos.getX(), pos.getY(), pos.getZ()), level); - // Get the valid directions we can go. - var dirs = getValidTransitionsFrom(pos, null, level, visitedBlocks); - - LOGGER.info("First step: " + dirs.size() + " valid moves."); - // If we aren't connected to at least 2 other valid blocks, it is impossible to have a completed network. - if (dirs.size() < 2) { - long endTime = System.currentTimeMillis(); - LOGGER.info("Discovery took " + (endTime - startTime) + "ms"); - return; - }; - - // Check the block we're at, first. - if (this == Blocks.COMPRESSOR_BLOCK.block().get()) encounteredCompressor = true; - else if (this == Blocks.COOLANT_METERING_BLOCK.block().get()) { encounteredMetering = true; prematureMetering = true; } - else if (this == Blocks.COOLANT_HEAT_SPREADER_BLOCK.block().get()) encounteredHeatSpreader = true; - - visitedBlocks.add(pos); - while (dirs.size() > 1) { - // Traverse the new block - lastTransitionDirection = (Direction) dirs.keySet().toArray()[0]; - BlockPos traversedPos = dirs.get(lastTransitionDirection); - - Block reachedBlock = level.getBlockState(traversedPos).getBlock(); - - LOGGER.info("Traversing through " + lastTransitionDirection + " to " + traversedPos + ", which is a " + getBlockName(reachedBlock)); - visitedBlocks.add(traversedPos); - - // If we reached a metering block for the first time - if (reachedBlock == Blocks.COOLANT_METERING_BLOCK.block().get()) { - if (encounteredMetering) { - LOGGER.info("Two metering devices in the same network! Not valid."); - long endTime = System.currentTimeMillis(); - LOGGER.info("Discovery took " + (endTime - startTime) + "ms"); - return; - } - // And we have already reached a heat spreader: - if (encounteredHeatSpreader) { - // Tell the system to check for another - encounteredHeatSpreader = false; - // Set the metering flag true - } else { - // We're searching prematurely! - // We'll search for it at the compressor instead. - prematureMetering = true; - } - encounteredMetering = true; - // The compressor has its own things to check.. - } else if (reachedBlock == Blocks.COMPRESSOR_BLOCK.block().get()) { - if (encounteredCompressor) { - LOGGER.info("Two compressors in the same network! Not valid."); - - long endTime = System.currentTimeMillis(); - LOGGER.info("Discovery took " + (endTime - startTime) + "ms"); - return; - } - - // Premature metering means we should check that a heat spreader is between the metering device and the compressor. - if (prematureMetering) { - if (!encounteredHeatSpreader) { - LOGGER.info("Metering Device and Compressor are too close together! Not valid."); - - long endTime = System.currentTimeMillis(); - LOGGER.info("Discovery took " + (endTime - startTime) + "ms"); - return; - } else { - encounteredCompressor = true; - } - } else { - encounteredCompressor = true; - } - // Heat spreaders may have many, so we just flip the flag to be true if we're just getting to one now. - } else if (reachedBlock == Blocks.COOLANT_HEAT_SPREADER_BLOCK.block().get()) { - if (!encounteredHeatSpreader) encounteredHeatSpreader = true; - } - - dirs = getValidTransitionsFrom(traversedPos, lastTransitionDirection, level, visitedBlocks); - } - - // Try to finalise stuff - we gotta have everything in our network. - // First, check that the last remaining direction takes us into a block we've already visited (the closed loop) - if (visitedBlocks.contains(dirs.values().iterator().next())) { - // Check for premature mode - if (prematureMetering) { - if (!(encounteredCompressor && encounteredHeatSpreader)) { - LOGGER.info("Two heat spreaders not found in valid configuration!"); - - long endTime = System.currentTimeMillis(); - LOGGER.info("Discovery took " + (endTime - startTime) + "ms"); - return; - } - } else { - if (!(encounteredCompressor && encounteredMetering && encounteredHeatSpreader)) { - LOGGER.info("Not encountered all components of the coolant system!"); - - long endTime = System.currentTimeMillis(); - LOGGER.info("Discovery took " + (endTime - startTime) + "ms"); - return; - } - } - } - - LOGGER.info("Valid coolant loop!"); long endTime = System.currentTimeMillis(); LOGGER.info("Discovery took " + (endTime - startTime) + "ms"); } - private Map getValidTransitionsFrom(BlockPos pos, Direction incoming, Level level, Set visited) { - Map dirs = new EnumMap<>(Direction.class); + private Set getValidTransitionsFrom(NetworkState state, Level level) { + Set dirs = new HashSet<>(); for (Direction dir : Direction.values()) { - if (incoming != null && dir == incoming) continue; - BlockPos newPos = pos.relative(dir); - if (visited.contains(newPos)) continue; - if (VALID_COOLANT_BLOCKS.contains(level.getBlockState(newPos).getBlock())) dirs.put(dir, newPos); + if (state.lastTransition != null && dir == state.lastTransition) continue; + BlockPos newPos = state.currentPos.relative(dir); + if (state.visited.contains(newPos)) continue; + if (VALID_COOLANT_BLOCKS.contains(level.getBlockState(newPos).getBlock())) dirs.add(new Transition(dir, newPos, level.getBlockState(newPos))); } return dirs; } + + // TODO: Revert the search to a known good state + private boolean searchForValidNetwork(BlockPos.MutableBlockPos pos, Level level) { + NetworkState state = new NetworkState(false, false, false, new HashSet<>(), false, null, pos); + Queue queue = new ArrayDeque<>(); + + LOGGER.info("Beginning network discovery"); + + // Get the valid directions we can go. + var dirs = getValidTransitionsFrom(state, level); + + LOGGER.info("First step: " + dirs.size() + " valid moves."); + // If we aren't connected to at least 2 other valid blocks, it is impossible to have a completed network. + if (dirs.size() < 2) { + return false; + } + + state.visited.add(pos); + + queue.add(new StateTransitions(state.copy(), dirs)); + + // Check the block we're at, first. + if (this == Blocks.COMPRESSOR_BLOCK.block().get()) state.compressor = true; + else if (this == Blocks.COOLANT_METERING_BLOCK.block().get()) { state.metering = true; state.premature = true; } + else if (this == Blocks.COOLANT_HEAT_SPREADER_BLOCK.block().get()) state.spreader = true; + + while(!queue.isEmpty()) { + // The search loop + search(queue.element(), level, queue); + + // Try to finalise stuff - we gotta have everything in our network. + // First, check that the last remaining direction takes us into a block we've already visited (the closed loop) + if (state.visited.contains(dirs.iterator().next().pos())) { + // Check for premature mode + if (state.premature) { + if (!(state.compressor && state.spreader)) { + LOGGER.info("Two heat spreaders not found in valid configuration!"); + queue.poll(); + } + } else { + if (!(state.compressor && state.metering && state.spreader)) { + LOGGER.info("Not encountered all components of the coolant system!"); + queue.poll(); + } + } + } + } + + LOGGER.info("Valid coolant loop!"); + return true; + } + + void search(StateTransitions st, Level level, Queue queue) { + while (st.transitions.size() > 1) { + // Traverse the new block + Transition transition = st.transitions.iterator().next(); + st.state.lastTransition = transition.dir(); + st.state.currentPos = transition.pos(); + + Block reachedBlock = transition.state().getBlock(); + + LOGGER.info("Traversing through " + st.state.lastTransition + " to " + st.state.currentPos + ", which is a " + getBlockName(reachedBlock)); + st.state.visited.add(st.state.currentPos); + + // If we reached a metering block for the first time + if (reachedBlock == Blocks.COOLANT_METERING_BLOCK.block().get()) { + st.state.metering(); + // The compressor has its own things to check.. + } else if (reachedBlock == Blocks.COMPRESSOR_BLOCK.block().get()) { + st.state.compressor(); + // Heat spreaders may have many, so we just flip the flag to be true if we're just getting to one now. + } else if (reachedBlock == Blocks.COOLANT_HEAT_SPREADER_BLOCK.block().get()) { + if (!st.state.spreader) st.state.spreader = true; + } + + Set newTransitions = getValidTransitionsFrom(st.state, level); + queue.add(new StateTransitions(st.state.copy(), newTransitions)); + } + } + private String getBlockName(Block block) { return block.getName().getContents().toString(); } + + record Transition(Direction dir, BlockPos pos, BlockState state) {} + + static class NetworkState { + boolean metering, compressor, spreader, premature; + Set visited; + Direction lastTransition; + BlockPos currentPos; + + public NetworkState(boolean metering, boolean compressor, boolean spreader, HashSet visited, boolean premature, Direction lastTransition, BlockPos pos) { + this.metering = metering; + this.compressor = compressor; + this.spreader = spreader; + this.premature = premature; + this.visited = visited; + this.lastTransition = lastTransition; + this.currentPos = pos; + } + + void metering() { + if (metering) + LOGGER.info("Two metering devices in the same network! Not valid."); + + // And we have already reached a heat spreader: + if (spreader) { + // Tell the system to check for another + spreader = false; + // Set the metering flag true + } else { + // We're searching prematurely! + // We'll search for it at the compressor instead. + premature = true; + } + metering = true; + } + + void compressor() { + if (compressor) + LOGGER.info("Two compressors in the same network! Not valid."); + + // Premature metering means we should check that a heat spreader is between the metering device and the compressor. + if (premature) { + if (!spreader) { + LOGGER.info("Metering Device and Compressor are too close together! Not valid."); + } else { + compressor = true; + } + } else { + compressor = true; + } + } + + NetworkState copy() { + return new NetworkState(metering, compressor, spreader, new HashSet<>(visited), premature, lastTransition, currentPos); + } + } + + record StateTransitions(NetworkState state, Set transitions) {} }