Data attachments and persistence internals
Status: Implemented (API 1.20). How attachments are stored, saved, synced and kept thread-safe, and what typed attachments add.
Reference: Persistence and data attachments.
Where the values live
| Scope | Stored in | Saved as |
|---|---|---|
| Entity | the entity’s extension record (created on the first value) | clight:data in the entity NBT, synced keys listed in clight:data_sync |
| Player | the player’s extension record | clight:data in the player file |
| Chunk | an immutable compound on the chunk (replaced on every change, so chunk snapshots for saving keep their own copy) | clight:data in the chunk NBT |
| Saved data | a cache per key on the server | data/<namespace>/<path>.dat (every level of a key in one file), written at lifecycle.level_save |
Vanilla ignores all of these keys and files. Celestial keeps the values of mods that are not installed exactly as they were, so removing a mod and adding it back loses nothing.
Threads
Values are read and written on the tick thread of the level that owns them (@context(TICK)): the server tick for entities, players, chunks and saved data. A client reads only the synced copies its connection received, on the client tick. There are no locks on the hot path; the per-key typed attachment table is read under a small mutex only while at least one key is registered (one relaxed atomic load otherwise).
Sync
A value set with CL_DATA_SYNC (or a key registered with CL_ATTACH_SYNC) goes on the clight:entities payload to every Celestial client with CLight that registered the channel and tracks the entity (the player itself included). It is sent again when a client starts tracking the entity. Vanilla clients get nothing. A removed synced key sends a removal. Synced values are at most 32 KiB.
Typed attachments
cl_attachment_register puts the key into a process-wide table (key, default SNBT, flags, owner mod):
- Defaults. A get of a missing key with a default returns the default. On the server, the default is first stored (with the key’s sync rule), then read back in the format asked for, so the binary get and the save see the same value. A client has no SNBT parser: it returns the default text for its copy, and the binary get of an unset key stays
CL_E_NOT_FOUND. - Sync. A set of a key with
CL_ATTACH_SYNCis sent as ifCL_DATA_SYNCwere passed. - Death. Celestial keeps the same player object through a respawn (vanilla makes a new
ServerPlayerand copies data withrestoreFrom). When a player respawns after dying, the keys registered withoutCL_ATTACH_COPY_ON_DEATHare removed from the player (a synced one sends its removal); the next get gives the default again. Returning from the End keeps everything.
Cost
Without mods there is no extension record and no table: the save and load paths check one pointer. The death hook runs only for players with data.