mirror of
https://github.com/Ale32bit-CC/Node.lua
synced 2024-06-18 03:09:58 +00:00
Rewrite
This commit is contained in:
parent
8416afe79c
commit
ea61a45658
434
node.lua
434
node.lua
|
@ -1,305 +1,362 @@
|
||||||
-- Node.lua
|
--[[
|
||||||
-- By Ale32bit
|
|
||||||
|
|
||||||
-- https://git.ale32bit.me/Ale32bit/Node.lua
|
Node.lua by Ale32bit
|
||||||
|
https://github.com/Ale32bit-CC/Node.lua
|
||||||
|
|
||||||
-- MIT License
|
MIT License
|
||||||
-- Copyright (c) 2019 Alessandro "Ale32bit"
|
|
||||||
|
|
||||||
-- Full license
|
Copyright (c) 2019 Ale32bit
|
||||||
-- https://git.ale32bit.me/Ale32bit/Node.lua/src/branch/master/LICENSE
|
|
||||||
|
|
||||||
-- utils
|
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:
|
||||||
|
|
||||||
local function genHex()
|
The above copyright notice and this permission notice shall be included in all
|
||||||
local rand = math.floor(math.random() * math.floor(16^8))
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
return rand
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
end
|
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.
|
||||||
|
|
||||||
-- coroutine manager
|
|
||||||
|
|
||||||
local procs = {}
|
-- DOCUMENTATION --
|
||||||
|
|
||||||
local function spawn(func)
|
To start:
|
||||||
local id = #procs + 1
|
|
||||||
procs[id] = {
|
|
||||||
kill = false,
|
|
||||||
thread = coroutine.create(func),
|
|
||||||
filter = nil,
|
|
||||||
}
|
|
||||||
return id
|
|
||||||
end
|
|
||||||
|
|
||||||
local function kill(pid)
|
local node = require("node")
|
||||||
procs[pid].kill = true
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Node functions
|
Functions:
|
||||||
|
|
||||||
local function on(event, func)
|
node.on( event, callback ): Event name (string), Callback (function)
|
||||||
assert(type(event) == "string", "bad argument #1 (expected string, got ".. type(func) ..")")
|
Returns table with listener details and thread
|
||||||
assert(type(func) == "function", "bad argument #2 (expected function, got ".. type(func) ..")")
|
|
||||||
local listener = {}
|
node.removeListener( listener ): Listener (table)
|
||||||
listener.event = event
|
Returns boolean if success
|
||||||
listener.func = func
|
|
||||||
listener.id = genHex()
|
node.setInterval( callback, interval, [...] ): Callback (function), Interval in seconds (number), ...Arguments for callback (anything)
|
||||||
listener.run = true
|
Returns table with interval details and thread
|
||||||
listener.stopped = false
|
|
||||||
listener.pid = spawn(function()
|
node.clearInterval( interval ): Interval (table)
|
||||||
while listener.run do
|
Returns boolean if success
|
||||||
local ev = {os.pullEvent(event)}
|
|
||||||
spawn(function() listener.func(unpack(ev, 2)) end)
|
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
|
end
|
||||||
end)
|
end
|
||||||
|
|
||||||
listener = setmetatable(listener, {
|
-- Thread functions --
|
||||||
__tostring = function()
|
|
||||||
return string.format("Listener 0x%x [%s] (%s)", listener.id, event, string.match(tostring(func),"%w+$"))
|
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
|
||||||
|
|
||||||
|
local function spawnThread(f) -- Spawn new thread
|
||||||
|
assertType(f, "function", 1, 2)
|
||||||
|
local pid = #threads + 1
|
||||||
|
local thread = {
|
||||||
|
tokill = false,
|
||||||
|
thread = coroutine.create(f),
|
||||||
|
filter = nil,
|
||||||
|
pid = pid,
|
||||||
|
}
|
||||||
|
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,
|
end,
|
||||||
})
|
})
|
||||||
|
threads[pid] = thread
|
||||||
|
return thread, pid
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Node functions --
|
||||||
|
|
||||||
|
-- Event Listener
|
||||||
|
local function on(event, callback)
|
||||||
|
assertType(event, "string", 1)
|
||||||
|
assertType(callback, "function", 2)
|
||||||
|
|
||||||
|
-- Create listener
|
||||||
|
local listener = {}
|
||||||
|
listener.event = event
|
||||||
|
listener.callback = callback
|
||||||
|
listener.run = true
|
||||||
|
listener.stopped = false
|
||||||
|
listener.thread = spawnThread(function()
|
||||||
|
while listener.run do
|
||||||
|
local ev = {os.pullEvent(listener.event)}
|
||||||
|
spawnThread(function() listener.callback(unpack(ev, 2)) end)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
return listener
|
return listener
|
||||||
end
|
end
|
||||||
|
|
||||||
local function removeListener(listener)
|
|
||||||
assert(type(listener) == "table", "bad argument #1 (expected listener, got ".. type(listener) ..")")
|
|
||||||
local id = listener.id
|
|
||||||
assert(id, "bad argument #1 (expected listener, got ".. type(listener) ..")")
|
|
||||||
|
|
||||||
|
-- Remove event listener
|
||||||
|
local function removeListener(listener)
|
||||||
|
assert(listener, "table")
|
||||||
|
if not listener.thread then
|
||||||
|
error("bad argument #1 (expected Event Listener)", 2)
|
||||||
|
end
|
||||||
if listener.stopped then
|
if listener.stopped then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
listener.run = false
|
listener.run = false
|
||||||
listener.stopped = true
|
listener.stopped = true
|
||||||
kill(listener.pid)
|
killThread(listener.thread.pid)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local function setInterval(func, s, ...)
|
-- Create new interval
|
||||||
assert(type(func) == "function", "bad argument #1 (expected function, got ".. type(func) ..")")
|
local function setInterval(callback, s, ...)
|
||||||
|
assertType(callback, "function", 1)
|
||||||
s = s or 0
|
s = s or 0
|
||||||
assert(type(s) == "number", "bad argument #2 (expected number, got ".. type(s) ..")")
|
assertType(s, "number", 2)
|
||||||
|
|
||||||
local interval = {}
|
local interval = {}
|
||||||
interval.interval = s
|
interval.interval = s
|
||||||
interval.func = func
|
interval.callback = callback
|
||||||
interval.args = {...}
|
interval.args = {...}
|
||||||
interval.id = genHex()
|
|
||||||
interval.run = true
|
interval.run = true
|
||||||
interval.stopped = false
|
interval.stopped = false
|
||||||
interval.pid = spawn(function()
|
interval.thread = spawnThread(function()
|
||||||
while interval.run do
|
while interval.run do
|
||||||
sleep(interval.interval)
|
local timer = os.startTimer( interval.interval )
|
||||||
spawn(function()
|
repeat
|
||||||
interval.func(unpack(interval.args))
|
local _, t = os.pullEvent("timer")
|
||||||
|
until t == timer
|
||||||
|
spawnThread(function()
|
||||||
|
interval.callback(unpack(interval.args))
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
interval = setmetatable(interval, {
|
|
||||||
__tostring = function()
|
|
||||||
return string.format("Interval 0x%x [%s]s (%s)", interval.id, s, string.match(tostring(func),"%w+$"))
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
return interval
|
return interval
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Clear interval
|
||||||
local function clearInterval(interval)
|
local function clearInterval(interval)
|
||||||
assert(type(interval) == "table", "bad argument #1 (expected interval, got ".. type(interval) ..")")
|
assert(interval, "table")
|
||||||
local id = interval.id
|
if not interval.thread then
|
||||||
assert(id, "bad argument #1 (expected interval, got ".. type(interval) ..")")
|
error("bad argument #1 (expected Interval)", 2)
|
||||||
|
end
|
||||||
if interval.stopped then
|
if interval.stopped then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
interval.run = false
|
interval.run = false
|
||||||
interval.stopped = true
|
interval.stopped = true
|
||||||
kill(interval.pid)
|
killThread(interval.thread.pid)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local function setTimeout(func, s, ...)
|
-- Create new timeout
|
||||||
assert(type(func) == "function", "bad argument #1 (expected function, got ".. type(func) ..")")
|
local function setTimeout(callback, s, ...)
|
||||||
|
assertType(callback, "function", 1)
|
||||||
s = s or 0
|
s = s or 0
|
||||||
assert(type(s) == "number", "bad argument #2 (expected number, got ".. type(s) ..")")
|
assertType(s, "number", 2)
|
||||||
local interval = {}
|
|
||||||
interval.timeout = s
|
local timeout = {}
|
||||||
interval.func = func
|
timeout.timeout = s
|
||||||
interval.args = {...}
|
timeout.callback = callback
|
||||||
interval.id = genHex()
|
timeout.args = {...}
|
||||||
interval.stopped = false
|
timeout.stopped = false
|
||||||
interval.pid = spawn(function()
|
timeout.thread = spawnThread(function()
|
||||||
sleep(interval.timeout)
|
local timer = os.startTimer( timeout.timeout )
|
||||||
spawn(function()
|
repeat
|
||||||
interval.func(unpack(interval.args))
|
local _, t = os.pullEvent("timer")
|
||||||
interval.stopped = true
|
until t == timer
|
||||||
end)
|
timeout.callback(unpack(timeout.args))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
interval = setmetatable(interval, {
|
return timeout
|
||||||
__tostring = function()
|
|
||||||
return string.format("Timeout 0x%x [%s]s (%s)", interval.id, s, string.match(tostring(func),"%w+$"))
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
return interval
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function clearTimeout(interval)
|
-- Clear timeout
|
||||||
assert(type(interval) == "table", "bad argument #1 (expected timeout, got ".. type(interval) ..")")
|
local function clearTimeout(timeout)
|
||||||
local id = interval.id
|
assert(timeout, "table")
|
||||||
assert(id, "bad argument #1 (expected timeout, got ".. type(interval) ..")")
|
if not timeout.thread then
|
||||||
|
error("bad argument #1 (expected Timeout)", 2)
|
||||||
if interval.stopped then
|
end
|
||||||
|
if timeout.stopped then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
interval.stopped = true
|
timeout.stopped = true
|
||||||
kill(interval.pid)
|
killThread(timeout.thread.pid)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local function spawnFunction(func)
|
-- New promise
|
||||||
assert(type(func) == "function", "bad argument #1 (expected function, got ".. type(func) ..")")
|
local function promise(executor, options)
|
||||||
local pid = spawn(func)
|
assertType(executor, "function", 1)
|
||||||
local thread = {}
|
options = options or {}
|
||||||
thread.pid = pid
|
options.errorOnUncaught = options.errorOnUncaught or false
|
||||||
thread.kill = function()
|
assertType(options, "table", 2)
|
||||||
return kill(pid)
|
|
||||||
end
|
|
||||||
thread.status = function()
|
|
||||||
return coroutine.status(procs[pid].thread)
|
|
||||||
end
|
|
||||||
|
|
||||||
local tostr = string.format("Thread 0x%s [%s] (%s)", string.match(tostring(procs[pid].thread),"%w+$"), thread.pid, string.match(tostring(func),"%w+$"))
|
|
||||||
|
|
||||||
thread = setmetatable(thread, {
|
|
||||||
__tostring = function()
|
|
||||||
return tostr
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
return thread
|
|
||||||
end
|
|
||||||
|
|
||||||
local function promise(func, errorOnUncaught) -- errorOnUncaught is temporary
|
|
||||||
assert(type(func) == "function", "bad argument #1 (expected function, got ".. type(func) ..")")
|
|
||||||
|
|
||||||
local promise = {}
|
local promise = {}
|
||||||
promise.id = genHex()
|
promise.options = options
|
||||||
promise.status = "pending" -- "resolved", "rejected"
|
promise.status = "pending"
|
||||||
promise.value = nil
|
promise.value = nil
|
||||||
|
|
||||||
promise.bind = function(func)
|
promise.bind = function( callback )
|
||||||
assert(type(func) == "function", "bad argument (expected function, got ".. type(func) ..")")
|
assertType(callback, "function")
|
||||||
promise.__then = func
|
promise.__bind = callback
|
||||||
end
|
end
|
||||||
|
|
||||||
promise.catch = function(func)
|
promise.catch = function( callback )
|
||||||
assert(type(func) == "function", "bad argument (expected function, got ".. type(func) ..")")
|
assertType(callback, "function")
|
||||||
promise.__catch = func
|
promise.__catch = callback
|
||||||
end
|
end
|
||||||
|
|
||||||
promise.finally = function(func)
|
promise.finally = function( callback )
|
||||||
assert(type(func) == "function", "bad argument (expected function, got ".. type(func) ..")")
|
assertType(callback, "function")
|
||||||
promise.__finally = func
|
promise.__finally = callback
|
||||||
end
|
end
|
||||||
|
|
||||||
local resolve = function(...)
|
promise.__resolve = function( value )
|
||||||
promise.value = {...}
|
|
||||||
promise.status = "resolved"
|
promise.status = "resolved"
|
||||||
if promise.__then then
|
promise.value = value
|
||||||
spawn(function()
|
killThread(promise.thread.pid)
|
||||||
promise.__then(unpack(promise.value))
|
if promise.__bind then
|
||||||
|
spawnThread(function()
|
||||||
|
promise.__bind(value)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
if promise.__finally then
|
if promise.__finally then
|
||||||
spawn(promise.__finally)
|
spawnThread(promise.__finally)
|
||||||
end
|
end
|
||||||
kill(promise.pid)
|
return value
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local reject = function(...)
|
promise.__reject = function( reason )
|
||||||
promise.value = {...}
|
|
||||||
promise.status = "rejected"
|
promise.status = "rejected"
|
||||||
|
promise.value = reason
|
||||||
|
killThread(promise.thread.pid)
|
||||||
if promise.__catch then
|
if promise.__catch then
|
||||||
spawn(function()
|
spawnThread(function()
|
||||||
promise.__catch(unpack(promise.value))
|
promise.__catch(reason)
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
if errorOnUncaught then
|
if promise.options.errorOnUncaught then
|
||||||
local val = {}
|
error("Uncaught (in promise) "..tostring(reason), 3)
|
||||||
for k, v in ipairs(promise.value) do
|
|
||||||
val[k] = tostring(v)
|
|
||||||
end
|
|
||||||
error("Uncaught (in promise) "..table.concat(promise.value, " "), 2)
|
|
||||||
else
|
else
|
||||||
printError("Uncaught (in promise)", unpack(promise.value))
|
printError("Uncaught (in promise) "..tostring(reason))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if promise.__finally then
|
if promise.__finally then
|
||||||
spawn(promise.__finally)
|
spawnThread(promise.__finally)
|
||||||
end
|
end
|
||||||
kill(promise.pid)
|
return reason
|
||||||
end
|
end
|
||||||
|
|
||||||
promise.pid = spawn(function()
|
promise.thread = spawnThread(function()
|
||||||
func(resolve, reject)
|
executor(promise.__resolve, promise.__reject)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
promise = setmetatable(promise, {
|
|
||||||
__tostring = function()
|
|
||||||
return string.format("Promise 0x%x [%s] (%s)", promise.id, promise.status, tostring(promise.value) or "nil")
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
end
|
end
|
||||||
|
|
||||||
local isRunning = false
|
-- Start the event loop engine
|
||||||
|
|
||||||
local function init()
|
local function init()
|
||||||
if isRunning then
|
assert(not isRunning, "Event loop engine already running")
|
||||||
error("Node Event Loop already running", 2)
|
|
||||||
end
|
|
||||||
isRunning = true
|
isRunning = true
|
||||||
while (function()
|
|
||||||
local c = 0
|
os.queueEvent("node_init")
|
||||||
for k,v in pairs(procs) do
|
|
||||||
c = c+1
|
while (function() -- Execute loop if threads count is higher than 0
|
||||||
|
local count = 0
|
||||||
|
for k,v in pairs(threads) do
|
||||||
|
count = count+1
|
||||||
end
|
end
|
||||||
return c > 0
|
return count > 0
|
||||||
end)() do
|
end)() do
|
||||||
local event = {coroutine.yield()}
|
local event = {coroutine.yield()}
|
||||||
for pid, proc in pairs(procs) do
|
for pid, thread in pairs(threads) do
|
||||||
if proc.kill then -- remove process if killed
|
if thread.tokill then
|
||||||
procs[pid] = nil
|
threads[pid] = nil -- Remove thread if killed (and don't resume it)
|
||||||
else
|
else
|
||||||
if proc.filter == nil or proc.filter == event[1] or event[1] == "terminate" then
|
if thread.filter == nil or thread.filter == event[1] or event[1] == "terminate" then -- filter events
|
||||||
local ok, par = coroutine.resume( proc.thread, unpack(event))
|
local ok, par = coroutine.resume( thread.thread, unpack(event))
|
||||||
if not ok then
|
|
||||||
|
|
||||||
|
if ok then -- If ok par should be the event os.pullEvent expects
|
||||||
|
threads[pid].filter = par
|
||||||
|
else -- else the thread crashed
|
||||||
isRunning = false
|
isRunning = false
|
||||||
error(par, 0)
|
error(par, 0) -- terminate event loop engine because of error
|
||||||
break
|
break -- just in case
|
||||||
else
|
|
||||||
procs[pid].filter = par
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if coroutine.status(proc.thread) == "dead" then
|
|
||||||
procs[pid] = nil
|
if coroutine.status(thread.thread) == "dead" then -- If thread died (ended with no errors) remove it from the threads table
|
||||||
|
threads[pid] = nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
isRunning = false
|
isRunning = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Returns status
|
||||||
local function status()
|
local function status()
|
||||||
return isRunning
|
return isRunning
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Export and set in _ENV --
|
||||||
|
|
||||||
local node = {
|
local node = {
|
||||||
on = on,
|
on = on,
|
||||||
|
@ -308,11 +365,14 @@ local node = {
|
||||||
clearInterval = clearInterval,
|
clearInterval = clearInterval,
|
||||||
setTimeout = setTimeout,
|
setTimeout = setTimeout,
|
||||||
clearTimeout = clearTimeout,
|
clearTimeout = clearTimeout,
|
||||||
spawn = spawnFunction,
|
spawn = spawnThread,
|
||||||
promise = promise,
|
promise = promise,
|
||||||
init = init,
|
init = init,
|
||||||
|
status = status,
|
||||||
}
|
}
|
||||||
|
|
||||||
_ENV.node = node
|
if _ENV then
|
||||||
|
_ENV.node = node
|
||||||
|
end
|
||||||
|
|
||||||
return node
|
return node
|
||||||
|
|
Loading…
Reference in New Issue
Block a user