Chunk API
Package: com.hypixel.hytale.server.core.universe.world.chunk
Chunks are 32x320x32 block columns. World height spans Y=0 to Y=319. See Blocks for reading and setting block data within chunks.
Chunk Access
Section titled “Chunk Access”// get loaded chunk (blocks if not in memory)WorldChunk chunk = world.getChunk(chunkIndex);
// get only if already loadedWorldChunk chunk = world.getChunkIfLoaded(chunkIndex);
// get only if in memory (may be non-ticking)WorldChunk chunk = world.getChunkIfInMemory(chunkIndex);
// get or load without forcing tick stateWorldChunk chunk = world.getNonTickingChunk(chunkIndex);
// async chunk loadingCompletableFuture<WorldChunk> future = world.getChunkAsync(chunkIndex);Chunk Indexing
Section titled “Chunk Indexing”// convert block coordinates to chunk indexlong chunkIndex = ChunkUtil.indexChunkFromBlock(worldX, worldZ);
// get chunk coordinates from indexint chunkX = ChunkUtil.xOfChunkIndex(chunkIndex);int chunkZ = ChunkUtil.zOfChunkIndex(chunkIndex);
// convert to block coordinatesint minBlockX = ChunkUtil.minBlock(chunkX); // chunkX * 32int minBlockZ = ChunkUtil.minBlock(chunkZ); // chunkZ * 32
// index within chunk (local block position)int localIndex = ChunkUtil.indexBlockInColumn(localX, y, localZ);WorldChunk
Section titled “WorldChunk”| Method | Description |
|---|---|
getX(), getZ() | Chunk coordinates |
getIndex() | Packed chunk index |
getWorld() | Parent world |
getBlock(x, y, z) | Get block ID at position |
setBlock(...) | Set block at position |
getHeight(x, z) | Heightmap value |
getState(x, y, z) | Get block state component |
markNeedsSaving() | Flag chunk for save |
Chunk Flags
Section titled “Chunk Flags”// check flag stateboolean isTicking = chunk.is(ChunkFlag.TICKING);
// set flagchunk.setFlag(ChunkFlag.TICKING, true);
// toggle flagboolean newValue = chunk.toggleFlag(ChunkFlag.TICKING);| Flag | Description |
|---|---|
START_INIT | Initialization has started |
INIT | Chunk is initialized |
NEWLY_GENERATED | Chunk was just generated (not loaded from disk) |
ON_DISK | Chunk has been saved to disk |
TICKING | Chunk is actively ticking |
Chunk Generation
Section titled “Chunk Generation”Chunks are generated on demand via getChunkReferenceAsync. The method tries to load from disk first, then generates if the chunk doesn’t exist:
ChunkStore chunkStore = world.getChunkStore();
// load from disk or generate (default)CompletableFuture<Ref<ChunkStore>> future = chunkStore.getChunkReferenceAsync(chunkIndex);
// with flags to control behaviorCompletableFuture<Ref<ChunkStore>> future = chunkStore.getChunkReferenceAsync(chunkIndex, flags);The flags parameter is a bitmask:
| Bit | Value | Effect |
|---|---|---|
| 0 | 1 | Skip disk load (generate only) |
| 1 | 2 | Skip generation (disk only — returns null for unexplored chunks) |
| 2 | 4 | Mark chunk as TICKING after load |
| 3 | 8 | Bypass loaded check (re-process even if already in memory) |
| 4 | 16 | Enable “still needed” check (cancels generation if no player needs the chunk) |
Common combinations:
| Flags | Effect |
|---|---|
0 | Load or generate (default) |
2 | Disk only — null for unexplored chunks |
3 | In-memory only (no I/O) |
4 | Load or generate, then mark as ticking |
9 | Force regeneration, skip disk (used by /chunk regenerate) |
Generation Pipeline
Section titled “Generation Pipeline”- Load or generate — runs on I/O / generator thread pool
- Pre-load processing — ChunkPreLoadProcessEvent fires (async thread, before chunk is in the store). Built-in plugins use this to initialize fluids, block ticks, and block entities.
- Post-load — chunk added to the store on the world thread. Lighting initialized. No event fires at this point — completion is signaled through the returned
CompletableFuture.
If saveNewChunks is enabled in world config, newly generated chunks are automatically marked dirty for saving.
Batch Generation
Section titled “Batch Generation”To generate a region of chunks (for pregeneration, map rendering, etc.):
int radius = 5;List<CompletableFuture<?>> futures = new ArrayList<>();
for (int x = -radius; x <= radius; x++) { for (int z = -radius; z <= radius; z++) { long index = ChunkUtil.indexChunk(centerX + x, centerZ + z); futures.add(chunkStore.getChunkReferenceAsync(index)); }}
CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)) .thenRun(() -> { // all chunks generated and in the store });Generation runs on a thread pool sized to 75% of available processors (minimum 2).
Chunk Sections
Section titled “Chunk Sections”Chunks are divided into 32x32x32 sections for storage efficiency:
// get section containing Y coordinateint sectionY = ChunkUtil.chunkCoordinate(blockY); // blockY >> 5
// access via ChunkColumnChunkColumn column = store.getComponent(chunkRef, ChunkColumn.getComponentType());Ref<ChunkStore> sectionRef = column.getSection(sectionY);
// get block section dataBlockSection blockSection = store.getComponent(sectionRef, BlockSection.getComponentType());int blockId = blockSection.get(localX, localY, localZ);Keep-Loaded Chunks
Section titled “Keep-Loaded Chunks”Chunks unload when no players are nearby. This causes problems when:
- Async operations need to read block data after loading chunks - the chunk might unload before the operation completes
- Spawn areas should remain available for instant respawns
- Important regions (arenas, scripted areas) need to stay active
Two mechanisms prevent unwanted unloading:
Reference Counting (Temporary)
Section titled “Reference Counting (Temporary)”For async operations that need chunks to stay loaded until complete. The server uses this internally for respawn validation - loading chunks around the spawn point, checking for collisions, then releasing them.
WorldChunk chunk = world.getChunk(chunkIndex);
chunk.addKeepLoaded(); // increment countertry { // chunk guaranteed to stay loaded readBlockData(chunk);} finally { chunk.removeKeepLoaded(); // decrement counter}The counter is an AtomicInteger - multiple operations can independently hold the same chunk. It only becomes unloadable when all holders release it.
Async pattern (multiple chunks):
List<CompletableFuture<WorldChunk>> futures = chunkIndices.stream() .map(index -> world.getChunkAsync(index).thenApply(chunk -> { chunk.addKeepLoaded(); return chunk; })) .toList();
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) .thenRun(() -> { // all chunks loaded and held - safe to use }) .whenComplete((v, ex) -> { // release all chunks when done for (var future : futures) { future.thenAccept(WorldChunk::removeKeepLoaded); } });Keep-Loaded Region (Permanent)
Section titled “Keep-Loaded Region (Permanent)”For areas that should never unload (spawn, lobbies). Configure via WorldConfig:
WorldConfig config = world.getWorldConfig();ChunkConfig chunkConfig = config.getChunkConfig();
// set region (block coordinates)chunkConfig.setKeepLoadedRegion(new Box2D( new Vector2d(minX, minZ), new Vector2d(maxX, maxZ)));config.markChanged();
// clear regionchunkConfig.setKeepLoadedRegion(null);config.markChanged();Or via commands:
/world settings keeploaded set <minX> <minZ> <maxX> <maxZ>/world settings keeploaded resetKeep-Loaded vs Ticking
Section titled “Keep-Loaded vs Ticking”Keep-loaded chunks stay in memory but may stop ticking if no players are nearby. Block data is preserved, but block updates, entity AI, and redstone won’t run until a player approaches.
/chunk info <x> <z> -- shows keepLoaded and ticking status