Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit61a33237e0

README.md

scene.shared.loader

Luau-side scene loader. Reads scene.json v6, dispatches to engine FFIs, runs the sibling entrypoint.luau.

Public surface

local loader = require("@builtin::assetTypes.scene.shared.loader")

loader.load(ref, opts?)
  -- ref: AssetRef<scene> | identity string | VFS path
  -- opts.layer: layer name (default "main")
  -- Returns: the Scene proxy (with player_config + camera_config attached).

loader.unload(layerName)
  -- Wrapper around layers.unload(name); symmetric to M.load.
  -- Cannot unload "main".

Behavior

  1. Resolve ref to a VFS path; append /scene.json if needed.
  2. vfs.read + Json.decode (from modules.json) → body table.
  3. Version check — refuse anything but version: 6.
  4. Attach body.player + body.camera to the Scene proxy as player_config / camera_config (consumed by player_spawner + camera_spawner).
  5. Activate the target layer via layers.find(layerName) (the loader threads the layer through entity.spawn opts), spawn body.entities in two passes (shells + transforms first, parents + components second), then restore the previous active layer.
  6. Apply body.lighting via lights.setup + lights.addPointLight. Field name mapping: directional → sun, clear_color → clearColor (both engine API names and v6 field names are accepted).
  7. Auto-discover sibling entrypoint.luau via vfs.exists; if present, compile with loadstring + run.

Performance

~5 ms for a 1000-entity scene (≈5k FFI calls × ~1 µs each). Acceptable for a load-time operation. If a particular call class becomes hot, batch primitives can be added in a narrow follow-up.

Why pure Luau

Per § 10 of the unification integration design, the scene format is data + dispatch — no Rust-only primitives needed. The existing ECS FFI (entity.spawn, entity(id).component.add, etc.) covers everything. Keeping the loader in Luau means iteration speed, auditability, and hot-reload.

FFI corrections vs. the original plan

The plan was drafted against assumed API names. The actual engine FFIs differ:

Plan assumedActual engine API
json.parse(s)require("..json").decode(s) (modules.json)
json.encode(t)require("..json").encode(t)
entity.spawn(n, { layer }).idlayer activation via layers.find(layer) + entity.spawn(n, opts).id
entity.setParent(id, pid)entity(id).setParent(pid)
unloadLayer(name)layers.unload(name)
lights.setup({ directional })lights.setup({ sun })
lights.setup({ clear_color })lights.setup({ clearColor })
lights.addPointLight(table)lights.addPointLight(name, x, y, z, opts)

The loader accepts both the v6 field names and the engine API names for the lighting fields so that round-tripping through the scene saver and back works without a conversion step.