404 lines
9.3 KiB
Lua
404 lines
9.3 KiB
Lua
-- eventloop 2.0.0
|
|
|
|
-- Basic event loop library for computer craft
|
|
--
|
|
-- Example usage:
|
|
--
|
|
-- local events = require('apis/eventloop')()
|
|
--
|
|
-- local disposeRedstoneEvent = events.register("redstone", function()
|
|
-- print("Redstone signal received!")
|
|
-- end)
|
|
--
|
|
-- events.register("key_up", function(k)
|
|
-- if k == keys.q then
|
|
-- disposeRedstoneEvent()
|
|
-- events.stopLoop()
|
|
-- end
|
|
-- end)
|
|
--
|
|
-- events.runLoop();
|
|
local next_eventloop_id = 1;
|
|
|
|
local function noop()
|
|
end
|
|
|
|
local STOP = '@libeventloop/STOP_HANDLER_SUBSCRIPTION';
|
|
|
|
local function createEventLoop()
|
|
local api = {}
|
|
|
|
local runningLoop = false;
|
|
local eventloop_id = next_eventloop_id;
|
|
next_eventloop_id = next_eventloop_id + 1;
|
|
|
|
local shouldCheckHandlers = true;
|
|
|
|
local handlersCounter = 0;
|
|
local allHandlers = {};
|
|
|
|
local runningHandlers = nil;
|
|
local unregisterQueue = {};
|
|
|
|
-- used when setTimeout are registered when runningLoop is false
|
|
local runningTimeoutHandler = false;
|
|
local timeoutFactoryCounter = 0;
|
|
local nextTimeoutFactoryId = 1;
|
|
local timeoutFactories = {};
|
|
local removeTimeoutQueue = {};
|
|
|
|
-- used at runtime
|
|
local timeoutHandlersCounter = 0;
|
|
local timeoutHandlers = {};
|
|
|
|
-- onStop handlers
|
|
local onStopHandlerCounter = 0;
|
|
local nextOnStopHandlerId = 1;
|
|
local onStopHandlers = {};
|
|
|
|
-- onStart handlers
|
|
local nextOnStartHandlerId = 1;
|
|
local onStartHandlers = {};
|
|
|
|
local function resetOnStartHandlers()
|
|
nextOnStartHandlerId = 1;
|
|
onStartHandlers = {};
|
|
end
|
|
|
|
local function resetOnStopHandlers()
|
|
onStopHandlerCounter = 0;
|
|
nextOnStopHandlerId = 1;
|
|
onStopHandlers = {};
|
|
end
|
|
|
|
local function addOnStopHandler(h)
|
|
local id = nextOnStopHandlerId;
|
|
|
|
nextOnStopHandlerId = nextOnStopHandlerId + 1;
|
|
onStopHandlerCounter = onStopHandlerCounter + 1;
|
|
|
|
onStopHandlers[id] = h;
|
|
|
|
return id;
|
|
end
|
|
|
|
local function execOnStartHandlers()
|
|
for _, h in pairs(onStartHandlers) do
|
|
h();
|
|
end
|
|
resetOnStartHandlers();
|
|
end
|
|
|
|
local function execOnStopHandlers()
|
|
for _, h in pairs(onStopHandlers) do
|
|
h();
|
|
end
|
|
resetOnStopHandlers();
|
|
end
|
|
|
|
local function shouldStop()
|
|
if shouldCheckHandlers then
|
|
return timeoutHandlersCounter + handlersCounter == 0 and timeoutFactoryCounter == 0;
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local function insertTimeoutHandler(id, handler)
|
|
timeoutHandlers[id] = handler;
|
|
timeoutHandlersCounter = timeoutHandlersCounter + 1
|
|
end
|
|
|
|
local function removeTimeoutHandler(id)
|
|
local function removeFn()
|
|
if timeoutHandlers[id] then
|
|
timeoutHandlers[id] = nil;
|
|
timeoutHandlersCounter = timeoutHandlersCounter - 1
|
|
|
|
if shouldStop() then
|
|
api.stopLoop();
|
|
end
|
|
end
|
|
end
|
|
|
|
table.insert(removeTimeoutQueue, removeFn);
|
|
end
|
|
|
|
local function resetFactories()
|
|
timeoutFactoryCounter = 0;
|
|
nextTimeoutFactoryId = 1;
|
|
timeoutFactories = {}
|
|
end
|
|
|
|
local function applyTimeoutFactories()
|
|
for _, f in pairs(timeoutFactories) do
|
|
local timerId, h = f()
|
|
insertTimeoutHandler(timerId, h)
|
|
end
|
|
|
|
resetFactories();
|
|
end
|
|
|
|
local function resetAll()
|
|
runningLoop = false;
|
|
handlersCounter = 0;
|
|
allHandlers = {};
|
|
runningHandlers = nil;
|
|
unregisterQueue = {};
|
|
|
|
resetFactories();
|
|
|
|
for k, _ in pairs(timeoutHandlers) do
|
|
os.cancelTimer(k);
|
|
end
|
|
|
|
runningTimeoutHandler = false;
|
|
timeoutHandlersCounter = 0;
|
|
timeoutHandlers = {};
|
|
removeTimeoutQueue = {};
|
|
|
|
resetOnStartHandlers();
|
|
resetOnStopHandlers();
|
|
end
|
|
|
|
local function flushUnregisterQueue()
|
|
if #unregisterQueue == 0 then
|
|
return
|
|
end
|
|
|
|
for _, f in pairs(unregisterQueue) do
|
|
f()
|
|
end
|
|
|
|
unregisterQueue = {}
|
|
end
|
|
|
|
local END_OF_LOOP = '@libeventloop/END_OF_LOOP/' .. eventloop_id
|
|
|
|
api.STOP = STOP
|
|
|
|
-- isRunningLoop
|
|
function api.isRunningLoop()
|
|
return runningLoop;
|
|
end
|
|
|
|
-- stopLoop
|
|
function api.stopLoop()
|
|
if api.isRunningLoop() then
|
|
os.queueEvent(END_OF_LOOP)
|
|
else
|
|
error("libeventloop error: loop is already stopped")
|
|
end
|
|
end
|
|
|
|
-- unregister
|
|
function api.unregister(eventName, handler)
|
|
assert(type(eventName) == 'string', 'bad argument #1 (string expected)')
|
|
assert(type(handler) == 'function', 'bad argument #2 (function expected)')
|
|
|
|
local function removeHandler()
|
|
local handlers = allHandlers[eventName]
|
|
|
|
if not handlers then
|
|
error("libeventloop error: no handler registered for the '" .. eventName .. "' event")
|
|
end
|
|
|
|
if handlers[handler] then
|
|
handlers[handler] = nil
|
|
handlersCounter = handlersCounter - 1;
|
|
if shouldStop() then
|
|
api.stopLoop();
|
|
end
|
|
end
|
|
end
|
|
|
|
if runningHandlers then
|
|
table.insert(unregisterQueue, function()
|
|
return removeHandler()
|
|
end)
|
|
return
|
|
end
|
|
|
|
return removeHandler()
|
|
end
|
|
|
|
-- register
|
|
function api.register(eventName, handler)
|
|
assert(type(eventName) == 'string', 'bad argument #1 (string expected)')
|
|
assert(type(handler) == 'function', 'bad argument #2 (function expected)')
|
|
|
|
if not allHandlers[eventName] then
|
|
allHandlers[eventName] = {}
|
|
end
|
|
|
|
local handlers = allHandlers[eventName]
|
|
if handlers[handler] then
|
|
error("libeventloop error: handler already registered for event '" .. eventName .. "'")
|
|
end
|
|
|
|
handlers[handler] = handler;
|
|
handlersCounter = handlersCounter + 1;
|
|
|
|
return function()
|
|
api.unregister(eventName, handler)
|
|
end
|
|
end
|
|
|
|
-- runLoop
|
|
function api.runLoop(noCheckHandlers)
|
|
if api.isRunningLoop() then
|
|
error("libeventloop error: event loop is already ran")
|
|
end
|
|
|
|
shouldCheckHandlers = not noCheckHandlers
|
|
|
|
applyTimeoutFactories()
|
|
|
|
if shouldStop() then
|
|
if onStopHandlerCounter > 0 then
|
|
execOnStopHandlers()
|
|
end
|
|
return
|
|
end
|
|
|
|
runningLoop = true;
|
|
execOnStartHandlers()
|
|
|
|
while true do
|
|
local packed = table.pack(os.pullEventRaw())
|
|
local eventName = table.remove(packed, 1)
|
|
|
|
if eventName == 'timer' then
|
|
-- setTimeout handlers
|
|
local timerId = packed[1];
|
|
|
|
runningTimeoutHandler = true
|
|
for k, h in pairs(timeoutHandlers) do
|
|
if k == timerId then
|
|
removeTimeoutHandler(k)
|
|
h();
|
|
end
|
|
end
|
|
runningTimeoutHandler = false
|
|
|
|
for _, removeFn in ipairs(removeTimeoutQueue) do
|
|
removeFn()
|
|
end
|
|
removeTimeoutQueue = {}
|
|
|
|
applyTimeoutFactories()
|
|
else
|
|
-- regular event handlers
|
|
local handlers = allHandlers[eventName]
|
|
if handlers then
|
|
runningHandlers = eventName
|
|
for _, handler in pairs(handlers) do
|
|
local result_handler = handler(table.unpack(packed))
|
|
if result_handler == api.STOP then
|
|
api.unregister(eventName, handler)
|
|
end
|
|
end
|
|
runningHandlers = nil
|
|
flushUnregisterQueue()
|
|
end
|
|
|
|
if eventName == END_OF_LOOP or eventName == 'terminate' then
|
|
execOnStopHandlers()
|
|
resetAll()
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- startLoop
|
|
api.startLoop = api.runLoop
|
|
|
|
-- setTimeout
|
|
function api.setTimeout(handler, seconds)
|
|
seconds = math.abs(seconds or 0);
|
|
|
|
assert(type(handler) == 'function', 'bad argument #1 (function expected)')
|
|
assert(type(seconds) == 'number', 'bad argument #2 (number expected)')
|
|
|
|
if api.isRunningLoop() and not runningTimeoutHandler then
|
|
local timerId = os.startTimer(seconds)
|
|
|
|
insertTimeoutHandler(timerId, handler)
|
|
|
|
return function()
|
|
-- clearTimeout
|
|
if timeoutHandlers[timerId] then
|
|
removeTimeoutHandler(timerId)
|
|
os.cancelTimer(timerId);
|
|
end
|
|
end
|
|
else
|
|
local timeoutFactoryId = nextTimeoutFactoryId;
|
|
nextTimeoutFactoryId = nextTimeoutFactoryId + 1
|
|
|
|
local timerId = nil;
|
|
|
|
local factory = function()
|
|
timerId = os.startTimer(seconds)
|
|
return timerId, handler;
|
|
end
|
|
|
|
timeoutFactories[timeoutFactoryId] = factory
|
|
timeoutFactoryCounter = timeoutFactoryCounter + 1;
|
|
|
|
return function()
|
|
-- clearTimeout
|
|
if timerId == nil and timeoutFactories[timeoutFactoryId] then
|
|
timeoutFactories[timeoutFactoryId] = nil
|
|
timeoutFactoryCounter = timeoutFactoryCounter - 1;
|
|
elseif timeoutHandlers[timerId] then
|
|
removeTimeoutHandler(timerId)
|
|
os.cancelTimer(timerId);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- onStop
|
|
function api.onStop(handler)
|
|
assert(type(handler) == 'function', 'bad argument #1 (function expected)')
|
|
|
|
local handlerId = addOnStopHandler(handler);
|
|
|
|
return function()
|
|
local h = onStopHandlers[handlerId];
|
|
if h then
|
|
h();
|
|
onStopHandlers[handlerId] = nil
|
|
onStopHandlerCounter = onStopHandlerCounter - 1;
|
|
end
|
|
end
|
|
end
|
|
|
|
-- onStart
|
|
function api.onStart(handler)
|
|
assert(type(handler) == 'function', 'bad argument #1 (function expected)');
|
|
|
|
if api.isRunningLoop() then
|
|
handler();
|
|
return noop;
|
|
else
|
|
local onStartId = nextOnStartHandlerId;
|
|
nextOnStartHandlerId = nextOnStartHandlerId + 1;
|
|
|
|
onStartHandlers[onStartId] = handler
|
|
|
|
return function()
|
|
if onStartHandlers[onStartId] then
|
|
onStartHandlers[onStartId] = nil;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return api;
|
|
end
|
|
|
|
return createEventLoop;
|