Skip to content

Petrichor.Events#

Petrichor.Events is an in-memory publish/subscribe bus shared by every luau mod in the same process. Use it for custom signals between mods without tight coupling.

Events are plain strings. The mod helpers on Script entry point publish events as <mod_id>:<event>.

Loading#

local Events = require("Petrichor.Events")
import * as Events from "Petrichor.Events"

Petrichor.Events is a prelude module. It is preloaded when the Luau host starts and cached by require().


API#

Events.on(event, fn)#

Registers fn to run every time event is fired. fn receives one argument: the data value passed to fire (or nil).

local Log = require("Petrichor.Log")

Events.on("hello-cow:moo", function(data)
    Log.info("listener", "heard a moo: " .. tostring(data.msg))
end)
import * as Log from "Petrichor.Log"

Events.on("hello-cow:moo", (data: { msg?: string }) => {
    Log.info("listener", `heard a moo: ${data.msg}`)
})

Events.once(event, fn)#

Same as on, but fn is removed automatically after the first delivery.

Events.off(event, fn)#

Removes fn from event. fn must be the same function reference passed to on or once.

Events.fire(event, data?)#

Calls every listener for event synchronously, in registration order. data can be any Luau value. Tables are typical.

Events.fire("hello-cow:moo", { msg = "Hello!" })
Events.fire("hello-cow:moo", { msg: "Hello!" })

If no listeners are registered, fire is a no-op.


Mod helpers#

Petrichor.Mod wraps the module above with namespacing. Prefer self:fire and self:on in your mod entry file instead of calling Events directly.

Method Fires / listens on
self:fire(event, data?) <self.__name>:<event>
self:on(event, mod_id, fn) <mod_id>:<event>
self:once(event, mod_id, fn) <mod_id>:<event>
self:off(event, mod_id, fn) <mod_id>:<event>

self.__name comes from the string passed to mod("my-mod-id"). Match it to the id in mod.json so other mods can subscribe predictably.

Listener callbacks receive only data, not self. Unlike hook helpers, event handlers are plain functions.


Notes#

  • Delivery is synchronous. A slow listener blocks every caller of fire on the main Luau thread.
  • There is no built-in error isolation. Errors in a listener propagate to the caller of fire.
  • Events are not persisted and do not cross process restarts.
  • You can call Events.fire with any string, not only mod_id:event names. The mod helpers exist to avoid collisions between mods.
  • CReact panels call the same bus when they emit("action"). The host fires <modId>:action on your behalf. See Courage.UI.

For patterns and examples, see Working with events.