changed os.clock to be the actual cpu time of the computer and introduced os.uptime as the replacement of the old functionality; dofile now properly throws when loading fails; graphicscard now stores the screen its bound to in the component, which allows for the computer to display messages after it crashed (by sending a display request to neighbors -> graphicscards installed in it); fixed potential timeout in os.signal; wrappers for pcall and xpcall to enforce timeout when they return, too

This commit is contained in:
Florian Nücke 2013-10-06 18:16:03 +02:00
parent a73ed001dd
commit d363d0322d
9 changed files with 262 additions and 154 deletions

View File

@ -590,7 +590,7 @@ end
function dofile(filename) function dofile(filename)
local program, reason = loadfile(filename) local program, reason = loadfile(filename)
if not program then if not program then
return nil, reason return error(reason)
end end
return program() return program()
end end

View File

@ -1,48 +1,52 @@
driver.gpu = {} driver.gpu = {}
function driver.gpu.setResolution(gpu, screen, w, h)
send(gpu, "gpu.resolution=", screen, w, h)
end
function driver.gpu.getResolution(gpu, screen)
return send(gpu, "gpu.resolution", screen)
end
function driver.gpu.getResolutions(gpu, screen)
return send(gpu, "gpu.resolutions", screen)
end
function driver.gpu.set(gpu, screen, col, row, value)
send(gpu, "gpu.set", screen, col, row, value)
end
function driver.gpu.fill(gpu, screen, col, row, w, h, value)
send(gpu, "gpu.fill", screen, col, row, w, h, value:sub(1, 1))
end
function driver.gpu.copy(gpu, screen, col, row, w, h, tx, ty)
send(gpu, "gpu.copy", screen, col, row, w, h, tx, ty)
end
function driver.gpu.bind(gpu, screen) function driver.gpu.bind(gpu, screen)
return { checkArg(1, gpu, "string")
setResolution = function(w, h) checkArg(2, screen, "string")
driver.gpu.setResolution(gpu, screen, w, h) return send(gpu, "gpu.bind", screen)
end, end
getResolution = function()
return driver.gpu.getResolution(gpu, screen) function driver.gpu.resolution(gpu, w, h)
end, checkArg(1, gpu, "string")
getResolutions = function() if w and h then
return driver.gpu.getResolutions(gpu, screen) checkArg(2, w, "number")
end, checkArg(3, h, "number")
set = function(col, row, value) return send(gpu, "gpu.resolution=", w, h)
driver.gpu.set(gpu, screen, col, row, value) else
end, return send(gpu, "gpu.resolution")
fill = function(col, ro, w, h, value) end
driver.gpu.fill(gpu, screen, col, ro, w, h, value) end
end,
copy = function(col, row, w, h, tx, ty) function driver.gpu.resolutions(gpu)
driver.gpu.copy(gpu, screen, col, row, w, h, tx, ty) checkArg(1, gpu, "string")
end return send(gpu, "gpu.resolutions")
} end
end
function driver.gpu.set(gpu, col, row, value)
checkArg(1, gpu, "string")
checkArg(2, col, "number")
checkArg(3, row, "number")
checkArg(4, value, "string")
return send(gpu, "gpu.set", col, row, value)
end
function driver.gpu.fill(gpu, col, row, w, h, value)
checkArg(1, gpu, "string")
checkArg(2, col, "number")
checkArg(3, row, "number")
checkArg(4, w, "number")
checkArg(5, h, "number")
checkArg(6, value, "string")
return send(gpu, "gpu.fill", col, row, w, h, value:sub(1, 1))
end
function driver.gpu.copy(gpu, col, row, w, h, tx, ty)
checkArg(1, gpu, "string")
checkArg(2, col, "number")
checkArg(3, row, "number")
checkArg(4, w, "number")
checkArg(5, h, "number")
checkArg(6, tx, "number")
checkArg(7, ty, "number")
return send(gpu, "gpu.copy", col, row, w, h, tx, ty)
end

View File

