diff --git a/lib/event.lua b/lib/event.lua new file mode 100644 index 00000000..50630460 --- /dev/null +++ b/lib/event.lua @@ -0,0 +1,258 @@ +local computer = require("computer") +local keyboard = require("keyboard") + +local event, listeners, timers = {}, {}, {} +local lastInterrupt = -math.huge + +local function call(callback, ...) + local result, message = pcall(callback, ...) + if not result and type(event.onError) == "function" then + pcall(event.onError, message) + return + end + return message +end + +local function dispatch(signal, ...) + if listeners[signal] then + local function callbacks() + local list = {} + for index, listener in ipairs(listeners[signal]) do + list[index] = listener + end + return list + end + for _, callback in ipairs(callbacks()) do + if call(callback, signal, ...) == false then + event.ignore(signal, callback) -- alternative method of removing a listener + end + end + end +end + +local function tick() + local function elapsed() + local list = {} + for id, timer in pairs(timers) do + if timer.after <= computer.uptime() then + table.insert(list, timer.callback) + timer.times = timer.times - 1 + if timer.times <= 0 then + timers[id] = nil + else + timer.after = computer.uptime() + timer.interval + end + end + end + return list + end + for _, callback in ipairs(elapsed()) do + call(callback) + end +end + +local function createPlainFilter(name, ...) + local filter = table.pack(...) + if name == nil and filter.n == 0 then + return nil + end + + return function(...) + local signal = table.pack(...) + if name and not (type(signal[1]) == "string" and signal[1]:match(name)) then + return false + end + for i = 1, filter.n do + if filter[i] ~= nil and filter[i] ~= signal[i + 1] then + return false + end + end + return true + end +end + +local function createMultipleFilter(...) + local filter = table.pack(...) + if filter.n == 0 then + return nil + end + + return function(...) + local signal = table.pack(...) + if type(signal[1]) ~= "string" then + return false + end + for i = 1, filter.n do + if filter[i] ~= nil and signal[1]:match(filter[i]) then + return true + end + end + return false + end +end +------------------------------------------------------------------------------- + +function event.cancel(timerId) + checkArg(1, timerId, "number") + if timers[timerId] then + timers[timerId] = nil + return true + end + return false +end + +function event.ignore(name, callback) + checkArg(1, name, "string") + checkArg(2, callback, "function") + if listeners[name] then + for i = 1, #listeners[name] do + if listeners[name][i] == callback then + table.remove(listeners[name], i) + if #listeners[name] == 0 then + listeners[name] = nil + end + return true + end + end + end + return false +end + +function event.listen(name, callback) + checkArg(1, name, "string") + checkArg(2, callback, "function") + if listeners[name] then + for i = 1, #listeners[name] do + if listeners[name][i] == callback then + return false + end + end + else + listeners[name] = {} + end + table.insert(listeners[name], callback) + return true +end + +function event.onError(message) + local log = io.open("/tmp/event.log", "a") + if log then + log:write(message .. "\n") + log:close() + end +end + +function event.pull(...) + local args = table.pack(...) + if type(args[1]) == "string" then + return event.pullFiltered(createPlainFilter(...)) + else + checkArg(1, args[1], "number", "nil") + checkArg(2, args[2], "string", "nil") + return event.pullFiltered(args[1], createPlainFilter(select(2, ...))) + end +end + +function event.pullMultiple(...) + local seconds + local args + if type(...) == "number" then + seconds = ... + args = table.pack(select(2,...)) + for i=1,args.n do + checkArg(i+1, args[i], "string", "nil") + end + else + args = table.pack(...) + for i=1,args.n do + checkArg(i, args[i], "string", "nil") + end + end + return event.pullFiltered(seconds, createMultipleFilter(table.unpack(args, 1, args.n))) + +end + +function event.pullFiltered(...) + local args = table.pack(...) + local seconds, filter + + if type(args[1]) == "function" then + filter = args[1] + else + checkArg(1, args[1], "number", "nil") + checkArg(2, args[2], "function", "nil") + seconds = args[1] + filter = args[2] + end + + local deadline = seconds and + (computer.uptime() + seconds) or + (filter and math.huge or 0) + repeat + local closest = seconds and deadline or math.huge + for _, timer in pairs(timers) do + closest = math.min(closest, timer.after) + end + local signal = table.pack(computer.pullSignal(closest - computer.uptime())) + if signal.n > 0 then + dispatch(table.unpack(signal, 1, signal.n)) + end + tick() + event.takeScreenshot() + if event.shouldInterrupt() then + lastInterrupt = computer.uptime() + error("interrupted", 0) + end + if event.shouldSoftInterrupt() and (filter == nil or filter("interrupted", computer.uptime() - lastInterrupt)) then + local awaited = computer.uptime() - lastInterrupt + lastInterrupt = computer.uptime() + return "interrupted", awaited + end + if not (seconds or filter) or filter == nil or filter(table.unpack(signal, 1, signal.n)) then + return table.unpack(signal, 1, signal.n) + end + until computer.uptime() >= deadline +end + +function event.shouldInterrupt() + return computer.uptime() - lastInterrupt > 1 and + keyboard.isControlDown() and + keyboard.isAltDown() and + keyboard.isKeyDown(keyboard.keys.c) +end + +function event.shouldSoftInterrupt() + return computer.uptime() - lastInterrupt > 1 and + keyboard.isControlDown() and + keyboard.isKeyDown(keyboard.keys.c) +end + +function event.takeScreenshot() + if keyboard.isControlDown and keyboard.isShiftDown and keyboard.isKeyDown(keyboard.keys.1) then + computer.beep(500) + image.screenshot("screenshot.jpg") + computer.beep(1200) + computer.beep(1200) + end +end + +function event.timer(interval, callback, times) + checkArg(1, interval, "number") + checkArg(2, callback, "function") + checkArg(3, times, "number", "nil") + local id + repeat + id = math.floor(math.random(1, 0x7FFFFFFF)) + until not timers[id] + timers[id] = { + interval = interval, + after = computer.uptime() + interval, + callback = callback, + times = times or 1 + } + return id +end + +------------------------------------------------------------------------------- + +return event