metric ton of fixes. somehow the number of touches files feels too low... anyway, also added option to LuaCallback annotation to mark callbacks as asynchronous (meaning they will be called directly from the executor thread) and synchronized the sh*t out of the network manager in the hopes that not all will crumble to pieces due to sending messages around while someone meddles with the network (program calls sth while player places/breaks block e.g.); oh, and we now take the memory baseline after loading the libs, meaning 32k is enough to run a computer again. also, loading libs is done asynchronously now, since it's loaded from the rom and nobody else should be able to access that anyway, meaning fast boot-times; command line program to manipulate redstone output and read input

This commit is contained in:
Florian Nücke 2013-10-27 15:06:21 +01:00
parent a7ec5c0ec0
commit 78a426c4fe
14 changed files with 361 additions and 218 deletions

View File

@ -52,48 +52,4 @@ local function flattenAndStore(k, v)
end end
-- Mark everything that's globally reachable at this point as permanent. -- Mark everything that's globally reachable at this point as permanent.
flattenAndStore("_ENV", _ENV) flattenAndStore("_ENV", _ENV)
--[[ Wrap message sending callbacks to synchronize them to the server thread.
We generate a wrapper that will yield a closure that will perform the
actual call. The host will switch to messaging state and perform the
actual in the server thread (by running the yielded function), meaning
we don't have to worry about mutlithreading interaction with other
components of Minecraft.
--]]
-- OK, I admit this is a little crazy... here goes:
local function wrap(f)
assert(f)
-- This is the function that replaces the original API function. It is
-- called from userland when it wants something from a driver.
return function(...)
local args = table.pack(...)
-- What it does, is that it yields a function. That function is called
-- from the server thread, to ensure synchronicity with the world.
local result = coroutine.yield(function()
-- It runs the actual API function protected mode. We return this as
-- a table because a) we need it like this on the outside anyway and
-- b) only the first item in the global stack is persisted.
local result = table.pack(pcall(f, table.unpack(args, 1, args.n)))
if not result[1] then
-- We apply tostring to error messages immediately because JNLua
-- pushes the original Java exceptions which cannot be persisted.
result[2] = tostring(result[2])
end
return result
end)
-- The next time our executor runs it pushes that result and calls
-- resume, so we get it via the yield. Thus: result = pcall(f, ...)
if result[1] then
-- API call was successful, return the results.
return table.unpack(result, 2, result.n)
else
-- API call failed, re-throw the error.
error(result[2], 2)
end
end
end
componentMethodsSynchronized = wrap(componentMethods)
componentInvokeSynchronized = wrap(componentInvoke)

View File

