Files
Ludovic Marie 7a84c93843 Server: feed the chunk workers between ticks while players' chunks load
A server thread (single player's, a LAN host's, a dedicated server's) ran
`streaming.update` once a tick, and `schedule_generation` keeps at most
three load or generate jobs per worker in flight. Joining a world was
capped at about six columns a tick (120 a second with two workers) while
the workers idled most of each tick. Vanilla's server runs its chunk
tasks while it waits for the next tick (`MinecraftServer.waitUntilNextTick`).

`pump_pregen`, now `pump_chunks`, already did that for a /pregen task and
force-loaded chunks; it now also pumps while the players' chunks are in
flight, except while the server is paused or a respawn search waits for
a chunk (its area is only asked for by the tick's own update).

Mac mini, --profile fast, offscreen, seed 1234, RD 12, server thread,
medians of 3, all 637 view columns in and meshed once:
  2 workers: create 8.05 -> 3.57 s, reload 3.72 -> 1.42 s
  4 workers: create 4.97 -> 2.16 s, reload 2.23 -> 1.27 s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 16:26:03 +01:00

3.1 KiB

Pregeneration: /pregen

/pregen generates every chunk of a square or a circle ahead of the players, lights it and saves it, as Chunky does on a Java server. It is Horizon's own command (vanilla has none), available in single player with cheats on and to operators (level 2) on a server, from the chat or the server console (horizon-server cmd pregen start 2500).

It is off until you turn it on, so /help and the command tree stay vanilla's:

  • a server: horizon-pregen = true under [properties] in server.toml (or horizon-pregen=true in a vanilla folder's server.properties), then restart it;
  • single player and LAN: start the game with --pregen.
/pregen start <radius> [square|circle] [<x> <z>]   radius in blocks; the centre defaults to where you
                                                   stand (the world spawn from the console)
/pregen status                                     chunks done, chunks per second, time left
/pregen pause | continue                           stop scheduling new chunks, and go on
/pregen cancel                                     stop; what is done stays saved

The area is the chunks within radius / 16 chunks of the centre chunk: a square of side 2 * (radius / 16) + 1 (radius 2500 is 313 x 313 = 97,969 chunks), or the chunks whose distance from the centre chunk is at most radius / 16. It runs in the dimension the command was run in; that level keeps ticking while the task runs, whoever is in it. The progress goes to the server log every five seconds and to the player who started it, and once more when it is done. A task is not kept across a restart: start it again and the chunks already saved load instead of generating.

How it works

  • horizon_client::pregen::Pregen cuts the area into strips at most 256 chunks wide and sweeps each north to south with a band of rows (streaming::Band) holding about a thousand chunks.
  • Streaming treats the band like a player's area: it generates its chunks and the rings around them that decoration and light need, and lights its own chunks. Once every chunk of the band's first row is lit and post-processed (LevelChunk.postProcessGeneration, which vanilla runs before a chunk is FULL), the band moves down a row; the rows that fall out of its reach unload and are saved like any chunk the players leave, so memory holds only the rows in flight.
  • The chunks a band reaches are ranked for decoration row by row as they come within its reach (Streaming::set_band). Decoration is order dependent where neighbours' writes overlap (DecorationOrder), and this makes a task decorate in the same order however its jobs are timed and shared among the workers: the same seed gives the same chunks.
  • Between server ticks the server waits for the workers' results and hands out the next jobs at once (Streaming::wait, pump_chunks), instead of once a tick: a chunk passes through five jobs (generate, decorate, finish, light, save), each needing its neighbours' earlier ones.

The chunks of the band's outer ring are finished (FULL) but not lit, as at the edge of what a player has loaded; they light when their neighbours load later.