DB Asset Server

Copy schematic files into the database at record time, rewrite KDL paths to db:…, and serve them on port N+1 while elodin-db runs.

Record a simulation once, then replay or share the database directory without shipping a separate assets/ tree. Meshes, custom .png icons, and skyboxes referenced by the schematic are copied into the DB, rewritten in stored KDL as db:… paths, and served over HTTP while the database runs.

This complements Replays and Schematic KDL: telemetry lives in the DB layout you already use; this page covers files the schematic needs at visualization time.

Architecture

flowchart LR
  subgraph record_phase ["Record"]
    Sim["Simulation"]
    Parse["Parse schematic KDL"]
    Copy["Copy bytes to db/assets"]
    Rewrite["Rewrite paths to db scheme"]
    Meta["Store schematic.content"]
  end
  subgraph serve_phase ["Serve"]
    TCP["Impeller TCP port N"]
    HTTP["DB Asset Server port N+1"]
  end
  subgraph consume_phase ["Consume"]
    Editor["Elodin Editor"]
    Follow["Follower asset sync"]
  end
  Sim --> Parse --> Copy --> Rewrite --> Meta
  Meta --> TCP
  Copy --> HTTP
  HTTP --> Editor
  HTTP --> Follow

Layers

LayerRole
Blob store{db_path}/assets/{relative_key} — opaque bytes, any file type
DB Asset ServerGET http://host:(tcp_port+1)/{relative_key} while elodin-db run is active
KDL rewriteLocal paths at record time become db:{relative_key} in schematic.content
ConsumersEditor resolves db: to HTTP (or mirrors to a local cache for skyboxes)

Ports

  • Impeller TCP on port N (default 2240)
  • DB Asset Server on port N + 1 (default 2241) — ASSETS_HTTP_PORT_OFFSET = 1

Do not run a follower Impeller server on N+1; that port is reserved for the DB Asset Server on the source.

The db: scheme

At record time, a local schematic path such as models/jet.glb is stored as db:models/jet.glb. The file lands at {db}/assets/models/jet.glb.

Paths are not rewritten when they are already:

  • db:…
  • http://… or https://…
  • icon builtin=… (no file; see below)

Relative keys must not contain .. (path traversal is rejected).

When assets are persisted

Persistence runs during simulation DB initialization (init_db), when the world has a schematic:

  • db_path argument to world.run(…, db_path=…), or
  • ELODIN_DB_PATH environment variable (Python SDK)

The schematic body is stored in DB metadata as schematic.content (with db: paths). The original schematic.path string is informational only; replay uses schematic.content from the DB when the file is missing.

Simulation source snapshot

When a Python simulation records to an explicit database path, Elodin also writes a source snapshot to {db}/simulation_source/:

{db}/simulation_source/manifest.json
{db}/simulation_source/files/...

This snapshot is for analysis and provenance. It contains the resolved project-local .py files that were imported after the simulation starts; it is not a compiled simulation, Python environment, dependency lockfile, or asset bundle.

The first-pass selection rule is:

  • inspect sys.modules
  • keep modules whose __file__ resolves to a .py file under the simulation entrypoint directory
  • exclude virtualenvs, site-packages / dist-packages, stdlib paths, __pycache__, cache/generated files, and non-Python assets

For example, the examples/rc-jet snapshot would typically include:

examples/rc-jet/main.py
examples/rc-jet/config.py
examples/rc-jet/sim.py
examples/rc-jet/aero.py
examples/rc-jet/propulsion.py
examples/rc-jet/actuators.py
examples/rc-jet/ground.py

Visual assets remain separate under {db}/assets/.

Consumption workflows

ModeTypical commandsAssets
Liveelodin editor examples/foo/main.pyHTTP from the sim DB during the session
Recorded DBelodin-db run 127.0.0.1:2240 ./my-db then elodin editor 127.0.0.1:2240HTTP from ./my-db/assets/
Replay presentationSame DB + elodin editor 127.0.0.1:2240 --replayHTTP; editor timeline reveals progressively
Followelodin-db run … --follows SOURCE:2240Follower copies db: assets from source HTTP into its own {db}/assets/

See Elodin CLI for editor --replay and elodin-db --follows.

Follow mode and assets

When a follower connects to a source elodin-db on port N, it replicates telemetry over Impeller TCP. Schematic assets are not streamed on that socket — they are fetched separately from the source DB Asset Server on port N+1.

On connect (and when schematic KDL updates), the follower:

  1. Reads schematic.content from replicated DB metadata
  2. Collects every db:… path referenced in the KDL (including skybox manifest sidecars)
  3. GETs each file from http://source:(N+1)/{key} with retries
  4. Writes bytes into its own {follower_db}/assets/

The follower can then serve those files from its own DB Asset Server on (follower_port + 1) to local editors, without copying the full source database directory.

# Source (sim or recorded DB)
elodin-db run 127.0.0.1:2240 ./source-db

# Follower — Impeller on 2242, DB Asset Server on 2243
elodin-db run 127.0.0.1:2242 ./follower-db --follows 127.0.0.1:2240

