util.event.emit_callback
SharedFires an event and returns a promise that resolves with the handler's return values, or rejects if the handler fails
Syntax
local result = util.event.emit_callback(
name,
options = nil,
...
)options is positional — pass nil or omit it entirely when no routing is needed.
- When routing options are needed — pass the options table as the second argument.
- When no routing options are needed — omit
optionsand pass arguments directly aftername.
Handlers are invoked in priority order, highest first.
- Different priority — the handler with the higher
priorityfires first. - Same priority — handlers fire in registration order.
Only serializable values survive remote transmission. Non-serializable values are silently dropped.
- Safe to send —
nil,bool,number,string,table(including nested tables). - Silently dropped — functions, userdata, threads, and any other non-serializable Lua values.
- Local-only emit (
remote = false) — no serialization occurs; all Lua values are forwarded as-is. - Nested tables — fully supported, but deeply nested structures increase payload size and serialization cost.
On the server, a client's remote event only reaches handlers registered with remote = true.
- Handlers without it are local-only and are skipped, so a client cannot trigger them.
- Handlers that do receive a client's event see variable
clientset to the sender's peer id. - Refer to the Event Cheatsheet.
Parameters
| Type | Name | Description |
|---|---|---|
string | name | Name of the event to fire |
table | options | Routing options table Refer Options section |
any | ... | Arguments forwarded to every handler |
Returns
Always returns a single promise regardless of how many handlers are registered or whether the call is local or remote.
For remote calls, the remote side aggregates all of its handlers using the same rules before sending a single reply — so the caller's promise resolves with the same structure described below.
How that promise resolves depends on the number of registered handlers and the collect_all option:
collect_all | Handlers | Resolved value |
|---|---|---|
false | None | Resolves immediately with no values |
false | One or more | Resolves directly with the first handler's return value(s), unpacked "First" means highest priority, then registration order |
true | None | Resolves immediately with no values |
true | One or more | Resolves with {[1]={...}, [2]={...}, ...}Each slot is always a table of that handler's return values, even if only one value was returned |
Failure Cases
A handler that throws a Lua error never leaves the promise pending. The error message is printed to the console of the side where the handler ran. It is not passed to the caller.
| Situation | Result |
|---|---|
collect_all = false, the handler errors | Promise is rejected - await returns ok = false with no valuesOnly the first handler is fired, the remaining handlers are not tried |
collect_all = true, a handler errors | Promise still resolves - that handler's slot is an empty table {}, all other slots hold their return valuesThis also applies when every handler errors |
| Remote call, the remote handler errors | Same rules as above - a single handler rejects the caller's promise, with collect_all the slot is empty |
| Server calls a peer that is not connected | Promise is rejected immediately and a warning is logged |
A client calls the server and no handler there has remote = true | Promise resolves with no values With collect_all = true, only handlers registered with remote = true are called and counted |
| Remote side never replies | Promise is rejected after 30 seconds (e.g. the peer disconnected or the resource stopped) |
An empty slot cannot be told apart from a handler that returned nothing.
With collect_all = true, results[i][1] == nil means either the handler failed or it simply returned no value. Return an explicit value (e.g. true) from handlers whose success you need to check.
Options
Server to client util.event.emit_callback requires a valid peer.
peeris mandatory when calling with{ remote = true }from the server — omitting it throws an error.peershould refer to a currently connected peer — a disconnected or invalid peer-id does not throw: a warning is logged and the returned promise is rejected immediately (ok = false).- Broadcasting a callback to multiple clients is not supported — use
util.event.emitinstead if you need to notify all peers without expecting a reply.
| Type | Key | Default | Side | Description |
|---|---|---|---|---|
bool | remote | false | Shared | • When true - transmits the event to the remote side; client sends to the server, server sends to the peer given by peer• When false - fires handlers on the current side only |
int | peer | 0 | Server | Target peer-id for server-side remote dispatch (ignored on client) • Required on the server when remote = true - omitting it or passing 0 throws an error• Sends to that specific peer only |
bool | collect_all | false | Shared | • When false - only the highest-priority handler is fired and its return values are resolved directly, unpacked• When true - all registered handlers are fired, in priority order, and results are resolved as a table of tables, one entry per handler |
Examples
util.event.on("on_get_score", function()
return 42
end)
util.thread.create(function(self)
local ok, score = self:await(util.event.emit_callback("on_get_score"))
core.engine.print("info", "Score:", score) -- 42
end):resume()util.event.on("on_query", function() return "handler_a" end)
util.event.on("on_query", function() return "handler_b" end)
util.thread.create(function(self)
local ok, results = self:await(util.event.emit_callback("on_query", { collect_all = true }))
core.engine.print("info", "Handler 1 returned:", results[1][1]) -- 'handler_a'
core.engine.print("info", "Handler 2 returned:", results[2][1]) -- 'handler_b'
end):resume()util.event.on("on_status", function() return "ok", 100 end)
util.event.on("on_status", function() return "ok", 200 end)
util.thread.create(function(self)
local ok, results = self:await(util.event.emit_callback("on_status", { collect_all = true }))
core.engine.print("info", results[1][1], results[1][2]) -- 'ok' 100
core.engine.print("info", results[2][1], results[2][2]) -- 'ok' 200
end):resume()if core.engine.get_platform() == "client" then
util.thread.create(function(self)
local ok, result, data = self:await(util.event.emit_callback("on_fetch_inventory", { remote = true }, "weapons"))
core.engine.print("info", "Server replied:", result) -- 'ok'
core.engine.iprint(data)
end):resume()
else
util.event.on("on_fetch_inventory", function(category)
local thread = util.thread.current()
thread:sleep(2000)
return "ok", { category = category, items = { "sword", "bow" } }
end, { async = true })
endif core.engine.get_platform() == "client" then
util.event.on("on_request_position", function()
local thread = util.thread.current()
thread:sleep(500)
return 12.5, 0.0, 87.3
end, { async = true })
else
util.thread.create(function(self)
local ok, x, y, z = self:await(util.event.emit_callback("on_request_position", { remote = true, peer = 1963414407 }))
core.engine.print("info", "Client position:", x, y, z)
end):resume()
endif core.engine.get_platform() == "client" then
util.event.on("on_status", function() return "ok" end)
util.event.on("on_status", function() return "ok", { detail = "extra" } end)
else
util.thread.create(function(self)
local ok, results = self:await(util.event.emit_callback("on_status", { remote = true, peer = 1963414407, collect_all = true }))
core.engine.print("info", "Handler 1:", results[1][1]) -- 'ok'
core.engine.print("info", "Handler 2:", results[2][1], tostring(results[2][2])) -- 'ok' 'table: 0x...'
end):resume()
endutil.event.on("on_risky", function() error("boom") end)
util.event.on("on_risky", function() return "fine" end)
util.thread.create(function(self)
-- Without collect_all only the first handler runs - it errors, so the promise is rejected
local ok, value = self:await(util.event.emit_callback("on_risky"))
core.engine.print("info", ok, value) -- false nil
-- With collect_all the failing handler is an empty slot, the others still return
local ok, results = self:await(util.event.emit_callback("on_risky", { collect_all = true }))
core.engine.print("info", ok, #results[1], results[2][1]) -- true 0 'fine'
end):resume()util.thread.create(function(self)
local ok, result = self:await(util.event.emit_callback("on_request_position", { remote = true, peer = 123456 }))
if not ok then
core.engine.print("warn", "Request failed (peer not connected or handler error)")
return
end
core.engine.print("info", "Reply:", result)
end):resume()