Measured, not guessed
One region of a real Paper 1.21.6 world (r.0.-1.mca, 5.4 MB, 811 generated
chunks, 19 769 sections):
total 180ms | decompress 39ms | nbt parse 141ms
...and that is before the surface extraction on top. The NBT parse is 78% of
it, and all of it is repeated every time the app starts, because #119's
cache is a Map in memory holding twelve regions.
A tile set for that region is about 1 MB raw and gzips to a fraction of it in
2–3 ms, with gunzip at ~1 ms. So the whole cost of a region can go from a few
hundred milliseconds to single-digit milliseconds — but only if the parse
survives a restart.
The disk cache was in #119's scope ("a tile cache on disk, keyed by region file
path + mtime") and I only built the in-memory half. This is the other half.
Scope
Cache parsed tiles on disk, keyed by the region path and its mtime, so a
region is parsed once ever and re-parsed only when the server rewrites it. On by
default — this is the setting that makes the map usable on a big world.
The cache key must carry a format version as well as the mtime. Without it,
changing the colour table or the foliage rule leaves every existing cache
serving the old picture forever, and the operator has no way to know why their
map looks wrong.
Per-server settings, because a 200-server box and a laptop want different
answers:
- Cache tiles on disk — default on.
- Regions kept in memory — the working set; more is faster and heavier.
- Delay between region parses — the brake that keeps the main thread
responsive; lower is faster and less polite to everything else.
- Cache size limit — with the oldest evicted, and a way to clear it.
Verify
The codec is pure and round-trips: colours, heights, the transparent columns
that must stay transparent, and structure marks. A cache written by an older
format version is refused rather than decoded into the wrong picture. A
truncated or corrupt file is refused rather than throwing. And the settings are
clamped — a parse gap of zero or a memory limit of a million is a way to hang
the process, and both arrive from a config file an operator can hand-edit.
Measured, not guessed
One region of a real Paper 1.21.6 world (
r.0.-1.mca, 5.4 MB, 811 generatedchunks, 19 769 sections):
...and that is before the surface extraction on top. The NBT parse is 78% of
it, and all of it is repeated every time the app starts, because #119's
cache is a
Mapin memory holding twelve regions.A tile set for that region is about 1 MB raw and gzips to a fraction of it in
2–3 ms, with gunzip at ~1 ms. So the whole cost of a region can go from a few
hundred milliseconds to single-digit milliseconds — but only if the parse
survives a restart.
The disk cache was in #119's scope ("a tile cache on disk, keyed by region file
path + mtime") and I only built the in-memory half. This is the other half.
Scope
Cache parsed tiles on disk, keyed by the region path and its mtime, so a
region is parsed once ever and re-parsed only when the server rewrites it. On by
default — this is the setting that makes the map usable on a big world.
The cache key must carry a format version as well as the mtime. Without it,
changing the colour table or the foliage rule leaves every existing cache
serving the old picture forever, and the operator has no way to know why their
map looks wrong.
Per-server settings, because a 200-server box and a laptop want different
answers:
responsive; lower is faster and less polite to everything else.
Verify
The codec is pure and round-trips: colours, heights, the transparent columns
that must stay transparent, and structure marks. A cache written by an older
format version is refused rather than decoded into the wrong picture. A
truncated or corrupt file is refused rather than throwing. And the settings are
clamped — a parse gap of zero or a memory limit of a million is a way to hang
the process, and both arrive from a config file an operator can hand-edit.