Skip to content

Enhance terrain generation with new biomes, fluid handling, and optimizations#350

Merged
NanCunChild merged 24 commits into
ferrumc-rs:masterfrom
NanCunChild:fix/lmdb-concurrency
Jun 5, 2026
Merged

Enhance terrain generation with new biomes, fluid handling, and optimizations#350
NanCunChild merged 24 commits into
ferrumc-rs:masterfrom
NanCunChild:fix/lmdb-concurrency

Conversation

@NanCunChild

Copy link
Copy Markdown
Collaborator

BREAKING CHANGE!
The Database is refactored and old database is no longer being supported, but optimization is obvious.

The storage backend wrapped the whole heed Env in a parking_lot::Mutex,
which serialised every read behind every write and threw away LMDB's core
concurrency model (unbounded lock-free readers + a single internal writer
lock). Worker-thread chunk loads and the tick-thread world sync therefore
contended on one mutex, coupling background generation IO to tick timing.

…ap divide algorithm bug fixed; fix: chunk sending make core panic, now core will skip the disconnected player instead of keep waiting.
…n generation, amplitude now is modulated with land argument and erosion. SEA_LEVEL becomes the second options just to ensure no dryseabed generated; test: 13 terrain test added; feat: new biomes
…le-on-load); test: cover settle seeding and once-only scan
…om row of a uniform-fluid layer can flow down
…rain on the tick thread; drop now-redundant parallel prewarm

This was the dominant cause of multi-hundred-ms tick overruns: every block a cascade probed across an unloaded chunk boundary triggered ~1.3ms of synchronous terrain generation, and the resulting mass of newly generated, modified chunks then flooded world_sync. Reads now hit the chunk cache only (an unloaded neighbour reads back as a wall) and writes are skipped for unloaded chunks, confining each cascade to the active region; the boundary resumes when the neighbour loads and is settled.
Previously sync() iterated the cache immutably and never cleared dirty, so every chunk ever modified stayed dirty forever and each 15s sync re-serialised, re-compressed and rewrote the entire accumulated set. The cost grew without bound as the world was explored and was overrunning the sync budget by seconds (16s seen), blocking the single scheduler thread and starving keepalive into client timeouts. Sync now uses iter_mut and marks each saved chunk clean, so a sync only writes chunks changed since the last one.
…read server TPS

world_age was hardcoded to 0; MiniHUD and similar derive server TPS from the world-age delta between time packets over wall-clock time, so a constant value reported TPS as unavailable. Now sends the running total game-tick count.
…d across ticks instead of freezing one; add fluids.settle_on_load and fluids.max_ticks_per_tick config

A settle storm or big in-view cascade could make a single process_fluid_ticks drain and evaluate tens of thousands of due ticks, freezing the tick for seconds (entities then visibly hang). process_fluid_ticks now drains at most fluids.max_ticks_per_tick (default 2048) per game tick via the new BlockTickScheduler::drain_due_capped; the remainder stays due and is processed on following ticks, so fluids settle a little slower but the server stays responsive. fluids.settle_on_load (default true) lets the settle-on-load pass be disabled entirely as an escape hatch.
…thread, off the tick thread

Adds settle_chunk: a bounded, in-chunk fluid simulation (chunk borders act as walls) run by the chunk-send worker right after generation, so a chunk arrives with its hanging fluids already flowed and the game-tick thread does no fluid work for it. This moves the dominant fluid cost (cave-breached oceans, perched springs) off the tick thread entirely. Config: fluids.settle_on_generate (default true) and fluids.max_settle_changes (per-chunk budget). The on-load settle now only mops up cross-chunk seams.
…ld sync

The storage backend wrapped the whole heed `Env` in a `parking_lot::Mutex`,
which serialised every read behind every write and threw away LMDB's core
concurrency model (unbounded lock-free readers + a single internal writer
lock). Worker-thread chunk loads and the tick-thread world sync therefore
contended on one mutex, coupling background generation IO to tick timing.

Changes:
- Share the `Env` directly via `Arc<Env<WithoutTls>>`; let LMDB serialise
  writes and run reads concurrently. Reads no longer block on writes.
- Cache opened database handles (`dbi`s) in an `RwLock<HashMap>` instead of
  re-opening the named database inside a transaction on every operation.
- Open the environment with `NO_SYNC` and rely on the existing periodic
  `force_sync` (driven by the world sync schedule) for durability, trading
  per-commit fsync for throughput on regenerable chunk data.
- Collapse `World::sync` from one transaction per dirty chunk into a single
  batched `batch_upsert` commit, the dominant cost when many chunks are dirty.

Existing concurrent read/write storage tests and the world fluid/chunk tests
pass unchanged.
@NanCunChild NanCunChild merged commit 0f93b07 into ferrumc-rs:master Jun 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant