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:
Florian Nücke 2013-10-24 18:34:15 +02:00
parent a8288b2623
commit 4a664bb2e5
14 changed files with 336 additions and 251 deletions

View File

@ -2,14 +2,50 @@
computer crashes. It should never ever return "normally", only when an computer crashes. It should never ever return "normally", only when an
error occurred. Shutdown / reboot are signalled via special yields. ]] 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. ]] --[[ 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 -- 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 -- as available on the Lua wiki here: http://lua-users.org/wiki/SandBoxes
assert = assert, assert = assert,
error = error, error = error,
pcall = pcall, load = function(ld, source, mode, env)
xpcall = xpcall, 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, ipairs = ipairs,
next = next, next = next,
@ -30,6 +66,8 @@ local sandbox = {
_VERSION = "Lua 5.2", _VERSION = "Lua 5.2",
checkArg = checkArg,
bit32 = { bit32 = {
arshift = bit32.arshift, arshift = bit32.arshift,
band = bit32.band, band = bit32.band,
@ -45,10 +83,48 @@ local sandbox = {
rshift = bit32.rshift 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 = { coroutine = {
create = coroutine.create, create = coroutine.create,
running = coroutine.running, 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 = { math = {
@ -75,7 +151,10 @@ local sandbox = {
pow = math.pow, pow = math.pow,
rad = math.rad, rad = math.rad,
random = math.random, random = math.random,
randomseed = math.randomseed, randomseed = function(seed)
checkArg(1, seed, "number")
math.randomseed(seed)
end,
sin = math.sin, sin = math.sin,
sinh = math.sinh, sinh = math.sinh,
sqrt = math.sqrt, sqrt = math.sqrt,
@ -86,7 +165,9 @@ local sandbox = {
os = { os = {
clock = os.clock, clock = os.clock,
date = os.date, date = os.date,
difftime = os.difftime, difftime = function(t2, t1)
return t2 - t1
end,
time = os.time, time = os.time,
uptime = os.uptime, uptime = os.uptime,
freeMemory = os.freeMemory, freeMemory = os.freeMemory,
@ -114,7 +195,11 @@ local sandbox = {
uchar = string.uchar, uchar = string.uchar,
ulen = string.ulen, ulen = string.ulen,
ureverse = string.ureverse, 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 = { table = {
@ -133,90 +218,6 @@ local sandbox = {
} }
sandbox._G = 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) function sandbox.os.shutdown(reboot)
@ -238,11 +239,17 @@ end
sandbox.driver = {} sandbox.driver = {}
function sandbox.driver.componentType(address) function sandbox.driver.componentType(address)
checkArg(1, address, "string")
return nodeName(address) return nodeName(address)
end end
do 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 }) { __index = sandbox, __newindex = sandbox })
for name, code in pairs(drivers()) do for name, code in pairs(drivers()) do
local driver, reason = load(code, "=" .. name, "t", env) 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. -- Custom dofile implementation since we don't have the baselib yet.
local function dofile(file) 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 if stream then
local buffer = "" local buffer = ""
repeat repeat
@ -281,10 +291,12 @@ local function main(args)
until not data until not data
stream:close() stream:close()
stream = nil stream = nil
local program = sandbox.load(buffer, "=" .. file) local program, reason = sandbox.load(buffer, "=" .. file)
buffer = nil buffer = nil
if program then if program then
return program() return program()
else
error("error loading lib '" .. file .. "': " .. reason)
end end
end end
end end
@ -306,8 +318,9 @@ local function main(args)
return coroutine.create(function(...) return coroutine.create(function(...)
sandbox.event.fire(...) -- handle the first signal sandbox.event.fire(...) -- handle the first signal
sandbox.os.execute("/bin/sh") while true do
coroutine.yield(false) -- this should never return, so we shut down sandbox.os.execute("/bin/sh")
end
end) end)
end end
local co = bootstrap() local co = bootstrap()

View File

@ -5,7 +5,7 @@ if #args == 0 then
end end
for i = 1, #args do 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 if not file then
print(reason) print(reason)
return return

View 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

View File

@ -1,33 +1,24 @@
local args = table.pack(...) local history = {}
if args.n > 0 then while true do
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
if not term.isAvailable() then -- don't clear when opened by another shell if not term.isAvailable() then -- don't clear when opened by another shell
while not term.isAvailable() do while not term.isAvailable() do
event.wait() os.sleep()
end end
term.clear() term.clear()
print("OpenOS v1.0 (" .. math.floor(os.totalMemory() / 1024) .. "k RAM)") print("OpenOS v1.0 (" .. math.floor(os.totalMemory() / 1024) .. "k RAM)")
end end
while running and term.isAvailable() do while term.isAvailable() do
io.write("> ") term.write("# ")
local command = io.read() local command = term.read(history)
if not command then if not command then
return -- eof return -- eof
end end
command = trim(command) while #history > 10 do
table.remove(history, 1)
end
command = string.trim(command)
if command == "exit" then if command == "exit" then
running = false return
elseif command ~= "" then elseif command ~= "" then
local result, reason = os.execute(command) local result, reason = os.execute(command)
if not result then if not result then
@ -36,5 +27,3 @@ while running do
end end
end end
end end
shell.cwd(dir) -- restore

View File

@ -1,2 +1,2 @@
print("Shutting down...") term.clear()
os.shutdown() os.shutdown()

View File

@ -21,10 +21,15 @@ end
event = {} event = {}
--[[ Error handler for ALL event callbacks. If this returns a value, --[[ Error handler for ALL event callbacks. If this doesn't return `true`,
the error will be rethrown, possibly leading to a computer crash. ]] the error will be printed and the computer will shut down. ]]
function event.error(message) 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 end
function event.fire(name, ...) function event.fire(name, ...)
@ -41,9 +46,11 @@ function event.fire(name, ...)
-- Copy the listener lists because they may be changed by callbacks. -- Copy the listener lists because they may be changed by callbacks.
local listeners = copy(listenersFor(name, false), listenersFor(name, true)) local listeners = copy(listenersFor(name, false), listenersFor(name, true))
for _, callback in ipairs(listeners) do for _, callback in ipairs(listeners) do
local result, message = xpcall(callback, event.error, name, ...) local result, message = pcall(callback, name, ...)
if not result and message then -- only if handler returned something. if not result then
error(message, 0) if not event.error or not event.error(message) then
os.shutdown()
end
elseif result and message == false then elseif result and message == false then
break break
end end
@ -57,9 +64,9 @@ function event.fire(name, ...)
end end
end end
for _, callback in ipairs(elapsed) do for _, callback in ipairs(elapsed) do
local result, message = xpcall(callback, event.error) local result, message = pcall(callback)
if not result and message then -- only if handler returned something. if not result and not (event.error and event.error(message)) then
error(message, 0) os.shutdown()
end end
end end
end end
@ -114,9 +121,10 @@ function event.timer(timeout, callback)
return id return id
end end
function event.wait(seconds) function event.wait(filter, seconds)
seconds = seconds or 0/0 checkArg(1, filter, "string", "nil")
checkArg(1, seconds, "number") seconds = seconds or (filter and math.huge or 0/0)
checkArg(2, seconds, "number")
local function isNaN(n) return n ~= n end local function isNaN(n) return n ~= n end
local target = os.uptime() + (isNaN(seconds) and 0 or seconds) local target = os.uptime() + (isNaN(seconds) and 0 or seconds)
repeat repeat
@ -126,6 +134,10 @@ function event.wait(seconds)
closest = info.after closest = info.after
end end
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 until os.uptime() >= target
end end

View File

@ -255,7 +255,7 @@ function file.new(mode, stream, nogc)
mode = mode, mode = mode,
stream = stream, stream = stream,
buffer = "", buffer = "",
bufferSize = math.min(8 * 1024, os.totalMemory() / 8), bufferSize = math.max(128, math.min(8 * 1024, os.freeMemory() / 8)),
bufferMode = "full" bufferMode = "full"
} }

View File

@ -25,7 +25,9 @@ os.remove = driver.filesystem.remove
os.rename = driver.filesystem.rename os.rename = driver.filesystem.rename
os.sleep = event.wait function os.sleep(timeout)
event.wait(nil, timeout)
end
function os.tmpname() function os.tmpname()
if driver.filesystem.exists("tmp") then if driver.filesystem.exists("tmp") then

View File

@ -1,7 +1,7 @@
local cwd = "/" local cwd = "/"
local path = {"/bin/", "/usr/bin/", "/home/bin/"} local path = {"/bin/", "/usr/bin/", "/home/bin/"}
local aliases = {dir="ls", move="mv", rename="mv", copy="cp", del="rm", 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) local function findFile(name, path, ext)
checkArg(1, name, "string") checkArg(1, name, "string")
@ -45,6 +45,93 @@ local function findFile(name, path, ext)
return false return false
end 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 = {} shell = {}
@ -82,91 +169,13 @@ function shell.execute(program, ...)
if not where then if not where then
return nil, "program not found" return nil, "program not found"
end end
local env, cleanup = newEnvironment()
-- 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 = {}})
program, reason = loadfile(where, "t", env) program, reason = loadfile(where, "t", env)
if not program then if not program then
return nil, reason return nil, reason
end end
local result = table.pack(pcall(program, ...)) local result = table.pack(pcall(program, ...))
cleanup()
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
return table.unpack(result, 1, result.n) return table.unpack(result, 1, result.n)
end end
@ -187,6 +196,19 @@ function shell.parse(...)
return args, options return args, options
end 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) function shell.resolve(path)
if path:usub(1, 1) == "/" then if path:usub(1, 1) == "/" then
return fs.canonical(path) return fs.canonical(path)

View File

@ -312,7 +312,7 @@ function term.read(history)
event.listen("clipboard", onClipboard) event.listen("clipboard", onClipboard)
term.cursorBlink(true) term.cursorBlink(true)
while term.isAvailable() and not result and not forceReturn do while term.isAvailable() and not result and not forceReturn do
event.wait() os.sleep()
end end
if history[#history] == "" then if history[#history] == "" then
table.remove(history) table.remove(history)

View File

@ -54,7 +54,12 @@ class LuaError {
sb.append(message); sb.append(message);
} }
if (cause != null) { if (cause != null) {
sb.append(cause); if (cause.getMessage() != null) {
sb.append(cause.getMessage());
}
else {
sb.append(cause);
}
} }
return sb.toString(); return sb.toString();
} }

View File

@ -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 * into an OpenGL display list, and only re-compiling that list when the
* text/display has actually changed. * 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 * 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 { current.screens.foreach {
screen => screen =>
screen.shouldCheckForMultiBlock = false screen.shouldCheckForMultiBlock = false
screen.hasChanged = true
pending.remove(screen) pending.remove(screen)
queue += screen queue += screen
} }

View File

@ -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) 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))) rom.foreach(rom => rom.network.foreach(_.remove(rom)))
tmp.foreach(tmp => tmp.network.foreach(_.remove(tmp))) tmp.foreach(tmp => tmp.network.foreach(_.remove(tmp)))
owner.network.foreach(_.sendToVisible(owner, "computer.stopped")) owner.network.foreach(_.sendToVisible(owner, "computer.stopped"))
// Clear any screens we use while we're at it.
owner.network.foreach(_.sendToNeighbors(owner, "gpu.fill", // If there was an error message (i.e. the computer crashed) display it on
1.0, 1.0, Double.PositiveInfinity, Double.PositiveInfinity, " ".getBytes("UTF-8"))) // 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. // 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 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. // Check if we should switch states.
stateMonitor.synchronized(state match { stateMonitor.synchronized(state match {
// Computer is rebooting. // Computer is rebooting.
@ -306,8 +308,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
if (nbt.hasKey("message")) if (nbt.hasKey("message"))
message = Some(nbt.getString("message")) message = Some(nbt.getString("message"))
// Clean up some after we're done and limit memory again. // Limit memory again.
lua.gc(LuaState.GcAction.COLLECT, 0)
recomputeMemory() recomputeMemory()
// Ensure the executor is started in the next update if necessary. // 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. // Unlimit memory while persisting.
val memory = lua.getTotalMemory
lua.setTotalMemory(Integer.MAX_VALUE) lua.setTotalMemory(Integer.MAX_VALUE)
try { try {
// Try persisting Lua, because that's what all of the rest depends on. // 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 { finally {
// Clean up some after we're done and limit memory again. // Limit memory again.
lua.gc(LuaState.GcAction.COLLECT, 0) recomputeMemory()
lua.setTotalMemory(memory)
} }
} }
@ -437,6 +436,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
private def init(): Boolean = { private def init(): Boolean = {
// Reset error state.
message = None
// Creates a new state with all base libraries and the persistence library // 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 // 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. // 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)) assert(lua.isThread(1))
try { 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. // Resume the Lua state and remember the number of results we get.
cpuStart = System.nanoTime() cpuStart = System.nanoTime()
val results = if (callReturn) { val results = if (callReturn) {

View File

@ -163,16 +163,6 @@ object LuaStateFactory {
}) })
state.setField(-2, "date") 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. // Pop the os table.
state.pop(1) state.pop(1)