mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
docs(cdclient): fdb copies and hot reload
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -125,6 +125,8 @@ locally. See [docs/UgcServer.md](docs/UgcServer.md).
|
||||
* **CDClient:** the hot lookup tables (ComponentsRegistry, ItemComponent, Objects) are read straight from the client's
|
||||
`cdclient.fdb`, memory-mapped once and shared by every server process (Windows, Linux, macOS), so loading a character
|
||||
with thousands of different items no longer stalls a world. The CDClient data is never modified.
|
||||
Servers read a copy of the client's `cdclient.fdb`, so it can be replaced while they run, and master reloads it on
|
||||
every server when it changes or on `/reloadcdclient` ([docs/CDClientFdb.md](docs/CDClientFdb.md)).
|
||||
* The chat server's old web API is removed; the dashboard's API covers online players, teams and announcements.
|
||||
|
||||
## License
|
||||
|
||||
70
docs/CDClientFdb.md
Normal file
70
docs/CDClientFdb.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# CDClient: the client's cdclient.fdb
|
||||
|
||||
## Reading rows from the fdb
|
||||
|
||||
ComponentsRegistry, ItemComponent and Objects read their rows from a memory-mapped copy of the client's `cdclient.fdb`
|
||||
(`FdbReader`, `CDFdb`), shared between all server processes through the OS page cache. `CDServer.sqlite` stays the
|
||||
source of truth: at load each table compares its rows in both files and reads the ids whose rows the cdserver
|
||||
migrations changed from SQLite (`CDFdb::FindChangedKeys`). Other tables read `CDServer.sqlite` as before.
|
||||
|
||||
## Copies, not the client's file
|
||||
|
||||
No server opens the client's `<res>/cdclient.fdb`. Master copies it into `resServer`, named by the 64-bit FNV-1a hash of
|
||||
its bytes, and makes the matching SQLite file:
|
||||
|
||||
| File | What |
|
||||
| --- | --- |
|
||||
| `resServer/cdclient-<hash>.fdb` | copy of the client's fdb; the hash is of the copy's bytes |
|
||||
| `resServer/CDServer-<hash>.sqlite` | made from that copy with `FdbToSqlite` and every cdserver migration |
|
||||
| `resServer/cdclient-current` | two lines: the fdb copy and the SQLite file every server opens |
|
||||
| `resServer/CDServer.sqlite` | the file used before copies existed; still used when the pointer names it |
|
||||
|
||||
A new version of the fdb gets new names, so nothing is replaced or truncated while a process has it mapped or open.
|
||||
Every file is written under a temporary name and renamed into place. Windows can't rename over or delete an open
|
||||
file, and the content-addressed names avoid both. The client's file is only read and never locked, since it is copied
|
||||
and not mapped.
|
||||
|
||||
On the first start with copies, master copies the fdb and points at the existing `CDServer.sqlite`. If the client's fdb
|
||||
changed while master was down, master makes the new `CDServer-<hash>.sqlite` at startup. Master then runs the cdserver
|
||||
migrations on whichever SQLite file is current, as before. Worlds read `cdclient-current` at startup
|
||||
(`FdbSnapshot::Resolve`). If it is missing, or names a file that isn't there, they use `CDServer.sqlite` without an fdb.
|
||||
A client with only packed files (no loose `cdclient.fdb`) keeps using `CDServer.sqlite`, and nothing is watched.
|
||||
|
||||
## Hot reload
|
||||
|
||||
Master checks the client's fdb every `cdclient_watch_seconds` (default 5, 0 = off). It compares the size and mtime and
|
||||
starts a reload once they differ from the current version and have stayed the same for one poll, so a file still being
|
||||
copied in isn't read halfway. A reload can also be started by:
|
||||
|
||||
* `/reloadcdclient` (GM 9, paired with the `cdclient_reload` dashboard permission);
|
||||
* a `CDCLIENT_RELOAD` message with no names sent to master (what `/reloadcdclient` sends; master also accepts it from
|
||||
the dashboard).
|
||||
|
||||
A reload runs these steps:
|
||||
|
||||
1. **Worker thread (master):** copy and hash the file. If the hash is the current one, stop. Otherwise make
|
||||
`CDServer-<hash>.sqlite` on its own SQLite connection (convert, then every cdserver migration, recorded in its
|
||||
`migration_history`), and compare the old and new copies table by table (row count and a content hash). The worker
|
||||
never logs or touches RakNet, the game database, the shared CDClient connection or config.
|
||||
2. **Main thread (master):** switch master's own CDClient connection and tables, write `cdclient-current`, log the
|
||||
changed tables (for example `Objects: 16012 -> 16015 rows`), and send `CDCLIENT_RELOAD` with both names to every
|
||||
ready world.
|
||||
3. **Each world, between frames:** `CDClientDatabase::Reconnect` opens the new SQLite file before closing the old one,
|
||||
and `CDClientManager::Reload` empties every table, maps the new copy and loads again (the changed-row lists are
|
||||
rebuilt). The old table entries and the old fdb view are kept alive, so objects already spawned keep what they
|
||||
loaded, and objects made afterwards read the new data. The world logs how long the switch took.
|
||||
|
||||
Master keeps the current and previous copies and removes older ones after each reload and at startup. On POSIX, a
|
||||
removed file that is still mapped stays readable until it is closed. On Windows the removal fails while a process maps
|
||||
it, and master tries again next time.
|
||||
|
||||
## Limits
|
||||
|
||||
* The world reloads its cached tables on the main thread, so a reload costs about as long as the CDClient part of
|
||||
startup, once per reload.
|
||||
* Data copied out of the tables into other caches (for example behaviors already built, or zone data read at world
|
||||
start) keeps the old values until those objects are made again.
|
||||
* The UGC and dashboard servers don't switch on a reload yet; they read the current files when they start.
|
||||
* The dashboard has no reload button yet; use `/reloadcdclient` or let master see the file change.
|
||||
* A world that starts while master is making a new copy can read the old pointer and miss the broadcast; it catches up
|
||||
at the next reload or restart.
|
||||
Reference in New Issue
Block a user