@ -91,6 +91,7 @@ local sandbox = {
date = os.date, date = os.date,
difftime = os.difftime, difftime = os.difftime,
time = os.time, time = os.time,
uptime = os.uptime,
freeMemory = os.freeMemory, freeMemory = os.freeMemory,
totalMemory = os.totalMemory, totalMemory = os.totalMemory,
address = os.address, address = os.address,
@ -138,7 +139,7 @@ function sandbox.checkArg(n, have, ...)
end end
end end
local msg = "bad argument #" .. n .. " (" .. table.concat({...}, " or ") .. " expected, got " .. have .. ")" local msg = "bad argument #" .. n .. " (" .. table.concat({...}, " or ") .. " expected, got " .. have .. ")"
error(debug.traceback(msg, 3), 2) error(debug.traceback(msg, 2), 2)
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@ -204,6 +205,18 @@ function sandbox.coroutine.yield(...)
return coroutine.yield(nil, ...) return coroutine.yield(nil, ...)
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() function sandbox.os.shutdown()
@ -215,13 +228,13 @@ function sandbox.os.reboot()
end end
function sandbox.os.signal(name, timeout) function sandbox.os.signal(name, timeout)
local waitUntil = os.clock() + (type(timeout) == "number" and timeout or math.huge) local waitUntil = os.uptime() + (type(timeout) == "number" and timeout or math.huge)
while os.clock() < waitUntil do repeat
local signal = table.pack(coroutine.yield(waitUntil - os.clock())) local signal = table.pack(coroutine.yield(waitUntil - os.uptime()))
if signal.n > 0 and (name == signal[1] or name == nil) then if signal.n > 0 and (name == signal[1] or name == nil) then
return table.unpack(signal, 1, signal.n) return table.unpack(signal, 1, signal.n)
end end
end until os.uptime() >= waitUntil
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------

View File

@ -55,7 +55,7 @@ function event.fire(name, ...)
end end
local elapsed = {} local elapsed = {}
for id, info in pairs(timers) do for id, info in pairs(timers) do
if info.after < os.clock() then if info.after < os.uptime() then
table.insert(elapsed, info.callback) table.insert(elapsed, info.callback)
timers[id] = nil timers[id] = nil
end end
@ -70,7 +70,7 @@ end
function event.timer(timeout, callback) function event.timer(timeout, callback)
local id = #timers + 1 local id = #timers + 1
timers[id] = {after = os.clock() + timeout, callback = callback} timers[id] = {after = os.uptime() + timeout, callback = callback}
return id return id
end end
@ -98,7 +98,7 @@ end
function coroutine.sleep(seconds) function coroutine.sleep(seconds)
seconds = seconds or math.huge seconds = seconds or math.huge
checkArg(1, seconds, "number") checkArg(1, seconds, "number")
local target = os.clock() + seconds local target = os.uptime() + seconds
repeat repeat
local closest = target local closest = target
for _, info in pairs(timers) do for _, info in pairs(timers) do
@ -106,6 +106,6 @@ function coroutine.sleep(seconds)
closest = info.after closest = info.after
end end
end end
event.fire(os.signal(nil, closest - os.clock())) event.fire(os.signal(nil, closest - os.uptime()))
until os.clock() >= target until os.uptime() >= target
end end

View File

@ -1,18 +1,20 @@
local gpu = nil local gpuAddress, screenAddress, keyboardAddress = nil, nil, nil
local gpuAddress, screenAddress, keyboardAddress = false, false, false
local width, height = 0, 0 local width, height = 0, 0
local cursorX, cursorY = 1, 1 local cursorX, cursorY = 1, 1
local cursorBlink = nil local cursorBlink = nil
local function bindIfPossible() local function rebind(gpu, screen)
if gpuAddress and screenAddress then if gpu == gpuAddress and screen == screenAddress then
if not gpu then return
gpu = driver.gpu.bind(gpuAddress, screenAddress) end
width, height = gpu.getResolution() local oldGpu, oldScreen = gpuAddress, screenAddress
event.fire("term_available") gpuAddress, screenAddress = gpu, screen
end if gpu and screen then
elseif gpu then driver.gpu.bind(gpuAddress, screenAddress)
gpu, width, height = nil, 0, 0 width, height = driver.gpu.resolution(gpuAddress)
event.fire("term_available")
elseif gpuAddress and screenAddress then
width, height = 0, 0
event.fire("term_unavailable") event.fire("term_unavailable")
end end
end end
@ -21,16 +23,20 @@ end
term = {} term = {}
function term.available()
return gpuAddress and screenAddress
end
function term.clear() function term.clear()
if gpu then if term.available() then
gpu.fill(1, 1, width, height, " ") driver.gpu.fill(term.gpu(), 1, 1, width, height, " ")
end end
cursorX, cursorY = 1, 1 cursorX, cursorY = 1, 1
end end
function term.clearLine() function term.clearLine()
if gpu then if term.available() then
gpu.fill(1, cursorY, width, 1, " ") driver.gpu.fill(term.gpu(), 1, cursorY, width, 1, " ")
end end
cursorX = 1 cursorX = 1
end end
@ -47,10 +53,10 @@ function term.cursorBlink(enabled)
if type(enabled) == "boolean" and enabled ~= (cursorBlink ~= nil) then if type(enabled) == "boolean" and enabled ~= (cursorBlink ~= nil) then
local function toggleBlink() local function toggleBlink()
cursorBlink.state = not cursorBlink.state cursorBlink.state = not cursorBlink.state
if gpu then if term.available() then
-- 0x2588 is a solid block. -- 0x2588 is a solid block.
local char = cursorBlink.state and string.char(0x2588) or " " local char = cursorBlink.state and string.char(0x2588) or " "
gpu.set(cursorX, cursorY, char) driver.gpu.set(term.gpu(), cursorX, cursorY, char)
end end
end end
if enabled then if enabled then
@ -67,25 +73,29 @@ function term.cursorBlink(enabled)
return cursorBlink ~= nil return cursorBlink ~= nil
end end
function term.gpu(address) function term.gpu(...)
if address ~= nil and ({boolean=true, string=true})[type(address)] then local args = table.pack(...)
gpuAddress = address if args.n > 0 then
bindIfPossible() checkArg(1, args[1], "string", "nil")
rebind(args[1], term.screen())
end end
return gpuAddress, gpu return gpuAddress
end end
function term.keyboard(address) function term.keyboard(...)
if address ~= nil and ({boolean=true, string=true})[type(address)] then local args = table.pack(...)
keyboardAddress = address if args.n > 0 then
checkArg(1, args[1], "string", "nil")
keyboardAddress = args[1]
end end
return keyboardAddress return keyboardAddress
end end
function term.screen(address) function term.screen(...)
if address ~= nil and ({boolean=true, string=true})[type(address)] then local args = table.pack(...)
screenAddress = address if args.n > 0 then
bindIfPossible() checkArg(1, args[1], "string", "nil")
rebind(term.gpu(), args[1])
end end
return screenAddress return screenAddress
end end
@ -97,7 +107,7 @@ end
function term.write(value, wrap) function term.write(value, wrap)
value = tostring(value) value = tostring(value)
local w, h = width, height local w, h = width, height
if value:len() == 0 or not gpu or w < 1 or h < 1 then if value:len() == 0 or not term.available() or w < 1 or h < 1 then
return return
end end
value = value:gsub("\t", " ") value = value:gsub("\t", " ")
@ -107,8 +117,8 @@ function term.write(value, wrap)
cursorY = cursorY + 1 cursorY = cursorY + 1
end end
if cursorY > h then if cursorY > h then
gpu.copy(1, 1, w, h, 0, -1) driver.gpu.copy(term.gpu(), 1, 1, w, h, 0, -1)
gpu.fill(1, h, w, 1, " ") driver.gpu.fill(term.gpu(), 1, h, w, 1, " ")
cursorY = h cursorY = h
end end
end end
@ -116,12 +126,12 @@ function term.write(value, wrap)
while wrap and line:len() > w - cursorX + 1 do while wrap and line:len() > w - cursorX + 1 do
local partial = line:sub(1, w - cursorX + 1) local partial = line:sub(1, w - cursorX + 1)
line = line:sub(partial:len() + 1) line = line:sub(partial:len() + 1)
gpu.set(cursorX, cursorY, partial) driver.gpu.set(term.gpu(), cursorX, cursorY, partial)
cursorX = cursorX + partial:len() cursorX = cursorX + partial:len()
checkCursor() checkCursor()
end end
if line:len() > 0 then if line:len() > 0 then
gpu.set(cursorX, cursorY, line) driver.gpu.set(term.gpu(), cursorX, cursorY, line)
cursorX = cursorX + line:len() cursorX = cursorX + line:len()
end end
if nl:len() == 1 then if nl:len() == 1 then
@ -136,34 +146,34 @@ end
event.listen("component_added", function(_, address) event.listen("component_added", function(_, address)
local type = component.type(address) local type = component.type(address)
if type == "gpu" and not gpuAddress then if type == "gpu" and not term.gpu() then
term.gpu(address) term.gpu(address)
elseif type == "screen" and not screenAddress then elseif type == "screen" and not term.screen() then
term.screen(address) term.screen(address)
elseif type == "keyboard" and not keyboardAddress then elseif type == "keyboard" and not term.keyboard() then
term.keyboard(address) term.keyboard(address)
end end
end) end)
event.listen("component_removed", function(_, address) event.listen("component_removed", function(_, address)
if gpuAddress == address then if term.gpu() == address then
term.gpu(false) term.gpu(nil)
for address in component.list() do for address in component.list() do
if component.type(address) == "gpu" then if component.type(address) == "gpu" then
term.gpu(address) term.gpu(address)
return return
end end
end end
elseif screenAddress == address then elseif term.screen() == address then
term.screen(false) term.screen(nil)
for address in component.list() do for address in component.list() do
if component.type(address) == "screen" then if component.type(address) == "screen" then
term.screen(address) term.screen(address)
return return
end end
end end
elseif keyboardAddress == address then elseif term.keyboard() == address then
term.keyboard(false) term.keyboard(nil)
for address in component.list() do for address in component.list() do
if component.type(address) == "keyboard" then if component.type(address) == "keyboard" then
term.keyboard(address) term.keyboard(address)
@ -174,7 +184,7 @@ event.listen("component_removed", function(_, address)
end) end)
event.listen("screen_resized", function(_, address, w, h) event.listen("screen_resized", function(_, address, w, h)
if address == screenAddress then if term.screen() == address then
width = w width = w
height = h height = h
end end

View File

@ -6,15 +6,15 @@ local isRunning = false
local function onKeyDown(_, address, char, code) local function onKeyDown(_, address, char, code)
if isRunning then return end -- ignore events while running a command if isRunning then return end -- ignore events while running a command
if address ~= term.keyboard() then return end if address ~= term.keyboard() then return end
local _, gpu = term.gpu() if not term.available() then return end
if not gpu then return end
local x, y = term.cursor() local x, y = term.cursor()
local w, h = term.size()
local keys = driver.keyboard.keys local keys = driver.keyboard.keys
if code == keys.back then if code == keys.back then
if command:len() == 0 then return end if command:len() == 0 then return end
command = command:sub(1, -2) command = command:sub(1, -2)
term.cursor(command:len() + 3, y) -- from leading "> " term.cursor(command:len() + 3, y) -- from leading "> "
gpu.set(x - 1, y, " ") -- overwrite cursor blink driver.gpu.set(term.gpu(), x - 1, y, " ") -- overwrite cursor blink
elseif code == keys.enter then elseif code == keys.enter then
if command:len() == 0 then return end if command:len() == 0 then return end
term.cursorBlink(false) term.cursorBlink(false)
@ -39,7 +39,7 @@ local function onKeyDown(_, address, char, code)
term.cursorBlink(true) term.cursorBlink(true)
elseif code == keys.up then elseif code == keys.up then
command = lastCommand command = lastCommand
gpu.fill(3, y, screenWidth, 1, " ") driver.gpu.fill(term.gpu(), 3, y, w, 1, " ")
term.cursor(3, y) term.cursor(3, y)
term.write(command) term.write(command)
term.cursor(command:len() + 3, y) term.cursor(command:len() + 3, y)

View File

@ -70,16 +70,22 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
private var timeStarted = 0L // Game-world time for os.clock(). private var timeStarted = 0L // Game-world time [ms] for os.uptime().
private var worldTime = 0L // Game-world time for os.time(). private var worldTime = 0L // Game-world time for os.time().
private var lastUpdate = 0L // Real-world time for pause detection. private var lastUpdate = 0L // Real-world time [ms] for pause detection.
private var sleepUntil = Double.PositiveInfinity // Real-world time. private var cpuTime = 0L // Pseudo-real-world time [ns] for os.clock().
private var cpuStart = 0L // Pseudo-real-world time [ns] for os.clock().
private var sleepUntil = Double.PositiveInfinity // Real-world time [ms].
private var wasRunning = false // To signal stops synchronously. private var wasRunning = false // To signal stops synchronously.
private var message: Option[String] = None // For error messages.
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def recomputeMemory() = if (lua != null) def recomputeMemory() = if (lua != null)
@ -154,10 +160,19 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
worldTime = owner.world.getWorldInfo.getWorldTotalTime worldTime = owner.world.getWorldInfo.getWorldTotalTime
// 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.
if (wasRunning && !isRunning) if (wasRunning && !isRunning) {
owner.network.foreach(_.sendToVisible(owner, "computer.stopped")) owner.network.foreach(_.sendToVisible(owner, "computer.stopped"))
owner.network.foreach(_.sendToNeighbors(owner, "gpu.fill", 1.0, 1.0, Double.PositiveInfinity, Double.PositiveInfinity, " ".getBytes("UTF-8")))
}
wasRunning = isRunning wasRunning = isRunning
if (message.isDefined) owner.network.foreach(network => {
for ((line, row) <- message.get.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.
@ -204,16 +219,14 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// This can happen if we run out of memory while converting a Java // This can happen if we run out of memory while converting a Java
// exception to a string (which we have to do to avoid keeping // exception to a string (which we have to do to avoid keeping
// userdata on the stack, which cannot be persisted). // userdata on the stack, which cannot be persisted).
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages message = Some("not enough memory")
//owner.network.foreach(_.sendToVisible(owner, "computer.crashed", "not enough memory"))
close() close()
case e: java.lang.Error if e.getMessage == "not enough memory" => case e: java.lang.Error if e.getMessage == "not enough memory" =>
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages message = Some("not enough memory")
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
//owner.network.foreach(_.sendToVisible(owner, "computer.crashed", "not enough memory"))
close() close()
case e: Throwable => { case e: Throwable => {
OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e) OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e)
message = Some("protocol error")
close() close()
} }
} }
@ -273,6 +286,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
rom.foreach(_.load(nbt.getCompoundTag("rom"))) rom.foreach(_.load(nbt.getCompoundTag("rom")))
kernelMemory = nbt.getInteger("kernelMemory") kernelMemory = nbt.getInteger("kernelMemory")
timeStarted = nbt.getLong("timeStarted") timeStarted = nbt.getLong("timeStarted")
cpuTime = nbt.getLong("cpuTime")
if (nbt.hasKey("message"))
message = Some(nbt.getString("message"))
// Clean up some after we're done and limit memory again. // Clean up some after we're done and limit memory again.
lua.gc(LuaState.GcAction.COLLECT, 0) lua.gc(LuaState.GcAction.COLLECT, 0)
@ -353,6 +369,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
nbt.setCompoundTag("rom", romNbt) nbt.setCompoundTag("rom", romNbt)
nbt.setInteger("kernelMemory", kernelMemory) nbt.setInteger("kernelMemory", kernelMemory)
nbt.setLong("timeStarted", timeStarted) nbt.setLong("timeStarted", timeStarted)
nbt.setLong("cpuTime", cpuTime)
if (message.isDefined)
nbt.setString("message", message.get)
} }
catch { catch {
case e: Throwable => { case e: Throwable => {
@ -417,12 +436,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
lua.getGlobal("os") lua.getGlobal("os")
// Custom os.clock() implementation returning the time the computer has // Custom os.clock() implementation returning the time the computer has
// been running, instead of the native library... // been actively running, instead of the native library...
lua.pushScalaFunction(lua => { lua.pushScalaFunction(lua => {
// World time is in ticks, and each second has 20 ticks. Since we lua.pushNumber((cpuTime + (System.nanoTime() - cpuStart)) * 10e-10)
// want os.clock() to return real seconds, though, we'll divide it
// accordingly.
lua.pushNumber((worldTime - timeStarted) / 20.0)
1 1
}) })
lua.setField(-2, "clock") lua.setField(-2, "clock")
@ -438,6 +454,16 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
}) })
lua.setField(-2, "time") lua.setField(-2, "time")
// The time the computer has been running, as opposed to the CPU time.
lua.pushScalaFunction(lua => {
// World time is in ticks, and each second has 20 ticks. Since we
// want os.uptime() to return real seconds, though, we'll divide it
// accordingly.
lua.pushNumber((worldTime - timeStarted) / 20.0)
1
})
lua.setField(-2, "uptime")
// Allow the system to read how much memory it uses and has available. // Allow the system to read how much memory it uses and has available.
lua.pushScalaFunction(lua => { lua.pushScalaFunction(lua => {
lua.pushInteger(lua.getTotalMemory - kernelMemory) lua.pushInteger(lua.getTotalMemory - kernelMemory)
@ -592,7 +618,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// underlying system (which may change across releases). Add some buffer // underlying system (which may change across releases). Add some buffer
// to avoid the init script eating up all the rest immediately. // to avoid the init script eating up all the rest immediately.
lua.gc(LuaState.GcAction.COLLECT, 0) lua.gc(LuaState.GcAction.COLLECT, 0)
kernelMemory = (lua.getTotalMemory - lua.getFreeMemory) + 2048 kernelMemory = (lua.getTotalMemory - lua.getFreeMemory) + 8 * 1024
recomputeMemory() recomputeMemory()
// Clear any left-over signals from a previous run. // Clear any left-over signals from a previous run.
@ -621,6 +647,8 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
kernelMemory = 0 kernelMemory = 0
signals.clear() signals.clear()
timeStarted = 0 timeStarted = 0
cpuTime = 0
cpuStart = 0
future = None future = None
sleepUntil = Long.MaxValue sleepUntil = Long.MaxValue
@ -628,6 +656,8 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
owner.markAsChanged() owner.markAsChanged()
}) })
// ----------------------------------------------------------------------- //
private def execute(value: Computer.State.Value) { private def execute(value: Computer.State.Value) {
assert(future.isEmpty) assert(future.isEmpty)
sleepUntil = Long.MaxValue sleepUntil = Long.MaxValue
@ -666,6 +696,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
try { try {
// 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()
val results = if (callReturn) { val results = if (callReturn) {
// If we were doing a synchronized call, continue where we left off. // If we were doing a synchronized call, continue where we left off.
assert(lua.getTop == 2) assert(lua.getTop == 2)
@ -685,6 +716,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
lua.resume(1, 1 + signal.args.length) lua.resume(1, 1 + signal.args.length)
} }
} }
cpuTime += System.nanoTime() - cpuStart
// Check if the kernel is still alive. // Check if the kernel is still alive.
stateMonitor.synchronized(if (lua.status(1) == LuaState.YIELD) { stateMonitor.synchronized(if (lua.status(1) == LuaState.YIELD) {
@ -746,9 +778,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
else { else {
// This can trigger another out of memory error if the original // This can trigger another out of memory error if the original
// error was an out of memory error. // error was an out of memory error.
OpenComputers.log.warning("Computer crashed.\n" + lua.toString(3)) // TODO remove this when we have a component that can display crash messages message = Some(lua.toString(3))
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
//owner.network.sendToAll(owner, "computer.crashed", lua.toString(3))
} }
close() close()
}) })
@ -756,16 +786,13 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
catch { catch {
case e: LuaRuntimeException => case e: LuaRuntimeException =>
OpenComputers.log.warning("Kernel crashed. This is a bug!\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat ")) OpenComputers.log.warning("Kernel crashed. This is a bug!\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat "))
message = Some("kernel panic")
close() close()
case e: LuaMemoryAllocationException => case e: LuaMemoryAllocationException =>
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages message = Some("not enough memory")
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
//owner.network.sendToAll(owner, "computer.crashed", "not enough memory")
close() close()
case e: java.lang.Error if e.getMessage == "not enough memory" => case e: java.lang.Error if e.getMessage == "not enough memory" =>
OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages message = Some("not enough memory")
// TODO get this to the world as a computer.crashed message. problem: synchronizing it.
//owner.network.sendToAll(owner, "computer.crashed", "not enough memory")
close() close()
} }
} }

