1
0
mirror of https://github.com/kepler155c/opus synced 2024-12-29 01:50:27 +00:00
opus/sys/apis/event.lua
2018-11-22 19:57:00 -05:00

276 lines
5.1 KiB
Lua

local os = _G.os
local table = _G.table
local Event = {
uid = 1, -- unique id for handlers
routines = { }, -- coroutines
types = { }, -- event handlers
timers = { }, -- named timers
terminate = false,
free = { },
}
-- Use a pool of coroutines for event handlers
local function createCoroutine(h)
local co = table.remove(Event.free)
if not co then
co = coroutine.create(function(_, ...)
local args = { ... }
while true do
h.fn(table.unpack(args))
h.co = nil
table.insert(Event.free, co)
args = { coroutine.yield() }
h = table.remove(args, 1)
h.co = co
end
end)
end
h.primeCo = true -- TODO: fix...
return co
end
local Routine = { }
function Routine:isDead()
if not self.co then
return true
end
return coroutine.status(self.co) == 'dead'
end
function Routine:terminate()
if self.co then
self:resume('terminate')
end
end
function Routine:resume(event, ...)
if not self.co then
error('Cannot resume a dead routine')
end
if not self.filter or self.filter == event or event == "terminate" then
local s, m
if self.primeCo then
-- Only need self passed when using a coroutine from the pool
s, m = coroutine.resume(self.co, self, event, ...)
self.primeCo = nil
else
s, m = coroutine.resume(self.co, event, ...)
end
if self:isDead() then
self.co = nil
self.filter = nil
Event.routines[self.uid] = nil
else
self.filter = m
end
if not s and event ~= 'terminate' then
error('\n' .. (m or 'Error processing event'))
end
return s, m
end
return true, self.filter
end
local function nextUID()
Event.uid = Event.uid + 1
return Event.uid - 1
end
function Event.on(events, fn)
events = type(events) == 'table' and events or { events }
local handler = setmetatable({
uid = nextUID(),
event = events,
fn = fn,
}, { __index = Routine })
for _,event in pairs(events) do
local handlers = Event.types[event]
if not handlers then
handlers = { }
Event.types[event] = handlers
end
handlers[handler.uid] = handler
end
return handler
end
function Event.off(h)
if h and h.event then
for _,event in pairs(h.event) do
local handler = Event.types[event][h.uid]
if handler then
handler:terminate()
end
Event.types[event][h.uid] = nil
end
elseif h and h.co then
h:terminate()
end
end
local function addTimer(interval, recurring, fn)
local timerId = os.startTimer(interval)
local handler
handler = Event.on('timer', function(t, id)
if timerId == id then
fn(t, id)
if recurring then
timerId = os.startTimer(interval)
else
Event.off(handler)
end
end
end)
return handler
end
function Event.onInterval(interval, fn)
return Event.addRoutine(function()
while true do
os.sleep(interval)
fn()
end
end)
end
function Event.onTimeout(timeout, fn)
return addTimer(timeout, false, fn)
end
function Event.addNamedTimer(name, interval, recurring, fn)
Event.cancelNamedTimer(name)
Event.timers[name] = addTimer(interval, recurring, fn)
end
function Event.cancelNamedTimer(name)
local timer = Event.timers[name]
if timer then
Event.off(timer)
end
end
function Event.waitForEvent(event, timeout)
local timerId = os.startTimer(timeout)
repeat
local e = { os.pullEvent() }
if e[1] == event then
return table.unpack(e)
end
until e[1] == 'timer' and e[2] == timerId
end
-- Set a handler for the terminate event. Within the function, return
-- true or false to indicate whether the event should be propagated to
-- all sub-threads
function Event.onTerminate(fn)
Event.termFn = fn
end
function Event.termFn()
Event.terminate = true
return true -- propagate
end
function Event.addRoutine(fn)
local r = setmetatable({
co = coroutine.create(fn),
uid = nextUID()
}, { __index = Routine })
Event.routines[r.uid] = r
r:resume()
return r
end
function Event.pullEvents(...)
for _, fn in ipairs({ ... }) do
Event.addRoutine(fn)
end
repeat
Event.pullEvent()
until Event.terminate
Event.terminate = false
end
function Event.exitPullEvents()
Event.terminate = true
os.sleep(0)
end
local function processHandlers(event)
local handlers = Event.types[event]
if handlers then
for _,h in pairs(handlers) do
if not h.co then
-- callbacks are single threaded (only 1 co per handler)
h.co = createCoroutine(h)
Event.routines[h.uid] = h
end
end
end
end
local function tokeys(t)
local keys = { }
for k in pairs(t) do
keys[#keys+1] = k
end
return keys
end
local function processRoutines(...)
local keys = tokeys(Event.routines)
for _,key in ipairs(keys) do
local r = Event.routines[key]
if r then
r:resume(...)
end
end
end
function Event.processEvent(e)
processHandlers(e[1])
processRoutines(table.unpack(e))
end
function Event.pullEvent(eventType)
while true do
local e = { os.pullEventRaw() }
local propagate = true -- don't like this...
if e[1] == 'terminate' then
propagate = Event.termFn()
end
if propagate then
processHandlers(e[1])
processRoutines(table.unpack(e))
end
if Event.terminate then
return { 'terminate' }
end
if not eventType or e[1] == eventType then
return e
end
end
end
return Event