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)
local program, reason = loadfile(filename)
if not program then
return nil, reason
return error(reason)
end
return program()
end

View File

@ -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
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

View File

@ -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
-------------------------------------------------------------------------------

View File

@ -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

View File

@ -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

View File

@ -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)

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 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()
}
}

View File

@ -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: _*)
})
}
}

View File

@ -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 {