Skip to content

Commit

Permalink
Redesign ChunkSearcher for improved flexibility and performance
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander01998 committed Sep 18, 2023
1 parent 26c65a6 commit af83dbe
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 100 deletions.
19 changes: 5 additions & 14 deletions src/main/java/net/wurstclient/hacks/CaveFinderHack.java
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ else if(chunkUpdates.contains(searcherPos))
if(remove)
{
searchers.remove(searcherPos);
searcher.cancelSearching();
searcher.cancel();
searchersChanged = true;
}
}
Expand All @@ -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
Expand Down Expand Up @@ -288,23 +288,14 @@ 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());
Comparator<BlockPos> comparator =
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)));
}
Expand Down
38 changes: 18 additions & 20 deletions src/main/java/net/wurstclient/hacks/SearchHack.java
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -97,6 +98,7 @@ public String getRenderName()
@Override
public void onEnable()
{
lastBlock = block.getBlock();
prevLimit = limit.getValueI();
notify = true;

Expand Down Expand Up @@ -141,23 +143,28 @@ public void onReceivedPacket(PacketInputEvent event)
@Override
public void onUpdate()
{
Block currentBlock = block.getBlock();
DimensionType dimension = MC.world.getDimension();
HashSet<ChunkPos> 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
Expand All @@ -171,7 +178,7 @@ else if(chunkUpdates.contains(searcherPos))
if(remove)
{
searchers.remove(searcherPos);
searcher.cancelSearching();
searcher.cancel();
searchersChanged = true;
}
}
Expand All @@ -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
Expand Down Expand Up @@ -280,23 +287,14 @@ 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());
Comparator<BlockPos> comparator =
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)));
}
Expand Down
113 changes: 47 additions & 66 deletions src/main/java/net/wurstclient/util/ChunkSearcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,138 +8,119 @@
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<BlockPos, BlockState> query;
private final Chunk chunk;
private final Block block;
private final DimensionType dimension;
private final ArrayList<BlockPos> matchingBlocks = new ArrayList<>();
private ChunkSearcher.Status status = Status.IDLE;
private Future<?> future;

public ChunkSearcher(Chunk chunk, Block block, DimensionType dimension)
private CompletableFuture<ArrayList<Result>> future;
private boolean interrupted;

public ChunkSearcher(Block block, Chunk chunk, DimensionType dimension)
{
this((pos, state) -> block == state.getBlock(), chunk, dimension);
}

public ChunkSearcher(BiPredicate<BlockPos, BlockState> 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<Result> searchNow()
{
if(status == Status.IDLE || status == Status.DONE
|| !matchingBlocks.isEmpty())
throw new IllegalStateException();

ArrayList<Result> 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()
{
return chunk.getPos();
}

public Block getBlock()
{
return block;
}

public DimensionType getDimension()
{
return dimension;
}

public Stream<BlockPos> getMatchingBlocks()
public Stream<Result> getMatches()
{
return matchingBlocks.stream();
if(future == null || future.isCancelled())
return Stream.empty();

return future.join().stream();
}

public ChunkSearcher.Status getStatus()
public Stream<BlockPos> 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)
{}
}

0 comments on commit af83dbe

Please sign in to comment.