Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit3bb1da2647

README.md

scene_instantiable (module)

The instantiation contract's shared implementation. An asset type opts into scene instantiation by defining instantiate(self, target?, opts?) on its behaviour ref table; this module carries the parts that mean the same thing for every type, so a caller writes ONE piece of code against all of them.

local root, idMap = ref:instantiate(target?, opts?)

The contract

IN — the base opts. target is an owning entity ref: the instance lands under (or, for a hierarchy type like a bundle, onto) that owner. With no target the type spawns a fresh root. position, rotation, scale, name and temporary place that root and mean the same for every type. rotation takes three numbers as pitch/yaw/roll in DEGREES, or four as a quaternion. A type may honour more opts of its own — params for a type built from declared inputs, idMap / diff / sourceTag for the bundle override contract — and says so in its own instantiate docs.

OUT — the two returned values. Every type returns the same pair:

  • root — the composed root, as an EntityRef. Composition is synchronous: the root and everything the type built under it are live the moment the call returns, so a caller can parent to it, read its components and hand it on in the same statement. There is no frame to wait for and no callback.
  • idMap — the originalId -> runtimeId map naming what the composition spawned, {} for a type with no addressable children. Never nil. A component that re-composes the asset on every load (Asset) keeps this map and passes it back in, which is how a cross-entity reference into the composition — SkinnedModel.skeletonRoot pointing at a bone — survives a reload.

The return is enforced, not merely described: AssetRef dispatches every ref:instantiate(...) through result on the way out, whether or not the type called it. A type that returns something else fails at its own call rather than handing its caller a nil root or a map that is sometimes absent.

Exports

  • M.root(self, target?, opts?) -> root — stand the root entity for a type that spawns one: parented to target, born temporary when opts.temporary, named opts.name else the asset's own name, placed.
  • M.place(root, opts?) -> root — apply the placement opts to a root the type already has (the adopt path: a bundle exploding onto its target, a sceneModule reconciling under one).
  • M.result(self, root, idMap?) -> (root, idMap) — return through the contract. Checks root is a live entity ref, normalises a missing map to {}, and errors naming the asset type when either is something else.
  • M.isOwned(opts?) -> boolean — whether a component drives this call. The Asset / SceneModule components tag their own calls with sourceTag; they hold the reference and re-compose on every load. An untagged call came straight from ref:instantiate(...) and has no such owner.
  • M.own(root, self, idMap?) -> root — hand an already-composed root to an Asset component pointing at self, so the reference and its map persist and the composition is rebuilt on the next load. The component adopts the live composition rather than building a second one.
  • M.check(value, constraint) -> ok, reason? — the sceneInstantiable field-constraint validator, also registered under that kind on load.

The field constraint

Field.instantiableRef emits constraint = { kind = "sceneInstantiable" }, and this module registers the validator for it on load. Given a written value:

  1. nil passes — the field is optional (no asset assigned).
  2. Reads the value's identity — a bare string, or a table's __ref / guid / identity / name.
  3. Resolves it (asset.resolve(identity) — any category) and asks the resolved ref ref:canInstantiate(): true iff the asset's type defines an instantiate method.

This is what lets Field.instantiableRef accept by CAPABILITY rather than a hardcoded type list — a new scene-instantiable asset type is accepted the moment it defines the hook, with no edit to the field or its consumers.