@ -236,36 +236,107 @@ end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
local component = {}
component.list = componentList
component.type = componentType
-- We use a custom "protocol" where we return a boolean first, indicating
-- whether we had an error that should be re-thrown.
function component.invokeAsync(...)
local result = table.pack(componentInvoke(...))
if not result[1] then
-- We caught an error but performed some custom tostring logic.
error(result[2], 2)
else
-- We should return the results as they are.
return table.unpack(result, 2, result.n)
end
end
-------------------------------------------------------------------------------
--[[ Wrap message sending callbacks to synchronize them to the server thread.
We generate a wrapper that will yield a closure that will perform the
actual call. The host will switch to messaging state and perform the
actual in the server thread (by running the yielded function), meaning
we don't have to worry about mutlithreading interaction with other
components of Minecraft.
--]]
-- OK, I admit this is a little crazy... here goes:
local function synchronized(f)
-- This is the function that replaces the original API function. It is
-- called from userland when it wants something from a driver.
return function(...)
local args = table.pack(...)
-- What it does, is that it yields a function. That function is called
-- from the server thread, to ensure synchronicity with the world.
local result = coroutine.yield(function()
-- It runs the actual API function protected mode. We return this as
-- a table because a) we need it like this on the outside anyway and
-- b) only the first item in the global stack is persisted.
local result = table.pack(pcall(f, table.unpack(args, 1, args.n)))
if not result[1] then
-- We apply tostring to error messages immediately because JNLua
-- pushes the original Java exceptions which cannot be persisted.
result[2] = tostring(result[2])
end
return result
end)
-- The next time our executor runs it pushes that result and calls
-- resume, so we get it via the yield. Thus: result = pcall(f, ...)
if result[1] then
-- API call was successful, return the results.
return table.unpack(result, 2, result.n)
else
-- API call failed, re-throw the error.
error(result[2], 2)
end
end
end
component.methods = synchronized(componentMethods)
component.invoke = synchronized(component.invokeAsync)
-------------------------------------------------------------------------------
sandbox.component = {} sandbox.component = {}
function sandbox.component.list(filter) function sandbox.component.list(filter)
checkArg(1, filter, "string", "nil") checkArg(1, filter, "string", "nil")
return pairs(componentList(filter)) return pairs(component.list(filter))
end end
function sandbox.component.type(address) function sandbox.component.type(address)
checkArg(1, address, "string") checkArg(1, address, "string")
return componentType(address) return component.type(address)
end end
function sandbox.component.invoke(address, method, ...) function sandbox.component.invoke(address, method, ...)
checkArg(1, address, "string") checkArg(1, address, "string")
checkArg(2, method, "string") checkArg(2, method, "string")
return componentInvokeSynchronized(address, method, ...) return component.invoke(address, method, ...)
end end
function sandbox.component.proxy(address) function sandbox.component.proxy(address)
checkArg(1, address, "string") checkArg(1, address, "string")
local type, reason = componentType(address) local type, reason = component.type(address)
if not type then if not type then
return nil, reason return nil, reason
end end
local proxy = {address = address, type = type} local proxy = {address = address, type = type}
local methods = componentMethodsSynchronized(address) local methods = component.methods(address)
if methods then if methods then
for _, method in ipairs(methods) do for method, asynchronous in pairs(methods) do
local invoke
if asynchronous then
invoke = component.invokeAsync
else
invoke = component.invoke
end
proxy[method] = function(...) proxy[method] = function(...)
return componentInvokeSynchronized(address, method, ...) return invoke(address, method, ...)
end end
end end
end end
@ -275,40 +346,14 @@ end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
local function main() local function main()
local args
local function bootstrap() local function bootstrap()
-- Prepare low-level print logic to give boot progress feedback.
-- local gpus = gpus()
-- for i = 1, #gpus do
-- local gpu = gpus[i]
-- gpus[i] = nil
-- local w, h = sandbox.driver.gpu.resolution(gpu)
-- if w then
-- if sandbox.driver.gpu.fill(gpu, 1, 1, w, h, " ") then
-- gpus[i] = {gpu, w, h}
-- end
-- end
-- end
-- local l = 0
-- local function print(s)
-- l = l + 1
-- for _, gpu in pairs(gpus) do
-- if l > gpu[3] then
-- sandbox.driver.gpu.copy(gpu[1], 1, 1, gpu[2], gpu[3], 0, -1)
-- sandbox.driver.gpu.fill(gpu[1], 1, gpu[3], gpu[2], 1, " ")
-- end
-- sandbox.driver.gpu.set(gpu[1], 1, math.min(l, gpu[3]), s)
-- end
-- end
print("Booting...")
print("Mounting ROM and temporary file system.")
local function rom(method, ...) local function rom(method, ...)
return componentInvoke(os.romAddress(), method, ...) return component.invokeAsync(os.romAddress(), method, ...)
end end
-- Custom dofile implementation since we don't have the baselib yet. -- Custom dofile implementation since we don't have the baselib yet.
local function dofile(file) local function dofile(file)
print(" " .. file)
local handle, reason = rom("open", file) local handle, reason = rom("open", file)
if not handle then if not handle then
error(reason) error(reason)
@ -337,7 +382,6 @@ local function main()
end end
end end
print("Loading libraries.")
local init = {} local init = {}
for _, api in ipairs(rom("list", "lib")) do for _, api in ipairs(rom("list", "lib")) do
local path = "lib/" .. api local path = "lib/" .. api
@ -349,32 +393,29 @@ local function main()
end end
end end
print("Initializing libraries.")
for _, install in ipairs(init) do for _, install in ipairs(init) do
install() install()
end end
init = nil init = nil
print("Starting shell.") -- Yield once to allow initializing up to here to get a memory baseline.
args = table.pack(coroutine.yield(0))
return coroutine.create(load([[ return coroutine.create(load([[
fs.mount(os.romAddress(), "/") fs.mount(os.romAddress(), "/")
fs.mount(os.tmpAddress(), "/tmp") fs.mount(os.tmpAddress(), "/tmp")
for c, t in component.list() do for c, t in component.list() do
event.fire("component_added", c, t) event.fire("component_added", c, t)
end end
term.clear()
while true do while true do
local result, reason = os.execute("/bin/sh") local result, reason = os.execute("/bin/sh -v")
if not result then if not result then
error(reason) error(reason)
end end
end]], "=init", "t", sandbox)) end]], "=init", "t", sandbox))
end end
local co = bootstrap() local co = bootstrap()
-- Yield once to allow initializing up to here to get a memory baseline.
assert(coroutine.yield() == "dummy")
local args = {n=0}
while true do while true do
deadline = os.realTime() + timeout -- timeout global is set by host deadline = os.realTime() + timeout -- timeout global is set by host
if not debug.gethook(co) then if not debug.gethook(co) then

View File

@ -0,0 +1,26 @@
local args = shell.parse(...)
if #args < 1 then
print("Usage: redstone <side> [<value>]")
return
end
local side = rs.sides[args[1]]
if not side then
print("Invalid side.")
end
if type(side) == "string" then
side = rs.sides[side]
end
if #args > 1 then
local value = args[2]
if tonumber(value) then
value = tonumber(value)
else
value = ({["true"]=true,["on"]=true,["yes"]=true})[value] and 15 or 0
end
component.primary("redstone").setOutput(side, value)
else
print("in: " .. component.primary("redstone").getInput(side))
print("out: " .. component.primary("redstone").getOutput(side))
end

View File

