instantiable
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 cod…
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 anEntityRef. 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— theoriginalId -> runtimeIdmap 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.skeletonRootpointing 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 totarget, born temporary whenopts.temporary, namedopts.nameelse 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. Checksrootis 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. TheAsset/SceneModulecomponents tag their own calls withsourceTag; they hold the reference and re-compose on every load. An untagged call came straight fromref:instantiate(...)and has no such owner.M.own(root, self, idMap?) -> root— hand an already-composed root to anAssetcomponent pointing atself, 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?— thesceneInstantiablefield-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:
nilpasses — the field is optional (no asset assigned).- Reads the value's identity — a bare string, or a table's
__ref/guid/identity/name. - Resolves it (
asset.resolve(identity)— any category) and asks the resolved refref:canInstantiate(): true iff the asset's type defines aninstantiatemethod.
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.
Scoped to this part · feeds back into the world's score.