opus/sys/modules/opus/event.lua

272 lines
5.1 KiB
Lua
Raw Normal View History

2018-11-22 18:52:45 +00:00
local os = _G.os
local table = _G.table
2017-10-08 21:45:01 +00:00
2016-12-11 19:24:52 +00:00
local Event = {
2018-01-24 22:39:38 +00:00
uid = 1, -- unique id for handlers
routines = { }, -- coroutines
types = { }, -- event handlers
terminate = false,
2019-02-24 11:58:26 +00:00
free = { }, -- allocated unused coroutines
2016-12-11 19:24:52 +00:00
}
2018-11-22 18:52:45 +00:00
-- 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
2017-07-28 23:01:59 +00:00
local Routine = { }
2016-12-11 19:24:52 +00:00
2017-07-28 23:01:59 +00:00
function Routine:isDead()
2018-01-24 22:39:38 +00:00
if not self.co then
return true
end
return coroutine.status(self.co) == 'dead'
2016-12-11 19:24:52 +00:00
end
2017-07-28 23:01:59 +00:00
function Routine:terminate()
2018-01-24 22:39:38 +00:00
if self.co then
self:resume('terminate')
end
2016-12-11 19:24:52 +00:00
end
2017-07-28 23:01:59 +00:00
function Routine:resume(event, ...)
2018-01-24 22:39:38 +00:00
if not self.co then
error('Cannot resume a dead routine')
end
2017-07-28 23:01:59 +00:00
2018-01-24 22:39:38 +00:00
if not self.filter or self.filter == event or event == "terminate" then
2018-11-22 18:52:45 +00:00
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 not s and event ~= 'terminate' then
2020-05-26 03:48:37 +00:00
if m and type(debug) == 'table' and debug.traceback then
local t = (debug.traceback(self.co, 1)) or ''
m = m .. '\n' .. t:match('%d\n(.+)')
end
end
2018-11-22 18:52:45 +00:00
if self:isDead() then
2018-01-24 22:39:38 +00:00
self.co = nil
self.filter = nil
Event.routines[self.uid] = nil
else
self.filter = m
end
2017-07-28 23:01:59 +00:00
2018-01-24 22:39:38 +00:00
if not s and event ~= 'terminate' then
error(m or 'Error processing event', -1)
2018-01-24 22:39:38 +00:00
end
2017-07-28 23:01:59 +00:00
2018-01-24 22:39:38 +00:00
return s, m
end
2017-07-28 23:01:59 +00:00
2018-01-24 22:39:38 +00:00
return true, self.filter
2016-12-11 19:24:52 +00:00
end
2017-07-28 23:01:59 +00:00
local function nextUID()
2018-01-24 22:39:38 +00:00
Event.uid = Event.uid + 1
return Event.uid - 1
2016-12-11 19:24:52 +00:00
end
function Event.on(events, fn)
2018-01-24 22:39:38 +00:00
events = type(events) == 'table' and events or { events }
2017-07-30 23:53:43 +00:00
2018-01-24 22:39:38 +00:00
local handler = setmetatable({
uid = nextUID(),
event = events,
fn = fn,
}, { __index = Routine })
2018-01-24 22:39:38 +00:00
for _,event in pairs(events) do
local handlers = Event.types[event]
if not handlers then
handlers = { }
Event.types[event] = handlers
end
2018-01-24 22:39:38 +00:00
handlers[handler.uid] = handler
end
2017-07-28 23:01:59 +00:00
2018-01-24 22:39:38 +00:00
return handler
2016-12-11 19:24:52 +00:00
end
2017-07-28 23:01:59 +00:00
function Event.off(h)
2018-01-24 22:39:38 +00:00
if h and h.event then
for _,event in pairs(h.event) do
2018-11-22 18:52:45 +00:00
local handler = Event.types[event][h.uid]
2018-11-23 00:57:00 +00:00
if handler then
handler:terminate()
end
2018-01-24 22:39:38 +00:00
Event.types[event][h.uid] = nil
end
2018-11-22 18:52:45 +00:00
elseif h and h.co then
h:terminate()
2018-01-24 22:39:38 +00:00
end
2016-12-11 19:24:52 +00:00
end
2017-07-28 23:01:59 +00:00
function Event.onInterval(interval, fn)
2019-04-25 18:44:36 +00:00
local h = Event.addRoutine(function()
2018-11-22 18:52:45 +00:00
while true do
os.sleep(interval)
fn()
end
end)
2019-04-25 18:44:36 +00:00
function h.updateInterval(i)
interval = i
end
return h
2017-07-28 23:01:59 +00:00
end
2016-12-11 19:24:52 +00:00
2017-07-28 23:01:59 +00:00
function Event.onTimeout(timeout, fn)
2018-01-24 22:39:38 +00:00
local timerId = os.startTimer(timeout)
2019-02-24 11:58:26 +00:00
local handler
handler = Event.on('timer', function(t, id)
if timerId == id then
fn(t, id)
Event.off(handler)
2018-01-24 22:39:38 +00:00
end
2019-02-24 11:58:26 +00:00
end)
return handler
2016-12-11 19:24:52 +00:00
end
2018-10-15 20:05:43 +00:00
-- 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
2017-07-28 23:01:59 +00:00
function Event.addRoutine(fn)
2018-01-24 22:39:38 +00:00
local r = setmetatable({
co = coroutine.create(fn),
uid = nextUID()
}, { __index = Routine })
2017-07-28 23:01:59 +00:00
2018-01-24 22:39:38 +00:00
Event.routines[r.uid] = r
r:resume()
2017-07-28 23:01:59 +00:00
2018-01-24 22:39:38 +00:00
return r
2016-12-11 19:24:52 +00:00
end
2017-07-24 02:37:07 +00:00
function Event.pullEvents(...)
2018-01-24 22:39:38 +00:00
for _, fn in ipairs({ ... }) do
Event.addRoutine(fn)
end
2017-07-24 02:37:07 +00:00
2018-01-24 22:39:38 +00:00
repeat
Event.pullEvent()
until Event.terminate
2018-01-20 12:18:13 +00:00
2018-01-24 22:39:38 +00:00
Event.terminate = false
2016-12-11 19:24:52 +00:00
end
2017-07-24 02:37:07 +00:00
function Event.exitPullEvents()
2018-01-24 22:39:38 +00:00
Event.terminate = true
os.sleep(0)
2016-12-27 03:26:43 +00:00
end
2017-07-30 23:53:43 +00:00
local function processHandlers(event)
2018-01-24 22:39:38 +00:00
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)
2018-11-22 18:52:45 +00:00
h.co = createCoroutine(h)
2018-01-24 22:39:38 +00:00
Event.routines[h.uid] = h
end
end
end
2017-07-28 23:01:59 +00:00
end
2017-08-02 13:52:41 +00:00
local function tokeys(t)
2018-01-24 22:39:38 +00:00
local keys = { }
for k in pairs(t) do
keys[#keys+1] = k
end
return keys
2017-08-02 13:52:41 +00:00
end
2017-07-28 23:01:59 +00:00
local function processRoutines(...)
2018-01-24 22:39:38 +00:00
local keys = tokeys(Event.routines)
for _,key in ipairs(keys) do
local r = Event.routines[key]
if r then
r:resume(...)
end
end
2017-07-28 23:01:59 +00:00
end
2019-01-26 05:27:56 +00:00
-- invoke the handlers registered for this event
function Event.trigger(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
h:resume(event, ...)
end
end
end
end
2017-08-09 14:19:00 +00:00
function Event.processEvent(e)
2018-01-24 22:39:38 +00:00
processHandlers(e[1])
processRoutines(table.unpack(e))
2017-08-09 14:19:00 +00:00
end
2017-07-24 02:37:07 +00:00
function Event.pullEvent(eventType)
2018-01-24 22:39:38 +00:00
while true do
local e = { os.pullEventRaw() }
2018-10-15 20:05:43 +00:00
local propagate = true -- don't like this...
2017-07-28 23:01:59 +00:00
2018-10-15 20:05:43 +00:00
if e[1] == 'terminate' then
propagate = Event.termFn()
end
2018-01-20 12:18:13 +00:00
2018-10-15 20:05:43 +00:00
if propagate then
processHandlers(e[1])
processRoutines(table.unpack(e))
end
2017-07-28 23:01:59 +00:00
2018-01-24 22:39:38 +00:00
if Event.terminate then
return { 'terminate' }
end
2016-12-11 19:24:52 +00:00
2018-01-24 22:39:38 +00:00
if not eventType or e[1] == eventType then
return e
end
end
2016-12-11 19:24:52 +00:00
end
return Event