You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

227 lines
5.3 KiB
Lua

--[=[
@c Emitter
@t ui
@mt mem
@d Implements an asynchronous event emitter where callbacks can be subscribed to
specific named events. When events are emitted, the callbacks are called in the
order that they were originally registered.
]=]
local timer = require('timer')
local wrap, yield = coroutine.wrap, coroutine.yield
local resume, running = coroutine.resume, coroutine.running
local insert, remove = table.insert, table.remove
local setTimeout, clearTimeout = timer.setTimeout, timer.clearTimeout
local Emitter = require('class')('Emitter')
function Emitter:__init()
self._listeners = {}
end
local function new(self, name, listener)
local listeners = self._listeners[name]
if not listeners then
listeners = {}
self._listeners[name] = listeners
end
insert(listeners, listener)
return listener.fn
end
--[=[
@m on
@p name string
@p fn function
@r function
@d Subscribes a callback to be called every time the named event is emitted.
Callbacks registered with this method will automatically be wrapped as a new
coroutine when they are called. Returns the original callback for convenience.
]=]
function Emitter:on(name, fn)
return new(self, name, {fn = fn})
end
--[=[
@m once
@p name string
@p fn function
@r function
@d Subscribes a callback to be called only the first time this event is emitted.
Callbacks registered with this method will automatically be wrapped as a new
coroutine when they are called. Returns the original callback for convenience.
]=]
function Emitter:once(name, fn)
return new(self, name, {fn = fn, once = true})
end
--[=[
@m onSync
@p name string
@p fn function
@r function
@d Subscribes a callback to be called every time the named event is emitted.
Callbacks registered with this method are not automatically wrapped as a
coroutine. Returns the original callback for convenience.
]=]
function Emitter:onSync(name, fn)
return new(self, name, {fn = fn, sync = true})
end
--[=[
@m onceSync
@p name string
@p fn function
@r function
@d Subscribes a callback to be called only the first time this event is emitted.
Callbacks registered with this method are not automatically wrapped as a coroutine.
Returns the original callback for convenience.
]=]
function Emitter:onceSync(name, fn)
return new(self, name, {fn = fn, once = true, sync = true})
end
--[=[
@m emit
@p name string
@op ... *
@r nil
@d Emits the named event and a variable number of arguments to pass to the event callbacks.
]=]
function Emitter:emit(name, ...)
local listeners = self._listeners[name]
if not listeners then return end
for i = 1, #listeners do
local listener = listeners[i]
if listener then
local fn = listener.fn
if listener.once then
listeners[i] = false
end
if listener.sync then
fn(...)
else
wrap(fn)(...)
end
end
end
if listeners._removed then
for i = #listeners, 1, -1 do
if not listeners[i] then
remove(listeners, i)
end
end
if #listeners == 0 then
self._listeners[name] = nil
end
listeners._removed = nil
end
end
--[=[
@m getListeners
@p name string
@r function
@d Returns an iterator for all callbacks registered to the named event.
]=]
function Emitter:getListeners(name)
local listeners = self._listeners[name]
if not listeners then return function() end end
local i = 0
return function()
while i < #listeners do
i = i + 1
if listeners[i] then
return listeners[i].fn
end
end
end
end
--[=[
@m getListenerCount
@p name string
@r number
@d Returns the number of callbacks registered to the named event.
]=]
function Emitter:getListenerCount(name)
local listeners = self._listeners[name]
if not listeners then return 0 end
local n = 0
for _, listener in ipairs(listeners) do
if listener then
n = n + 1
end
end
return n
end
--[=[
@m removeListener
@p name string
@p fn function
@r nil
@d Unregisters all instances of the callback from the named event.
]=]
function Emitter:removeListener(name, fn)
local listeners = self._listeners[name]
if not listeners then return end
for i, listener in ipairs(listeners) do
if listener and listener.fn == fn then
listeners[i] = false
end
end
listeners._removed = true
end
--[=[
@m removeAllListeners
@p name string/nil
@r nil
@d Unregisters all callbacks for the emitter. If a name is passed, then only
callbacks for that specific event are unregistered.
]=]
function Emitter:removeAllListeners(name)
if name then
self._listeners[name] = nil
else
for k in pairs(self._listeners) do
self._listeners[k] = nil
end
end
end
--[=[
@m waitFor
@p name string
@op timeout number
@op predicate function
@r boolean
@r ...
@d When called inside of a coroutine, this will yield the coroutine until the
named event is emitted. If a timeout (in milliseconds) is provided, the function
will return after the time expires, regardless of whether the event is emitted,
and `false` will be returned; otherwise, `true` is returned. If a predicate is
provided, events that do not pass the predicate will be ignored.
]=]
function Emitter:waitFor(name, timeout, predicate)
local thread = running()
local fn
fn = self:onSync(name, function(...)
if predicate and not predicate(...) then return end
if timeout then
clearTimeout(timeout)
end
self:removeListener(name, fn)
return assert(resume(thread, true, ...))
end)
timeout = timeout and setTimeout(timeout, function()
self:removeListener(name, fn)
return assert(resume(thread, false))
end)
return yield()
end
return Emitter