@ -1,11 +1,19 @@
local args, options = shell.parse(...)
local history = {} local history = {}
if options.v then
print("OpenOS v1.0 (" .. math.floor(os.totalMemory() / 1024) .. "k RAM)")
end
while true do while true do
if not term.isAvailable() then -- don't clear when opened by another shell if not term.isAvailable() then -- don't clear unless we lost the term
while not term.isAvailable() do while not term.isAvailable() do
os.sleep() os.sleep()
end end
term.clear() term.clear()
print("OpenOS v1.0 (" .. math.floor(os.totalMemory() / 1024) .. "k RAM)") if options.v then
print("OpenOS v1.0 (" .. math.floor(os.totalMemory() / 1024) .. "k RAM)")
end
end end
while term.isAvailable() do while term.isAvailable() do
term.write("# ") term.write("# ")

View File

@ -2,9 +2,10 @@ local function onComponentAvailable(_, componentType)
if (componentType == "screen" and component.isAvailable("gpu")) or if (componentType == "screen" and component.isAvailable("gpu")) or
(componentType == "gpu" and component.isAvailable("screen")) (componentType == "gpu" and component.isAvailable("screen"))
then then
component.primary("gpu").bind(component.primary("screen").address) local gpu = component.primary("gpu")
gpu.bind(component.primary("screen").address)
local maxX, maxY = gpu.maxResolution() local maxX, maxY = gpu.maxResolution()
component.primary("gpu").setResolution(maxX, maxY) gpu.setResolution(maxX, maxY)
end end
end end

View File

@ -1,7 +1,7 @@
local cwd = "/" local cwd = "/"
local path = {"/bin/", "/usr/bin/", "/home/bin/"} local path = {"/bin/", "/usr/bin/", "/home/bin/"}
local aliases = {dir="ls", move="mv", rename="mv", copy="cp", del="rm", local aliases = {dir="ls", move="mv", rename="mv", copy="cp", del="rm",
md="mkdir", cls="clear", more="less"} md="mkdir", cls="clear", more="less", rs="redstone"}
local function findFile(name, path, ext) local function findFile(name, path, ext)
checkArg(1, name, "string") checkArg(1, name, "string")

View File

@ -21,4 +21,15 @@ public @interface LuaCallback {
* @return the name of the function. * @return the name of the function.
*/ */
String value() default ""; String value() default "";
/**
* Whether this function may be called asynchronously, i.e. directly from
* the computer's executor thread.
* <p/>
* You will have to ensure anything your callback does is thread safe when
* setting this to <tt>true</tt>. Use this for minor lookups, for example.
* This is mainly intended to allow functions to perform faster than when
* called synchronously (where the call takes at least one server tick).
*/
boolean asynchronous() default false;
} }

View File

