Log inGet started
World

Builtin Library

Branchmain
Branchesmainpublish
Commit61a33237e0

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

  1. Receives the written VFS path.
  2. 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).
  3. Loads that type's <type>.assetType/behavior.luau via @builtin::assetTypes.assetType.shared.ref.loadTypeModule.
  4. If the module exports an onChange function, calls onChange(ref, change) where ref is the typed AssetRef for the changed asset and change = { 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 — owns loadTypeModule + the per-type ref method dispatch.
  • assetTypes/assetType.assetType/template/behavior.luau — the documented template that shows how to author ref, global, and onChange.
  • assetTypes/dynamicAsset.assetType — the reference type that uses onChange for prompt-driven regeneration with version control.