Node.lua/node.lua

379 lines
9.0 KiB
Lua
Raw Normal View History

2019-02-16 22:25:56 +00:00
--[[
2019-01-26 00:07:24 +00:00
2019-02-16 22:25:56 +00:00
Node.lua by Ale32bit
https://github.com/Ale32bit-CC/Node.lua
2019-01-26 00:07:24 +00:00
2019-02-16 22:25:56 +00:00
MIT License
2019-01-26 00:07:24 +00:00
2019-02-16 22:25:56 +00:00
Copyright (c) 2019 Ale32bit
2019-01-26 00:07:24 +00:00
2019-02-16 22:25:56 +00:00
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
2019-01-26 00:07:24 +00:00
2019-02-16 22:25:56 +00:00
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-- DOCUMENTATION --
To start:
local node = require("node")
Functions:
node.on( event, callback ): Event name (string), Callback (function)
Returns table with listener details and thread
node.removeListener( listener ): Listener (table)
Returns boolean if success
node.setInterval( callback, interval, [...] ): Callback (function), Interval in seconds (number), ...Arguments for callback (anything)
Returns table with interval details and thread
node.clearInterval( interval ): Interval (table)
Returns boolean if success
2019-01-26 00:07:24 +00:00
2019-02-16 22:25:56 +00:00
node.setTimeout( callback, timeout, [...] ): Callback (function), Timeout in seconds (number), ...Arguments for callback (anything)
Returns table with timeout details and thread
node.clearTimeout( timeout ): Timeout (table)
Returns boolean if success
node.spawn( function ): Function to execute in the coroutine manager (function)
Returns table containing details and methods
node.promise( executor, [options] ): Executor (function), Options (table): options.errorOnUncaught (boolean) use error() or just printError in case of uncaught reject
Returns table, 3 functions: bind(cb) on fulfill, catch(cb) on reject and finally(cb) after these functions. They all want callback functions.
node.init(): Start the event loop engine !! REQUIRED TO RUN !!
node.isRunning(): Check if event loop engine is running
Returns boolean
]]--
-- Variables --
local isRunning = false
local threads = {} -- Store all threads here
-- Utils --
local function assertType(v, exp, n, level)
n = n or 1
level = level or 3
if type(v) ~= exp then
error("bad argument #"..n.." (expected "..exp..", got "..type(v)..")", level)
end
2019-01-26 00:07:24 +00:00
end
2019-02-16 22:25:56 +00:00
-- Thread functions --
2019-01-26 00:07:24 +00:00
2019-02-16 22:25:56 +00:00
local function killThread(pid) -- Kill a thread
assertType(pid, "number", 1, 2)
if threads[pid] then
threads[pid].tokill = true
return true
end
return false
end
2019-01-26 00:07:24 +00:00
2019-02-16 22:25:56 +00:00
local function spawnThread(f) -- Spawn new thread
assertType(f, "function", 1, 2)
local pid = #threads + 1
local thread = {
tokill = false,
thread = coroutine.create(f),
2019-01-26 00:07:24 +00:00
filter = nil,
2019-02-16 22:25:56 +00:00
pid = pid,
2019-01-26 00:07:24 +00:00
}
2019-02-16 22:25:56 +00:00
thread = setmetatable(thread, {
__index = {
kill = function(self)
killThread(self.pid)
end,
status = function(self)
return coroutine.status(self.thread)
end,
},
__tostring = function(self)
return "Thread "..self.pid..": "..self:status()
end,
})
threads[pid] = thread
return thread, pid
2019-01-26 00:07:24 +00:00
end
2019-02-16 22:25:56 +00:00
-- Node functions --
2019-01-26 00:07:24 +00:00
2019-02-16 22:25:56 +00:00
-- Event Listener
local function on(event, callback)
assertType(event, "string", 1)
assertType(callback, "function", 2)
-- Create listener
2019-01-26 00:07:24 +00:00
local listener = {}
listener.event = event
2019-02-16 22:25:56 +00:00
listener.callback = callback
2019-01-26 00:07:24 +00:00
listener.run = true
listener.stopped = false
2019-02-16 22:25:56 +00:00
listener.thread = spawnThread(function()
2019-01-26 00:07:24 +00:00
while listener.run do
2019-02-16 22:25:56 +00:00
local ev = {os.pullEvent(listener.event)}
spawnThread(function() listener.callback(unpack(ev, 2)) end)
2019-01-26 00:07:24 +00:00
end
end)
return listener
end
2019-02-16 22:25:56 +00:00
-- Remove event listener
2019-01-26 21:46:45 +00:00
local function removeListener(listener)
2019-02-16 22:25:56 +00:00
assert(listener, "table")
if not listener.thread then
error("bad argument #1 (expected Event Listener)", 2)
end
2019-01-26 00:07:24 +00:00
if listener.stopped then
return false
end
listener.run = false
listener.stopped = true
2019-02-16 22:25:56 +00:00
killThread(listener.thread.pid)
2019-01-26 00:07:24 +00:00
return true
end
2019-02-16 22:25:56 +00:00
-- Create new interval
local function setInterval(callback, s, ...)
assertType(callback, "function", 1)
2019-01-26 00:07:24 +00:00
s = s or 0
2019-02-16 22:25:56 +00:00
assertType(s, "number", 2)
2019-01-26 00:07:24 +00:00
local interval = {}
interval.interval = s
2019-02-16 22:25:56 +00:00
interval.callback = callback
2019-01-26 00:07:24 +00:00
interval.args = {...}
interval.run = true
interval.stopped = false
2019-02-16 22:25:56 +00:00
interval.thread = spawnThread(function()
2019-01-27 15:46:57 +00:00
while interval.run do
2019-02-16 22:25:56 +00:00
local timer = os.startTimer( interval.interval )
repeat
local _, t = os.pullEvent("timer")
until t == timer
spawnThread(function()
interval.callback(unpack(interval.args))
end)
2019-01-26 00:07:24 +00:00
end
end)
return interval
end
2019-02-16 22:25:56 +00:00
-- Clear interval
2019-01-26 21:46:45 +00:00
local function clearInterval(interval)
2019-02-16 22:25:56 +00:00
assert(interval, "table")
if not interval.thread then
error("bad argument #1 (expected Interval)", 2)
end
2019-01-26 00:07:24 +00:00
if interval.stopped then
return false
end
interval.run = false
interval.stopped = true
2019-02-16 22:25:56 +00:00
killThread(interval.thread.pid)
2019-01-26 00:07:24 +00:00
return true
end
2019-02-16 22:25:56 +00:00
-- Create new timeout
local function setTimeout(callback, s, ...)
assertType(callback, "function", 1)
2019-01-26 00:07:24 +00:00
s = s or 0
2019-02-16 22:25:56 +00:00
assertType(s, "number", 2)
2019-01-26 00:07:24 +00:00
2019-02-16 22:25:56 +00:00
local timeout = {}
timeout.timeout = s
timeout.callback = callback
timeout.args = {...}
timeout.stopped = false
timeout.thread = spawnThread(function()
local timer = os.startTimer( timeout.timeout )
repeat
local _, t = os.pullEvent("timer")
until t == timer
timeout.callback(unpack(timeout.args))
end)
2019-01-26 00:07:24 +00:00
2019-02-16 22:25:56 +00:00
return timeout
2019-01-26 00:07:24 +00:00
end
2019-02-16 22:25:56 +00:00
-- Clear timeout
local function clearTimeout(timeout)
assert(timeout, "table")
if not timeout.thread then
error("bad argument #1 (expected Timeout)", 2)
end
if timeout.stopped then
2019-01-26 00:07:24 +00:00
return false
end
2019-02-16 22:25:56 +00:00
timeout.stopped = true
killThread(timeout.thread.pid)
2019-01-26 00:07:24 +00:00
return true
end
2019-02-16 22:25:56 +00:00
-- New promise
local function promise(executor, options)
assertType(executor, "function", 1)
options = options or {}
options.errorOnUncaught = options.errorOnUncaught or false
assertType(options, "table", 2)
2019-01-27 22:18:56 +00:00
local promise = {}
2019-02-16 22:25:56 +00:00
promise.options = options
promise.status = "pending"
2019-01-27 22:18:56 +00:00
promise.value = nil
2019-02-16 22:25:56 +00:00
promise.bind = function( callback )
assertType(callback, "function")
promise.__bind = callback
2019-01-27 22:18:56 +00:00
end
2019-02-16 22:25:56 +00:00
promise.catch = function( callback )
assertType(callback, "function")
promise.__catch = callback
2019-01-27 22:18:56 +00:00
end
2019-02-16 22:25:56 +00:00
promise.finally = function( callback )
assertType(callback, "function")
promise.__finally = callback
2019-01-27 22:18:56 +00:00
end
2019-02-16 22:25:56 +00:00
promise.__resolve = function( value )
2019-01-27 22:18:56 +00:00
promise.status = "resolved"
2019-02-16 22:25:56 +00:00
promise.value = value
killThread(promise.thread.pid)
if promise.__bind then
spawnThread(function()
promise.__bind(value)
2019-01-27 22:18:56 +00:00
end)
end
if promise.__finally then
2019-02-16 22:25:56 +00:00
spawnThread(promise.__finally)
2019-01-27 22:18:56 +00:00
end
2019-02-16 22:25:56 +00:00
return value
2019-01-27 22:18:56 +00:00
end
2019-02-16 22:25:56 +00:00
promise.__reject = function( reason )
2019-01-27 22:18:56 +00:00
promise.status = "rejected"
2019-02-16 22:25:56 +00:00
promise.value = reason
killThread(promise.thread.pid)
2019-01-27 22:18:56 +00:00
if promise.__catch then
2019-02-16 22:25:56 +00:00
spawnThread(function()
promise.__catch(reason)
2019-01-27 22:18:56 +00:00
end)
else
2019-02-16 22:25:56 +00:00
if promise.options.errorOnUncaught then
error("Uncaught (in promise) "..tostring(reason), 3)
2019-01-27 22:18:56 +00:00
else
2019-02-16 22:25:56 +00:00
printError("Uncaught (in promise) "..tostring(reason))
2019-01-27 22:18:56 +00:00
end
end
if promise.__finally then
2019-02-16 22:25:56 +00:00
spawnThread(promise.__finally)
2019-01-27 22:18:56 +00:00
end
2019-02-16 22:25:56 +00:00
return reason
2019-01-27 22:18:56 +00:00
end
2019-02-16 22:25:56 +00:00
promise.thread = spawnThread(function()
executor(promise.__resolve, promise.__reject)
2019-01-27 22:18:56 +00:00
end)
return promise
end
2019-02-16 22:25:56 +00:00
-- Start the event loop engine
2019-01-26 21:46:45 +00:00
local function init()
2019-02-16 22:25:56 +00:00
assert(not isRunning, "Event loop engine already running")
isRunning = true
2019-02-16 22:25:56 +00:00
os.queueEvent("node_init")
while (function() -- Execute loop if threads count is higher than 0
local count = 0
for k,v in pairs(threads) do
count = count+1
2019-01-26 00:07:24 +00:00
end
2019-02-16 22:25:56 +00:00
return count > 0
2019-01-26 00:07:24 +00:00
end)() do
local event = {coroutine.yield()}
2019-02-16 22:25:56 +00:00
for pid, thread in pairs(threads) do
if thread.tokill then
threads[pid] = nil -- Remove thread if killed (and don't resume it)
2019-01-26 00:07:24 +00:00
else
2019-02-16 22:25:56 +00:00
if thread.filter == nil or thread.filter == event[1] or event[1] == "terminate" then -- filter events
local ok, par = coroutine.resume( thread.thread, unpack(event))
if ok then -- If ok par should be the event os.pullEvent expects
threads[pid].filter = par
else -- else the thread crashed
isRunning = false
2019-02-16 22:25:56 +00:00
error(par, 0) -- terminate event loop engine because of error
break -- just in case
2019-01-26 00:07:24 +00:00
end
end
2019-02-16 22:25:56 +00:00
if coroutine.status(thread.thread) == "dead" then -- If thread died (ended with no errors) remove it from the threads table
threads[pid] = nil
2019-01-26 00:07:24 +00:00
end
end
end
end
2019-02-16 22:25:56 +00:00
isRunning = false
2019-01-26 00:07:24 +00:00
end
2019-02-16 22:25:56 +00:00
-- Returns status
2019-01-27 22:18:56 +00:00
local function status()
return isRunning
end
2019-02-16 22:25:56 +00:00
-- Export and set in _ENV --
2019-01-26 00:07:24 +00:00
local node = {
2019-01-26 21:46:45 +00:00
on = on,
removeListener = removeListener,
setInterval = setInterval,
clearInterval = clearInterval,
setTimeout = setTimeout,
clearTimeout = clearTimeout,
2019-02-16 22:25:56 +00:00
spawn = spawnThread,
2019-01-27 22:18:56 +00:00
promise = promise,
2019-01-26 21:46:45 +00:00
init = init,
2019-02-16 22:25:56 +00:00
status = status,
2019-01-26 21:46:45 +00:00
}
2019-02-16 22:25:56 +00:00
if _ENV then
_ENV.node = node
end
return node