From d363d0322ddb3ea71c857de9fbeef9e857693d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sun, 6 Oct 2013 18:16:03 +0200 Subject: [PATCH] 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 --- .../opencomputers/lua/drivers/filesystem.lua | 2 +- assets/opencomputers/lua/drivers/gpu.lua | 94 +++++++++--------- assets/opencomputers/lua/kernel.lua | 23 ++++- assets/opencomputers/lua/rom/api/event.lua | 10 +- assets/opencomputers/lua/rom/api/term.lua | 98 ++++++++++--------- assets/opencomputers/lua/rom/bin/sh.lua | 8 +- li/cil/oc/server/component/Computer.scala | 75 +++++++++----- li/cil/oc/server/component/GraphicsCard.scala | 74 ++++++++++---- li/cil/oc/server/network/Network.scala | 32 ++++-- 9 files changed, 262 insertions(+), 154 deletions(-) diff --git a/assets/opencomputers/lua/drivers/filesystem.lua b/assets/opencomputers/lua/drivers/filesystem.lua index 7bdf7f708..b7e87abd6 100644 --- a/assets/opencomputers/lua/drivers/filesystem.lua +++ b/assets/opencomputers/lua/drivers/filesystem.lua @@ -590,7 +590,7 @@ end function dofile(filename) local program, reason = loadfile(filename) if not program then - return nil, reason + return error(reason) end return program() end diff --git a/assets/opencomputers/lua/drivers/gpu.lua b/assets/opencomputers/lua/drivers/gpu.lua index c3c17a3b0..5bfcfd7fe 100644 --- a/assets/opencomputers/lua/drivers/gpu.lua +++ b/assets/opencomputers/lua/drivers/gpu.lua @@ -1,48 +1,52 @@ 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) - return { - setResolution = function(w, h) - driver.gpu.setResolution(gpu, screen, w, h) - end, - getResolution = function() - return driver.gpu.getResolution(gpu, screen) - end, - getResolutions = function() - return driver.gpu.getResolutions(gpu, screen) - end, - set = function(col, row, value) - driver.gpu.set(gpu, screen, col, row, value) - end, - fill = function(col, ro, w, h, value) - driver.gpu.fill(gpu, screen, col, ro, w, h, value) - end, - copy = function(col, row, w, h, tx, ty) - driver.gpu.copy(gpu, screen, col, row, w, h, tx, ty) - end - } -end \ No newline at end of file + checkArg(1, gpu, "string") + checkArg(2, screen, "string") + return send(gpu, "gpu.bind", screen) +end + +function driver.gpu.resolution(gpu, w, h) + checkArg(1, gpu, "string") + if w and h then + checkArg(2, w, "number") + checkArg(3, h, "number") + return send(gpu, "gpu.resolution=", w, h) + else + return send(gpu, "gpu.resolution") + end +end + +function driver.gpu.resolutions(gpu) + checkArg(1, gpu, "string") + return send(gpu, "gpu.resolutions") +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 diff --git a/assets/opencomputers/lua/kernel.lua b/assets/opencomputers/lua/kernel.lua index f959db31c..81efdceaf 100644 --- a/assets/opencomputers/lua/kernel.lua +++ b/assets/opencomputers/lua/kernel.lua @@ -91,6 +91,7 @@ local sandbox = { date = os.date, difftime = os.difftime, time = os.time, + uptime = os.uptime, freeMemory = os.freeMemory, totalMemory = os.totalMemory, address = os.address, @@ -138,7 +139,7 @@ function sandbox.checkArg(n, have, ...) end end local msg = "bad argument #" .. n .. " (" .. table.concat({...}, " or ") .. " expected, got " .. have .. ")" - error(debug.traceback(msg, 3), 2) + error(debug.traceback(msg, 2), 2) end ------------------------------------------------------------------------------- @@ -204,6 +205,18 @@ function sandbox.coroutine.yield(...) return coroutine.yield(nil, ...) 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() @@ -215,13 +228,13 @@ function sandbox.os.reboot() end function sandbox.os.signal(name, timeout) - local waitUntil = os.clock() + (type(timeout) == "number" and timeout or math.huge) - while os.clock() < waitUntil do - local signal = table.pack(coroutine.yield(waitUntil - os.clock())) + local waitUntil = os.uptime() + (type(timeout) == "number" and timeout or math.huge) + repeat + local signal = table.pack(coroutine.yield(waitUntil - os.uptime())) if signal.n > 0 and (name == signal[1] or name == nil) then return table.unpack(signal, 1, signal.n) end - end + until os.uptime() >= waitUntil end ------------------------------------------------------------------------------- diff --git a/assets/opencomputers/lua/rom/api/event.lua b/assets/opencomputers/lua/rom/api/event.lua index 13b9b7f9a..16d438fb2 100644 --- a/assets/opencomputers/lua/rom/api/event.lua +++ b/assets/opencomputers/lua/rom/api/event.lua @@ -55,7 +55,7 @@ function event.fire(name, ...) end local elapsed = {} for id, info in pairs(timers) do - if info.after < os.clock() then + if info.after < os.uptime() then table.insert(elapsed, info.callback) timers[id] = nil end @@ -70,7 +70,7 @@ end function event.timer(timeout, callback) local id = #timers + 1 - timers[id] = {after = os.clock() + timeout, callback = callback} + timers[id] = {after = os.uptime() + timeout, callback = callback} return id end @@ -98,7 +98,7 @@ end function coroutine.sleep(seconds) seconds = seconds or math.huge checkArg(1, seconds, "number") - local target = os.clock() + seconds + local target = os.uptime() + seconds repeat local closest = target for _, info in pairs(timers) do @@ -106,6 +106,6 @@ function coroutine.sleep(seconds) closest = info.after end end - event.fire(os.signal(nil, closest - os.clock())) - until os.clock() >= target + event.fire(os.signal(nil, closest - os.uptime())) + until os.uptime() >= target end diff --git a/assets/opencomputers/lua/rom/api/term.lua b/assets/opencomputers/lua/rom/api/term.lua index cbf2a8a86..51344b5f9 100644 --- a/assets/opencomputers/lua/rom/api/term.lua +++ b/assets/opencomputers/lua/rom/api/term.lua @@ -1,18 +1,20 @@ -local gpu = nil -local gpuAddress, screenAddress, keyboardAddress = false, false, false +local gpuAddress, screenAddress, keyboardAddress = nil, nil, nil local width, height = 0, 0 local cursorX, cursorY = 1, 1 local cursorBlink = nil -local function bindIfPossible() - if gpuAddress and screenAddress then - if not gpu then - gpu = driver.gpu.bind(gpuAddress, screenAddress) - width, height = gpu.getResolution() - event.fire("term_available") - end - elseif gpu then - gpu, width, height = nil, 0, 0 +local function rebind(gpu, screen) + if gpu == gpuAddress and screen == screenAddress then + return + end + local oldGpu, oldScreen = gpuAddress, screenAddress + gpuAddress, screenAddress = gpu, screen + if gpu and screen then + driver.gpu.bind(gpuAddress, screenAddress) + width, height = driver.gpu.resolution(gpuAddress) + event.fire("term_available") + elseif gpuAddress and screenAddress then + width, height = 0, 0 event.fire("term_unavailable") end end @@ -21,16 +23,20 @@ end term = {} +function term.available() + return gpuAddress and screenAddress +end + function term.clear() - if gpu then - gpu.fill(1, 1, width, height, " ") + if term.available() then + driver.gpu.fill(term.gpu(), 1, 1, width, height, " ") end cursorX, cursorY = 1, 1 end function term.clearLine() - if gpu then - gpu.fill(1, cursorY, width, 1, " ") + if term.available() then + driver.gpu.fill(term.gpu(), 1, cursorY, width, 1, " ") end cursorX = 1 end @@ -47,10 +53,10 @@ function term.cursorBlink(enabled) if type(enabled) == "boolean" and enabled ~= (cursorBlink ~= nil) then local function toggleBlink() cursorBlink.state = not cursorBlink.state - if gpu then + if term.available() then -- 0x2588 is a solid block. local char = cursorBlink.state and string.char(0x2588) or " " - gpu.set(cursorX, cursorY, char) + driver.gpu.set(term.gpu(), cursorX, cursorY, char) end end if enabled then @@ -67,25 +73,29 @@ function term.cursorBlink(enabled) return cursorBlink ~= nil end -function term.gpu(address) - if address ~= nil and ({boolean=true, string=true})[type(address)] then - gpuAddress = address - bindIfPossible() +function term.gpu(...) + local args = table.pack(...) + if args.n > 0 then + checkArg(1, args[1], "string", "nil") + rebind(args[1], term.screen()) end - return gpuAddress, gpu + return gpuAddress end -function term.keyboard(address) - if address ~= nil and ({boolean=true, string=true})[type(address)] then - keyboardAddress = address +function term.keyboard(...) + local args = table.pack(...) + if args.n > 0 then + checkArg(1, args[1], "string", "nil") + keyboardAddress = args[1] end return keyboardAddress end -function term.screen(address) - if address ~= nil and ({boolean=true, string=true})[type(address)] then - screenAddress = address - bindIfPossible() +function term.screen(...) + local args = table.pack(...) + if args.n > 0 then + checkArg(1, args[1], "string", "nil") + rebind(term.gpu(), args[1]) end return screenAddress end @@ -97,7 +107,7 @@ end function term.write(value, wrap) value = tostring(value) 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 end value = value:gsub("\t", " ") @@ -107,8 +117,8 @@ function term.write(value, wrap) cursorY = cursorY + 1 end if cursorY > h then - gpu.copy(1, 1, w, h, 0, -1) - gpu.fill(1, h, w, 1, " ") + driver.gpu.copy(term.gpu(), 1, 1, w, h, 0, -1) + driver.gpu.fill(term.gpu(), 1, h, w, 1, " ") cursorY = h end end @@ -116,12 +126,12 @@ function term.write(value, wrap) while wrap and line:len() > w - cursorX + 1 do local partial = line:sub(1, w - cursorX + 1) line = line:sub(partial:len() + 1) - gpu.set(cursorX, cursorY, partial) + driver.gpu.set(term.gpu(), cursorX, cursorY, partial) cursorX = cursorX + partial:len() checkCursor() end if line:len() > 0 then - gpu.set(cursorX, cursorY, line) + driver.gpu.set(term.gpu(), cursorX, cursorY, line) cursorX = cursorX + line:len() end if nl:len() == 1 then @@ -136,34 +146,34 @@ end event.listen("component_added", function(_, address) local type = component.type(address) - if type == "gpu" and not gpuAddress then + if type == "gpu" and not term.gpu() then term.gpu(address) - elseif type == "screen" and not screenAddress then + elseif type == "screen" and not term.screen() then term.screen(address) - elseif type == "keyboard" and not keyboardAddress then + elseif type == "keyboard" and not term.keyboard() then term.keyboard(address) end end) event.listen("component_removed", function(_, address) - if gpuAddress == address then - term.gpu(false) + if term.gpu() == address then + term.gpu(nil) for address in component.list() do if component.type(address) == "gpu" then term.gpu(address) return end end - elseif screenAddress == address then - term.screen(false) + elseif term.screen() == address then + term.screen(nil) for address in component.list() do if component.type(address) == "screen" then term.screen(address) return end end - elseif keyboardAddress == address then - term.keyboard(false) + elseif term.keyboard() == address then + term.keyboard(nil) for address in component.list() do if component.type(address) == "keyboard" then term.keyboard(address) @@ -174,7 +184,7 @@ event.listen("component_removed", function(_, address) end) event.listen("screen_resized", function(_, address, w, h) - if address == screenAddress then + if term.screen() == address then width = w height = h end diff --git a/assets/opencomputers/lua/rom/bin/sh.lua b/assets/opencomputers/lua/rom/bin/sh.lua index 2208d6db8..1d2cf9120 100644 --- a/assets/opencomputers/lua/rom/bin/sh.lua +++ b/assets/opencomputers/lua/rom/bin/sh.lua @@ -6,15 +6,15 @@ local isRunning = false local function onKeyDown(_, address, char, code) if isRunning then return end -- ignore events while running a command if address ~= term.keyboard() then return end - local _, gpu = term.gpu() - if not gpu then return end + if not term.available() then return end local x, y = term.cursor() + local w, h = term.size() local keys = driver.keyboard.keys if code == keys.back then if command:len() == 0 then return end command = command:sub(1, -2) 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 if command:len() == 0 then return end term.cursorBlink(false) @@ -39,7 +39,7 @@ local function onKeyDown(_, address, char, code) term.cursorBlink(true) elseif code == keys.up then command = lastCommand - gpu.fill(3, y, screenWidth, 1, " ") + driver.gpu.fill(term.gpu(), 3, y, w, 1, " ") term.cursor(3, y) term.write(command) term.cursor(command:len() + 3, y) diff --git a/li/cil/oc/server/component/Computer.scala b/li/cil/oc/server/component/Computer.scala index ba232ac55..656942035 100644 --- a/li/cil/oc/server/component/Computer.scala +++ b/li/cil/oc/server/component/Computer.scala @@ -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 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 message: Option[String] = None // For error messages. + // ----------------------------------------------------------------------- // def recomputeMemory() = if (lua != null) @@ -154,10 +160,19 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl worldTime = owner.world.getWorldInfo.getWorldTotalTime // 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(_.sendToNeighbors(owner, "gpu.fill", 1.0, 1.0, Double.PositiveInfinity, Double.PositiveInfinity, " ".getBytes("UTF-8"))) + } 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. stateMonitor.synchronized(state match { // 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 // exception to a string (which we have to do to avoid keeping // 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 - //owner.network.foreach(_.sendToVisible(owner, "computer.crashed", "not enough memory")) + message = Some("not enough memory") close() 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 - // TODO get this to the world as a computer.crashed message. problem: synchronizing it. - //owner.network.foreach(_.sendToVisible(owner, "computer.crashed", "not enough memory")) + message = Some("not enough memory") close() case e: Throwable => { OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e) + message = Some("protocol error") close() } } @@ -273,6 +286,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl rom.foreach(_.load(nbt.getCompoundTag("rom"))) kernelMemory = nbt.getInteger("kernelMemory") 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. 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.setInteger("kernelMemory", kernelMemory) nbt.setLong("timeStarted", timeStarted) + nbt.setLong("cpuTime", cpuTime) + if (message.isDefined) + nbt.setString("message", message.get) } catch { case e: Throwable => { @@ -417,12 +436,9 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl lua.getGlobal("os") // 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 => { - // World time is in ticks, and each second has 20 ticks. Since we - // want os.clock() to return real seconds, though, we'll divide it - // accordingly. - lua.pushNumber((worldTime - timeStarted) / 20.0) + lua.pushNumber((cpuTime + (System.nanoTime() - cpuStart)) * 10e-10) 1 }) lua.setField(-2, "clock") @@ -438,6 +454,16 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl }) 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. lua.pushScalaFunction(lua => { 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 // to avoid the init script eating up all the rest immediately. lua.gc(LuaState.GcAction.COLLECT, 0) - kernelMemory = (lua.getTotalMemory - lua.getFreeMemory) + 2048 + kernelMemory = (lua.getTotalMemory - lua.getFreeMemory) + 8 * 1024 recomputeMemory() // 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 signals.clear() timeStarted = 0 + cpuTime = 0 + cpuStart = 0 future = None sleepUntil = Long.MaxValue @@ -628,6 +656,8 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl owner.markAsChanged() }) + // ----------------------------------------------------------------------- // + private def execute(value: Computer.State.Value) { assert(future.isEmpty) sleepUntil = Long.MaxValue @@ -666,6 +696,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl try { // Resume the Lua state and remember the number of results we get. + cpuStart = System.nanoTime() val results = if (callReturn) { // If we were doing a synchronized call, continue where we left off. 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) } } + cpuTime += System.nanoTime() - cpuStart // Check if the kernel is still alive. stateMonitor.synchronized(if (lua.status(1) == LuaState.YIELD) { @@ -746,9 +778,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl else { // This can trigger another out of memory error if the original // 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 - // TODO get this to the world as a computer.crashed message. problem: synchronizing it. - //owner.network.sendToAll(owner, "computer.crashed", lua.toString(3)) + message = Some(lua.toString(3)) } close() }) @@ -756,16 +786,13 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl catch { case e: LuaRuntimeException => OpenComputers.log.warning("Kernel crashed. This is a bug!\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat ")) + message = Some("kernel panic") close() case e: LuaMemoryAllocationException => - OpenComputers.log.warning("Out of memory!") // TODO remove this when we have a component that can display crash messages - // TODO get this to the world as a computer.crashed message. problem: synchronizing it. - //owner.network.sendToAll(owner, "computer.crashed", "not enough memory") + message = Some("not enough memory") close() 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 - // TODO get this to the world as a computer.crashed message. problem: synchronizing it. - //owner.network.sendToAll(owner, "computer.crashed", "not enough memory") + message = Some("not enough memory") close() } } diff --git a/li/cil/oc/server/component/GraphicsCard.scala b/li/cil/oc/server/component/GraphicsCard.scala index 90957c414..246a6d82e 100644 --- a/li/cil/oc/server/component/GraphicsCard.scala +++ b/li/cil/oc/server/component/GraphicsCard.scala @@ -1,10 +1,16 @@ package li.cil.oc.server.component 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 { val supportedResolutions = List(List(40, 24), List(80, 24)) + private var screen: Option[String] = None + + // ----------------------------------------------------------------------- // + override def name = "gpu" override def visibility = Visibility.Neighbors @@ -12,32 +18,66 @@ class GraphicsCard extends Node { override def receive(message: Message) = { super.receive(message) 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))) - trySend(new String(screen, "UTF-8"), "screen.resolution=", w.toInt, h.toInt) + trySend("screen.resolution=", w.toInt, h.toInt) else Some(Array(Unit, "unsupported resolution")) - case Array(screen: Array[Byte]) if message.name == "gpu.resolution" => - trySend(new String(screen, "UTF-8"), "screen.resolution") - case Array(screen: Array[Byte]) if message.name == "gpu.resolutions" => - trySend(new String(screen, "UTF-8"), "screen.resolutions") match { - case Some(Array(resolutions@_*)) => - Some(Array(supportedResolutions.intersect(resolutions): _*)) - case _ => None - } - case Array(screen: Array[Byte], x: Double, y: Double, value: Array[Byte]) if message.name == "gpu.set" => - 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" => + case Array() if message.name == "gpu.resolution" => trySend("screen.resolution") + case Array() if message.name == "gpu.resolutions" => trySend("screen.resolutions") match { + case Some(Array(resolutions@_*)) => + 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(x: Double, y: Double, w: Double, h: Double, value: Array[Byte]) if message.name == "gpu.fill" => val s = new String(value, "UTF-8") 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 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" => - trySend(new String(screen, "UTF-8"), "screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt) + case Array(x: Double, y: Double, w: Double, h: Double, tx: Double, ty: Double) if message.name == "gpu.copy" => + trySend("screen.copy", x.toInt - 1, y.toInt - 1, w.toInt, h.toInt, tx.toInt, ty.toInt) 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: _*) + }) + } } \ No newline at end of file diff --git a/li/cil/oc/server/network/Network.scala b/li/cil/oc/server/network/Network.scala index d836b4c32..7f9309e3b 100644 --- a/li/cil/oc/server/network/Network.scala +++ b/li/cil/oc/server/network/Network.scala @@ -26,6 +26,8 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No addressedNodes.values.foreach(_.data.network = Some(this)) unaddressedNodes.foreach(_.data.network = Some(this)) + // ----------------------------------------------------------------------- // + override def connect(nodeA: api.network.Node, nodeB: api.network.Node) = { val containsA = contains(nodeA) 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 { case Some(node) => Some(node.data) 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*) = { if (source.network.isEmpty || source.network.get != this) 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 } + // ----------------------------------------------------------------------- // + private def contains(node: api.network.Node) = (node.address match { case None => unaddressedNodes.find(_.data == node) 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 def onChunkUnload(e: ChunkEvent.Unload) = 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)) } - 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) { val edges = ArrayBuffer.empty[Edge] @@ -382,6 +394,8 @@ object Network extends api.detail.NetworkAPI { }) filter (_.nonEmpty) map (_.get) } + // ----------------------------------------------------------------------- // + private class Message(@BeanProperty val source: api.network.Node, @BeanProperty val name: String, @BeanProperty val data: Array[Any] = Array()) extends api.network.Message {