@ -50,6 +50,7 @@ trait ComponentInventory extends Inventory with Environment with Persistable {
case Some(driver) => case Some(driver) =>
Option(driver.createEnvironment(stack)) match { Option(driver.createEnvironment(stack)) match {
case Some(environment) => case Some(environment) =>
environment.node.load(driver.nbt(stack))
environment.load(driver.nbt(stack)) environment.load(driver.nbt(stack))
Some(environment) Some(environment)
case _ => None case _ => None
@ -66,7 +67,9 @@ trait ComponentInventory extends Inventory with Environment with Persistable {
case (stack, slot) => components(slot) match { case (stack, slot) => components(slot) match {
case Some(environment) => case Some(environment) =>
// We're guaranteed to have a driver for entries. // We're guaranteed to have a driver for entries.
environment.save(Registry.driverFor(stack).get.nbt(stack)) val nbt = Registry.driverFor(stack).get.nbt(stack)
environment.save(nbt)
environment.node.save(nbt)
case _ => // Nothing special to save. case _ => // Nothing special to save.
} }
} }
@ -82,6 +85,7 @@ trait ComponentInventory extends Inventory with Environment with Persistable {
case Some(driver) => Option(driver.createEnvironment(item)) match { case Some(driver) => Option(driver.createEnvironment(item)) match {
case Some(environment) => case Some(environment) =>
components(slot) = Some(environment) components(slot) = Some(environment)
environment.node.load(driver.nbt(item))
environment.load(driver.nbt(item)) environment.load(driver.nbt(item))
node.network.connect(node, environment.node) node.network.connect(node, environment.node)
case _ => // No environment (e.g. RAM). case _ => // No environment (e.g. RAM).
@ -100,7 +104,11 @@ trait ComponentInventory extends Inventory with Environment with Persistable {
// being installed into a different computer, even!) // being installed into a different computer, even!)
components(slot) = None components(slot) = None
environment.node.network.remove(environment.node) environment.node.network.remove(environment.node)
Registry.driverFor(item).foreach(driver => environment.save(driver.nbt(item))) Registry.driverFor(item).foreach(driver => {
val nbt = driver.nbt(item)
environment.save(nbt)
environment.node.save(nbt)
})
case _ => // Nothing to do. case _ => // Nothing to do.
} }
} }

View File

@ -329,8 +329,16 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
}.toArray) }.toArray)
}) })
rom.foreach(_.load(computerNbt.getCompoundTag("rom"))) rom.foreach(rom => {
tmp.foreach(_.load(computerNbt.getCompoundTag("tmp"))) val romNbt = computerNbt.getCompoundTag("rom")
rom.node.load(romNbt)
rom.load(romNbt)
})
tmp.foreach(tmp => {
val tmpNbt = computerNbt.getCompoundTag("tmp")
tmp.node.load(tmpNbt)
tmp.load(tmpNbt)
})
kernelMemory = computerNbt.getInteger("kernelMemory") kernelMemory = computerNbt.getInteger("kernelMemory")
timeStarted = computerNbt.getLong("timeStarted") timeStarted = computerNbt.getLong("timeStarted")
cpuTime = computerNbt.getLong("cpuTime") cpuTime = computerNbt.getLong("cpuTime")
@ -417,11 +425,17 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
computerNbt.setTag("signals", signalsNbt) computerNbt.setTag("signals", signalsNbt)
val romNbt = new NBTTagCompound() val romNbt = new NBTTagCompound()
rom.foreach(_.save(romNbt)) rom.foreach(rom => {
rom.save(romNbt)
rom.node.save(romNbt)
})
computerNbt.setCompoundTag("rom", romNbt) computerNbt.setCompoundTag("rom", romNbt)
val tmpNbt = new NBTTagCompound() val tmpNbt = new NBTTagCompound()
tmp.foreach(_.save(tmpNbt)) tmp.foreach(tmp => {
tmp.save(tmpNbt)
tmp.node.save(tmpNbt)
})
computerNbt.setCompoundTag("tmp", tmpNbt) computerNbt.setCompoundTag("tmp", tmpNbt)
computerNbt.setInteger("kernelMemory", kernelMemory) computerNbt.setInteger("kernelMemory", kernelMemory)
@ -626,11 +640,16 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
lua.pushScalaFunction(lua => { lua.pushScalaFunction(lua => {
owner.node.network.sendToAddress(owner.node, lua.checkString(1), "component.methods") match { owner.node.network.sendToAddress(owner.node, lua.checkString(1), "component.methods") match {
case Array(names: Array[String]) => case Array(methods: Array[_]) =>
lua.newTable() lua.newTable()
for ((name, index) <- names.zipWithIndex) { for ((method, index) <- methods.zipWithIndex) {
lua.pushString(name) method match {
lua.rawSet(-2, index + 1) case (name: String, asynchronous: Boolean) =>
lua.pushString(name)
lua.pushBoolean(asynchronous)
lua.rawSet(-3)
case _ =>
}
} }
1 1
case _ => case _ =>
@ -704,41 +723,52 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
owner.node.network.sendToAddress(owner.node, address, "component.invoke", Seq(method) ++ args: _*) owner.node.network.sendToAddress(owner.node, address, "component.invoke", Seq(method) ++ args: _*)
case _ => throw new Exception("no such component") case _ => throw new Exception("no such component")
}) match { }) match {
case Array(results@_*) => case results: Array[_] =>
lua.pushBoolean(true)
results.foreach(pushResult(lua, _)) results.foreach(pushResult(lua, _))
results.length 1 + results.length
case _ => case _ =>
0 lua.pushBoolean(true)
1
} }
} catch { } catch {
case e: Throwable if e.getMessage != null && !e.getMessage.isEmpty =>
lua.pushNil()
lua.pushString(e.getMessage)
2
case _: FileNotFoundException =>
lua.pushNil()
lua.pushString("file not found")
2
case _: SecurityException =>
lua.pushNil()
lua.pushString("access denied")
2
case _: IOException =>
lua.pushNil()
lua.pushString("i/o error")
2
case _: NoSuchMethodException => case _: NoSuchMethodException =>
lua.pushNil() lua.pushBoolean(false)
lua.pushString("no such method") lua.pushString("no such method")
2 2
case e: IllegalArgumentException if e.getMessage != null =>
lua.pushBoolean(false)
lua.pushString(e.getMessage)
2
case _: IllegalArgumentException => case _: IllegalArgumentException =>
lua.pushNil() lua.pushBoolean(false)
lua.pushString("bad argument") lua.pushString("bad argument")
2 2
case e: Throwable if e.getMessage != null =>
lua.pushBoolean(true)
lua.pushNil()
lua.pushString(e.getMessage)
3
case _: FileNotFoundException =>
lua.pushBoolean(true)
lua.pushNil()
lua.pushString("file not found")
3
case _: SecurityException =>
lua.pushBoolean(true)
lua.pushNil()
lua.pushString("access denied")
3
case _: IOException =>
lua.pushBoolean(true)
lua.pushNil()
lua.pushString("i/o error")
3
case _: Throwable => case _: Throwable =>
lua.pushBoolean(true)
lua.pushNil() lua.pushNil()
lua.pushString("unknown error") lua.pushString("unknown error")
2 3
} }
}) })
lua.setGlobal("componentInvoke") lua.setGlobal("componentInvoke")
@ -771,23 +801,13 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
// timeouts. // timeouts.
lua.load(classOf[Computer].getResourceAsStream(Config.scriptPath + "kernel.lua"), "=kernel", "t") lua.load(classOf[Computer].getResourceAsStream(Config.scriptPath + "kernel.lua"), "=kernel", "t")
lua.newThread() // Left as the first value on the stack. lua.newThread() // Left as the first value on the stack.
// Run to the first yield in kernel, to get a good idea of how much // // Run to the first yield in kernel, to get a good idea of how much
// memory all the basic functionality we provide needs. // // memory all the basic functionality we provide needs.
val results = lua.resume(1, 0) // val results = lua.resume(1, 0)
if (lua.status(1) != LuaState.YIELD) // if (lua.status(1) != LuaState.YIELD)
if (!lua.toBoolean(-2)) throw new Exception(lua.toString(-1)) // if (!lua.toBoolean(-2)) throw new Exception(lua.toString(-1))
else throw new Exception("kernel return unexpectedly") // else throw new Exception("kernel return unexpectedly")
lua.pop(results) // lua.pop(results)
// Run the garbage collector to get rid of stuff left behind after the
// initialization phase to get a good estimate of the base memory usage
// the kernel has. We remember that size to grant user-space programs a
// fixed base amount of memory, regardless of the memory need of the
// 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) + Config.baseMemory
recomputeMemory()
// Clear any left-over signals from a previous run. // Clear any left-over signals from a previous run.
signals.clear() signals.clear()
@ -862,7 +882,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
try { try {
// Help out the GC a little. The emergency GC has a few limitations that // Help out the GC a little. The emergency GC has a few limitations that
// will make it free less memory than doing a full step manually. // will make it free less memory than doing a full step manually.
lua.gc(LuaState.GcAction.COLLECT, 0) // lua.gc(LuaState.GcAction.COLLECT, 0)
// Resume the Lua state and remember the number of results we get. // Resume the Lua state and remember the number of results we get.
cpuStart = System.nanoTime() cpuStart = System.nanoTime()
val results = if (callReturn) { val results = if (callReturn) {
@ -887,6 +907,19 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl
} }
cpuTime += System.nanoTime() - cpuStart cpuTime += System.nanoTime() - cpuStart
// Check if this was the first run, meaning the one used for initializing
// the kernel (loading the libs, establishing a memory baseline).
if (kernelMemory == 0) {
// Run the garbage collector to get rid of stuff left behind after the
// initialization phase to get a good estimate of the base memory usage
// the kernel has. We remember that size to grant user-space programs a
// fixed base amount of memory, regardless of the memory need of the
// underlying system (which may change across releases).
lua.gc(LuaState.GcAction.COLLECT, 0)
kernelMemory = (lua.getTotalMemory - lua.getFreeMemory) + Config.baseMemory
recomputeMemory()
}
// 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) {
// Intermediate state in some cases. Satisfies the assert in execute(). // Intermediate state in some cases. Satisfies the assert in execute().

View File

@ -25,7 +25,7 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent {
} }
} }
@LuaCallback("getResolution") @LuaCallback(value = "getResolution", asynchronous = true)
def getResolution(message: Message): Array[Object] = trySend("screen.resolution") def getResolution(message: Message): Array[Object] = trySend("screen.resolution")
@LuaCallback("setResolution") @LuaCallback("setResolution")
@ -39,7 +39,7 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent {
Array(Unit, "unsupported resolution") Array(Unit, "unsupported resolution")
} }
@LuaCallback("maxResolution") @LuaCallback(value = "maxResolution", asynchronous = true)
def maxResolution(message: Message): Array[Object] = def maxResolution(message: Message): Array[Object] =
trySend("screen.resolution") match { trySend("screen.resolution") match {
case Array(w: Integer, h: Integer) => case Array(w: Integer, h: Integer) =>
@ -62,7 +62,7 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent {
val y = message.checkInteger(2) val y = message.checkInteger(2)
val w = message.checkInteger(3) val w = message.checkInteger(3)
val h = message.checkInteger(4) val h = message.checkInteger(4)
val value = message.checkString(4) val value = message.checkString(5)
if (value.length == 1) if (value.length == 1)
trySend("screen.fill", x - 1, y - 1, w, h, value.charAt(0)) trySend("screen.fill", x - 1, y - 1, w, h, value.charAt(0))
else else
@ -96,13 +96,11 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent {
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override def load(nbt: NBTTagCompound) { override def load(nbt: NBTTagCompound) {
super.load(nbt)
if (nbt.hasKey("oc.gpu.screen")) if (nbt.hasKey("oc.gpu.screen"))
screen = Some(nbt.getString("oc.gpu.screen")) screen = Some(nbt.getString("oc.gpu.screen"))
} }
override def save(nbt: NBTTagCompound) { override def save(nbt: NBTTagCompound) {
super.save(nbt)
if (screen.isDefined) if (screen.isDefined)
nbt.setString("oc.gpu.screen", screen.get) nbt.setString("oc.gpu.screen", screen.get)
} }

View File

@ -8,14 +8,14 @@ import net.minecraftforge.common.ForgeDirection
class RedstoneCard extends ManagedComponent { class RedstoneCard extends ManagedComponent {
val node = api.Network.createComponent(api.Network.createNode(this, "redstone", Visibility.Neighbors)) val node = api.Network.createComponent(api.Network.createNode(this, "redstone", Visibility.Neighbors))
@LuaCallback("getInput") @LuaCallback(value = "getInput", asynchronous = true)
def getInput(message: Message): Array[Object] = { def getInput(message: Message): Array[Object] = {
val side = message.checkInteger(1) val side = message.checkInteger(1)
node.network.sendToAddress(node, message.source.address, node.network.sendToAddress(node, message.source.address,
"redstone.input", ForgeDirection.getOrientation(side)) "redstone.input", ForgeDirection.getOrientation(side))
} }
@LuaCallback("getOutput") @LuaCallback(value = "getOutput", asynchronous = true)
def getOutput(message: Message): Array[Object] = { def getOutput(message: Message): Array[Object] = {
val side = message.checkInteger(1) val side = message.checkInteger(1)
node.network.sendToAddress(node, message.source.address, node.network.sendToAddress(node, message.source.address,

View File

@ -63,11 +63,13 @@ class Component(host: Environment, name: String, reachability: Visibility) exten
override def receive(message: Message) = { override def receive(message: Message) = {
if (message.name == "component.methods") if (message.name == "component.methods")
if (canBeSeenBy(message.source)) if (canBeSeenBy(message.source))
Array(Array(luaCallbacks.keys.toSeq: _*)) Array(luaCallbacks.map {
case (method, (asynchronous, _)) => (method, asynchronous)
}.toArray)
else null else null
else if (message.name == "component.invoke") { else if (message.name == "component.invoke") {
luaCallbacks.get(message.data()(0).asInstanceOf[String]) match { luaCallbacks.get(message.data()(0).asInstanceOf[String]) match {
case Some(callback) => callback(host, message) case Some((_, callback)) => callback(host, message)
case _ => throw new NoSuchMethodException() case _ => throw new NoSuchMethodException()
} }
} }
@ -76,25 +78,25 @@ class Component(host: Environment, name: String, reachability: Visibility) exten
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
override def save(nbt: NBTTagCompound) { override def load(nbt: NBTTagCompound) {
super.load(nbt) super.load(nbt)
if (nbt.hasKey("visibility")) if (nbt.hasKey("visibility"))
visibility_ = Visibility.values()(nbt.getInteger("visibility")) visibility_ = Visibility.values()(nbt.getInteger("visibility"))
} }
override def load(nbt: NBTTagCompound) { override def save(nbt: NBTTagCompound) {
super.save(nbt) super.save(nbt)
nbt.setInteger("visibility", visibility_.ordinal()) nbt.setInteger("visibility", visibility_.ordinal())
} }
} }
object Component { object Component {
private val cache = mutable.Map.empty[Class[_], immutable.Map[String, (Object, api.network.Message) => Array[Object]]] private val cache = mutable.Map.empty[Class[_], immutable.Map[String, (Boolean, (Object, api.network.Message) => Array[Object])]]
def callbacks(clazz: Class[_]) = cache.getOrElseUpdate(clazz, analyze(clazz)) def callbacks(clazz: Class[_]) = cache.getOrElseUpdate(clazz, analyze(clazz))
private def analyze(clazz: Class[_]) = { private def analyze(clazz: Class[_]) = {
val callbacks = mutable.Map.empty[String, (Object, api.network.Message) => Array[Object]] val callbacks = mutable.Map.empty[String, (Boolean, (Object, api.network.Message) => Array[Object])]
var c = clazz var c = clazz
while (c != classOf[Object]) { while (c != classOf[Object]) {
val ms = c.getDeclaredMethods val ms = c.getDeclaredMethods
@ -112,7 +114,7 @@ object Component {
throw new IllegalArgumentException("Invalid use of LuaCallback annotation (name must not be null or empty).") throw new IllegalArgumentException("Invalid use of LuaCallback annotation (name must not be null or empty).")
} }
else if (!callbacks.contains(a.value)) { else if (!callbacks.contains(a.value)) {
callbacks += a.value -> ((o, e) => try { callbacks += a.value ->(a.asynchronous(), (o: Object, e: Message) => try {
m.invoke(o, e).asInstanceOf[Array[Object]] m.invoke(o, e).asInstanceOf[Array[Object]]
} catch { } catch {
case e: InvocationTargetException => throw e.getCause case e: InvocationTargetException => throw e.getCause

View File

@ -18,7 +18,7 @@ import scala.collection.mutable.ArrayBuffer
class Network private(private val addressedNodes: mutable.Map[String, Network.Node] = mutable.Map.empty, class Network private(private val addressedNodes: mutable.Map[String, Network.Node] = mutable.Map.empty,
private val unaddressedNodes: mutable.ArrayBuffer[Network.Node] = mutable.ArrayBuffer.empty) extends api.network.Network { private val unaddressedNodes: mutable.ArrayBuffer[Network.Node] = mutable.ArrayBuffer.empty) extends api.network.Network {
def this(node: Node) = { def this(node: network.Node) = {
this() this()
addNew(node) addNew(node)
if (node.address != null) if (node.address != null)
@ -31,14 +31,18 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def connect(nodeA: api.network.Node, nodeB: api.network.Node) = { def connect(nodeA: api.network.Node, nodeB: api.network.Node) = {
if (nodeA == nodeB) throw new IllegalArgumentException(
"Cannot connect a node to itself.")
if (!nodeA.isInstanceOf[network.Node]) throw new IllegalArgumentException( if (!nodeA.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!") "Unsupported node implementation. Don't implement the interface yourself!")
if (!nodeB.isInstanceOf[network.Node]) throw new IllegalArgumentException( if (!nodeB.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!") "Unsupported node implementation. Don't implement the interface yourself!")
connect0(nodeA.asInstanceOf[network.Node], nodeB.asInstanceOf[network.Node])
}
private def connect0(nodeA: network.Node, nodeB: network.Node) = this.synchronized {
if (nodeA == nodeB) throw new IllegalArgumentException(
"Cannot connect a node to itself.")
val containsA = contains(nodeA) val containsA = contains(nodeA)
val containsB = contains(nodeB) val containsB = contains(nodeB)
@ -65,19 +69,23 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
} }
else false // That connection already exists. else false // That connection already exists.
} }
else if (containsA) add(oldNodeA, nodeB.asInstanceOf[network.Node]) else if (containsA) add(oldNodeA, nodeB)
else add(oldNodeB, nodeA.asInstanceOf[network.Node]) else add(oldNodeB, nodeA)
} }
def disconnect(nodeA: api.network.Node, nodeB: api.network.Node) = { def disconnect(nodeA: api.network.Node, nodeB: api.network.Node) = {
if (nodeA == nodeB) throw new IllegalArgumentException(
"Cannot disconnect a node from itself.")
if (!nodeA.isInstanceOf[network.Node]) throw new IllegalArgumentException( if (!nodeA.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!") "Unsupported node implementation. Don't implement the interface yourself!")
if (!nodeB.isInstanceOf[network.Node]) throw new IllegalArgumentException( if (!nodeB.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!") "Unsupported node implementation. Don't implement the interface yourself!")
disconnect0(nodeA.asInstanceOf[network.Node], nodeB.asInstanceOf[network.Node])
}
private def disconnect0(nodeA: network.Node, nodeB: network.Node) = this.synchronized {
if (nodeA == nodeB) throw new IllegalArgumentException(
"Cannot disconnect a node from itself.")
val containsA = contains(nodeA) val containsA = contains(nodeA)
val containsB = contains(nodeB) val containsB = contains(nodeB)
@ -100,61 +108,95 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
} }
} }
def remove(node: api.network.Node) = (Option(node.address) match { def remove(node: api.network.Node) = {
case Some(address) => addressedNodes.remove(address) if (!node.isInstanceOf[network.Node]) throw new IllegalArgumentException(
case _ => unaddressedNodes.indexWhere(_.data == node) match { "Unsupported node implementation. Don't implement the interface yourself!")
case -1 => None
case index => Some(unaddressedNodes.remove(index)) remove0(node.asInstanceOf[network.Node])
}
private def remove0(node: network.Node) = this.synchronized {
(Option(node.address) match {
case Some(address) => addressedNodes.remove(address)
case _ => unaddressedNodes.indexWhere(_.data == node) match {
case -1 => None
case index => Some(unaddressedNodes.remove(index))
}
}) match {
case Some(entry) => {
node.asInstanceOf[Node].network = null
val subGraphs = entry.remove()
val targets = Iterable(node) ++ (entry.data.reachability match {
case Visibility.None => Iterable.empty[api.network.Node]
case Visibility.Neighbors => entry.edges.map(_.other(entry).data)
case Visibility.Network => subGraphs.map {
case (addressed, _) => addressed.values.map(_.data)
}.flatten
})
handleSplit(subGraphs)
send(Network.DisconnectMessage(node), targets)
true
}
case _ => false
} }
}) match {
case Some(entry) => {
node.asInstanceOf[Node].network = null
val subGraphs = entry.remove()
val targets = Iterable(node) ++ (entry.data.reachability match {
case Visibility.None => Iterable.empty[api.network.Node]
case Visibility.Neighbors => entry.edges.map(_.other(entry).data)
case Visibility.Network => subGraphs.map {
case (addressed, _) => addressed.values.map(_.data)
}.flatten
})
handleSplit(subGraphs)
send(Network.DisconnectMessage(node), targets)
true
}
case _ => false
} }
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def node(address: String) = addressedNodes.get(address) match { def node(address: String) = this.synchronized {
case Some(node) => node.data addressedNodes.get(address) match {
case _ => null case Some(node) => node.data
case _ => null
}
} }
def nodes = addressedNodes.values.map(_.data.asInstanceOf[api.network.Node]).asJava def nodes = this.synchronized(addressedNodes.values.map(_.data.asInstanceOf[api.network.Node]).asJava)
def nodes(reference: api.network.Node) = { def nodes(reference: api.network.Node) = {
if (!reference.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!")
nodes0(reference.asInstanceOf[network.Node])
}
private def nodes0(reference: api.network.Node) = {
val referenceNeighbors = neighbors(reference).toSet.asJava val referenceNeighbors = neighbors(reference).toSet.asJava
nodes.filter(node => node != reference && (node.reachability == Visibility.Network || nodes.filter(node => node != reference && (node.reachability == Visibility.Network ||
(node.reachability == Visibility.Neighbors && referenceNeighbors.contains(node)))).asJava (node.reachability == Visibility.Neighbors && referenceNeighbors.contains(node)))).asJava
} }
def neighbors(node: api.network.Node) = Option(node.address) match { def neighbors(node: api.network.Node) = {
case Some(address) => if (!node.isInstanceOf[network.Node]) throw new IllegalArgumentException(
addressedNodes.get(address) match { "Unsupported node implementation. Don't implement the interface yourself!")
case Some(n) => n.edges.map(_.other(n).data)
case _ => throw new IllegalArgumentException("Node must be in this network.") neighbors0(node.asInstanceOf[network.Node])
} }
case None =>
unaddressedNodes.find(_.data == node) match { private def neighbors0(node: network.Node) = this.synchronized {
case Some(n) => n.edges.map(_.other(n).data) Option(node.address) match {
case _ => throw new IllegalArgumentException("Node must be in this network.") case Some(address) =>
} addressedNodes.get(address) match {
case Some(n) => n.edges.map(_.other(n).data)
case _ => throw new IllegalArgumentException("Node must be in this network.")
}
case None =>
unaddressedNodes.find(_.data == node) match {
case Some(n) => n.edges.map(_.other(n).data)
case _ => throw new IllegalArgumentException("Node must be in this network.")
}
}
} }
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def sendToAddress(source: api.network.Node, target: String, name: String, data: AnyRef*) = { def sendToAddress(source: api.network.Node, target: String, name: String, data: AnyRef*): Array[Object] = {
if (!source.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!")
sendToAddress0(source.asInstanceOf[network.Node], target, name, data: _*)
}
private def sendToAddress0(source: network.Node, target: String, name: String, data: AnyRef*) = this.synchronized {
if (source.network == null || source.network != this) if (source.network == null || source.network != this)
throw new IllegalArgumentException("Source node must be in this network.") throw new IllegalArgumentException("Source node must be in this network.")
if (source.address != null) addressedNodes.get(target) match { if (source.address != null) addressedNodes.get(target) match {
@ -165,7 +207,14 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
} else null } else null
} }
def sendToNeighbors(source: api.network.Node, name: String, data: AnyRef*) = { def sendToNeighbors(source: api.network.Node, name: String, data: AnyRef*): Array[Object] = {
if (!source.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!")
sendToNeighbors0(source.asInstanceOf[network.Node], name, data: _*)
}
private def sendToNeighbors0(source: network.Node, name: String, data: AnyRef*) = this.synchronized {
if (source.network == null || source.network != this) if (source.network == null || source.network != this)
throw new IllegalArgumentException("Source node must be in this network.") throw new IllegalArgumentException("Source node must be in this network.")
if (source.address != null) if (source.address != null)
@ -173,7 +222,14 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No
else null else null
} }
def sendToVisible(source: api.network.Node, name: String, data: AnyRef*) = { def sendToVisible(source: api.network.Node, name: String, data: AnyRef*): Array[Object] = {
if (!source.isInstanceOf[network.Node]) throw new IllegalArgumentException(
"Unsupported node implementation. Don't implement the interface yourself!")
sendToVisible0(source.asInstanceOf[network.Node], name, data: _*)
}
private def sendToVisible0(source: network.Node, name: String, data: AnyRef*) = this.synchronized {
if (source.network == null || source.network != this) if (source.network == null || source.network != this)
throw new IllegalArgumentException("Source node must be in this network.") throw new IllegalArgumentException("Source node must be in this network.")
if (source.address != null) if (source.address != null)
@ -476,12 +532,12 @@ object Network extends api.detail.NetworkAPI {
private def checkCount(count: Int, name: String) = private def checkCount(count: Int, name: String) =
if (data.length <= count) throw new IllegalArgumentException( if (data.length <= count) throw new IllegalArgumentException(
"bad arguments #%d (%s expected, got no value)". "bad arguments #%d (%s expected, got no value)".
format(count + 1, name)) format(count, name))
private def typeError(index: Int, have: AnyRef, want: String) = private def typeError(index: Int, have: AnyRef, want: String) =
new IllegalArgumentException( new IllegalArgumentException(
"bad argument #%d (%s expected, got %s)". "bad argument #%d (%s expected, got %s)".
format(index + 1, want, typeName(have))) format(index, want, typeName(have)))
private def typeName(value: AnyRef): String = value match { private def typeName(value: AnyRef): String = value match {
case null => "nil" case null => "nil"

View File

@ -27,18 +27,21 @@ class TextBuffer(var width: Int, var height: Int) {
* buffer valid as possible if the size decreases, i.e. only data outside the * buffer valid as possible if the size decreases, i.e. only data outside the
* new buffer size will be truncated, all data still inside will be copied. * new buffer size will be truncated, all data still inside will be copied.
*/ */
def size_=(value: (Int, Int)): Boolean = if (size != value) { def size_=(value: (Int, Int)): Boolean = {
val (w, h) = value val (iw, ih) = value
val newBuffer = Array.fill(h, w)(' ') val (w, h) = (iw max 1, ih max 1)
(0 until (h min height)) foreach { if (width != w || height != h) {
y => Array.copy(buffer(y), 0, newBuffer(y), 0, w min width) val newBuffer = Array.fill(h, w)(' ')
(0 until (h min height)) foreach {
y => Array.copy(buffer(y), 0, newBuffer(y), 0, w min width)
}
buffer = newBuffer
width = w
height = h
true
} }
buffer = newBuffer else false
width = w
height = h
true
} }
else false
/** /**
* String based fill starting at a specified location. * String based fill starting at a specified location.