From af83dbe4518c1a8fcfb98bea99529f0ca9b06453 Mon Sep 17 00:00:00 2001 From: Alexander01998 Date: Mon, 18 Sep 2023 15:46:56 +0200 Subject: [PATCH] Redesign ChunkSearcher for improved flexibility and performance --- .../net/wurstclient/hacks/CaveFinderHack.java | 19 +-- .../net/wurstclient/hacks/SearchHack.java | 38 +++--- .../net/wurstclient/util/ChunkSearcher.java | 113 ++++++++---------- 3 files changed, 70 insertions(+), 100 deletions(-) diff --git a/src/main/java/net/wurstclient/hacks/CaveFinderHack.java b/src/main/java/net/wurstclient/hacks/CaveFinderHack.java index d93f7b0dc7..445b894689 100644 --- a/src/main/java/net/wurstclient/hacks/CaveFinderHack.java +++ b/src/main/java/net/wurstclient/hacks/CaveFinderHack.java @@ -169,7 +169,7 @@ else if(chunkUpdates.contains(searcherPos)) if(remove) { searchers.remove(searcherPos); - searcher.cancelSearching(); + searcher.cancel(); searchersChanged = true; } } @@ -182,16 +182,16 @@ else if(chunkUpdates.contains(searcherPos)) continue; ChunkSearcher searcher = - new ChunkSearcher(chunk, Blocks.CAVE_AIR, dimension); + new ChunkSearcher(Blocks.CAVE_AIR, chunk, dimension); searchers.put(chunkPos, searcher); - searcher.startSearching(threadPool); + searcher.start(threadPool); searchersChanged = true; } if(searchersChanged) stopBuildingBuffer(); - if(!areAllChunkSearchersDone()) + if(!searchers.values().stream().allMatch(ChunkSearcher::isDone)) return; // check if limit has changed @@ -288,15 +288,6 @@ private void stopBuildingBuffer() bufferUpToDate = false; } - private boolean areAllChunkSearchersDone() - { - for(ChunkSearcher searcher : searchers.values()) - if(searcher.getStatus() != ChunkSearcher.Status.DONE) - return false; - - return true; - } - private void startGetMatchingBlocksTask() { BlockPos eyesPos = BlockPos.ofFloored(RotationUtils.getEyesPos()); @@ -304,7 +295,7 @@ private void startGetMatchingBlocksTask() Comparator.comparingInt(pos -> eyesPos.getManhattanDistance(pos)); getMatchingBlocksTask = forkJoinPool.submit(() -> searchers.values() - .parallelStream().flatMap(ChunkSearcher::getMatchingBlocks) + .parallelStream().flatMap(ChunkSearcher::getMatchingPositions) .sorted(comparator).limit(limit.getValueLog()) .collect(Collectors.toCollection(HashSet::new))); } diff --git a/src/main/java/net/wurstclient/hacks/SearchHack.java b/src/main/java/net/wurstclient/hacks/SearchHack.java index 584c2b2c1b..6854093802 100644 --- a/src/main/java/net/wurstclient/hacks/SearchHack.java +++ b/src/main/java/net/wurstclient/hacks/SearchHack.java @@ -53,6 +53,7 @@ public final class SearchHack extends Hack { private final BlockSetting block = new BlockSetting("Block", "The type of block to search for.", "minecraft:diamond_ore", false); + private Block lastBlock; private final ChunkAreaSetting area = new ChunkAreaSetting("Area", "The area around the player to search in.\n" @@ -97,6 +98,7 @@ public String getRenderName() @Override public void onEnable() { + lastBlock = block.getBlock(); prevLimit = limit.getValueI(); notify = true; @@ -141,23 +143,28 @@ public void onReceivedPacket(PacketInputEvent event) @Override public void onUpdate() { - Block currentBlock = block.getBlock(); DimensionType dimension = MC.world.getDimension(); HashSet chunkUpdates = clearChunksToUpdate(); boolean searchersChanged = false; + // clear ChunkSearchers if block has changed + Block currentBlock = block.getBlock(); + if(currentBlock != lastBlock) + { + searchers.values().forEach(ChunkSearcher::cancel); + searchers.clear(); + lastBlock = currentBlock; + searchersChanged = true; + } + // remove outdated ChunkSearchers for(ChunkSearcher searcher : new ArrayList<>(searchers.values())) { boolean remove = false; ChunkPos searcherPos = searcher.getPos(); - // wrong block - if(currentBlock != searcher.getBlock()) - remove = true; - // wrong dimension - else if(dimension != searcher.getDimension()) + if(dimension != searcher.getDimension()) remove = true; // out of range @@ -171,7 +178,7 @@ else if(chunkUpdates.contains(searcherPos)) if(remove) { searchers.remove(searcherPos); - searcher.cancelSearching(); + searcher.cancel(); searchersChanged = true; } } @@ -184,16 +191,16 @@ else if(chunkUpdates.contains(searcherPos)) continue; ChunkSearcher searcher = - new ChunkSearcher(chunk, currentBlock, dimension); + new ChunkSearcher(currentBlock, chunk, dimension); searchers.put(chunkPos, searcher); - searcher.startSearching(threadPool); + searcher.start(threadPool); searchersChanged = true; } if(searchersChanged) stopBuildingBuffer(); - if(!areAllChunkSearchersDone()) + if(!searchers.values().stream().allMatch(ChunkSearcher::isDone)) return; // check if limit has changed @@ -280,15 +287,6 @@ private void stopBuildingBuffer() bufferUpToDate = false; } - private boolean areAllChunkSearchersDone() - { - for(ChunkSearcher searcher : searchers.values()) - if(searcher.getStatus() != ChunkSearcher.Status.DONE) - return false; - - return true; - } - private void startGetMatchingBlocksTask() { BlockPos eyesPos = BlockPos.ofFloored(RotationUtils.getEyesPos()); @@ -296,7 +294,7 @@ private void startGetMatchingBlocksTask() Comparator.comparingInt(pos -> eyesPos.getManhattanDistance(pos)); getMatchingBlocksTask = forkJoinPool.submit(() -> searchers.values() - .parallelStream().flatMap(ChunkSearcher::getMatchingBlocks) + .parallelStream().flatMap(ChunkSearcher::getMatchingPositions) .sorted(comparator).limit(limit.getValueLog()) .collect(Collectors.toCollection(HashSet::new))); } diff --git a/src/main/java/net/wurstclient/util/ChunkSearcher.java b/src/main/java/net/wurstclient/util/ChunkSearcher.java index 615db33430..fc646c310b 100644 --- a/src/main/java/net/wurstclient/util/ChunkSearcher.java +++ b/src/main/java/net/wurstclient/util/ChunkSearcher.java @@ -8,106 +8,89 @@ package net.wurstclient.util; import java.util.ArrayList; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; +import java.util.function.BiPredicate; import java.util.stream.Stream; import net.minecraft.block.Block; -import net.minecraft.client.world.ClientWorld; +import net.minecraft.block.BlockState; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.dimension.DimensionType; -import net.wurstclient.WurstClient; /** - * Searches a {@link Chunk} for a particular type of {@link Block}. + * Searches the given {@link Chunk} for blocks matching the given query. + * Intended to be used in a thread pool to efficiently search large areas. */ public final class ChunkSearcher { + private final BiPredicate query; private final Chunk chunk; - private final Block block; private final DimensionType dimension; - private final ArrayList matchingBlocks = new ArrayList<>(); - private ChunkSearcher.Status status = Status.IDLE; - private Future future; - public ChunkSearcher(Chunk chunk, Block block, DimensionType dimension) + private CompletableFuture> future; + private boolean interrupted; + + public ChunkSearcher(Block block, Chunk chunk, DimensionType dimension) { + this((pos, state) -> block == state.getBlock(), chunk, dimension); + } + + public ChunkSearcher(BiPredicate query, Chunk chunk, + DimensionType dimension) + { + this.query = query; this.chunk = chunk; - this.block = block; this.dimension = dimension; } - public void startSearching(ExecutorService pool) + public void start(ExecutorService pool) { - if(status != Status.IDLE) + if(future != null || interrupted) throw new IllegalStateException(); - status = Status.SEARCHING; - future = pool.submit(this::searchNow); + future = CompletableFuture.supplyAsync(this::searchNow, pool); } - private void searchNow() + private ArrayList searchNow() { - if(status == Status.IDLE || status == Status.DONE - || !matchingBlocks.isEmpty()) - throw new IllegalStateException(); - + ArrayList results = new ArrayList<>(); ChunkPos chunkPos = chunk.getPos(); - ClientWorld world = WurstClient.MC.world; int minX = chunkPos.getStartX(); - int minY = world.getBottomY(); + int minY = chunk.getBottomY(); int minZ = chunkPos.getStartZ(); int maxX = chunkPos.getEndX(); - int maxY = world.getTopY(); + int maxY = ChunkUtils.getHighestNonEmptySectionYOffset(chunk) + 16; int maxZ = chunkPos.getEndZ(); for(int x = minX; x <= maxX; x++) for(int y = minY; y <= maxY; y++) for(int z = minZ; z <= maxZ; z++) { - if(status == Status.INTERRUPTED || Thread.interrupted()) - return; + if(interrupted) + return results; BlockPos pos = new BlockPos(x, y, z); - Block block = BlockUtils.getBlock(pos); - if(!this.block.equals(block)) + BlockState state = chunk.getBlockState(pos); + if(!query.test(pos, state)) continue; - matchingBlocks.add(pos); + results.add(new Result(pos, state)); } - status = Status.DONE; - } - - public void cancelSearching() - { - new Thread(this::cancelNow, "ChunkSearcher-canceller").start(); + return results; } - private void cancelNow() + public void cancel() { - if(future != null) - try - { - status = Status.INTERRUPTED; - future.get(); - - }catch(InterruptedException | ExecutionException e) - { - e.printStackTrace(); - } + if(future == null || future.isDone()) + return; - matchingBlocks.clear(); - status = Status.IDLE; - } - - public Chunk getChunk() - { - return chunk; + interrupted = true; + future.cancel(false); } public ChunkPos getPos() @@ -115,31 +98,29 @@ public ChunkPos getPos() return chunk.getPos(); } - public Block getBlock() - { - return block; - } - public DimensionType getDimension() { return dimension; } - public Stream getMatchingBlocks() + public Stream getMatches() { - return matchingBlocks.stream(); + if(future == null || future.isCancelled()) + return Stream.empty(); + + return future.join().stream(); } - public ChunkSearcher.Status getStatus() + public Stream getMatchingPositions() { - return status; + return getMatches().map(Result::pos); } - public static enum Status + public boolean isDone() { - IDLE, - SEARCHING, - INTERRUPTED, - DONE; + return future != null && future.isDone(); } + + public record Result(BlockPos pos, BlockState state) + {} }