mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-17 19:25:20 -04:00
some cleanup; automatically restarting the shell if it stops (avoids the occasional ctrl+c shutting down the computer); added os.sleep and extended event.wait to take a filter, which basically makes it similar to cc's pullEvent with an optional timeout; determining io buffer size based on current free ram and added a minimum size; some better "sandboxing" for programs (which really isn't that, just making automatic listener cleanup as consistent as possible); using error messages in (uncaught) exceptions where possible, making for some nicer error messages, for example for out of memory errors when pushing results from the host side; made difftime a lua function; properly initializing text renderer on multi-screens; added program for paged file viewing
This commit is contained in:
parent
a8288b2623
commit
4a664bb2e5
@ -2,14 +2,50 @@
|
||||
computer crashes. It should never ever return "normally", only when an
|
||||
error occurred. Shutdown / reboot are signalled via special yields. ]]
|
||||
|
||||
local deadline = 0
|
||||
|
||||
local function checkDeadline()
|
||||
if os.realTime() > deadline then
|
||||
error("too long without yielding", 0)
|
||||
end
|
||||
end
|
||||
|
||||
local function checkArg(n, have, ...)
|
||||
have = type(have)
|
||||
local function check(want, ...)
|
||||
if not want then
|
||||
return false
|
||||
else
|
||||
return have == want or check(...)
|
||||
end
|
||||
end
|
||||
if not check(...) then
|
||||
local msg = string.format("bad argument #%d (%s expected, got %s)", n, table.concat({...}, " or "), have)
|
||||
error(msg, 2)
|
||||
end
|
||||
end
|
||||
|
||||
--[[ Set up the global environment we make available to userland programs. ]]
|
||||
local sandbox = {
|
||||
local sandbox
|
||||
sandbox = {
|
||||
-- Top level values. The selection of kept methods rougly follows the list
|
||||
-- as available on the Lua wiki here: http://lua-users.org/wiki/SandBoxes
|
||||
assert = assert,
|
||||
error = error,
|
||||
pcall = pcall,
|
||||
xpcall = xpcall,
|
||||
load = function(ld, source, mode, env)
|
||||
assert((mode or "t") == "t", "unsupported mode")
|
||||
return load(ld, source, "t", env or sandbox)
|
||||
end,
|
||||
pcall = function(...)
|
||||
local result = table.pack(pcall(...))
|
||||
checkDeadline()
|
||||
return table.unpack(result, 1, result.n)
|
||||
end,
|
||||
xpcall = function(...)
|
||||
local result = table.pack(xpcall(...))
|
||||
checkDeadline()
|
||||
return table.unpack(result, 1, result.n)
|
||||
end,
|
||||
|
||||
ipairs = ipairs,
|
||||
next = next,
|
||||
@ -30,6 +66,8 @@ local sandbox = {
|
||||
|
||||
_VERSION = "Lua 5.2",
|
||||
|
||||
checkArg = checkArg,
|
||||
|
||||
bit32 = {
|
||||
arshift = bit32.arshift,
|
||||
band = bit32.band,
|
||||
@ -45,10 +83,48 @@ local sandbox = {
|
||||
rshift = bit32.rshift
|
||||
},
|
||||
|
||||
--[[ Install wrappers for coroutine management that reserves the first value
|
||||
returned by yields for internal stuff. Used for sleeping and message
|
||||
calls (sendToAddress) that happen synchronized (Server thread).
|
||||
--]]
|
||||
coroutine = {
|
||||
create = coroutine.create,
|
||||
running = coroutine.running,
|
||||
status = coroutine.status
|
||||
resume = function(co, ...)
|
||||
local args = table.pack(...)
|
||||
while true do
|
||||
if not debug.gethook(co) then -- don't reset counter
|
||||
debug.sethook(co, checkDeadline, "", 10000)
|
||||
end
|
||||
local result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n)))
|
||||
checkDeadline()
|
||||
if result[1] then
|
||||
local isSystemYield = coroutine.status(co) ~= "dead" and result[2] ~= nil
|
||||
if isSystemYield then
|
||||
args = table.pack(coroutine.yield(result[2]))
|
||||
else
|
||||
return true, table.unpack(result, 3, result.n)
|
||||
end
|
||||
else -- error: result = (bool, string)
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
end
|
||||
end,
|
||||
status = coroutine.status,
|
||||
yield = function(...)
|
||||
return coroutine.yield(nil, ...)
|
||||
end,
|
||||
wrap = function(f) -- for sandbox's coroutine.resume
|
||||
local co = sandbox.coroutine.create(f)
|
||||
return function(...)
|
||||
local result = table.pack(sandbox.coroutine.resume(co, ...))
|
||||
if result[1] then
|
||||
return table.unpack(result, 2, result.n)
|
||||
else
|
||||
error(result[2], 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
},
|
||||
|
||||
math = {
|
||||
@ -75,7 +151,10 @@ local sandbox = {
|
||||
pow = math.pow,
|
||||
rad = math.rad,
|
||||
random = math.random,
|
||||
randomseed = math.randomseed,
|
||||
randomseed = function(seed)
|
||||
checkArg(1, seed, "number")
|
||||
math.randomseed(seed)
|
||||
end,
|
||||
sin = math.sin,
|
||||
sinh = math.sinh,
|
||||
sqrt = math.sqrt,
|
||||
@ -86,7 +165,9 @@ local sandbox = {
|
||||
os = {
|
||||
clock = os.clock,
|
||||
date = os.date,
|
||||
difftime = os.difftime,
|
||||
difftime = function(t2, t1)
|
||||
return t2 - t1
|
||||
end,
|
||||
time = os.time,
|
||||
uptime = os.uptime,
|
||||
freeMemory = os.freeMemory,
|
||||
@ -114,7 +195,11 @@ local sandbox = {
|
||||
uchar = string.uchar,
|
||||
ulen = string.ulen,
|
||||
ureverse = string.ureverse,
|
||||
usub = string.usub
|
||||
usub = string.usub,
|
||||
trim = function(s) -- from http://lua-users.org/wiki/StringTrim
|
||||
local from = s:match("^%s*()")
|
||||
return from > #s and "" or s:match(".*%S", from)
|
||||
end
|
||||
},
|
||||
|
||||
table = {
|
||||
@ -133,90 +218,6 @@ local sandbox = {
|
||||
}
|
||||
sandbox._G = sandbox
|
||||
|
||||
function sandbox.load(ld, source, mode, env)
|
||||
assert((mode or "t") == "t", "unsupported mode")
|
||||
return load(ld, source, "t", env or sandbox)
|
||||
end
|
||||
|
||||
function sandbox.checkArg(n, have, ...)
|
||||
have = type(have)
|
||||
local function check(want, ...)
|
||||
if not want then
|
||||
return false
|
||||
else
|
||||
return have == want or check(...)
|
||||
end
|
||||
end
|
||||
if not check(...) then
|
||||
local msg = string.format("bad argument #%d (%s expected, got %s)", n, table.concat({...}, " or "), have)
|
||||
--error(debug.traceback(msg, 2), 0)
|
||||
error(msg, 2)
|
||||
end
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
--[[ Install wrappers for coroutine management that reserves the first value
|
||||
returned by yields for internal stuff. Used for sleeping and message
|
||||
calls (sendToAddress) that happen synchronized (Server thread).
|
||||
--]]
|
||||
local deadline = 0
|
||||
|
||||
local function checkDeadline()
|
||||
if os.realTime() > deadline then
|
||||
error("too long without yielding", 0)
|
||||
end
|
||||
end
|
||||
|
||||
function sandbox.coroutine.resume(co, ...)
|
||||
local args = table.pack(...)
|
||||
while true do
|
||||
if not debug.gethook(co) then -- don't reset counter
|
||||
debug.sethook(co, checkDeadline, "", 10000)
|
||||
end
|
||||
local result = table.pack(coroutine.resume(co, table.unpack(args, 1, args.n)))
|
||||
checkDeadline()
|
||||
if result[1] then
|
||||
local isSystemYield = coroutine.status(co) ~= "dead" and result[2] ~= nil
|
||||
if isSystemYield then
|
||||
args = table.pack(coroutine.yield(result[2]))
|
||||
else
|
||||
return true, table.unpack(result, 3, result.n)
|
||||
end
|
||||
else -- error: result = (bool, string)
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function sandbox.coroutine.yield(...)
|
||||
return coroutine.yield(nil, ...)
|
||||
end
|
||||
|
||||
function sandbox.coroutine.wrap(f) -- for sandbox's coroutine.resume
|
||||
local co = sandbox.coroutine.create(f)
|
||||
return function(...)
|
||||
local result = table.pack(sandbox.coroutine.resume(co, ...))
|
||||
if result[1] then
|
||||
return table.unpack(result, 2, result.n)
|
||||
else
|
||||
error(result[2], 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function sandbox.pcall(...)
|
||||
local result = table.pack(pcall(...))
|
||||
checkDeadline()
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
|
||||
function sandbox.xpcall(...)
|
||||
local result = table.pack(xpcall(...))
|
||||
checkDeadline()
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
function sandbox.os.shutdown(reboot)
|
||||
@ -238,11 +239,17 @@ end
|
||||
sandbox.driver = {}
|
||||
|
||||
function sandbox.driver.componentType(address)
|
||||
checkArg(1, address, "string")
|
||||
return nodeName(address)
|
||||
end
|
||||
|
||||
do
|
||||
local env = setmetatable({ send = sendToAddress },
|
||||
local function send(address, name, ...)
|
||||
checkArg(1, address, "string")
|
||||
checkArg(2, name, "string")
|
||||
return sendToAddress(address, name, ...)
|
||||
end
|
||||
local env = setmetatable({send = send},
|
||||
{ __index = sandbox, __newindex = sandbox })
|
||||
for name, code in pairs(drivers()) do
|
||||
local driver, reason = load(code, "=" .. name, "t", env)
|
||||
@ -270,7 +277,10 @@ local function main(args)
|
||||
|
||||
-- Custom dofile implementation since we don't have the baselib yet.
|
||||
local function dofile(file)
|
||||
local stream = fs.open(file)
|
||||
local stream, reason = fs.open(file)
|
||||
if not stream then
|
||||
error(reason)
|
||||
end
|
||||
if stream then
|
||||
local buffer = ""
|
||||
repeat
|
||||
@ -281,10 +291,12 @@ local function main(args)
|
||||
until not data
|
||||
stream:close()
|
||||
stream = nil
|
||||
local program = sandbox.load(buffer, "=" .. file)
|
||||
local program, reason = sandbox.load(buffer, "=" .. file)
|
||||
buffer = nil
|
||||
if program then
|
||||
return program()
|
||||
else
|
||||
error("error loading lib '" .. file .. "': " .. reason)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -306,8 +318,9 @@ local function main(args)
|
||||
|
||||
return coroutine.create(function(...)
|
||||
sandbox.event.fire(...) -- handle the first signal
|
||||
sandbox.os.execute("/bin/sh")
|
||||
coroutine.yield(false) -- this should never return, so we shut down
|
||||
while true do
|
||||
sandbox.os.execute("/bin/sh")
|
||||
end
|
||||
end)
|
||||
end
|
||||
local co = bootstrap()
|
||||
|
@ -5,7 +5,7 @@ if #args == 0 then
|
||||
end
|
||||
|
||||
for i = 1, #args do
|
||||
local file, reason = io.open(shell.resolve(args[i]), "r")
|
||||
local file, reason = io.open(shell.resolve(args[i]))
|
||||
if not file then
|
||||
print(reason)
|
||||
return
|
||||
|
46
assets/opencomputers/lua/rom/bin/less.lua
Normal file
46
assets/opencomputers/lua/rom/bin/less.lua
Normal file
@ -0,0 +1,46 @@
|
||||
local args = shell.parse(...)
|
||||
if #args == 0 then
|
||||
print("Usage: less <filename1>")
|
||||
return
|
||||
end
|
||||
|
||||
local file, reason = io.open(shell.resolve(args[1]))
|
||||
if not file then
|
||||
print(reason)
|
||||
return
|
||||
end
|
||||
|
||||
local line = nil
|
||||
while true do
|
||||
local w, h = gpu.resolution()
|
||||
term.clear()
|
||||
term.cursorBlink(false)
|
||||
local i = 1
|
||||
while i < h do
|
||||
if not line then
|
||||
line = file:read("*l")
|
||||
if not line then -- eof
|
||||
return
|
||||
end
|
||||
end
|
||||
if line:ulen() > w then
|
||||
print(line:usub(1, w))
|
||||
line = line:usub(w + 1)
|
||||
else
|
||||
print(line)
|
||||
line = nil
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
term.cursor(1, h)
|
||||
term.write(":")
|
||||
term.cursorBlink(true)
|
||||
local event, address, char, code = event.wait("key_down")
|
||||
if component.isPrimary(address) then
|
||||
if code == keyboard.keys.q then
|
||||
term.cursorBlink(false)
|
||||
term.clearLine()
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
@ -1,33 +1,24 @@
|
||||
local args = table.pack(...)
|
||||
if args.n > 0 then
|
||||
os.execute(table.concat(args, " ", 1, args.n))
|
||||
return
|
||||
end
|
||||
|
||||
local function trim(s) -- from http://lua-users.org/wiki/StringTrim
|
||||
local from = s:match"^%s*()"
|
||||
return from > #s and "" or s:match(".*%S", from)
|
||||
end
|
||||
local dir = shell.cwd() -- backup in case we're being run by another shell
|
||||
local running = true
|
||||
|
||||
while running do
|
||||
local history = {}
|
||||
while true do
|
||||
if not term.isAvailable() then -- don't clear when opened by another shell
|
||||
while not term.isAvailable() do
|
||||
event.wait()
|
||||
os.sleep()
|
||||
end
|
||||
term.clear()
|
||||
print("OpenOS v1.0 (" .. math.floor(os.totalMemory() / 1024) .. "k RAM)")
|
||||
end
|
||||
while running and term.isAvailable() do
|
||||
io.write("> ")
|
||||
local command = io.read()
|
||||
while term.isAvailable() do
|
||||
term.write("# ")
|
||||
local command = term.read(history)
|
||||
if not command then
|
||||
return -- eof
|
||||
end
|
||||
command = trim(command)
|
||||
while #history > 10 do
|
||||
table.remove(history, 1)
|
||||
end
|
||||
command = string.trim(command)
|
||||
if command == "exit" then
|
||||
running = false
|
||||
return
|
||||
elseif command ~= "" then
|
||||
local result, reason = os.execute(command)
|
||||
if not result then
|
||||
@ -36,5 +27,3 @@ while running do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shell.cwd(dir) -- restore
|
||||
|
@ -1,2 +1,2 @@
|
||||
print("Shutting down...")
|
||||
term.clear()
|
||||
os.shutdown()
|
@ -21,10 +21,15 @@ end
|
||||
|
||||
event = {}
|
||||
|
||||
--[[ Error handler for ALL event callbacks. If this returns a value,
|
||||
the error will be rethrown, possibly leading to a computer crash. ]]
|
||||
--[[ Error handler for ALL event callbacks. If this doesn't return `true`,
|
||||
the error will be printed and the computer will shut down. ]]
|
||||
function event.error(message)
|
||||
return debug.traceback(message)
|
||||
local log = io.open("tmp/event.log", "a")
|
||||
if log then
|
||||
log:write(message .. "\n")
|
||||
log:close()
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function event.fire(name, ...)
|
||||
@ -41,9 +46,11 @@ function event.fire(name, ...)
|
||||
-- Copy the listener lists because they may be changed by callbacks.
|
||||
local listeners = copy(listenersFor(name, false), listenersFor(name, true))
|
||||
for _, callback in ipairs(listeners) do
|
||||
local result, message = xpcall(callback, event.error, name, ...)
|
||||
if not result and message then -- only if handler returned something.
|
||||
error(message, 0)
|
||||
local result, message = pcall(callback, name, ...)
|
||||
if not result then
|
||||
if not event.error or not event.error(message) then
|
||||
os.shutdown()
|
||||
end
|
||||
elseif result and message == false then
|
||||
break
|
||||
end
|
||||
@ -57,9 +64,9 @@ function event.fire(name, ...)
|
||||
end
|
||||
end
|
||||
for _, callback in ipairs(elapsed) do
|
||||
local result, message = xpcall(callback, event.error)
|
||||
if not result and message then -- only if handler returned something.
|
||||
error(message, 0)
|
||||
local result, message = pcall(callback)
|
||||
if not result and not (event.error and event.error(message)) then
|
||||
os.shutdown()
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -114,9 +121,10 @@ function event.timer(timeout, callback)
|
||||
return id
|
||||
end
|
||||
|
||||
function event.wait(seconds)
|
||||
seconds = seconds or 0/0
|
||||
checkArg(1, seconds, "number")
|
||||
function event.wait(filter, seconds)
|
||||
checkArg(1, filter, "string", "nil")
|
||||
seconds = seconds or (filter and math.huge or 0/0)
|
||||
checkArg(2, seconds, "number")
|
||||
local function isNaN(n) return n ~= n end
|
||||
local target = os.uptime() + (isNaN(seconds) and 0 or seconds)
|
||||
repeat
|
||||
@ -126,6 +134,10 @@ function event.wait(seconds)
|
||||
closest = info.after
|
||||
end
|
||||
end
|
||||
event.fire(os.signal(nil, closest - os.uptime()))
|
||||
local signal = table.pack(os.signal(nil, closest - os.uptime()))
|
||||
event.fire(table.unpack(signal, 1, signal.n))
|
||||
if filter and type(signal[1]) == "string" and signal[1]:match(filter) then
|
||||
return table.unpack(signal, 1, signal.n)
|
||||
end
|
||||
until os.uptime() >= target
|
||||
end
|
||||
|
@ -255,7 +255,7 @@ function file.new(mode, stream, nogc)
|
||||
mode = mode,
|
||||
stream = stream,
|
||||
buffer = "",
|
||||
bufferSize = math.min(8 * 1024, os.totalMemory() / 8),
|
||||
bufferSize = math.max(128, math.min(8 * 1024, os.freeMemory() / 8)),
|
||||
bufferMode = "full"
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,9 @@ os.remove = driver.filesystem.remove
|
||||
|
||||
os.rename = driver.filesystem.rename
|
||||
|
||||
os.sleep = event.wait
|
||||
function os.sleep(timeout)
|
||||
event.wait(nil, timeout)
|
||||
end
|
||||
|
||||
function os.tmpname()
|
||||
if driver.filesystem.exists("tmp") then
|
||||
|
@ -1,7 +1,7 @@
|
||||
local cwd = "/"
|
||||
local path = {"/bin/", "/usr/bin/", "/home/bin/"}
|
||||
local aliases = {dir="ls", move="mv", rename="mv", copy="cp", del="rm",
|
||||
md="mkdir", cls="clear"}
|
||||
md="mkdir", cls="clear", more="less"}
|
||||
|
||||
local function findFile(name, path, ext)
|
||||
checkArg(1, name, "string")
|
||||
@ -45,6 +45,93 @@ local function findFile(name, path, ext)
|
||||
return false
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- We pseudo-sandbox programs we start via the shell. Pseudo because it's
|
||||
-- really just a matter of convenience: listeners and timers get automatically
|
||||
-- cleaned up when the program exits/crashes. This can be easily circumvented
|
||||
-- by getting the parent environment via `getmetatable(_ENV).__index`. But if
|
||||
-- you do that you will probably know what you're doing.
|
||||
function newEnvironment()
|
||||
local listeners, timers, e = {[false]={}, [true]={}}, {}, {}
|
||||
local env = setmetatable(e, {__index=_ENV})
|
||||
|
||||
e._G = e
|
||||
e.event = {}
|
||||
function e.event.ignore(name, callback, weak)
|
||||
weak = weak or false
|
||||
if listeners[weak][name] and listeners[weak][name][callback] then
|
||||
listeners[weak][name][callback] = nil
|
||||
return event.ignore(name, callback, weak)
|
||||
end
|
||||
return false
|
||||
end
|
||||
function e.event.listen(name, callback, weak)
|
||||
weak = weak or false
|
||||
if event.listen(name, callback, weak) then
|
||||
listeners[weak][name] = listeners[weak][name] or {}
|
||||
listeners[weak][name][callback] = true
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
function e.event.cancel(timerId)
|
||||
if timers[timerId] then
|
||||
timers[timerId] = nil
|
||||
return event.cancel(timerId)
|
||||
end
|
||||
return false
|
||||
end
|
||||
function e.event.timer(timeout, callback)
|
||||
local id
|
||||
local function onTimer()
|
||||
timers[id] = nil
|
||||
callback()
|
||||
end
|
||||
id = event.timer(timeout, onTimer)
|
||||
timers[id] = true
|
||||
return id
|
||||
end
|
||||
function e.event.interval(frequency, callback)
|
||||
local interval = {}
|
||||
local function onTimer()
|
||||
interval.id = env.event.timer(frequency, onTimer)
|
||||
callback()
|
||||
end
|
||||
interval.id = env.event.timer(frequency, onTimer)
|
||||
return interval
|
||||
end
|
||||
setmetatable(e.event, {__index=event, __newindex=event})
|
||||
|
||||
function e.load(ld, source, mode, environment)
|
||||
return load(ld, source, mode, environment or env)
|
||||
end
|
||||
function e.loadfile(filename, mode, environment)
|
||||
return loadfile(filename, mode, environment or env)
|
||||
end
|
||||
function e.dofile(filename)
|
||||
local program, reason = env.loadfile(filename)
|
||||
if not program then
|
||||
return env.error(reason, 0)
|
||||
end
|
||||
return program()
|
||||
end
|
||||
|
||||
function cleanup()
|
||||
for weak, list in pairs(listeners) do
|
||||
for name, callbacks in pairs(list) do
|
||||
for callback in pairs(callbacks) do
|
||||
event.ignore(name, callback, weak)
|
||||
end
|
||||
end
|
||||
end
|
||||
for id in pairs(timers) do
|
||||
event.cancel(id)
|
||||
end
|
||||
end
|
||||
|
||||
return env, cleanup
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
shell = {}
|
||||
@ -82,91 +169,13 @@ function shell.execute(program, ...)
|
||||
if not where then
|
||||
return nil, "program not found"
|
||||
end
|
||||
|
||||
-- Track listeners and timers registered by spawned programs so we can kill
|
||||
-- them all when the coroutine dies. Note that this is only intended as a
|
||||
-- convenience, and is easily circumvented (e.g. by using dofile or such).
|
||||
local listeners, weakListeners, timers = {}, {}, {}
|
||||
local pevent = {}
|
||||
function pevent.ignore(name, callback, weak)
|
||||
local list
|
||||
if weak then
|
||||
if weakListeners[name] and weakListeners[name][callback] then
|
||||
list = weakListeners
|
||||
end
|
||||
elseif listeners[name] and listeners[name][callback] then
|
||||
list = listeners
|
||||
end
|
||||
if list then
|
||||
event.ignore(name, callback)
|
||||
list[name][callback] = nil
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
function pevent.listen(name, callback, weak)
|
||||
if event.listen(name, callback, weak) then
|
||||
if weak then
|
||||
weakListeners[name] = weakListeners[name] or {}
|
||||
weakListeners[name][callback] = true
|
||||
else
|
||||
listeners[name] = listeners[name] or {}
|
||||
listeners[name][callback] = nil
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
function pevent.cancel(timerId)
|
||||
if timers[timerId] then
|
||||
timers[timerId] = nil
|
||||
return event.cancel(timerId)
|
||||
end
|
||||
return false
|
||||
end
|
||||
function pevent.timer(timeout, callback)
|
||||
local id
|
||||
local function onTimer()
|
||||
timers[id] = nil
|
||||
callback()
|
||||
end
|
||||
id = event.timer(timeout, onTimer)
|
||||
timers[id] = true
|
||||
return id
|
||||
end
|
||||
function pevent.interval(timeout, callback)
|
||||
local interval = {}
|
||||
local function onTimer()
|
||||
interval.id = pevent.timer(timeout, onTimer)
|
||||
callback()
|
||||
end
|
||||
interval.id = pevent.timer(timeout, onTimer)
|
||||
return interval
|
||||
end
|
||||
pevent = setmetatable(pevent, {__index = event, __metatable = {}})
|
||||
local env = setmetatable({event = pevent}, {__index = _ENV, __metatable = {}})
|
||||
|
||||
local env, cleanup = newEnvironment()
|
||||
program, reason = loadfile(where, "t", env)
|
||||
if not program then
|
||||
return nil, reason
|
||||
end
|
||||
|
||||
local result = table.pack(pcall(program, ...))
|
||||
|
||||
for name, list in pairs(listeners) do
|
||||
for listener in pairs(list) do
|
||||
event.ignore(name, listener, false)
|
||||
end
|
||||
end
|
||||
for name, list in pairs(weakListeners) do
|
||||
for listener in pairs(list) do
|
||||
event.ignore(name, listener, true)
|
||||
end
|
||||
end
|
||||
for id in pairs(timers) do
|
||||
event.cancel(id)
|
||||
end
|
||||
|
||||
cleanup()
|
||||
return table.unpack(result, 1, result.n)
|
||||
end
|
||||
|
||||
@ -187,6 +196,19 @@ function shell.parse(...)
|
||||
return args, options
|
||||
end
|
||||
|
||||
function shell.path(...)
|
||||
local result = table.concat(path, ":")
|
||||
local args = table.pack(...)
|
||||
if args.n > 0 then
|
||||
checkArg(1, args[1], "string")
|
||||
path = {}
|
||||
for segment in string:gmatch(args[1], "[^:]") do
|
||||
table.insert(path, string.trim(segment))
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function shell.resolve(path)
|
||||
if path:usub(1, 1) == "/" then
|
||||
return fs.canonical(path)
|
||||
|
@ -312,7 +312,7 @@ function term.read(history)
|
||||
event.listen("clipboard", onClipboard)
|
||||
term.cursorBlink(true)
|
||||
while term.isAvailable() and not result and not forceReturn do
|
||||
event.wait()
|
||||
os.sleep()
|
||||
end
|
||||
if history[#history] == "" then
|
||||
table.remove(history)
|
||||
|
@ -54,7 +54,12 @@ class LuaError {
|
||||
sb.append(message);
|
||||
}
|
||||
if (cause != null) {
|
||||
sb.append(cause);
|
||||
if (cause.getMessage() != null) {
|
||||
sb.append(cause.getMessage());
|
||||
}
|
||||
else {
|
||||
sb.append(cause);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ abstract class Screen extends Rotatable with component.Screen.Environment with R
|
||||
* into an OpenGL display list, and only re-compiling that list when the
|
||||
* text/display has actually changed.
|
||||
*/
|
||||
var hasChanged = false
|
||||
var hasChanged = true
|
||||
|
||||
/**
|
||||
* Check for multi-block screen option in next update. We do this in the
|
||||
@ -133,6 +133,7 @@ abstract class Screen extends Rotatable with component.Screen.Environment with R
|
||||
current.screens.foreach {
|
||||
screen =>
|
||||
screen.shouldCheckForMultiBlock = false
|
||||
screen.hasChanged = true
|
||||
pending.remove(screen)
|
||||
queue += screen
|
||||
}
|
||||
|
@ -92,8 +92,10 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
def recomputeMemory() = if (lua != null)
|
||||
def recomputeMemory() = if (lua != null) {
|
||||
lua.gc(LuaState.GcAction.COLLECT, 0)
|
||||
lua.setTotalMemory(kernelMemory + owner.installedMemory)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
@ -165,9 +167,21 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
rom.foreach(rom => rom.network.foreach(_.remove(rom)))
|
||||
tmp.foreach(tmp => tmp.network.foreach(_.remove(tmp)))
|
||||
owner.network.foreach(_.sendToVisible(owner, "computer.stopped"))
|
||||
// Clear any screens we use while we're at it.
|
||||
owner.network.foreach(_.sendToNeighbors(owner, "gpu.fill",
|
||||
1.0, 1.0, Double.PositiveInfinity, Double.PositiveInfinity, " ".getBytes("UTF-8")))
|
||||
|
||||
// If there was an error message (i.e. the computer crashed) display it on
|
||||
// any screens we used (stored in GPUs).
|
||||
if (message.isDefined) {
|
||||
println(message.get) // TODO remove this at some point (add a tool that can read these error messages?)
|
||||
|
||||
// Clear any screens we use before displaying the error message on them.
|
||||
owner.network.foreach(_.sendToNeighbors(owner, "gpu.fill",
|
||||
1.0, 1.0, Double.PositiveInfinity, Double.PositiveInfinity, " ".getBytes("UTF-8")))
|
||||
owner.network.foreach(network => {
|
||||
for ((line, row) <- message.get.replace("\t", " ").lines.zipWithIndex) {
|
||||
network.sendToNeighbors(owner, "gpu.set", 1.0, 1.0 + row, line.getBytes("UTF-8"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Signal stops to the network. This is used to close file handles, for example.
|
||||
@ -176,18 +190,6 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
}
|
||||
wasRunning = isRunning
|
||||
|
||||
// If there was an error message (i.e. the computer crashed) display it on
|
||||
// any screens we used (stored in GPUs).
|
||||
if (message.isDefined) {
|
||||
println(message.get)
|
||||
owner.network.foreach(network => {
|
||||
for ((line, row) <- message.get.replace("\t", " ").lines.zipWithIndex) {
|
||||
network.sendToNeighbors(owner, "gpu.set", 1.0, 1.0 + row, line.getBytes("UTF-8"))
|
||||
}
|
||||
})
|
||||
message = None
|
||||
}
|
||||
|
||||
// Check if we should switch states.
|
||||
stateMonitor.synchronized(state match {
|
||||
// Computer is rebooting.
|
||||
@ -306,8 +308,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
if (nbt.hasKey("message"))
|
||||
message = Some(nbt.getString("message"))
|
||||
|
||||
// Clean up some after we're done and limit memory again.
|
||||
lua.gc(LuaState.GcAction.COLLECT, 0)
|
||||
// Limit memory again.
|
||||
recomputeMemory()
|
||||
|
||||
// Ensure the executor is started in the next update if necessary.
|
||||
@ -350,7 +351,6 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
}
|
||||
|
||||
// Unlimit memory while persisting.
|
||||
val memory = lua.getTotalMemory
|
||||
lua.setTotalMemory(Integer.MAX_VALUE)
|
||||
try {
|
||||
// Try persisting Lua, because that's what all of the rest depends on.
|
||||
@ -400,9 +400,8 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// Clean up some after we're done and limit memory again.
|
||||
lua.gc(LuaState.GcAction.COLLECT, 0)
|
||||
lua.setTotalMemory(memory)
|
||||
// Limit memory again.
|
||||
recomputeMemory()
|
||||
}
|
||||
}
|
||||
|
||||
@ -437,6 +436,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private def init(): Boolean = {
|
||||
// Reset error state.
|
||||
message = None
|
||||
|
||||
// Creates a new state with all base libraries and the persistence library
|
||||
// loaded into it. This means the state has much more power than it
|
||||
// rightfully should have, so we sandbox it a bit in the following.
|
||||
@ -748,6 +750,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
|
||||
assert(lua.isThread(1))
|
||||
|
||||
try {
|
||||
// Help out the GC a little. The emergency GC has a few limitations that
|
||||
// will make it free less memory than doing a full step manually.
|
||||
lua.gc(LuaState.GcAction.COLLECT, 0)
|
||||
// Resume the Lua state and remember the number of results we get.
|
||||
cpuStart = System.nanoTime()
|
||||
val results = if (callReturn) {
|
||||
|
@ -163,16 +163,6 @@ object LuaStateFactory {
|
||||
})
|
||||
state.setField(-2, "date")
|
||||
|
||||
// Custom os.difftime(). For most Lua implementations this would be the
|
||||
// same anyway, but just to be on the safe side.
|
||||
state.pushScalaFunction(lua => {
|
||||
val t2 = lua.checkNumber(1)
|
||||
val t1 = lua.checkNumber(2)
|
||||
lua.pushNumber(t2 - t1)
|
||||
1
|
||||
})
|
||||
state.setField(-2, "difftime")
|
||||
|
||||
// Pop the os table.
|
||||
state.pop(1)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user