diff --git a/assets/opencomputers/lua/boot.lua b/assets/opencomputers/lua/boot.lua index f42818e3d..e76342707 100644 --- a/assets/opencomputers/lua/boot.lua +++ b/assets/opencomputers/lua/boot.lua @@ -52,48 +52,4 @@ local function flattenAndStore(k, v) end -- Mark everything that's globally reachable at this point as permanent. -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) +flattenAndStore("_ENV", _ENV) \ No newline at end of file diff --git a/assets/opencomputers/lua/kernel.lua b/assets/opencomputers/lua/kernel.lua index bec2317c2..79ccf003f 100644 --- a/assets/opencomputers/lua/kernel.lua +++ b/assets/opencomputers/lua/kernel.lua @@ -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 = {} function sandbox.component.list(filter) checkArg(1, filter, "string", "nil") - return pairs(componentList(filter)) + return pairs(component.list(filter)) end function sandbox.component.type(address) checkArg(1, address, "string") - return componentType(address) + return component.type(address) end function sandbox.component.invoke(address, method, ...) checkArg(1, address, "string") checkArg(2, method, "string") - return componentInvokeSynchronized(address, method, ...) + return component.invoke(address, method, ...) end function sandbox.component.proxy(address) checkArg(1, address, "string") - local type, reason = componentType(address) + local type, reason = component.type(address) if not type then return nil, reason end local proxy = {address = address, type = type} - local methods = componentMethodsSynchronized(address) + local methods = component.methods(address) 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(...) - return componentInvokeSynchronized(address, method, ...) + return invoke(address, method, ...) end end end @@ -275,40 +346,14 @@ end ------------------------------------------------------------------------------- local function main() + local args 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, ...) - return componentInvoke(os.romAddress(), method, ...) + return component.invokeAsync(os.romAddress(), method, ...) end -- Custom dofile implementation since we don't have the baselib yet. local function dofile(file) - print(" " .. file) local handle, reason = rom("open", file) if not handle then error(reason) @@ -337,7 +382,6 @@ local function main() end end - print("Loading libraries.") local init = {} for _, api in ipairs(rom("list", "lib")) do local path = "lib/" .. api @@ -349,32 +393,29 @@ local function main() end end - print("Initializing libraries.") for _, install in ipairs(init) do install() end 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([[ fs.mount(os.romAddress(), "/") fs.mount(os.tmpAddress(), "/tmp") for c, t in component.list() do event.fire("component_added", c, t) end + term.clear() while true do - local result, reason = os.execute("/bin/sh") + local result, reason = os.execute("/bin/sh -v") if not result then error(reason) end end]], "=init", "t", sandbox)) end 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 deadline = os.realTime() + timeout -- timeout global is set by host if not debug.gethook(co) then diff --git a/assets/opencomputers/lua/rom/bin/redstone.lua b/assets/opencomputers/lua/rom/bin/redstone.lua new file mode 100644 index 000000000..cd4c64b3d --- /dev/null +++ b/assets/opencomputers/lua/rom/bin/redstone.lua @@ -0,0 +1,26 @@ +local args = shell.parse(...) +if #args < 1 then + print("Usage: redstone []") + 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 diff --git a/assets/opencomputers/lua/rom/bin/sh.lua b/assets/opencomputers/lua/rom/bin/sh.lua index 2034283d3..f9f0af5a9 100644 --- a/assets/opencomputers/lua/rom/bin/sh.lua +++ b/assets/opencomputers/lua/rom/bin/sh.lua @@ -1,11 +1,19 @@ +local args, options = shell.parse(...) local history = {} + +if options.v then + print("OpenOS v1.0 (" .. math.floor(os.totalMemory() / 1024) .. "k RAM)") +end + 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 os.sleep() end 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 while term.isAvailable() do term.write("# ") diff --git a/assets/opencomputers/lua/rom/lib/gpu.lua b/assets/opencomputers/lua/rom/lib/gpu.lua index dbaa33327..73fcd8b41 100644 --- a/assets/opencomputers/lua/rom/lib/gpu.lua +++ b/assets/opencomputers/lua/rom/lib/gpu.lua @@ -2,9 +2,10 @@ local function onComponentAvailable(_, componentType) if (componentType == "screen" and component.isAvailable("gpu")) or (componentType == "gpu" and component.isAvailable("screen")) 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() - component.primary("gpu").setResolution(maxX, maxY) + gpu.setResolution(maxX, maxY) end end diff --git a/assets/opencomputers/lua/rom/lib/shell.lua b/assets/opencomputers/lua/rom/lib/shell.lua index 8be5d79a8..60278e9ca 100644 --- a/assets/opencomputers/lua/rom/lib/shell.lua +++ b/assets/opencomputers/lua/rom/lib/shell.lua @@ -1,7 +1,7 @@ local cwd = "/" local path = {"/bin/", "/usr/bin/", "/home/bin/"} 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) checkArg(1, name, "string") diff --git a/li/cil/oc/api/network/environment/LuaCallback.java b/li/cil/oc/api/network/environment/LuaCallback.java index 4a0efd3c7..0c257e01b 100644 --- a/li/cil/oc/api/network/environment/LuaCallback.java +++ b/li/cil/oc/api/network/environment/LuaCallback.java @@ -21,4 +21,15 @@ public @interface LuaCallback { * @return the name of the function. */ String value() default ""; + + /** + * Whether this function may be called asynchronously, i.e. directly from + * the computer's executor thread. + *

