events
Per-asset runtime for declared asset events. An assetType's `behavior.luau` declares an `events` schema the same way a component declares one; this module turns that schema into the live objects an asset's ref fires and listens on.
asset_events
Per-asset runtime for declared asset events. An assetType's behavior.luau
declares an events schema the same way a component declares one; this
module turns that schema into the live objects an asset's ref fires and
listens on.
Three faces, one Signal — keyed by the asset
The construction is component_events': one Signal per declared event,
reachable through a private fire-capable table, an owner-side emitter the
type's own behavior fires with, and a subscribe-only facade every other
holder of the ref sees. The facade exposes connect / once / wait and
has no fire at any key, so firing authority stays with the type.
What differs is the owner. A component event belongs to one instance on one
entity; an asset event belongs to the asset, so the runtime is keyed by
the asset's stable guid. Every resolver of that guid subscribes to the same
Signals, and a ref that is reclaimed and re-resolved re-attaches to the
subscriptions already there — the same reason asset_ref's runtime table
is guid-keyed rather than stored on the envelope.
Both tables reject an undeclared event name: the private table's metatable raises on a bad key and the facade raises on a bad key, so a typo surfaces at the subscribe call instead of returning nil.
Declaring events on a type
-- <name>.assetType/behavior.luau
local M = {}
M.events = {
changed = { payload = { value = Field.number(0, NoSync) } },
}
M.ref = {
poke = function(self)
local emitter = require("@builtin::assetTypes.assetType.shared.events").emitter(self.guid)
if emitter ~= nil then emitter.changed:fire({ value = 1 }) end
end,
}
return M
-- any holder of the ref
local ref = asset.resolve("@builtin::…")
ref.events.changed:connect(function(p) print(p.value) end)
Exports
M.forAsset(guid, eventSchema)— the runtime for one guid, built on first use and reused after. A later call with a different schema reconciles: surviving events keep their Signal and their subscribers, new events get a fresh one, removed events are disconnected and dropped.M.facade(guid)— the subscribe-only view, or nil before a runtime exists.M.emitter(guid)— the owner-side:fireview, or nil before a runtime exists.M.has(guid)— whether a guid holds a live runtime.M.teardown(guid)— drop one asset's runtime, disconnecting subscribers.M.teardownAll()— drop every runtime. Called on an engine mode flip so a play-mode subscription does not survive into edit.M.liveGuids()— every guid holding a runtime, sorted.
Notes
- Subscriptions made through the facade are ordinary
signal.moduleconnections and disconnect the same way any other does. - The runtimes table is held strongly, and its lifetime is bounded by the mode flip that clears it.
Scoped to this part · feeds back into the world's score.