Skip to content

Data Storage

In: Developer · Same group: QinhRuinsAPI · Provider & Bridges Related: Core Concepts · How data is stored · Full config.yml

QR's data falls into two kinds: instances / runtime state bound to world coordinates all go to local YAML (pointless across servers), and only the player codex discoveries can optionally go to a database for cross-server sharing. This page covers what each store holds, where, and when it goes to a database.


1. Storage responsibilities in one line

Recipes (templates / blueprints / archives / loot tables / affixes) = disk YAML + structure files, take effect on /qr reload
Instances and runtime state (anchors / claims / slots / mechanisms / purge vessels) = local YAML, bound to world coordinates → should be local
Player codex discoveries = optionally to a database (cross-server shared), the rest still local

The one-line boundary: "Does this data still mean anything off this server?" Anchors / chest claims / slot records are bound to this server's world coordinates and are useless across servers → local; "which ruin templates a player has discovered" is a portable, cross-server collection book → can go to a database.


2. Storage facade: RuinStorage

RuinStorage is the only storage facade that follows the QCL database, and it only handles the codex. The other stores are all standalone local YAML files that don't go through it.

kotlin
RuinStorage.init(plugin, config)   // Reads storage.type, decides whether the codex goes YAML or database
RuinStorage.isDatabase(): Boolean  // Whether the codex currently goes to a database

The storage.type switch

storage.typeCodex destination
yaml (default)Codex discoveries written to local discoveries.yml
databaseCodex discoveries written to the QCL database table qr_codex

In database mode, if the QCL database is unavailable / table creation fails, it logs a warning and automatically falls back to local YAML, never crashing the server over it:

[QinhRuins] storage.type=database, but the QinhCoreLib database is unavailable; the codex falls back to local YAML

The database backend follows QCL: MySQL = true cross-server sharing; SQLite = local single-instance.

Tables and prefix

TableUseStructure
qr_codexPlayer codex: discovered templates(player VARCHAR(36), template VARCHAR(64)), primary key (player, template)

QR itself only creates the one qr_codex table (prefix qr_). In the Qinhuai ecosystem, other table prefixes (e.g. QI's qi_) belong to their respective plugins, and QR doesn't touch them.


3. The stores at a glance

StoreFileWhat it holdsKeyTo database?
AnchorStoreanchors.ymlAnchor instances (template / coords / orientation / dimensions / state / clear time)anchor id❌ local only
DiscoveryStorediscoveries.ymlPlayer-discovered anchors + discovered templates (codex)player UUIDAnchor discoveries local only; template codex can go to a database
LootClaimStoreloot_claims.ymlReward chests a player has claimed`UUID → anchorIdchestId`
MechFiredStoremech_fired.ymlOne-time mechanisms already triggeredanchorId → mechId❌ local only
VesselStorevessels.ymlContent snapshot of each player's individual purge vessel`...anchorId
SharedVesselStoreshared_vessels.ymlWhether the server-shared vessel has been filled`anchorId...`
SpinStorespins.ymlA player's available purge-slot spin grants`UUIDanchorId`
SnapshotStoresnapshots/<anchorId>.nbtPre-generation terrain snapshot (restored on fade / remove)anchor id❌ local only (binary NBT)

All are init-ed at onEnable by file path, all under plugins/QinhRuins/.


4. Why these "should be local"

  • Anchors (AnchorStore): records "there's a ruin at (123, 64, -45) in world X". That coordinate doesn't exist at all on another server, so storing it in a database is meaningless.
  • Claims / slots / mechanisms (LootClaimStore / SpinStore / MechFiredStore): all recorded by concrete anchor id, and anchors themselves are local, so the records naturally follow local.
  • Vessel contents (VesselStore / SharedVesselStore): bound to a specific container slot of a specific ruin.
  • Snapshots (SnapshotStore): the raw backup of world blocks, pure local binary, with an extra volume cap (cleanup.max-snapshot-volume, default 500000); over the limit it skips the snapshot to avoid stalling the main thread.

5. The codex: the only cross-server data

DiscoveryStore manages two things at once:

  • Anchor discoveries (discoveries.<UUID>): which concrete anchors a player has walked up to — always local (anchors themselves are local).
  • Template discoveries / codex (template-discoveries.<UUID> or the qr_codex table): which ruin kinds a player has seen — this is a collection achievement, can be cross-server.

The codex's YAML / database switch is decided by RuinStorage.isDatabase():

  • YAML mode: the template codex is written into the template-discoveries section of discoveries.yml.
  • Database mode: the template codex is written to the qr_codex table; local YAML no longer writes template-discoveries. Writes go async (RuinStorage.runAsync) to avoid blocking the main thread.

Cross-server sync: CodexSyncListener

In database mode, CodexSyncListener handles the loading / unloading of the cross-server codex:

WhenAction
Player joinsAsync-read that player's codex from qr_codex → switch back to the main thread, DiscoveryStore.mergeTemplates merges it into memory
Player quitsDiscoveryStore.unloadTemplates unloads it from memory (to avoid cache bloat)

This way the ruin kinds a player discovered on server A are visible when they log into server B (same database). In YAML mode this listener idles (returns immediately when isDatabase() is false).


6. When data is written to disk

StoreWhen it writes
AnchorStoreUnified saveAll by the anchor manager (on generation / state change / recycle)
DiscoveryStore / LootClaimStore / MechFiredStore / SpinStoreSave immediately on each new record (in database mode the codex switches to async DB writes)
SharedVesselStoreMarked dirty + batch save every 600 ticks (about 30 seconds), plus save on shutdown
VesselStoreMarked dirty, save on anchor cleanup (clearAnchor) and on shutdown (no timer)
SnapshotStoreCapture on generation, delete the snapshot file after a successful restore

On shutdown (onDisable), RuinStorage.close() + VesselStore.save() + SharedVesselStore.save() provide a final fallback flush to disk.


yaml
storage:
  type: yaml        # yaml | database (database follows the QCL database, cross-server shared codex)

cleanup:
  snapshot-restore: true       # Whether to restore terrain on remove / fade
  max-snapshot-volume: 500000  # Snapshot volume cap, skipped over the limit (prevents large structures stalling the server)

For the full config see Full config.yml.


Next