2024-11-07 18:32:46 +01:00

167 lines
3.8 KiB
Lua

---Event system for Druid
---@class druid.event
local M = {}
M.COUNTER = 0
-- Forward declaration
local EVENT_METATABLE
-- Local versions
local pcall = pcall
local tinsert = table.insert
local tremove = table.remove
--- Return new event instance
---@param callback fun()|nil Subscribe the callback on new event, if callback exist
---@param callback_context any|nil Additional context as first param to callback call
---@return druid.event
---@nodiscard
function M.create(callback, callback_context)
local instance = setmetatable({}, EVENT_METATABLE)
if callback then
instance:subscribe(callback, callback_context)
end
M.COUNTER = M.COUNTER + 1
return instance
end
--- Check is event subscribed.
---@param callback fun() Callback itself
---@param callback_context any|nil Additional context as first param to callback call
---@return boolean, number|nil Is event subscribed, return index of callback in event as second param
function M:is_subscribed(callback, callback_context)
if #self == 0 then
return false, nil
end
for index = 1, #self do
local cb = self[index]
if cb[1] == callback and cb[2] == callback_context then
return true, index
end
end
return false, nil
end
---Subscribe callback on event
---@param callback fun() Callback itself
---@param callback_context any|nil Additional context as first param to callback call, usually it's self
---@return boolean
function M:subscribe(callback, callback_context)
assert(type(self) == "table", "You should subscribe to event with : syntax")
assert(callback, "A function must be passed to subscribe to an event")
if self:is_subscribed(callback, callback_context) then
return false
end
tinsert(self, { callback, callback_context })
return true
end
---Unsubscribe callback on event
---@param callback fun() Callback itself
---@param callback_context any|nil Additional context as first param to callback call
---@return boolean
function M:unsubscribe(callback, callback_context)
assert(callback, "A function must be passed to subscribe to an event")
local _, event_index = self:is_subscribed(callback, callback_context)
if not event_index then
return false
end
tremove(self, event_index)
return true
end
---Return true, if event have at lease one handler
---@return boolean
function M:is_exist()
return #self > 0
end
---Return true, if event not have handler
---@return boolean True if event not have handlers
function M:is_empty()
return #self == 0
end
---Clear the all event handlers
function M:clear()
for index = #self, 1, -1 do
self[index] = nil
end
end
--- Trigger the event and call all subscribed callbacks
---@param ... any All event params
---@return any result Last returned value from subscribers
function M:trigger(...)
if #self == 0 then
return
end
local result = nil
local call_callback = self.call_callback
for index = 1, #self do
result = call_callback(self, self[index], ...)
end
return result
end
---@param callback table Callback data {function, context}
---@param ... any All event params
---@return any result Result of the callback
function M:call_callback(callback, ...)
local event_callback = callback[1]
local event_callback_context = callback[2]
-- Call callback
local ok, result_or_error
if event_callback_context then
ok, result_or_error = pcall(event_callback, event_callback_context, ...)
else
ok, result_or_error = pcall(event_callback, ...)
end
-- Handle errors
if not ok then
local caller_info = debug.getinfo(2)
pprint("An error occurred during event processing", {
trigger = caller_info.short_src .. ":" .. caller_info.currentline,
error = result_or_error,
})
pprint("Traceback", debug.traceback())
return nil
end
return result_or_error
end
-- Construct event metatable
EVENT_METATABLE = {
__index = M,
__call = M.trigger,
}
return setmetatable(M, {
__call = function(_, callback)
return M.create(callback)
end,
})