Hello! There are a bunch of new releases for Defold Event library.
What is new?
Queues
Queues let you store events and process them later in FIFO order. Unlike regular events that run callbacks immediately, queue events stay in the queue until a handler returns a non-nil value (to mark the event as handled).
Use instance-based queues with queue.create() or global queues via the queues module and a string id. Push from anywhere. When you subscribe a handler, it is called for each pending and future events in the queue. You can also process events manually with process() or process_next() without using subscribers. So you can use queues in a different way.
I’m using queues to store logic events to process them later in UI one by one if required. Or to communicate between GO and GUI context at init step sometimes.
Example — UI events queue processing:
local queues = require("event.queues")
local function process_next_event(self)
queues.process_next("quest_events", function(self, data)
if data.type == "quest_started" then
animate_quest_start(data)
elseif data.type == "quest_completed" then
animate_quest_complete(data)
end
return true
end, self)
end
Example — Cross-context communication:
-- GUI context
local queues = require("event.queues")
function M:init(self)
queues.push("get_atlas_path", {
texture_name = gui.get_texture(self.node),
sender = msg.url(),
}, self._on_get_atlas_path, self)
end
function M:_on_get_atlas_path(atlas_path)
local atlas_data = resource.get_atlas(atlas_path)
local tex_info = resource.get_texture_info(atlas_data.texture)
end
-- GO script
---@param request druid.get_atlas_path_request
---@return string?
local function get_atlas_path(self, request)
if not is_my_url(request.sender) then
return nil
end
return go.get(request.sender, "textures", { key = request.texture_name })
end
function init(self)
queues.subscribe("get_atlas_path", get_atlas_path, self)
end
function final(self)
queues.unsubscribe("get_atlas_path", get_atlas_path, self)
end
Promises
The Promise module wraps asynchronous work and lets you chain with :next(), :catch(), and :finally(). Create a promise with promise.create(executor) or use promise.resolved(value) / promise.rejected(reason). Use promise.all() to wait for multiple promises and promise.race() for the first to finish. You can resolve or reject manually with :resolve() and :reject(), and build pipelines with :append() and :tail().
Example — wrap function to promise:
local promise = require("event.promise")
---@param url string
---@return promise
local function load_data(url)
local p = promise.create()
http.request(url, "GET", function(response)
if response.status == 200 then
p:resolve(response.body)
else
p:reject(response.status)
end
end)
return p
end
Example — build promise pipeline:
local promise = require("event.promise")
function M:load_next_level()
return promise.resolved()
:next(self.unload_level, nil, self)
:next(self.wait_level_unload, nil, self)
:next(levels.get_next_level, nil, self)
:next(self.load_level, nil, self)
:next(self.animate_level_appear, nil, self)
end
Event as callback
You can subscribe an event to another event: when the parent triggers, the child event is triggered too. Handy for forwarding or composing events. You can pass a callback context when subscribing an event, e.g. extra data that will be passed as the first argument when the child event runs (e.g. event_2:subscribe(event_1, "extra data")).
Example — forward one event to another:
local event = require("event.event")
local on_click = event.create(function(self) print("clicked!") end)
local on_ui_action = event.create()
on_ui_action:subscribe(on_click) -- any trigger of on_ui_action also triggers on_click
on_ui_action:trigger()
Example — event with context:
local event_1 = event.create(function(self, x, y) print(self, x, y) end)
local event_2 = event.create()
event_2:subscribe(event_1, "my_context") -- event_1 runs with "my_context" as self
event_2:trigger(10, 20) -- prints "my_context", 10, 20
Subscribe Once
Subscribe a handler so it runs only once: after the first trigger (or first handled queue event), it is automatically unsubscribed. Available on events and queues: event:subscribe_once(), events.subscribe_once(), queue:subscribe_once(), and queues.subscribe_once(). Perfect for one-shot reactions like “when loaded”, “first click”, or “handle next item only”.
Example — event, one-time:
local event = require("event.event")
local on_loaded = event.create()
on_loaded:subscribe_once(function(self) print("Loaded once!") end, self)
on_loaded:trigger() -- runs and unsubscribes
on_loaded:trigger() -- no callback
Example — queue, handle one item then stop:
local queue = require("event.queue")
local task_queue = queue.create()
task_queue:subscribe_once(function(self, task)
return run_task(task) -- return non-nil to handle and auto-unsubscribe
end, self)
task_queue:push(task_a)
task_queue:push(task_b) -- only first is handled by this subscriber
Event Modes
You can set how the event module runs callbacks and handles errors with event.set_mode(mode).
pcall(default): continue on error, not full tracebacks.xpcall: continue on error, same idea but with full tracebacks (more memory).none: stop on error, rethrow the error with full traceback.
Example:
local event = require("event.event")
-- Set inside game.project file or in your code:
event.set_mode("pcall") -- safe default: log errors, continue
event.set_mode("xpcall") -- full tracebacks, still continue
event.set_mode("none") -- errors rethrown with full traceback
Full changes for 12-15 versions
V12
- MIGRATION: Replace
require("event.defer")withrequire("event.queues")in case of usingdefermodule - BREAKING CHANGE: Refactored queue system to be instance-based like event system.
queue.luanow creates queue instances withqueue.create()instead of global system - Added
queues.luafor global queues operations (renamed from old defer.lua functionality) - Added Promise module on top of event module
- Fixed queue event processing order from LIFO to FIFO (events now processed in correct queue order)
- Added
event.set_modefunction to set the event processing mode (pcall,xpcallandnone) - The
nonemode to disable context changing in event callback and usingpcallby default
V13
- Added
queue:process_nextfunction to process exactly one event in the queue with a specific handler (subscribers will not be called) - Make
promise:resolveandpromise:rejectpublic functions - Added
promise:appendfunction to append a task to the current promise without reassigning it. - Added
promise:tailandpromise:resetfunctions to manage the promise tail
V14
- Enable cross-context for
noneevent mode - In
nonemode, when an error occurs in a callback, the Lua error is rethrown with full traceback
V15
- subscribe_once: Added
event:subscribe_once,events.subscribe_once,queue:subscribe_once, andqueues.subscribe_onceto subscribe a handler for a single invocation. The handler is automatically unsubscribed after the first call. - Unsubscribe during trigger: Calling
unsubscribefrom inside a callback no longer breaks the current trigger iteration. Removals are applied after the trigger finishes, so all callbacks in the current trigger still run. - Fix when
callback_contextfor subscription can’t befalse - Event as callback: Support for
callback_contextwhen subscribing an event to another event (e.g.event_2:subscribe(event_1, "any additional data")). - Unsubscribe event from event: Fixed unsubscribing one event from another so the correct subscription is found and removed.
- Various edge cases fixes and improvements.
- Documentation and API pages updates.