feat(eventloop): with net compat
This commit is contained in:
parent
56ce0e58f3
commit
dab6e0011e
411
apis/eventloop.lua
Normal file
411
apis/eventloop.lua
Normal file
@ -0,0 +1,411 @@
|
||||
-- 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(net) -- TODO listenMessages
|
||||
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
|
||||
|
||||
function api.listenMessages(givenChannel, handler)
|
||||
net.openChannel(givenChannel)
|
||||
|
||||
return api.register("modem_message", function(_, channel, _, payload)
|
||||
if net.isPayloadOk(payload) and channel == givenChannel then
|
||||
handler(payload.message);
|
||||
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;
|
||||
@ -130,9 +130,15 @@ local function createNetwork(modem, routingChannel, timeoutInSec)
|
||||
return nil;
|
||||
end
|
||||
|
||||
local function openChannel(chan)
|
||||
modem.open(chan);
|
||||
end
|
||||
|
||||
return {
|
||||
send = send,
|
||||
waitMessage = waitMessage
|
||||
waitMessage = waitMessage,
|
||||
isPayloadOk = isPayloadOk,
|
||||
openChannel = openChannel,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ local LIST_FILES = {
|
||||
'router.lua',
|
||||
'startup/servers.lua',
|
||||
'apis/net.lua',
|
||||
'apis/eventloop.lua',
|
||||
};
|
||||
|
||||
local REPO_PREFIX = 'https://raw.githubusercontent.com/guillaumearm/cc-libs/master/'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user