Builtin Library
README.md
asset_change_dispatch (module)
Installs _G.__zero_dispatch_asset_change, the Luau half of the
asset-type change-callback system. The engine calls it once per VFS
source write (via zero_scripting::ffi_callbacks::fire_asset_change_dispatch,
queued as VfsMutation::AssetSourceWritten).
What it does
- Receives the written VFS path.
- Walks up the path to the enclosing
<name>.<type>/typed-asset folder (deepest registered-type suffix wins, so the direct owner of the write is chosen for nested typed assets). - Loads that type's
<type>.assetType/behavior.luauvia@builtin::assetTypes.assetType.shared.ref.loadTypeModule. - If the module exports an
onChangefunction, callsonChange(ref, change)whererefis the typedAssetReffor the changed asset andchange = { path, asset, type }.
The engine runs each dispatch as its own yieldable task, so a hook can wait
on what it reads: a vfs.read of bytes only the blob store holds (a binary
file on the web build) fetches them. A hook that reads only what is resident
finishes inside the dispatch that started it. The writes routed to one asset
react in the order they were routed: a write routed while that asset's hook
is waiting queues behind it and reacts once the hook has finished.
This is the type-level analogue of the component-centric
onAssetReload(field) fan-out: components react to assets they
reference; an asset type reacts to writes inside its own instances.
The source-write seam
A write inside a typed asset is also offered to the importer type's shared
layer (onAssetWrite(assetPath, path, origin)), the way a write with no
enclosing asset is offered to onLooseWrite. The asset's import record names
the file it was imported from (by guid and path); a write to that file re-runs
the importers on it, in place; the write that arrived is the only input. A type whose own onChange already keeps its retained source current
(a texture re-encoding its image, a sound clip its audio) sets
reencodesRetainedSource = true on its module, and the seam leaves that edit
to it.
Re-entrancy
While an asset's onChange runs, a dispatch for that same asset made from
the hook's own coroutine is suppressed, so a handler that routes a write
back into its own folder inline doesn't recurse. A write the hook makes is
routed on a later frame like any other and reaches onChange again once
the hook has finished. onChange handlers must therefore be convergent: diff the meaningful
state before acting and short-circuit when there's nothing to do (the
dynamicAsset example only regenerates when the prompt actually changed
and bails while a generation is in flight). Idempotency is the contract,
exactly as it is for services' start/stop.
Related
@builtin::assetTypes.assetType.shared.ref— ownsloadTypeModule+ the per-typerefmethod dispatch.assetTypes/assetType.assetType/template/behavior.luau— the documented template that shows how to authorref,global, andonChange.assetTypes/dynamicAsset.assetType— the reference type that usesonChangefor prompt-driven regeneration with version control.