Event

util.event.on

Shared

Registers a handler function for the specified event

Syntax

local status = util.event.on(
    name,
    exec,
    config = nil
)

The thread instance is only available inside async handlers.

  • When async = true — handler runs inside a dedicated thread; thread can be accessed via util.thread.current().
  • When async = false — handler runs inline on the calling thread.

On the server, handlers are local-only unless registered with remote = true.

  • Local-only (default) — the handler fires for events emitted on the server itself. An event sent by a client never reaches it.
  • remote = true — the handler can also be triggered by clients. Inside it, variable client holds the caller's peer id.
  • Built-in signals — handlers for signals such as network:peer:join are local-only too, unless you set remote = true.
  • Refer to the Event Cheatsheet for patterns.

Parameters

TypeNameDescription
stringnameName of the event to subscribe to
functionexecHandler function to invoke when the event fires
tableconfigConfiguration table
Refer Config section

Returns

TypeNameDescription
boolstatustrue on successful registration, false otherwise

Config

TypeKeyDefaultDescription
boolasyncfalse• When true - runs the handler inside a dedicated thread
• When false - runs the handler inline on the calling thread
intsubscription_limitnilMaximum number of times this handler will fire before being automatically removed
• Must be >= 1 if set
• Omit for unlimited invocations
intpriority0Determines fire order relative to other handlers on the same event
• Higher values fire earlier, similar to a z-index
• Handlers with equal priority fire in registration order
• May be negative
boolremotefalseServer only. Lets clients trigger this handler
• When true - events a client emits with remote = true reach this handler, and variable client holds the caller's peer id
• When false - only events emitted on the server reach it, a client's events skip it
• Has no effect on a client, where handlers always receive events from the server

Callback: exec

exec(...)
TypeNameDescription
any...Arguments forwarded from the emitting call

Variable: client

A variable the engine sets while a handler runs, so the handler can tell who triggered the event. It exists only on the server.

core.engine.print("info", client) -- peer id (int) when a client triggered the event, nil otherwise
ValueMeaning
intThe event was triggered by a client. This is that client's peer id, taken from the connection, so a client cannot fake it. Only handlers registered with remote = true are ever called this way
nilThe event was emitted locally on the server, or the code is running on a client
  • Per resource — client is set in the environment the handler function itself runs in. Other resources, and functions called from another resource, do not see it. Pass the peer id along as an argument if they need it.
  • Nested events — an event emitted locally from inside a handler sees client = nil, and the outer handler's value is restored afterwards.
  • Existing global — a resource's own global named client is hidden while a handler runs and restored when it returns.
  • Async handlers — the value is only available until the handler first yields (for example on thread:sleep or thread:await). Copy it into a local variable before the first yield.

Examples

Register a basic event handler
util.event.on("on_player_join", function(player)
    core.engine.print("info", "Player joined:", player)
end)
Let clients trigger a handler and identify the caller
util.event.on("on_buy_item", function(item)
    -- `client` is the real peer id, never trust a peer id sent in the arguments
    core.engine.print("info", "Peer", client, "wants to buy:", item)
end, { remote = true })
Local-only handler, clients cannot reach it
util.event.on("on_round_end", function(winner)
    give_rewards(winner) -- only runs when the server emits on_round_end itself
end)
Tell a client call from a server call in a remote handler
util.event.on("on_set_name", function(name)
    if client then
        name = tostring(name):sub(1, 16) -- called by a client, sanitize the input
    end
    set_player_name(client or 0, name)
end, { remote = true })
Keep the caller across a yield in an async handler
util.event.on("on_buy_item", function(item)
    local buyer = client -- copy before the first yield
    util.thread.current():sleep(1000)
    core.engine.print("info", "Peer", buyer, "bought:", item)
end, { async = true, remote = true })
Register a handler with a subscription limit
util.event.on("on_pickup_collected", function(pickup)
    core.engine.print("info", "Collected:", pickup)
end, { subscription_limit = 1 })
Register an async handler that sleeps
util.event.on("on_round_start", function()
    local thread = util.thread.current()
    core.engine.print("info", "Round started, waiting 5 seconds...")
    thread:sleep(5000)
    core.engine.print("info", "5 seconds elapsed")
end, { async = true })
Register handlers with explicit fire order using priority
util.event.on("sandbox:draw", function()
    core.engine.print("info", "Fires first - highest priority")
end, { priority = 10 })

util.event.on("sandbox:draw", function()
    core.engine.print("info", "Fires second - default priority")
end)

util.event.on("sandbox:draw", function()
    core.engine.print("info", "Fires last - lowest priority")
end, { priority = -10 })

On this page