From 4439190e9cd045fba53c8f1364c60d130e47cc32 Mon Sep 17 00:00:00 2001 From: Stickia Date: Tue, 15 Jul 2025 16:19:20 -0400 Subject: [PATCH 1/4] Added ChunkScanning for a clean interface to scan/cache chunks using `ServerChunkCache#getChunkFuture` --- .../hexcasting/api/utils/ChunkScanning.kt | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt b/Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt new file mode 100644 index 0000000000..4fc1c4b034 --- /dev/null +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt @@ -0,0 +1,68 @@ +package at.petrak.hexcasting.api.utils + +import at.petrak.hexcasting.api.HexAPI +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap +import net.minecraft.core.BlockPos +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.level.ChunkPos +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.chunk.ChunkStatus +import net.minecraft.world.level.chunk.ImposterProtoChunk + +/** + * This is a helper class to efficiently scan chunks in ways Minecraft did not intend for. This is for only reading chunks, not writing + */ +class ChunkScanning(var level: ServerLevel) { + var chunks: Long2ObjectLinkedOpenHashMap = Long2ObjectLinkedOpenHashMap() + + /** + * This attempts to cache a chunk to the local [chunks] + * @param ChunkPos the chunk to try to cache + * @return If the function could cache the chunk or not + */ + fun cacheChunk(chunk: ChunkPos): Boolean { + val chunkLong = chunk.toLong() + // We have the chunk already, so we can skip it + if (chunks.contains(chunkLong)){ + return true + } + val future = level.chunkSource.getChunkFuture(chunk.x,chunk.z, ChunkStatus.EMPTY,true).get() + if (future.left().isPresent){ + chunks.put(chunkLong, future.left().get() as ImposterProtoChunk) + return true + } + HexAPI.LOGGER.warn("Failed to get chunk at {}!",chunk) + return false + } + + fun cacheChunk(chunk: Long): Boolean{ + return cacheChunk(ChunkPos(chunk)) + } + + fun getBlock(blockPos: BlockPos): BlockState? { + val chunkPos = ChunkPos(blockPos).toLong() + if (!cacheChunk(chunkPos)){ + return null + } + return chunks.get(chunkPos).getBlockState(blockPos) + } + + fun getBlockEntity(blockPos: BlockPos): BlockEntity? { + val chunkPos = ChunkPos(blockPos).toLong() + if (!cacheChunk(chunkPos)){ + return null + } + return chunks.get(chunkPos).getBlockEntity(blockPos) + } + + // Maybe not required, but still not a bad idea to have a Clear method + fun clearCache(){ + chunks.clear() + } + + // Might not be needed + fun containsChunk(chunk: ChunkPos): Boolean{ + return chunks.contains(chunk.toLong()) + } +} \ No newline at end of file From d3a99d9f655eb24dc65be3094a78c3f6f02da916 Mon Sep 17 00:00:00 2001 From: Stickia Date: Tue, 15 Jul 2025 16:20:58 -0400 Subject: [PATCH 2/4] Implemented ChunkScanning into CircleExecutionState --- .../api/casting/circles/CircleExecutionState.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/CircleExecutionState.java b/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/CircleExecutionState.java index f95fa84f01..902e5e4f16 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/CircleExecutionState.java +++ b/Common/src/main/java/at/petrak/hexcasting/api/casting/circles/CircleExecutionState.java @@ -5,6 +5,7 @@ import at.petrak.hexcasting.api.casting.eval.vm.CastingImage; import at.petrak.hexcasting.api.misc.Result; import at.petrak.hexcasting.api.pigment.FrozenPigment; +import at.petrak.hexcasting.api.utils.ChunkScanning; import at.petrak.hexcasting.api.utils.HexUtils; import com.mojang.datafixers.util.Pair; import net.minecraft.ChatFormatting; @@ -95,13 +96,17 @@ protected CircleExecutionState(BlockPos impetusPos, Direction impetusDir, Set(); var seenGoodPositions = new ArrayList(); + var scanning = new ChunkScanning(level); while (!todo.isEmpty()) { var pair = todo.pop(); var enterDir = pair.getFirst(); var herePos = pair.getSecond(); + var hereBs = scanning.getBlock(herePos); - var hereBs = level.getBlockState(herePos); + if (hereBs == null){ + continue; + } if (!(hereBs.getBlock() instanceof ICircleComponent cmp)) { continue; } @@ -118,6 +123,7 @@ protected CircleExecutionState(BlockPos impetusPos, Direction impetusDir, Set(null); From a707b2ce67396030fcd71b690c561e9ee218babf Mon Sep 17 00:00:00 2001 From: Stickia <93604201+Stick404@users.noreply.github.com> Date: Sun, 24 May 2026 13:05:55 -0400 Subject: [PATCH 3/4] Update Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt Co-authored-by: Aly Cerruti --- .../hexcasting/api/utils/ChunkScanning.kt | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt b/Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt index 4fc1c4b034..083bee061c 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt @@ -21,39 +21,35 @@ class ChunkScanning(var level: ServerLevel) { * @param ChunkPos the chunk to try to cache * @return If the function could cache the chunk or not */ - fun cacheChunk(chunk: ChunkPos): Boolean { + fun cacheChunk(chunk: ChunkPos): ImposterProtoChunk? { val chunkLong = chunk.toLong() - // We have the chunk already, so we can skip it - if (chunks.contains(chunkLong)){ - return true + // If we have the chunk already, we can skip fetching it + val existing = chunks.get(chunkLong) + if (existing != null){ + return existing } val future = level.chunkSource.getChunkFuture(chunk.x,chunk.z, ChunkStatus.EMPTY,true).get() if (future.left().isPresent){ - chunks.put(chunkLong, future.left().get() as ImposterProtoChunk) - return true + val next = future.left().get() as ImposterProtoChunk + chunks.put(chunkLong, next) + return next } HexAPI.LOGGER.warn("Failed to get chunk at {}!",chunk) - return false + return null } - fun cacheChunk(chunk: Long): Boolean{ + fun cacheChunk(chunk: Long): ImposterProtoChunk? { return cacheChunk(ChunkPos(chunk)) } fun getBlock(blockPos: BlockPos): BlockState? { val chunkPos = ChunkPos(blockPos).toLong() - if (!cacheChunk(chunkPos)){ - return null - } - return chunks.get(chunkPos).getBlockState(blockPos) + return cacheChunk(chunkPos)?.getBlockState(blockPos) } fun getBlockEntity(blockPos: BlockPos): BlockEntity? { val chunkPos = ChunkPos(blockPos).toLong() - if (!cacheChunk(chunkPos)){ - return null - } - return chunks.get(chunkPos).getBlockEntity(blockPos) + return cacheChunk(chunkPos)?.getBlockEntity(blockPos) } // Maybe not required, but still not a bad idea to have a Clear method From be997d13966b18351afa317b9644ea127093cd6c Mon Sep 17 00:00:00 2001 From: stickia Date: Sun, 24 May 2026 13:23:58 -0400 Subject: [PATCH 4/4] Removed possibly dangerous cast in `ChunkScanning` Improved Comment and removed unused import --- .../at/petrak/hexcasting/api/utils/ChunkScanning.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt b/Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt index 083bee061c..d6dfa5f933 100644 --- a/Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt +++ b/Common/src/main/java/at/petrak/hexcasting/api/utils/ChunkScanning.kt @@ -7,21 +7,21 @@ import net.minecraft.server.level.ServerLevel import net.minecraft.world.level.ChunkPos import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.state.BlockState +import net.minecraft.world.level.chunk.ChunkAccess import net.minecraft.world.level.chunk.ChunkStatus -import net.minecraft.world.level.chunk.ImposterProtoChunk /** * This is a helper class to efficiently scan chunks in ways Minecraft did not intend for. This is for only reading chunks, not writing */ class ChunkScanning(var level: ServerLevel) { - var chunks: Long2ObjectLinkedOpenHashMap = Long2ObjectLinkedOpenHashMap() + var chunks: Long2ObjectLinkedOpenHashMap = Long2ObjectLinkedOpenHashMap() /** * This attempts to cache a chunk to the local [chunks] * @param ChunkPos the chunk to try to cache - * @return If the function could cache the chunk or not + * @return Either the ChunkAccess gained, or null */ - fun cacheChunk(chunk: ChunkPos): ImposterProtoChunk? { + fun cacheChunk(chunk: ChunkPos): ChunkAccess? { val chunkLong = chunk.toLong() // If we have the chunk already, we can skip fetching it val existing = chunks.get(chunkLong) @@ -30,7 +30,7 @@ class ChunkScanning(var level: ServerLevel) { } val future = level.chunkSource.getChunkFuture(chunk.x,chunk.z, ChunkStatus.EMPTY,true).get() if (future.left().isPresent){ - val next = future.left().get() as ImposterProtoChunk + val next = future.left().get() chunks.put(chunkLong, next) return next } @@ -38,7 +38,7 @@ class ChunkScanning(var level: ServerLevel) { return null } - fun cacheChunk(chunk: Long): ImposterProtoChunk? { + fun cacheChunk(chunk: Long): ChunkAccess? { return cacheChunk(ChunkPos(chunk)) }