From 4a664bb2e5da2fa68d82db26554fa0fc9b73441b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Thu, 24 Oct 2013 18:34:15 +0200 Subject: [PATCH] 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 --- assets/opencomputers/lua/kernel.lua | 205 ++++++++++-------- assets/opencomputers/lua/rom/bin/cat.lua | 2 +- assets/opencomputers/lua/rom/bin/less.lua | 46 ++++ assets/opencomputers/lua/rom/bin/sh.lua | 33 +-- assets/opencomputers/lua/rom/bin/shutdown.lua | 2 +- assets/opencomputers/lua/rom/lib/event.lua | 38 ++-- assets/opencomputers/lua/rom/lib/io.lua | 2 +- assets/opencomputers/lua/rom/lib/os.lua | 4 +- assets/opencomputers/lua/rom/lib/shell.lua | 184 +++++++++------- assets/opencomputers/lua/rom/lib/term.lua | 2 +- com/naef/jnlua/LuaError.java | 7 +- li/cil/oc/common/tileentity/Screen.scala | 3 +- li/cil/oc/server/component/Computer.scala | 49 +++-- li/cil/oc/util/LuaStateFactory.scala | 10 - 14 files changed, 336 insertions(+), 251 deletions(-) create mode 100644 assets/opencomputers/lua/rom/bin/less.lua diff --git a/assets/opencomputers/lua/kernel.lua b/assets/opencomputers/lua/kernel.lua index c975e2af7..a67aa3a02 100644 --- a/assets/opencomputers/lua/kernel.lua +++ b/assets/opencomputers/lua/kernel.lua @@ -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() diff --git a/assets/opencomputers/lua/rom/bin/cat.lua b/assets/opencomputers/lua/rom/bin/cat.lua index 1b97f7313..c57987002 100644 --- a/assets/opencomputers/lua/rom/bin/cat.lua +++ b/assets/opencomputers/lua/rom/bin/cat.lua @@ -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 diff --git a/assets/opencomputers/lua/rom/bin/less.lua b/assets/opencomputers/lua/rom/bin/less.lua new file mode 100644 index 000000000..f2c986953 --- /dev/null +++ b/assets/opencomputers/lua/rom/bin/less.lua @@ -0,0 +1,46 @@ +local args = shell.parse(...) +if #args == 0 then + print("Usage: less ") + 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 diff --git a/assets/opencomputers/lua/rom/bin/sh.lua b/assets/opencomputers/lua/rom/bin/sh.lua index 34230f589..2034283d3 100644 --- a/assets/opencomputers/lua/rom/bin/sh.lua +++ b/assets/opencomputers/lua/rom/bin/sh.lua @@ -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 diff --git a/assets/opencomputers/lua/rom/bin/shutdown.lua b/assets/opencomputers/lua/rom/bin/shutdown.lua index 97b019755..5730b6c51 100644 --- a/assets/opencomputers/lua/rom/bin/shutdown.lua +++ b/assets/opencomputers/lua/rom/bin/shutdown.lua @@ -1,2 +1,2 @@ -print("Shutting down...") +term.clear() os.shutdown() \ No newline at end of file diff --git a/assets/opencomputers/lua/rom/lib/event.lua b/assets/opencomputers/lua/rom/lib/event.lua index a6f837975..a05cf1ea1 100644 --- a/assets/opencomputers/lua/rom/lib/event.lua +++ b/assets/opencomputers/lua/rom/lib/event.lua @@ -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 diff --git a/assets/opencomputers/lua/rom/lib/io.lua b/assets/opencomputers/lua/rom/lib/io.lua index b633f72d5..4521e3b71 100644 --- a/assets/opencomputers/lua/rom/lib/io.lua +++ b/assets/opencomputers/lua/rom/lib/io.lua @@ -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" } diff --git a/assets/opencomputers/lua/rom/lib/os.lua b/assets/opencomputers/lua/rom/lib/os.lua index c672ff3de..ef4808d63 100644 --- a/assets/opencomputers/lua/rom/lib/os.lua +++ b/assets/opencomputers/lua/rom/lib/os.lua @@ -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 diff --git a/assets/opencomputers/lua/rom/lib/shell.lua b/assets/opencomputers/lua/rom/lib/shell.lua index 82625454e..3cbfb149e 100644 --- a/assets/opencomputers/lua/rom/lib/shell.lua +++ b/assets/opencomputers/lua/rom/lib/shell.lua @@ -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) diff --git a/assets/opencomputers/lua/rom/lib/term.lua b/assets/opencomputers/lua/rom/lib/term.lua index 996b4169b..9ea675911 100644 --- a/assets/opencomputers/lua/rom/lib/term.lua +++ b/assets/opencomputers/lua/rom/lib/term.lua @@ -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) diff --git a/com/naef/jnlua/LuaError.java b/com/naef/jnlua/LuaError.java index ebd03f7c7..9f62ba085 100644 --- a/com/naef/jnlua/LuaError.java +++ b/com/naef/jnlua/LuaError.java @@ -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(); } diff --git a/li/cil/oc/common/tileentity/Screen.scala b/li/cil/oc/common/tileentity/Screen.scala index 6216c57e1..c045be2b6 100644 --- a/li/cil/oc/common/tileentity/Screen.scala +++ b/li/cil/oc/common/tileentity/Screen.scala @@ -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 } diff --git a/li/cil/oc/server/component/Computer.scala b/li/cil/oc/server/component/Computer.scala index 35a6bacda..045f28ec0 100644 --- a/li/cil/oc/server/component/Computer.scala +++ b/li/cil/oc/server/component/Computer.scala @@ -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) { diff --git a/li/cil/oc/util/LuaStateFactory.scala b/li/cil/oc/util/LuaStateFactory.scala index 380923df5..261d89412 100644 --- a/li/cil/oc/util/LuaStateFactory.scala +++ b/li/cil/oc/util/LuaStateFactory.scala @@ -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)