View File

@ -1,10 +1,16 @@
package li.cil.oc.server.component package li.cil.oc.server.component
import li.cil.oc.api.network.{Node, Visibility, Message} import li.cil.oc.api.network.{Node, Visibility, Message}
import li.cil.oc.common.component.ScreenEnvironment
import net.minecraft.nbt.NBTTagCompound
class GraphicsCard extends Node { class GraphicsCard extends Node {
val supportedResolutions = List(List(40, 24), List(80, 24)) val supportedResolutions = List(List(40, 24), List(80, 24))
private var screen: Option[String] = None
// ----------------------------------------------------------------------- //
override def name = "gpu" override def name = "gpu"
override def visibility = Visibility.Neighbors override def visibility = Visibility.Neighbors
@ -12,32 +18,66 @@ class GraphicsCard extends Node {
override def receive(message: Message) = { override def receive(message: Message) = {
super.receive(message) super.receive(message)
message.data match { message.data match {
case Array(screen: Array[Byte], w: Double, h: Double) if message.name == "gpu.resolution=" => case Array(address: Array[Byte]) if message.name == "gpu.bind" =>
network.fold(None: Option[Array[Any]])(network => {
network.node(new String(address, "UTF-8")) match {
case None => Some(Array(Unit, "invalid address"))
case Some(node: ScreenEnvironment) =>
screen = node.address
Some(Array(true.asInstanceOf[Any]))
case _ => Some(Array(Unit, "not a screen"))
}
})
case Array() if message.name == "network.disconnect" && message.source.address == screen => screen = None; None
case Array(w: Double, h: Double) if message.name == "gpu.resolution=" =>
if (supportedResolutions.contains((w.toInt, h.toInt))) if (supportedResolutions.contains((w.toInt, h.toInt)))
trySend(new String(screen, "UTF-8"), "screen.resolution=", w.toInt, h.toInt) trySend("screen.resolution=", w.toInt, h.toInt)
else else
Some(Array(Unit, "unsupported resolution")) Some(Array(Unit, "unsupported resolution"))
case Array(screen: Array[Byte]) if message.name == "gpu.resolution" => case Array() if message.name == "gpu.resolution" => trySend("screen.resolution")
trySend(new String(screen, "UTF-8"), "screen.resolution") case Array() if message.name == "gpu.resolutions" => trySend("screen.resolutions") match {
case Array(screen: Array[Byte]) if message.name == "gpu.resolutions" => case Some(Array(resolutions@_*)) =>
trySend(new String(screen, "UTF-8"), "screen.resolutions") match { Some(Array(supportedResolutions.intersect(resolutions): _*))
case Some(Array(resolutions@_*)) => case _ => None
Some(Array(supportedResolutions.intersect(resolutions): _*)) }
case _ => None case Array(x: Double, y: Double, value: Array[Byte]) if message.name == "gpu.set" =>
} trySend("screen.set", x.toInt - 1, y.toInt - 1, new String(value, "UTF-8"))
case Array(screen: Array[Byte], x: Double, y: Double, value: Array[Byte]) if message.name == "gpu.set" => case Array(x: Double, y: Double, w: Double, h: Double, value: Array[Byte]) if message.name == "gpu.fill" =>
trySend(new String(screen, "UTF-8"), "screen.set", x.toInt - 1, y.toInt - 1, new String(value, "UTF-8"))
case Array(screen: Array[Byte], x: Double, y: Double, w: Double, h: Double, value: Array[Byte]) if message.name == "gpu.fill" =>
val s = new String(value, "UTF-8") val s = new String(value, "UTF-8")
if (s.length == 1) if (s.length == 1)
trySend(new String(screen, "UTF-8"), "screen.fill", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, s.charAt(0)) trySend("screen.fill", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, s.charAt(0))
else else
Some(Array(Unit, "invalid fill value")) Some(Array(Unit, "invalid fill value"))
case Array(screen: Array[Byte], x: Double, y: Double, w: Double, h: Double, tx: Double, ty: Double) if message.name == "gpu.copy" => case Array(x: Double, y: Double, w: Double, h: Double, tx: Double, ty: Double) if message.name == "gpu.copy" =>
trySend(new String(screen, "UTF-8"), "screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt) trySend("screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt)
case _ => None case _ => None
} }
} }
private def trySend(target: String, name: String, data: Any*) = network.fold(None: Option[Array[Any]])(net => net.sendToAddress(this, target, name, data: _*)) override protected def onDisconnect() = {
super.onDisconnect()
screen = None
}
override def load(nbt: NBTTagCompound) = {
super.load(nbt)
if (nbt.hasKey("screen"))
screen = Some(nbt.getString("screen"))
}
override def save(nbt: NBTTagCompound) = {
super.save(nbt)
if (screen.isDefined)
nbt.setString("screen", screen.get)
}
// ----------------------------------------------------------------------- //
private def trySend(name: String, data: Any*): Option[Array[Any]] =
screen match {
case None => Some(Array(Unit, "no screen"))
case Some(screenAddress) => network.fold(None: Option[Array[Any]])(net => {
net.sendToAddress(this, screenAddress, name, data: _*)
})
}
} }

View File

@ -26,6 +26,8 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
addressedNodes.values.foreach(_.data.network = Some(this)) addressedNodes.values.foreach(_.data.network = Some(this))
unaddressedNodes.foreach(_.data.network = Some(this)) unaddressedNodes.foreach(_.data.network = Some(this))
// ----------------------------------------------------------------------- //
override def connect(nodeA: api.network.Node, nodeB: api.network.Node) = { override def connect(nodeA: api.network.Node, nodeB: api.network.Node) = {
val containsA = contains(nodeA) val containsA = contains(nodeA)
val containsB = contains(nodeB) val containsB = contains(nodeB)
@ -104,6 +106,8 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
} }
} }
// ----------------------------------------------------------------------- //
override def node(address: String) = addressedNodes.get(address) match { override def node(address: String) = addressedNodes.get(address) match {
case Some(node) => Some(node.data) case Some(node) => Some(node.data)
case _ => None case _ => None
@ -130,6 +134,8 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
} }
} }
// ----------------------------------------------------------------------- //
override def sendToAddress(source: api.network.Node, target: String, name: String, data: Any*) = { override def sendToAddress(source: api.network.Node, target: String, name: String, data: Any*) = {
if (source.network.isEmpty || source.network.get != this) if (source.network.isEmpty || source.network.get != this)
throw new IllegalArgumentException("Source node must be in this network.") throw new IllegalArgumentException("Source node must be in this network.")
@ -157,6 +163,8 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
else None else None
} }
// ----------------------------------------------------------------------- //
private def contains(node: api.network.Node) = (node.address match { private def contains(node: api.network.Node) = (node.address match {
case None => unaddressedNodes.find(_.data == node) case None => unaddressedNodes.find(_.data == node)
case Some(address) => addressedNodes.get(address) case Some(address) => addressedNodes.get(address)
@ -306,6 +314,18 @@ object Network extends api.detail.NetworkAPI {
} }
} }
private def getNetworkNode(world: IBlockAccess, x: Int, y: Int, z: Int): Option[TileEntity with api.network.Node] =
Option(Block.blocksList(world.getBlockId(x, y, z))) match {
case Some(block) if block.hasTileEntity(world.getBlockMetadata(x, y, z)) =>
world.getBlockTileEntity(x, y, z) match {
case tileEntity: TileEntity with api.network.Node => Some(tileEntity)
case _ => None
}
case _ => None
}
// ----------------------------------------------------------------------- //
@ForgeSubscribe @ForgeSubscribe
def onChunkUnload(e: ChunkEvent.Unload) = def onChunkUnload(e: ChunkEvent.Unload) =
onUnload(e.world, e.getChunk.chunkTileEntityMap.values.asScala.map(_.asInstanceOf[TileEntity])) onUnload(e.world, e.getChunk.chunkTileEntityMap.values.asScala.map(_.asInstanceOf[TileEntity]))
@ -326,15 +346,7 @@ object Network extends api.detail.NetworkAPI {
tileEntities.foreach(t => joinOrCreateNetwork(w, t.xCoord, t.yCoord, t.zCoord)) tileEntities.foreach(t => joinOrCreateNetwork(w, t.xCoord, t.yCoord, t.zCoord))
} }
private def getNetworkNode(world: IBlockAccess, x: Int, y: Int, z: Int): Option[TileEntity with api.network.Node] = // ----------------------------------------------------------------------- //
Option(Block.blocksList(world.getBlockId(x, y, z))) match {
case Some(block) if block.hasTileEntity(world.getBlockMetadata(x, y, z)) =>
world.getBlockTileEntity(x, y, z) match {
case tileEntity: TileEntity with api.network.Node => Some(tileEntity)
case _ => None
}
case _ => None
}
private class Node(val data: api.network.Node) { private class Node(val data: api.network.Node) {
val edges = ArrayBuffer.empty[Edge] val edges = ArrayBuffer.empty[Edge]
@ -382,6 +394,8 @@ object Network extends api.detail.NetworkAPI {
}) filter (_.nonEmpty) map (_.get) }) filter (_.nonEmpty) map (_.get)
} }
// ----------------------------------------------------------------------- //
private class Message(@BeanProperty val source: api.network.Node, private class Message(@BeanProperty val source: api.network.Node,
@BeanProperty val name: String, @BeanProperty val name: String,
@BeanProperty val data: Array[Any] = Array()) extends api.network.Message { @BeanProperty val data: Array[Any] = Array()) extends api.network.Message {