+ * You will have to ensure anything your callback does is thread safe when + * setting this to true. 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; } diff --git a/li/cil/oc/common/tileentity/ComponentInventory.scala b/li/cil/oc/common/tileentity/ComponentInventory.scala index 2ffe79c1b..85608b321 100644 --- a/li/cil/oc/common/tileentity/ComponentInventory.scala +++ b/li/cil/oc/common/tileentity/ComponentInventory.scala @@ -50,6 +50,7 @@ trait ComponentInventory extends Inventory with Environment with Persistable { case Some(driver) => Option(driver.createEnvironment(stack)) match { case Some(environment) => + environment.node.load(driver.nbt(stack)) environment.load(driver.nbt(stack)) Some(environment) case _ => None @@ -66,7 +67,9 @@ trait ComponentInventory extends Inventory with Environment with Persistable { case (stack, slot) => components(slot) match { case Some(environment) => // 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. } } @@ -82,6 +85,7 @@ trait ComponentInventory extends Inventory with Environment with Persistable { case Some(driver) => Option(driver.createEnvironment(item)) match { case Some(environment) => components(slot) = Some(environment) + environment.node.load(driver.nbt(item)) environment.load(driver.nbt(item)) node.network.connect(node, environment.node) 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!) components(slot) = None 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. } } diff --git a/li/cil/oc/server/component/Computer.scala b/li/cil/oc/server/component/Computer.scala index a4d55e669..b53cd3f68 100644 --- a/li/cil/oc/server/component/Computer.scala +++ b/li/cil/oc/server/component/Computer.scala @@ -329,8 +329,16 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl }.toArray) }) - rom.foreach(_.load(computerNbt.getCompoundTag("rom"))) - tmp.foreach(_.load(computerNbt.getCompoundTag("tmp"))) + rom.foreach(rom => { + 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") timeStarted = computerNbt.getLong("timeStarted") cpuTime = computerNbt.getLong("cpuTime") @@ -417,11 +425,17 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl computerNbt.setTag("signals", signalsNbt) val romNbt = new NBTTagCompound() - rom.foreach(_.save(romNbt)) + rom.foreach(rom => { + rom.save(romNbt) + rom.node.save(romNbt) + }) computerNbt.setCompoundTag("rom", romNbt) val tmpNbt = new NBTTagCompound() - tmp.foreach(_.save(tmpNbt)) + tmp.foreach(tmp => { + tmp.save(tmpNbt) + tmp.node.save(tmpNbt) + }) computerNbt.setCompoundTag("tmp", tmpNbt) computerNbt.setInteger("kernelMemory", kernelMemory) @@ -626,11 +640,16 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl lua.pushScalaFunction(lua => { owner.node.network.sendToAddress(owner.node, lua.checkString(1), "component.methods") match { - case Array(names: Array[String]) => + case Array(methods: Array[_]) => lua.newTable() - for ((name, index) <- names.zipWithIndex) { - lua.pushString(name) - lua.rawSet(-2, index + 1) + for ((method, index) <- methods.zipWithIndex) { + method match { + case (name: String, asynchronous: Boolean) => + lua.pushString(name) + lua.pushBoolean(asynchronous) + lua.rawSet(-3) + case _ => + } } 1 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: _*) case _ => throw new Exception("no such component") }) match { - case Array(results@_*) => + case results: Array[_] => + lua.pushBoolean(true) results.foreach(pushResult(lua, _)) - results.length + 1 + results.length case _ => - 0 + lua.pushBoolean(true) + 1 } } 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 => - lua.pushNil() + lua.pushBoolean(false) lua.pushString("no such method") 2 + case e: IllegalArgumentException if e.getMessage != null => + lua.pushBoolean(false) + lua.pushString(e.getMessage) + 2 case _: IllegalArgumentException => - lua.pushNil() + lua.pushBoolean(false) lua.pushString("bad argument") 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 => + lua.pushBoolean(true) lua.pushNil() lua.pushString("unknown error") - 2 + 3 } }) lua.setGlobal("componentInvoke") @@ -771,23 +801,13 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl // timeouts. lua.load(classOf[Computer].getResourceAsStream(Config.scriptPath + "kernel.lua"), "=kernel", "t") 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 - // memory all the basic functionality we provide needs. - val results = lua.resume(1, 0) - if (lua.status(1) != LuaState.YIELD) - if (!lua.toBoolean(-2)) throw new Exception(lua.toString(-1)) - else throw new Exception("kernel return unexpectedly") - 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() + // // Run to the first yield in kernel, to get a good idea of how much + // // memory all the basic functionality we provide needs. + // val results = lua.resume(1, 0) + // if (lua.status(1) != LuaState.YIELD) + // if (!lua.toBoolean(-2)) throw new Exception(lua.toString(-1)) + // else throw new Exception("kernel return unexpectedly") + // lua.pop(results) // Clear any left-over signals from a previous run. signals.clear() @@ -862,7 +882,7 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl try { // Help out the GC a little. The emergency GC has a few limitations that // will make it free less memory than doing a full step manually. - lua.gc(LuaState.GcAction.COLLECT, 0) + // lua.gc(LuaState.GcAction.COLLECT, 0) // Resume the Lua state and remember the number of results we get. cpuStart = System.nanoTime() val results = if (callReturn) { @@ -887,6 +907,19 @@ class Computer(val owner: Computer.Environment) extends Persistable with Runnabl } 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. stateMonitor.synchronized(if (lua.status(1) == LuaState.YIELD) { // Intermediate state in some cases. Satisfies the assert in execute(). diff --git a/li/cil/oc/server/component/GraphicsCard.scala b/li/cil/oc/server/component/GraphicsCard.scala index 39d9962cd..32542beb9 100644 --- a/li/cil/oc/server/component/GraphicsCard.scala +++ b/li/cil/oc/server/component/GraphicsCard.scala @@ -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") @LuaCallback("setResolution") @@ -39,7 +39,7 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent { Array(Unit, "unsupported resolution") } - @LuaCallback("maxResolution") + @LuaCallback(value = "maxResolution", asynchronous = true) def maxResolution(message: Message): Array[Object] = trySend("screen.resolution") match { case Array(w: Integer, h: Integer) => @@ -62,7 +62,7 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent { val y = message.checkInteger(2) val w = message.checkInteger(3) val h = message.checkInteger(4) - val value = message.checkString(4) + val value = message.checkString(5) if (value.length == 1) trySend("screen.fill", x - 1, y - 1, w, h, value.charAt(0)) else @@ -96,13 +96,11 @@ class GraphicsCard(val maxResolution: (Int, Int)) extends ManagedComponent { // ----------------------------------------------------------------------- // override def load(nbt: NBTTagCompound) { - super.load(nbt) if (nbt.hasKey("oc.gpu.screen")) screen = Some(nbt.getString("oc.gpu.screen")) } override def save(nbt: NBTTagCompound) { - super.save(nbt) if (screen.isDefined) nbt.setString("oc.gpu.screen", screen.get) } diff --git a/li/cil/oc/server/component/RedstoneCard.scala b/li/cil/oc/server/component/RedstoneCard.scala index 3f4cb2b2c..67a0a0400 100644 --- a/li/cil/oc/server/component/RedstoneCard.scala +++ b/li/cil/oc/server/component/RedstoneCard.scala @@ -8,14 +8,14 @@ import net.minecraftforge.common.ForgeDirection class RedstoneCard extends ManagedComponent { 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] = { val side = message.checkInteger(1) node.network.sendToAddress(node, message.source.address, "redstone.input", ForgeDirection.getOrientation(side)) } - @LuaCallback("getOutput") + @LuaCallback(value = "getOutput", asynchronous = true) def getOutput(message: Message): Array[Object] = { val side = message.checkInteger(1) node.network.sendToAddress(node, message.source.address, diff --git a/li/cil/oc/server/network/Component.scala b/li/cil/oc/server/network/Component.scala index 6b8c5e68a..9cb9ff3c5 100644 --- a/li/cil/oc/server/network/Component.scala +++ b/li/cil/oc/server/network/Component.scala @@ -63,11 +63,13 @@ class Component(host: Environment, name: String, reachability: Visibility) exten override def receive(message: Message) = { if (message.name == "component.methods") if (canBeSeenBy(message.source)) - Array(Array(luaCallbacks.keys.toSeq: _*)) + Array(luaCallbacks.map { + case (method, (asynchronous, _)) => (method, asynchronous) + }.toArray) else null else if (message.name == "component.invoke") { luaCallbacks.get(message.data()(0).asInstanceOf[String]) match { - case Some(callback) => callback(host, message) + case Some((_, callback)) => callback(host, message) 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) if (nbt.hasKey("visibility")) visibility_ = Visibility.values()(nbt.getInteger("visibility")) } - override def load(nbt: NBTTagCompound) { + override def save(nbt: NBTTagCompound) { super.save(nbt) nbt.setInteger("visibility", visibility_.ordinal()) } } 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)) 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 while (c != classOf[Object]) { 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).") } 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]] } catch { case e: InvocationTargetException => throw e.getCause diff --git a/li/cil/oc/server/network/Network.scala b/li/cil/oc/server/network/Network.scala index 81f9abef1..d28b5d2cb 100644 --- a/li/cil/oc/server/network/Network.scala +++ b/li/cil/oc/server/network/Network.scala @@ -18,7 +18,7 @@ import scala.collection.mutable.ArrayBuffer 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 { - def this(node: Node) = { + def this(node: network.Node) = { this() addNew(node) 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) = { - if (nodeA == nodeB) throw new IllegalArgumentException( - "Cannot connect a node to itself.") - if (!nodeA.isInstanceOf[network.Node]) throw new IllegalArgumentException( "Unsupported node implementation. Don't implement the interface yourself!") if (!nodeB.isInstanceOf[network.Node]) throw new IllegalArgumentException( "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 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 if (containsA) add(oldNodeA, nodeB.asInstanceOf[network.Node]) - else add(oldNodeB, nodeA.asInstanceOf[network.Node]) + else if (containsA) add(oldNodeA, nodeB) + else add(oldNodeB, nodeA) } 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( "Unsupported node implementation. Don't implement the interface yourself!") if (!nodeB.isInstanceOf[network.Node]) throw new IllegalArgumentException( "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 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 { - case Some(address) => addressedNodes.remove(address) - case _ => unaddressedNodes.indexWhere(_.data == node) match { - case -1 => None - case index => Some(unaddressedNodes.remove(index)) + def remove(node: api.network.Node) = { + if (!node.isInstanceOf[network.Node]) throw new IllegalArgumentException( + "Unsupported node implementation. Don't implement the interface yourself!") + + 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 { - case Some(node) => node.data - case _ => null + def node(address: String) = this.synchronized { + addressedNodes.get(address) match { + 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) = { + 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 nodes.filter(node => node != reference && (node.reachability == Visibility.Network || (node.reachability == Visibility.Neighbors && referenceNeighbors.contains(node)))).asJava } - def neighbors(node: api.network.Node) = Option(node.address) match { - 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 neighbors(node: api.network.Node) = { + if (!node.isInstanceOf[network.Node]) throw new IllegalArgumentException( + "Unsupported node implementation. Don't implement the interface yourself!") + + neighbors0(node.asInstanceOf[network.Node]) + } + + private def neighbors0(node: network.Node) = this.synchronized { + Option(node.address) match { + 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) throw new IllegalArgumentException("Source node must be in this network.") 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 } - 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) throw new IllegalArgumentException("Source node must be in this network.") if (source.address != null) @@ -173,7 +222,14 @@ class Network private(private val addressedNodes: mutable.Map[String, Network.No 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) throw new IllegalArgumentException("Source node must be in this network.") if (source.address != null) @@ -476,12 +532,12 @@ object Network extends api.detail.NetworkAPI { private def checkCount(count: Int, name: String) = if (data.length <= count) throw new IllegalArgumentException( "bad arguments #%d (%s expected, got no value)". - format(count + 1, name)) + format(count, name)) private def typeError(index: Int, have: AnyRef, want: String) = new IllegalArgumentException( "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 { case null => "nil" diff --git a/li/cil/oc/util/TextBuffer.scala b/li/cil/oc/util/TextBuffer.scala index 98ad6a4c1..6b0a12a56 100644 --- a/li/cil/oc/util/TextBuffer.scala +++ b/li/cil/oc/util/TextBuffer.scala @@ -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 * new buffer size will be truncated, all data still inside will be copied. */ - def size_=(value: (Int, Int)): Boolean = if (size != value) { - val (w, h) = value - val newBuffer = Array.fill(h, w)(' ') - (0 until (h min height)) foreach { - y => Array.copy(buffer(y), 0, newBuffer(y), 0, w min width) + def size_=(value: (Int, Int)): Boolean = { + val (iw, ih) = value + val (w, h) = (iw max 1, ih max 1) + if (width != w || height != h) { + 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 - width = w - height = h - true + else false } - else false /** * String based fill starting at a specified location.