# Editor attaches to the follower
elodin editor 127.0.0.1:2242

Point --follows at the source Impeller port (N), not the asset port (N+1).

Supported asset types

GLB meshes (object_3dglb path=…)

StageBehavior
Local pathResolved via schematic directory, ELODIN_ASSETS_DIR (default ./assets), or cwd
On disk in DBassets/{key}.glb (key preserves subdirectories, e.g. models/rocket.glb)
Stored KDLpath="db:models/rocket.glb"
Editordb:…http://127.0.0.1:2241/… via Bevy AssetServer + WebAssetPlugin (non-blocking)

.png icons (object_3dicon path=…)

Same pipeline as GLB: persist, rewrite, HTTP load.

icon builtin=… (Material Icons) is not copied into the DB. The editor rasterizes built-in glyphs locally; only custom path= .png files are persisted in the DB.

Skybox (skybox name="…")

Skyboxes are indirect: the KDL node names a manifest entry, not a single file path.

StageBehavior
Local filesassets/skyboxes/manifest.ron plus the active entry's *.cubemap.ktx2
On disk in DBassets/skyboxes/manifest.ron + assets/skyboxes/{name}.cubemap.ktx2
Stored KDLskybox name="mojave_desert" unchanged (name is logical)
EditorAsync download from DB HTTP into the local skybox cache, then SetActiveSkybox
ClearEmpty skybox.active metadata or schematic without a skybox node → skybox cleared in the editor

The skybox plugin today reads from a local cache directory; the editor mirrors DB assets there before activation. GLB meshes and .png icons use HTTP directly through Bevy.

Carried in the DB without extra asset files

  • Full schematic KDL in schematic.content
  • Procedural meshes (sphere, box, cylinder, …)
  • Built-in color scheme names in theme scheme=…
  • Telemetry components (separate from this asset pipeline)

Not supported today

ItemWhy
Custom color_schemes/*.json on diskOnly the scheme name is in KDL; JSON must exist locally
window path="other.kdl"Secondary schematic path is stored, not the file contents
video_stream panelsH.264 lives in message logs, not assets/
Arbitrary external URLs in KDLNot copied into the DB by design

Environment variables

VariablePurpose
ELODIN_DB_PATHDirectory for the simulation database (record)
ELODIN_ASSETS_DIRRoot for resolving local asset paths at record (default ./assets)

Verification

With elodin-db run listening on 2240:

export DB_PATH=./my-db
curl -sf -o /dev/null -w "%{http_code}\n" http://127.0.0.1:2241/f22.glb
ls -lh "$DB_PATH/assets/"

Expect HTTP 200 and non-empty files under assets/ after a sim that references those assets.

Troubleshooting

SymptomCheck
Icon only, no meshcurl http://127.0.0.1:2241/…404 means assets were not persisted; set ELODIN_DB_PATH / db_path and re-run
Port in use2240 = Impeller, 2241 = DB Asset Server; do not bind follower Impeller on 2241
Skybox missing on replayls "$DB_PATH/assets/skyboxes/" for manifest.ron and the .ktx2
Empty assets/ after simworld.run without db_path or ELODIN_DB_PATH uses a temp DB
Follower missing meshesConfirm source DB Asset Server responds: curl http://SOURCE:2241/models/rocket.glb

Adding a new asset type

A — Simple file referenced by path= in KDL

Use this when the schematic stores a direct relative path (like a .glb mesh or .png icon).

  1. KDL (libs/impeller2/kdl) — parse and serialize the path field on the relevant node.
  2. Collect & rewrite (libs/impeller2/kdl/src/rewrite.rs):
    • collect_local_asset_paths — include new local paths
    • collect_db_asset_names — include db: keys (for follow sync)
    • rewrite_asset_paths — rewrite local → db:… on record
  3. Persist — no change if the path appears in collect; persist_schematic_assets in libs/nox-py/src/impeller2_server.rs is generic.
  4. Follow — no change if the path appears in collect_db_asset_names; sync_schematic_assets_from_source copies all listed keys.
  5. Editor — if Bevy can load the format: resolve with resolve_db_asset_url and AssetServer.load(url). No blocking HTTP in Bevy systems.
  6. Tests — unit tests in impeller2-kdl (collect/rewrite) and nox-py (persist).

B — Indirect reference (name → manifest → file)

Use this when the KDL stores a logical name (like skybox name=…).

  1. Add a single resolver in impeller2/kdl (manifest parse → extra storage keys).
  2. Persist — extend collect after resolving (see add_local_skybox_cubemap_path in impeller2_server.rs).
  3. Follow — after syncing the manifest bytes, resolve and fetch dependent files (assets_http.rs).
  4. Editor — either teach the consumer to load via HTTP, or mirror into a local cache (skybox pattern, async via IoTaskPool).
  5. Avoid copying manifest-resolution logic into three places; share one resolver.
  • Schematic KDLobject_3d, icon, skybox syntax
  • Replays — legacy replay directory layout (distinct from Elodin DB with assets/)
  • Elodin DB overview — database capabilities and follow mode
  • Elodin CLIeditor, elodin-db run, --replay, --follows