From 1335536c5d15116c011684f4ff3f951a9b46bfd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sat, 18 Jan 2014 16:54:12 +0100 Subject: [PATCH] fgsfds --- li/cil/oc/server/component/Machine.scala | 70 +-- .../component/machine/LuaArchitecture.scala | 527 ---------------- .../machine/LuaJLuaArchitecture.scala | 389 +++++++++++- .../machine/NativeLuaArchitecture.scala | 567 ++++++++++++++++-- li/cil/oc/util/ExtendedLuaState.scala | 9 +- li/cil/oc/util/LuaState.scala | 311 ---------- li/cil/oc/util/LuaStateFactory.scala | 3 +- 7 files changed, 960 insertions(+), 916 deletions(-) delete mode 100644 li/cil/oc/server/component/machine/LuaArchitecture.scala delete mode 100644 li/cil/oc/util/LuaState.scala diff --git a/li/cil/oc/server/component/Machine.scala b/li/cil/oc/server/component/Machine.scala index e90ca30d2..3848d0d33 100644 --- a/li/cil/oc/server/component/Machine.scala +++ b/li/cil/oc/server/component/Machine.scala @@ -6,7 +6,7 @@ import li.cil.oc.api.network._ import li.cil.oc.common.tileentity import li.cil.oc.server import li.cil.oc.server.PacketSender -import li.cil.oc.server.component.machine.{NativeLuaArchitecture, ExecutionResult, LuaArchitecture} +import li.cil.oc.server.component.machine.{NativeLuaArchitecture, ExecutionResult} import li.cil.oc.util.ExtendedNBT._ import li.cil.oc.util.ThreadPoolFactory import li.cil.oc.{OpenComputers, Settings} @@ -496,45 +496,45 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi nbt.getTagList("users").foreach[NBTTagString](u => _users += u.data) if (state.size > 0 && state.top != Machine.State.Stopped && init()) { - architecture.load(nbt) + architecture.load(nbt) - components ++= nbt.getTagList("components").iterator[NBTTagCompound].map(c => - c.getString("address") -> c.getString("name")) + components ++= nbt.getTagList("components").iterator[NBTTagCompound].map(c => + c.getString("address") -> c.getString("name")) - signals ++= nbt.getTagList("signals").iterator[NBTTagCompound].map(signalNbt => { - val argsNbt = signalNbt.getCompoundTag("args") - val argsLength = argsNbt.getInteger("length") - new Machine.Signal(signalNbt.getString("name"), - (0 until argsLength).map("arg" + _).map(argsNbt.getTag).map { - case tag: NBTTagByte if tag.data == -1 => Unit - case tag: NBTTagByte => tag.data == 1 - case tag: NBTTagDouble => tag.data - case tag: NBTTagString => tag.data - case tag: NBTTagByteArray => tag.byteArray - case tag: NBTTagList => - val data = mutable.Map.empty[String, String] - for (i <- 0 until tag.tagCount by 2) { - (tag.tagAt(i), tag.tagAt(i + 1)) match { - case (key: NBTTagString, value: NBTTagString) => data += key.data -> value.data - case _ => - } + signals ++= nbt.getTagList("signals").iterator[NBTTagCompound].map(signalNbt => { + val argsNbt = signalNbt.getCompoundTag("args") + val argsLength = argsNbt.getInteger("length") + new Machine.Signal(signalNbt.getString("name"), + (0 until argsLength).map("arg" + _).map(argsNbt.getTag).map { + case tag: NBTTagByte if tag.data == -1 => Unit + case tag: NBTTagByte => tag.data == 1 + case tag: NBTTagDouble => tag.data + case tag: NBTTagString => tag.data + case tag: NBTTagByteArray => tag.byteArray + case tag: NBTTagList => + val data = mutable.Map.empty[String, String] + for (i <- 0 until tag.tagCount by 2) { + (tag.tagAt(i), tag.tagAt(i + 1)) match { + case (key: NBTTagString, value: NBTTagString) => data += key.data -> value.data + case _ => } - data - case _ => Unit - }.toArray) - }) + } + data + case _ => Unit + }.toArray) + }) - rom.foreach(rom => rom.load(nbt.getCompoundTag("rom"))) - tmp.foreach(tmp => tmp.load(nbt.getCompoundTag("tmp"))) - timeStarted = nbt.getLong("timeStarted") - cpuTime = nbt.getLong("cpuTime") - remainingPause = nbt.getInteger("remainingPause") - if (nbt.hasKey("message")) { - message = Some(nbt.getString("message")) - } + rom.foreach(rom => rom.load(nbt.getCompoundTag("rom"))) + tmp.foreach(tmp => tmp.load(nbt.getCompoundTag("tmp"))) + timeStarted = nbt.getLong("timeStarted") + cpuTime = nbt.getLong("cpuTime") + remainingPause = nbt.getInteger("remainingPause") + if (nbt.hasKey("message")) { + message = Some(nbt.getString("message")) + } - // Delay execution for a second to allow the world around us to settle. - pause(Settings.get.startupDelay) + // Delay execution for a second to allow the world around us to settle. + pause(Settings.get.startupDelay) } else close() // Clean up in case we got a weird state stack. } diff --git a/li/cil/oc/server/component/machine/LuaArchitecture.scala b/li/cil/oc/server/component/machine/LuaArchitecture.scala deleted file mode 100644 index 4a05c6c5a..000000000 --- a/li/cil/oc/server/component/machine/LuaArchitecture.scala +++ /dev/null @@ -1,527 +0,0 @@ -package li.cil.oc.server.component.machine - -import com.naef.jnlua -import java.io.{IOException, FileNotFoundException} -import java.util.logging.Level -import li.cil.oc.server.component.Machine -import li.cil.oc.util.ExtendedLuaState.extendLuaState -import li.cil.oc.util.{LuaState, GameTimeFormatter, LuaStateFactory} -import li.cil.oc.{OpenComputers, server, Settings} -import scala.Some -import scala.collection.convert.WrapAsScala._ - -abstract class LuaArchitecture(val machine: Machine) extends Architecture { - protected var lua: LuaState - - protected var kernelMemory = 0 - - protected val ramScale = if (LuaStateFactory.is64Bit) Settings.get.ramScaleFor64Bit else 1.0 - - // ----------------------------------------------------------------------- // - - protected def node = machine.node - - protected def state = machine.state - - protected def components = machine.components - - // ----------------------------------------------------------------------- // - - def isInitialized = kernelMemory > 0 - - // ----------------------------------------------------------------------- // - - def runSynchronized() { - // These three asserts are all guaranteed by run(). - assert(lua.getTop == 2) - assert(lua.isThread(1)) - assert(lua.isFunction(2)) - - try { - // Synchronized call protocol requires the called function to return - // a table, which holds the results of the call, to be passed back - // to the coroutine.yield() that triggered the call. - lua.call(0, 1) - lua.checkType(2, jnlua.LuaType.TABLE) - } - catch { - case _: jnlua.LuaMemoryAllocationException => - // 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). - throw new java.lang.OutOfMemoryError("not enough memory") - } - } - - def runThreaded(enterState: Machine.State.Value): ExecutionResult = { - try { - // The kernel thread will always be at stack index one. - assert(lua.isThread(1)) - - if (Settings.get.activeGC) { - // 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(jnlua.LuaState.GcAction.COLLECT, 0) - } - - // Resume the Lua state and remember the number of results we get. - val results = enterState match { - case Machine.State.SynchronizedReturn => - // If we were doing a synchronized call, continue where we left off. - assert(lua.getTop == 2) - assert(lua.isTable(2)) - lua.resume(1, 1) - case Machine.State.Yielded => - if (kernelMemory == 0) { - // We're doing the initialization run. - if (lua.resume(1, 0) > 0) { - // We expect to get nothing here, if we do we had an error. - 0 - } - else { - // 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 (including libraries). 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(jnlua.LuaState.GcAction.COLLECT, 0) - kernelMemory = math.max(lua.getTotalMemory - lua.getFreeMemory, 1) - recomputeMemory() - - // Fake zero sleep to avoid stopping if there are no signals. - lua.pushInteger(0) - 1 - } - } - else machine.popSignal() match { - case Some(signal) => - lua.pushString(signal.name) - signal.args.foreach(arg => lua.pushValue(arg)) - lua.resume(1, 1 + signal.args.length) - case _ => - lua.resume(1, 0) - } - case s => throw new AssertionError("Running computer from invalid state " + s.toString) - } - - // Check if the kernel is still alive. - if (lua.status(1) == jnlua.LuaState.YIELD) { - // If we get one function it must be a wrapper for a synchronized - // call. The protocol is that a closure is pushed that is then called - // from the main server thread, and returns a table, which is in turn - // passed to the originating coroutine.yield(). - if (results == 1 && lua.isFunction(2)) { - new ExecutionResult.SynchronizedCall() - } - // Check if we are shutting down, and if so if we're rebooting. This - // is signalled by boolean values, where `false` means shut down, - // `true` means reboot (i.e shutdown then start again). - else if (results == 1 && lua.isBoolean(2)) { - new ExecutionResult.Shutdown(lua.toBoolean(2)) - } - else { - // If we have a single number, that's how long we may wait before - // resuming the state again. Note that the sleep may be interrupted - // early if a signal arrives in the meantime. If we have something - // else we just process the next signal or wait for one. - val ticks = if (results == 1 && lua.isNumber(2)) (lua.toNumber(2) * 20).toInt else Int.MaxValue - lua.pop(results) - new ExecutionResult.Sleep(ticks) - } - } - // The kernel thread returned. If it threw we'd be in the catch below. - else { - assert(lua.isThread(1)) - // We're expecting the result of a pcall, if anything, so boolean + (result | string). - if (!lua.isBoolean(2) || !(lua.isString(3) || lua.isNil(3))) { - OpenComputers.log.warning("Kernel returned unexpected results.") - } - // The pcall *should* never return normally... but check for it nonetheless. - if (lua.toBoolean(2)) { - OpenComputers.log.warning("Kernel stopped unexpectedly.") - new ExecutionResult.Shutdown(false) - } - else { - lua.setTotalMemory(Int.MaxValue) - val error = lua.toString(3) - if (error != null) new ExecutionResult.Error(error) - else new ExecutionResult.Error("unknown error") - } - } - } - catch { - case e: jnlua.LuaRuntimeException => - OpenComputers.log.warning("Kernel crashed. This is a bug!\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat ")) - new ExecutionResult.Error("kernel panic: this is a bug, check your log file and report it") - case e: jnlua.LuaGcMetamethodException => - if (e.getMessage != null) new ExecutionResult.Error("kernel panic:\n" + e.getMessage) - else new ExecutionResult.Error("kernel panic:\nerror in garbage collection metamethod") - case e: jnlua.LuaMemoryAllocationException => - new ExecutionResult.Error("not enough memory") - case e: java.lang.Error if e.getMessage == "not enough memory" => - new ExecutionResult.Error("not enough memory") - case e: Throwable => - OpenComputers.log.log(Level.WARNING, "Unexpected error in kernel. This is a bug!\n", e) - new ExecutionResult.Error("kernel panic: this is a bug, check your log file and report it") - } - } - - // ----------------------------------------------------------------------- // - - def init(): Boolean = { - if (!createState()) return false - - // Push a couple of functions that override original Lua API functions or - // that add new functionality to it. - lua.getGlobal("os") - - // Custom os.clock() implementation returning the time the computer has - // been actively running, instead of the native library... - lua.pushScalaFunction(lua => { - lua.pushNumber((machine.cpuTime + (System.nanoTime() - machine.cpuStart)) * 10e-10) - 1 - }) - lua.setField(-2, "clock") - - // Date formatting function. - lua.pushScalaFunction(lua => { - val format = - if (lua.getTop > 0 && lua.isString(1)) lua.toString(1) - else "%d/%m/%y %H:%M:%S" - val time = - if (lua.getTop > 1 && lua.isNumber(2)) lua.toNumber(2) * 1000 / 60 / 60 - else machine.worldTime + 6000 - - val dt = GameTimeFormatter.parse(time) - def fmt(format: String) { - if (format == "*t") { - lua.newTable(0, 8) - lua.pushInteger(dt.year) - lua.setField(-2, "year") - lua.pushInteger(dt.month) - lua.setField(-2, "month") - lua.pushInteger(dt.day) - lua.setField(-2, "day") - lua.pushInteger(dt.hour) - lua.setField(-2, "hour") - lua.pushInteger(dt.minute) - lua.setField(-2, "min") - lua.pushInteger(dt.second) - lua.setField(-2, "sec") - lua.pushInteger(dt.weekDay) - lua.setField(-2, "wday") - lua.pushInteger(dt.yearDay) - lua.setField(-2, "yday") - } - else { - lua.pushString(GameTimeFormatter.format(format, dt)) - } - } - - // Just ignore the allowed leading '!', Minecraft has no time zones... - if (format.startsWith("!")) - fmt(format.substring(1)) - else - fmt(format) - 1 - }) - lua.setField(-2, "date") - - // Return ingame time for os.time(). - lua.pushScalaFunction(lua => { - // Game time is in ticks, so that each day has 24000 ticks, meaning - // one hour is game time divided by one thousand. Also, Minecraft - // starts days at 6 o'clock, so we add those six hours. Thus: - // timestamp = (time + 6000) * 60[kh] * 60[km] / 1000[s] - lua.pushNumber((machine.worldTime + 6000) * 60 * 60 / 1000) - 1 - }) - lua.setField(-2, "time") - - // Pop the os table. - lua.pop(1) - - // Computer API, stuff that kinda belongs to os, but we don't want to - // clutter it. - lua.newTable() - - // Allow getting the real world time for timeouts. - lua.pushScalaFunction(lua => { - lua.pushNumber(System.currentTimeMillis() / 1000.0) - 1 - }) - lua.setField(-2, "realTime") - - // 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 uptime() to return real seconds, though, we'll divide it - // accordingly. - lua.pushNumber((machine.worldTime - machine.timeStarted) / 20.0) - 1 - }) - lua.setField(-2, "uptime") - - // Allow the computer to figure out its own id in the component network. - lua.pushScalaFunction(lua => { - Option(node.address) match { - case None => lua.pushNil() - case Some(address) => lua.pushString(address) - } - 1 - }) - lua.setField(-2, "address") - - // Are we a robot? (No this is not a CAPTCHA.) - lua.pushScalaFunction(lua => { - lua.pushBoolean(machine.isRobot) - 1 - }) - lua.setField(-2, "isRobot") - - lua.pushScalaFunction(lua => { - // This is *very* unlikely, but still: avoid this getting larger than - // what we report as the total memory. - lua.pushInteger(((lua.getFreeMemory min (lua.getTotalMemory - kernelMemory)) / ramScale).toInt) - 1 - }) - lua.setField(-2, "freeMemory") - - // Allow the system to read how much memory it uses and has available. - lua.pushScalaFunction(lua => { - lua.pushInteger(((lua.getTotalMemory - kernelMemory) / ramScale).toInt) - 1 - }) - lua.setField(-2, "totalMemory") - - lua.pushScalaFunction(lua => { - lua.pushBoolean(machine.signal(lua.checkString(1), lua.toSimpleJavaObjects(2): _*)) - 1 - }) - lua.setField(-2, "pushSignal") - - // And its ROM address. - lua.pushScalaFunction(lua => { - machine.rom.foreach(rom => Option(rom.node.address) match { - case None => lua.pushNil() - case Some(address) => lua.pushString(address) - }) - 1 - }) - lua.setField(-2, "romAddress") - - // And it's /tmp address... - lua.pushScalaFunction(lua => { - machine.tmp.foreach(tmp => Option(tmp.node.address) match { - case None => lua.pushNil() - case Some(address) => lua.pushString(address) - }) - 1 - }) - lua.setField(-2, "tmpAddress") - - // User management. - lua.pushScalaFunction(lua => { - val users = machine.users - users.foreach(lua.pushString) - users.length - }) - lua.setField(-2, "users") - - lua.pushScalaFunction(lua => try { - machine.addUser(lua.checkString(1)) - lua.pushBoolean(true) - 1 - } catch { - case e: Throwable => - lua.pushNil() - lua.pushString(Option(e.getMessage).getOrElse(e.toString)) - 2 - }) - lua.setField(-2, "addUser") - - lua.pushScalaFunction(lua => { - lua.pushBoolean(machine.removeUser(lua.checkString(1))) - 1 - }) - lua.setField(-2, "removeUser") - - lua.pushScalaFunction(lua => { - lua.pushNumber(node.globalBuffer) - 1 - }) - lua.setField(-2, "energy") - - lua.pushScalaFunction(lua => { - lua.pushNumber(node.globalBufferSize) - 1 - }) - lua.setField(-2, "maxEnergy") - - // Set the computer table. - lua.setGlobal("computer") - - // Until we get to ingame screens we log to Java's stdout. - lua.pushScalaFunction(lua => { - println((1 to lua.getTop).map(i => lua.`type`(i) match { - case jnlua.LuaType.NIL => "nil" - case jnlua.LuaType.BOOLEAN => lua.toBoolean(i) - case jnlua.LuaType.NUMBER => lua.toNumber(i) - case jnlua.LuaType.STRING => lua.toString(i) - case jnlua.LuaType.TABLE => "table" - case jnlua.LuaType.FUNCTION => "function" - case jnlua.LuaType.THREAD => "thread" - case jnlua.LuaType.LIGHTUSERDATA | jnlua.LuaType.USERDATA => "userdata" - }).mkString(" ")) - 0 - }) - lua.setGlobal("print") - - // Whether bytecode may be loaded directly. - lua.pushScalaFunction(lua => { - lua.pushBoolean(Settings.get.allowBytecode) - 1 - }) - lua.setGlobal("allowBytecode") - - // How long programs may run without yielding before we stop them. - lua.pushNumber(Settings.get.timeout) - lua.setGlobal("timeout") - - // Component interaction stuff. - lua.newTable() - - lua.pushScalaFunction(lua => components.synchronized { - val filter = if (lua.isString(1)) Option(lua.toString(1)) else None - lua.newTable(0, components.size) - for ((address, name) <- components) { - if (filter.isEmpty || name.contains(filter.get)) { - lua.pushString(address) - lua.pushString(name) - lua.rawSet(-3) - } - } - 1 - }) - lua.setField(-2, "list") - - lua.pushScalaFunction(lua => components.synchronized { - components.get(lua.checkString(1)) match { - case Some(name: String) => - lua.pushString(name) - 1 - case _ => - lua.pushNil() - lua.pushString("no such component") - 2 - } - }) - lua.setField(-2, "type") - - lua.pushScalaFunction(lua => { - Option(node.network.node(lua.checkString(1))) match { - case Some(component: server.network.Component) if component.canBeSeenFrom(node) || component == node => - lua.newTable() - for (method <- component.methods()) { - lua.pushString(method) - lua.pushBoolean(component.isDirect(method)) - lua.rawSet(-3) - } - 1 - case _ => - lua.pushNil() - lua.pushString("no such component") - 2 - } - }) - lua.setField(-2, "methods") - - lua.pushScalaFunction(lua => { - val address = lua.checkString(1) - val method = lua.checkString(2) - val args = lua.toSimpleJavaObjects(3) - try { - machine.invoke(address, method, args) match { - case results: Array[_] => - lua.pushBoolean(true) - results.foreach(result => lua.pushValue(result)) - 1 + results.length - case _ => - lua.pushBoolean(true) - 1 - } - } - catch { - case e: Throwable => - if (Settings.get.logLuaCallbackErrors && !e.isInstanceOf[Machine.LimitReachedException]) { - OpenComputers.log.log(Level.WARNING, "Exception in Lua callback.", e) - } - e match { - case _: Machine.LimitReachedException => - 0 - case e: IllegalArgumentException if e.getMessage != null => - lua.pushBoolean(false) - lua.pushString(e.getMessage) - 2 - case e: Throwable if e.getMessage != null => - lua.pushBoolean(true) - lua.pushNil() - lua.pushString(e.getMessage) - if (Settings.get.logLuaCallbackErrors) { - lua.pushString(e.getStackTraceString.replace("\r\n", "\n")) - 4 - } - else 3 - case _: IndexOutOfBoundsException => - lua.pushBoolean(false) - lua.pushString("index out of bounds") - 2 - case _: IllegalArgumentException => - lua.pushBoolean(false) - lua.pushString("bad argument") - 2 - case _: NoSuchMethodException => - lua.pushBoolean(false) - lua.pushString("no such method") - 2 - 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 e: Throwable => - OpenComputers.log.log(Level.WARNING, "Unexpected error in Lua callback.", e) - lua.pushBoolean(true) - lua.pushNil() - lua.pushString("unknown error") - 3 - } - } - }) - lua.setField(-2, "invoke") - - lua.setGlobal("component") - - lua.load(classOf[Machine].getResourceAsStream(Settings.scriptPath + "kernel.lua"), "=kernel") - lua.newThread() // Left as the first value on the stack. - - true - } - - def close() { - kernelMemory = 0 - } - - protected def createState(): Boolean -} \ No newline at end of file diff --git a/li/cil/oc/server/component/machine/LuaJLuaArchitecture.scala b/li/cil/oc/server/component/machine/LuaJLuaArchitecture.scala index 1d80a2989..df2f8d8f1 100644 --- a/li/cil/oc/server/component/machine/LuaJLuaArchitecture.scala +++ b/li/cil/oc/server/component/machine/LuaJLuaArchitecture.scala @@ -2,15 +2,396 @@ package li.cil.oc.server.component.machine import li.cil.oc.server.component.Machine import net.minecraft.nbt.NBTTagCompound -import org.luaj.vm2.lib.jse.JsePlatform +import org.luaj.vm2.lib.jse.{CoerceJavaToLua, JsePlatform} +import li.cil.oc.server.component.Machine.State +import org.luaj.vm2.{LuaFunction, LuaValue, Globals} +import li.cil.oc.util.{GameTimeFormatter, LuaStateFactory} +import li.cil.oc.{OpenComputers, server, Settings} +import java.util.logging.Level +import java.io.{IOException, FileNotFoundException} -class LuaJLuaArchitecture(machine: Machine) extends LuaArchitecture(machine) { - protected def createState() = { +class LuaJLuaArchitecture(machine: Machine) extends Architecture { + private var lua: Globals = _ + + // ----------------------------------------------------------------------- // + + private def node = machine.node + + private def state = machine.state + + private def components = machine.components + + // ----------------------------------------------------------------------- // + + def isInitialized = ??? + + def recomputeMemory() {} + + // ----------------------------------------------------------------------- // + + def runSynchronized() { + try { + lua.set(0, lua.get(0).call()) + } + catch { + case _: OutOfMemoryError => + // 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). + throw new java.lang.OutOfMemoryError("not enough memory") + } + } + + def runThreaded(enterState: State.Value) = ??? + + // ----------------------------------------------------------------------- // + + def init() = { lua = JsePlatform.standardGlobals() + CoerceJavaToLua.coerce() + val os = lua.get("os") + os.set("clock", () => (machine.cpuTime + (System.nanoTime() - machine.cpuStart)) * 10e-10) + + // Date formatting function. + lua.pushScalaFunction(lua => { + val format = + if (lua.getTop > 0 && lua.isString(1)) lua.toString(1) + else "%d/%m/%y %H:%M:%S" + val time = + if (lua.getTop > 1 && lua.isNumber(2)) lua.toNumber(2) * 1000 / 60 / 60 + else machine.worldTime + 6000 + + val dt = GameTimeFormatter.parse(time) + def fmt(format: String) { + if (format == "*t") { + lua.newTable(0, 8) + lua.pushInteger(dt.year) + lua.setField(-2, "year") + lua.pushInteger(dt.month) + lua.setField(-2, "month") + lua.pushInteger(dt.day) + lua.setField(-2, "day") + lua.pushInteger(dt.hour) + lua.setField(-2, "hour") + lua.pushInteger(dt.minute) + lua.setField(-2, "min") + lua.pushInteger(dt.second) + lua.setField(-2, "sec") + lua.pushInteger(dt.weekDay) + lua.setField(-2, "wday") + lua.pushInteger(dt.yearDay) + lua.setField(-2, "yday") + } + else { + lua.pushString(GameTimeFormatter.format(format, dt)) + } + } + + // Just ignore the allowed leading '!', Minecraft has no time zones... + if (format.startsWith("!")) + fmt(format.substring(1)) + else + fmt(format) + 1 + }) + lua.setField(-2, "date") + + // Return ingame time for os.time(). + lua.pushScalaFunction(lua => { + // Game time is in ticks, so that each day has 24000 ticks, meaning + // one hour is game time divided by one thousand. Also, Minecraft + // starts days at 6 o'clock, so we add those six hours. Thus: + // timestamp = (time + 6000) * 60[kh] * 60[km] / 1000[s] + lua.pushNumber((machine.worldTime + 6000) * 60 * 60 / 1000) + 1 + }) + lua.setField(-2, "time") + + // Pop the os table. + lua.pop(1) + + // Computer API, stuff that kinda belongs to os, but we don't want to + // clutter it. + lua.newTable() + + // Allow getting the real world time for timeouts. + lua.pushScalaFunction(lua => { + lua.pushNumber(System.currentTimeMillis() / 1000.0) + 1 + }) + lua.setField(-2, "realTime") + + // 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 uptime() to return real seconds, though, we'll divide it + // accordingly. + lua.pushNumber((machine.worldTime - machine.timeStarted) / 20.0) + 1 + }) + lua.setField(-2, "uptime") + + // Allow the computer to figure out its own id in the component network. + lua.pushScalaFunction(lua => { + Option(node.address) match { + case None => lua.pushNil() + case Some(address) => lua.pushString(address) + } + 1 + }) + lua.setField(-2, "address") + + // Are we a robot? (No this is not a CAPTCHA.) + lua.pushScalaFunction(lua => { + lua.pushBoolean(machine.isRobot) + 1 + }) + lua.setField(-2, "isRobot") + + lua.pushScalaFunction(lua => { + // This is *very* unlikely, but still: avoid this getting larger than + // what we report as the total memory. + lua.pushInteger(((lua.getFreeMemory min (lua.getTotalMemory - kernelMemory)) / ramScale).toInt) + 1 + }) + lua.setField(-2, "freeMemory") + + // Allow the system to read how much memory it uses and has available. + lua.pushScalaFunction(lua => { + lua.pushInteger(((lua.getTotalMemory - kernelMemory) / ramScale).toInt) + 1 + }) + lua.setField(-2, "totalMemory") + + lua.pushScalaFunction(lua => { + lua.pushBoolean(machine.signal(lua.checkString(1), lua.toSimpleJavaObjects(2): _*)) + 1 + }) + lua.setField(-2, "pushSignal") + + // And its ROM address. + lua.pushScalaFunction(lua => { + machine.rom.foreach(rom => Option(rom.node.address) match { + case None => lua.pushNil() + case Some(address) => lua.pushString(address) + }) + 1 + }) + lua.setField(-2, "romAddress") + + // And it's /tmp address... + lua.pushScalaFunction(lua => { + machine.tmp.foreach(tmp => Option(tmp.node.address) match { + case None => lua.pushNil() + case Some(address) => lua.pushString(address) + }) + 1 + }) + lua.setField(-2, "tmpAddress") + + // User management. + lua.pushScalaFunction(lua => { + val users = machine.users + users.foreach(lua.pushString) + users.length + }) + lua.setField(-2, "users") + + lua.pushScalaFunction(lua => try { + machine.addUser(lua.checkString(1)) + lua.pushBoolean(true) + 1 + } catch { + case e: Throwable => + lua.pushNil() + lua.pushString(Option(e.getMessage).getOrElse(e.toString)) + 2 + }) + lua.setField(-2, "addUser") + + lua.pushScalaFunction(lua => { + lua.pushBoolean(machine.removeUser(lua.checkString(1))) + 1 + }) + lua.setField(-2, "removeUser") + + lua.pushScalaFunction(lua => { + lua.pushNumber(node.globalBuffer) + 1 + }) + lua.setField(-2, "energy") + + lua.pushScalaFunction(lua => { + lua.pushNumber(node.globalBufferSize) + 1 + }) + lua.setField(-2, "maxEnergy") + + // Set the computer table. + lua.setGlobal("computer") + + // Until we get to ingame screens we log to Java's stdout. + lua.pushScalaFunction(lua => { + println((1 to lua.getTop).map(i => lua.`type`(i) match { + case LuaType.NIL => "nil" + case LuaType.BOOLEAN => lua.toBoolean(i) + case LuaType.NUMBER => lua.toNumber(i) + case LuaType.STRING => lua.toString(i) + case LuaType.TABLE => "table" + case LuaType.FUNCTION => "function" + case LuaType.THREAD => "thread" + case LuaType.LIGHTUSERDATA | LuaType.USERDATA => "userdata" + }).mkString(" ")) + 0 + }) + lua.setGlobal("print") + + // Whether bytecode may be loaded directly. + lua.pushScalaFunction(lua => { + lua.pushBoolean(Settings.get.allowBytecode) + 1 + }) + lua.setGlobal("allowBytecode") + + // How long programs may run without yielding before we stop them. + lua.pushNumber(Settings.get.timeout) + lua.setGlobal("timeout") + + // Component interaction stuff. + lua.newTable() + + lua.pushScalaFunction(lua => components.synchronized { + val filter = if (lua.isString(1)) Option(lua.toString(1)) else None + lua.newTable(0, components.size) + for ((address, name) <- components) { + if (filter.isEmpty || name.contains(filter.get)) { + lua.pushString(address) + lua.pushString(name) + lua.rawSet(-3) + } + } + 1 + }) + lua.setField(-2, "list") + + lua.pushScalaFunction(lua => components.synchronized { + components.get(lua.checkString(1)) match { + case Some(name: String) => + lua.pushString(name) + 1 + case _ => + lua.pushNil() + lua.pushString("no such component") + 2 + } + }) + lua.setField(-2, "type") + + lua.pushScalaFunction(lua => { + Option(node.network.node(lua.checkString(1))) match { + case Some(component: server.network.Component) if component.canBeSeenFrom(node) || component == node => + lua.newTable() + for (method <- component.methods()) { + lua.pushString(method) + lua.pushBoolean(component.isDirect(method)) + lua.rawSet(-3) + } + 1 + case _ => + lua.pushNil() + lua.pushString("no such component") + 2 + } + }) + lua.setField(-2, "methods") + + lua.pushScalaFunction(lua => { + val address = lua.checkString(1) + val method = lua.checkString(2) + val args = lua.toSimpleJavaObjects(3) + try { + machine.invoke(address, method, args) match { + case results: Array[_] => + lua.pushBoolean(true) + results.foreach(result => lua.pushValue(result)) + 1 + results.length + case _ => + lua.pushBoolean(true) + 1 + } + } + catch { + case e: Throwable => + if (Settings.get.logLuaCallbackErrors && !e.isInstanceOf[Machine.LimitReachedException]) { + OpenComputers.log.log(Level.WARNING, "Exception in Lua callback.", e) + } + e match { + case _: Machine.LimitReachedException => + 0 + case e: IllegalArgumentException if e.getMessage != null => + lua.pushBoolean(false) + lua.pushString(e.getMessage) + 2 + case e: Throwable if e.getMessage != null => + lua.pushBoolean(true) + lua.pushNil() + lua.pushString(e.getMessage) + if (Settings.get.logLuaCallbackErrors) { + lua.pushString(e.getStackTraceString.replace("\r\n", "\n")) + 4 + } + else 3 + case _: IndexOutOfBoundsException => + lua.pushBoolean(false) + lua.pushString("index out of bounds") + 2 + case _: IllegalArgumentException => + lua.pushBoolean(false) + lua.pushString("bad argument") + 2 + case _: NoSuchMethodException => + lua.pushBoolean(false) + lua.pushString("no such method") + 2 + 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 e: Throwable => + OpenComputers.log.log(Level.WARNING, "Unexpected error in Lua callback.", e) + lua.pushBoolean(true) + lua.pushNil() + lua.pushString("unknown error") + 3 + } + } + }) + lua.setField(-2, "invoke") + + lua.setGlobal("component") + + initPerms() + + lua.load(classOf[Machine].getResourceAsStream(Settings.scriptPath + "kernel.lua"), "=kernel", "t") + lua.newThread() // Left as the first value on the stack. + true } - def recomputeMemory() {} + def close() = ??? + + // ----------------------------------------------------------------------- // def load(nbt: NBTTagCompound) {} diff --git a/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala b/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala index c194d4dd8..f3477f17e 100644 --- a/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala +++ b/li/cil/oc/server/component/machine/NativeLuaArchitecture.scala @@ -1,19 +1,40 @@ package li.cil.oc.server.component.machine +import com.naef.jnlua._ +import java.io.{IOException, FileNotFoundException} +import java.util.logging.Level import li.cil.oc.server.component.Machine +import li.cil.oc.util.ExtendedLuaState.extendLuaState +import li.cil.oc.util.{GameTimeFormatter, LuaStateFactory} +import li.cil.oc.{OpenComputers, server, Settings} import net.minecraft.nbt.NBTTagCompound -import li.cil.oc.OpenComputers +import scala.Some +import scala.collection.convert.WrapAsScala._ import scala.collection.mutable -import li.cil.oc.util.LuaStateFactory -import com.naef.jnlua -class NativeLuaArchitecture(machine: Machine) extends LuaArchitecture(machine) { - override var lua: jnlua.LuaState = _ +class NativeLuaArchitecture(val machine: Machine) extends Architecture { + private var lua: LuaState = null + + private var kernelMemory = 0 + + private val ramScale = if (LuaStateFactory.is64Bit) Settings.get.ramScaleFor64Bit else 1.0 + + // ----------------------------------------------------------------------- // + + private def node = machine.node + + private def state = machine.state + + private def components = machine.components + + // ----------------------------------------------------------------------- // + + def isInitialized = kernelMemory > 0 def recomputeMemory() = Option(lua) match { case Some(l) => l.setTotalMemory(Int.MaxValue) - l.gc(jnlua.LuaState.GcAction.COLLECT, 0) + l.gc(LuaState.GcAction.COLLECT, 0) if (kernelMemory > 0) { l.setTotalMemory(kernelMemory + math.ceil(machine.owner.installedMemory * ramScale).toInt) } @@ -22,36 +43,512 @@ class NativeLuaArchitecture(machine: Machine) extends LuaArchitecture(machine) { // ----------------------------------------------------------------------- // - override def init(): Boolean = { - if (super.init()) { - initPerms() - true + def runSynchronized() { + // These three asserts are all guaranteed by run(). + assert(lua.getTop == 2) + assert(lua.isThread(1)) + assert(lua.isFunction(2)) + + try { + // Synchronized call protocol requires the called function to return + // a table, which holds the results of the call, to be passed back + // to the coroutine.yield() that triggered the call. + lua.call(0, 1) + lua.checkType(2, LuaType.TABLE) + } + catch { + case _: LuaMemoryAllocationException => + // 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). + throw new java.lang.OutOfMemoryError("not enough memory") } - else false } - override def close() { + def runThreaded(enterState: Machine.State.Value): ExecutionResult = { + try { + // The kernel thread will always be at stack index one. + assert(lua.isThread(1)) + + if (Settings.get.activeGC) { + // 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) + } + + // Resume the Lua state and remember the number of results we get. + val results = enterState match { + case Machine.State.SynchronizedReturn => + // If we were doing a synchronized call, continue where we left off. + assert(lua.getTop == 2) + assert(lua.isTable(2)) + lua.resume(1, 1) + case Machine.State.Yielded => + if (kernelMemory == 0) { + // We're doing the initialization run. + if (lua.resume(1, 0) > 0) { + // We expect to get nothing here, if we do we had an error. + 0 + } + else { + // 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 (including libraries). 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 = math.max(lua.getTotalMemory - lua.getFreeMemory, 1) + recomputeMemory() + + // Fake zero sleep to avoid stopping if there are no signals. + lua.pushInteger(0) + 1 + } + } + else machine.popSignal() match { + case Some(signal) => + lua.pushString(signal.name) + signal.args.foreach(arg => lua.pushValue(arg)) + lua.resume(1, 1 + signal.args.length) + case _ => + lua.resume(1, 0) + } + case s => throw new AssertionError("Running computer from invalid state " + s.toString) + } + + // Check if the kernel is still alive. + if (lua.status(1) == LuaState.YIELD) { + // If we get one function it must be a wrapper for a synchronized + // call. The protocol is that a closure is pushed that is then called + // from the main server thread, and returns a table, which is in turn + // passed to the originating coroutine.yield(). + if (results == 1 && lua.isFunction(2)) { + new ExecutionResult.SynchronizedCall() + } + // Check if we are shutting down, and if so if we're rebooting. This + // is signalled by boolean values, where `false` means shut down, + // `true` means reboot (i.e shutdown then start again). + else if (results == 1 && lua.isBoolean(2)) { + new ExecutionResult.Shutdown(lua.toBoolean(2)) + } + else { + // If we have a single number, that's how long we may wait before + // resuming the state again. Note that the sleep may be interrupted + // early if a signal arrives in the meantime. If we have something + // else we just process the next signal or wait for one. + val ticks = if (results == 1 && lua.isNumber(2)) (lua.toNumber(2) * 20).toInt else Int.MaxValue + lua.pop(results) + new ExecutionResult.Sleep(ticks) + } + } + // The kernel thread returned. If it threw we'd be in the catch below. + else { + assert(lua.isThread(1)) + // We're expecting the result of a pcall, if anything, so boolean + (result | string). + if (!lua.isBoolean(2) || !(lua.isString(3) || lua.isNil(3))) { + OpenComputers.log.warning("Kernel returned unexpected results.") + } + // The pcall *should* never return normally... but check for it nonetheless. + if (lua.toBoolean(2)) { + OpenComputers.log.warning("Kernel stopped unexpectedly.") + new ExecutionResult.Shutdown(false) + } + else { + lua.setTotalMemory(Int.MaxValue) + val error = lua.toString(3) + if (error != null) new ExecutionResult.Error(error) + else new ExecutionResult.Error("unknown error") + } + } + } + catch { + case e: LuaRuntimeException => + OpenComputers.log.warning("Kernel crashed. This is a bug!\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat ")) + new ExecutionResult.Error("kernel panic: this is a bug, check your log file and report it") + case e: LuaGcMetamethodException => + if (e.getMessage != null) new ExecutionResult.Error("kernel panic:\n" + e.getMessage) + else new ExecutionResult.Error("kernel panic:\nerror in garbage collection metamethod") + case e: LuaMemoryAllocationException => + new ExecutionResult.Error("not enough memory") + case e: java.lang.Error if e.getMessage == "not enough memory" => + new ExecutionResult.Error("not enough memory") + case e: Throwable => + OpenComputers.log.log(Level.WARNING, "Unexpected error in kernel. This is a bug!\n", e) + new ExecutionResult.Error("kernel panic: this is a bug, check your log file and report it") + } + } + + // ----------------------------------------------------------------------- // + + def init(): Boolean = { + // Creates a new state with all base libraries and the persistence library + // loaded into it. This means the state has much more power than it + // rightfully should have, so we sandbox it a bit in the following. + LuaStateFactory.createState() match { + case None => + lua = null + machine.message = Some("native libraries not available") + return false + case Some(value) => lua = value + } + + // Push a couple of functions that override original Lua API functions or + // that add new functionality to it. + lua.getGlobal("os") + + // Custom os.clock() implementation returning the time the computer has + // been actively running, instead of the native library... + lua.pushScalaFunction(lua => { + lua.pushNumber((machine.cpuTime + (System.nanoTime() - machine.cpuStart)) * 10e-10) + 1 + }) + lua.setField(-2, "clock") + + // Date formatting function. + lua.pushScalaFunction(lua => { + val format = + if (lua.getTop > 0 && lua.isString(1)) lua.toString(1) + else "%d/%m/%y %H:%M:%S" + val time = + if (lua.getTop > 1 && lua.isNumber(2)) lua.toNumber(2) * 1000 / 60 / 60 + else machine.worldTime + 6000 + + val dt = GameTimeFormatter.parse(time) + def fmt(format: String) { + if (format == "*t") { + lua.newTable(0, 8) + lua.pushInteger(dt.year) + lua.setField(-2, "year") + lua.pushInteger(dt.month) + lua.setField(-2, "month") + lua.pushInteger(dt.day) + lua.setField(-2, "day") + lua.pushInteger(dt.hour) + lua.setField(-2, "hour") + lua.pushInteger(dt.minute) + lua.setField(-2, "min") + lua.pushInteger(dt.second) + lua.setField(-2, "sec") + lua.pushInteger(dt.weekDay) + lua.setField(-2, "wday") + lua.pushInteger(dt.yearDay) + lua.setField(-2, "yday") + } + else { + lua.pushString(GameTimeFormatter.format(format, dt)) + } + } + + // Just ignore the allowed leading '!', Minecraft has no time zones... + if (format.startsWith("!")) + fmt(format.substring(1)) + else + fmt(format) + 1 + }) + lua.setField(-2, "date") + + // Return ingame time for os.time(). + lua.pushScalaFunction(lua => { + // Game time is in ticks, so that each day has 24000 ticks, meaning + // one hour is game time divided by one thousand. Also, Minecraft + // starts days at 6 o'clock, so we add those six hours. Thus: + // timestamp = (time + 6000) * 60[kh] * 60[km] / 1000[s] + lua.pushNumber((machine.worldTime + 6000) * 60 * 60 / 1000) + 1 + }) + lua.setField(-2, "time") + + // Pop the os table. + lua.pop(1) + + // Computer API, stuff that kinda belongs to os, but we don't want to + // clutter it. + lua.newTable() + + // Allow getting the real world time for timeouts. + lua.pushScalaFunction(lua => { + lua.pushNumber(System.currentTimeMillis() / 1000.0) + 1 + }) + lua.setField(-2, "realTime") + + // 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 uptime() to return real seconds, though, we'll divide it + // accordingly. + lua.pushNumber((machine.worldTime - machine.timeStarted) / 20.0) + 1 + }) + lua.setField(-2, "uptime") + + // Allow the computer to figure out its own id in the component network. + lua.pushScalaFunction(lua => { + Option(node.address) match { + case None => lua.pushNil() + case Some(address) => lua.pushString(address) + } + 1 + }) + lua.setField(-2, "address") + + // Are we a robot? (No this is not a CAPTCHA.) + lua.pushScalaFunction(lua => { + lua.pushBoolean(machine.isRobot) + 1 + }) + lua.setField(-2, "isRobot") + + lua.pushScalaFunction(lua => { + // This is *very* unlikely, but still: avoid this getting larger than + // what we report as the total memory. + lua.pushInteger(((lua.getFreeMemory min (lua.getTotalMemory - kernelMemory)) / ramScale).toInt) + 1 + }) + lua.setField(-2, "freeMemory") + + // Allow the system to read how much memory it uses and has available. + lua.pushScalaFunction(lua => { + lua.pushInteger(((lua.getTotalMemory - kernelMemory) / ramScale).toInt) + 1 + }) + lua.setField(-2, "totalMemory") + + lua.pushScalaFunction(lua => { + lua.pushBoolean(machine.signal(lua.checkString(1), lua.toSimpleJavaObjects(2): _*)) + 1 + }) + lua.setField(-2, "pushSignal") + + // And its ROM address. + lua.pushScalaFunction(lua => { + machine.rom.foreach(rom => Option(rom.node.address) match { + case None => lua.pushNil() + case Some(address) => lua.pushString(address) + }) + 1 + }) + lua.setField(-2, "romAddress") + + // And it's /tmp address... + lua.pushScalaFunction(lua => { + machine.tmp.foreach(tmp => Option(tmp.node.address) match { + case None => lua.pushNil() + case Some(address) => lua.pushString(address) + }) + 1 + }) + lua.setField(-2, "tmpAddress") + + // User management. + lua.pushScalaFunction(lua => { + val users = machine.users + users.foreach(lua.pushString) + users.length + }) + lua.setField(-2, "users") + + lua.pushScalaFunction(lua => try { + machine.addUser(lua.checkString(1)) + lua.pushBoolean(true) + 1 + } catch { + case e: Throwable => + lua.pushNil() + lua.pushString(Option(e.getMessage).getOrElse(e.toString)) + 2 + }) + lua.setField(-2, "addUser") + + lua.pushScalaFunction(lua => { + lua.pushBoolean(machine.removeUser(lua.checkString(1))) + 1 + }) + lua.setField(-2, "removeUser") + + lua.pushScalaFunction(lua => { + lua.pushNumber(node.globalBuffer) + 1 + }) + lua.setField(-2, "energy") + + lua.pushScalaFunction(lua => { + lua.pushNumber(node.globalBufferSize) + 1 + }) + lua.setField(-2, "maxEnergy") + + // Set the computer table. + lua.setGlobal("computer") + + // Until we get to ingame screens we log to Java's stdout. + lua.pushScalaFunction(lua => { + println((1 to lua.getTop).map(i => lua.`type`(i) match { + case LuaType.NIL => "nil" + case LuaType.BOOLEAN => lua.toBoolean(i) + case LuaType.NUMBER => lua.toNumber(i) + case LuaType.STRING => lua.toString(i) + case LuaType.TABLE => "table" + case LuaType.FUNCTION => "function" + case LuaType.THREAD => "thread" + case LuaType.LIGHTUSERDATA | LuaType.USERDATA => "userdata" + }).mkString(" ")) + 0 + }) + lua.setGlobal("print") + + // Whether bytecode may be loaded directly. + lua.pushScalaFunction(lua => { + lua.pushBoolean(Settings.get.allowBytecode) + 1 + }) + lua.setGlobal("allowBytecode") + + // How long programs may run without yielding before we stop them. + lua.pushNumber(Settings.get.timeout) + lua.setGlobal("timeout") + + // Component interaction stuff. + lua.newTable() + + lua.pushScalaFunction(lua => components.synchronized { + val filter = if (lua.isString(1)) Option(lua.toString(1)) else None + lua.newTable(0, components.size) + for ((address, name) <- components) { + if (filter.isEmpty || name.contains(filter.get)) { + lua.pushString(address) + lua.pushString(name) + lua.rawSet(-3) + } + } + 1 + }) + lua.setField(-2, "list") + + lua.pushScalaFunction(lua => components.synchronized { + components.get(lua.checkString(1)) match { + case Some(name: String) => + lua.pushString(name) + 1 + case _ => + lua.pushNil() + lua.pushString("no such component") + 2 + } + }) + lua.setField(-2, "type") + + lua.pushScalaFunction(lua => { + Option(node.network.node(lua.checkString(1))) match { + case Some(component: server.network.Component) if component.canBeSeenFrom(node) || component == node => + lua.newTable() + for (method <- component.methods()) { + lua.pushString(method) + lua.pushBoolean(component.isDirect(method)) + lua.rawSet(-3) + } + 1 + case _ => + lua.pushNil() + lua.pushString("no such component") + 2 + } + }) + lua.setField(-2, "methods") + + lua.pushScalaFunction(lua => { + val address = lua.checkString(1) + val method = lua.checkString(2) + val args = lua.toSimpleJavaObjects(3) + try { + machine.invoke(address, method, args) match { + case results: Array[_] => + lua.pushBoolean(true) + results.foreach(result => lua.pushValue(result)) + 1 + results.length + case _ => + lua.pushBoolean(true) + 1 + } + } + catch { + case e: Throwable => + if (Settings.get.logLuaCallbackErrors && !e.isInstanceOf[Machine.LimitReachedException]) { + OpenComputers.log.log(Level.WARNING, "Exception in Lua callback.", e) + } + e match { + case _: Machine.LimitReachedException => + 0 + case e: IllegalArgumentException if e.getMessage != null => + lua.pushBoolean(false) + lua.pushString(e.getMessage) + 2 + case e: Throwable if e.getMessage != null => + lua.pushBoolean(true) + lua.pushNil() + lua.pushString(e.getMessage) + if (Settings.get.logLuaCallbackErrors) { + lua.pushString(e.getStackTraceString.replace("\r\n", "\n")) + 4 + } + else 3 + case _: IndexOutOfBoundsException => + lua.pushBoolean(false) + lua.pushString("index out of bounds") + 2 + case _: IllegalArgumentException => + lua.pushBoolean(false) + lua.pushString("bad argument") + 2 + case _: NoSuchMethodException => + lua.pushBoolean(false) + lua.pushString("no such method") + 2 + 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 e: Throwable => + OpenComputers.log.log(Level.WARNING, "Unexpected error in Lua callback.", e) + lua.pushBoolean(true) + lua.pushNil() + lua.pushString("unknown error") + 3 + } + } + }) + lua.setField(-2, "invoke") + + lua.setGlobal("component") + + initPerms() + + lua.load(classOf[Machine].getResourceAsStream(Settings.scriptPath + "kernel.lua"), "=kernel", "t") + lua.newThread() // Left as the first value on the stack. + + true + } + + def close() { if (lua != null) { lua.setTotalMemory(Integer.MAX_VALUE) lua.close() } lua = null - super.close() - } - - protected def createState() = { - // Creates a new state with all base libraries and the persistence library - // loaded into it. This means the state has much more power than it - // rightfully should have, so we sandbox it a bit in the following. - LuaStateFactory.createState() match { - case Some(value) => - lua = value - true - case _ => - lua = null - machine.message = Some("native libraries not available") - false - } + kernelMemory = 0 } // ----------------------------------------------------------------------- // @@ -82,7 +579,7 @@ class NativeLuaArchitecture(machine: Machine) extends LuaArchitecture(machine) { kernelMemory = (nbt.getInteger("kernelMemory") * ramScale).toInt } catch { - case e: jnlua.LuaRuntimeException => + case e: LuaRuntimeException => OpenComputers.log.warning("Could not unpersist computer.\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat ")) state.push(Machine.State.Stopping) } @@ -109,7 +606,7 @@ class NativeLuaArchitecture(machine: Machine) extends LuaArchitecture(machine) { nbt.setInteger("kernelMemory", math.ceil(kernelMemory / ramScale).toInt) } catch { - case e: jnlua.LuaRuntimeException => + case e: LuaRuntimeException => OpenComputers.log.warning("Could not persist computer.\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat ")) nbt.removeTag("state") } @@ -183,15 +680,15 @@ class NativeLuaArchitecture(machine: Machine) extends LuaArchitecture(machine) { lua.getGlobal("_G") /* ... perms uperms k v */ flattenAndStore() /* ... perms uperms */ - lua.setField(jnlua.LuaState.REGISTRYINDEX, "uperms") /* ... perms */ - lua.setField(jnlua.LuaState.REGISTRYINDEX, "perms") /* ... */ + lua.setField(LuaState.REGISTRYINDEX, "uperms") /* ... perms */ + lua.setField(LuaState.REGISTRYINDEX, "perms") /* ... */ } private def persist(index: Int): Array[Byte] = { lua.getGlobal("eris") /* ... eris */ lua.getField(-1, "persist") /* ... eris persist */ if (lua.isFunction(-1)) { - lua.getField(jnlua.LuaState.REGISTRYINDEX, "perms") /* ... eris persist perms */ + lua.getField(LuaState.REGISTRYINDEX, "perms") /* ... eris persist perms */ lua.pushValue(index) // ... eris persist perms obj try { lua.call(2, 1) // ... eris str? @@ -215,7 +712,7 @@ class NativeLuaArchitecture(machine: Machine) extends LuaArchitecture(machine) { lua.getGlobal("eris") // ... eris lua.getField(-1, "unpersist") // ... eris unpersist if (lua.isFunction(-1)) { - lua.getField(jnlua.LuaState.REGISTRYINDEX, "uperms") /* ... eris persist uperms */ + lua.getField(LuaState.REGISTRYINDEX, "uperms") /* ... eris persist uperms */ lua.pushByteArray(value) // ... eris unpersist uperms str lua.call(2, 1) // ... eris obj lua.insert(-2) // ... obj eris @@ -225,4 +722,4 @@ class NativeLuaArchitecture(machine: Machine) extends LuaArchitecture(machine) { lua.pop(1) false } -} +} \ No newline at end of file diff --git a/li/cil/oc/util/ExtendedLuaState.scala b/li/cil/oc/util/ExtendedLuaState.scala index 5bd9f89c4..df466c68c 100644 --- a/li/cil/oc/util/ExtendedLuaState.scala +++ b/li/cil/oc/util/ExtendedLuaState.scala @@ -1,19 +1,22 @@ package li.cil.oc.util -import com.naef.jnlua.LuaType +import com.naef.jnlua.{LuaType, JavaFunction, LuaState} import li.cil.oc.OpenComputers import scala.collection.convert.WrapAsScala._ import scala.collection.mutable import scala.language.implicitConversions import scala.math.ScalaNumber import scala.runtime.BoxedUnit -import li.cil.oc.util.LuaState object ExtendedLuaState { implicit def extendLuaState(state: LuaState) = new ExtendedLuaState(state) class ExtendedLuaState(val lua: LuaState) { + def pushScalaFunction(f: (LuaState) => Int) = lua.pushJavaFunction(new JavaFunction { + override def invoke(state: LuaState) = f(state) + }) + def pushValue(value: Any) { (value match { case number: ScalaNumber => number.underlying @@ -73,7 +76,7 @@ object ExtendedLuaState { case LuaType.BOOLEAN => Boolean.box(lua.toBoolean(index)) case LuaType.NUMBER => Double.box(lua.toNumber(index)) case LuaType.STRING => lua.toByteArray(index) - case LuaType.TABLE => lua.toMap(index) + case LuaType.TABLE => lua.toJavaObject(index, classOf[java.util.Map[_, _]]) case _ => null } diff --git a/li/cil/oc/util/LuaState.scala b/li/cil/oc/util/LuaState.scala deleted file mode 100644 index c9795a2ff..000000000 --- a/li/cil/oc/util/LuaState.scala +++ /dev/null @@ -1,311 +0,0 @@ -package li.cil.oc.util - -import com.naef.jnlua -import java.io.InputStream -import org.luaj -import com.naef.jnlua.LuaState.GcAction -import com.naef.jnlua.LuaType - -trait LuaState { - def getTop: Int - - def setTop(n: Int) - - def pop(n: Int) - - def `type`(index: Int): jnlua.LuaType - - def isNil(index: Int): Boolean - - def isBoolean(index: Int): Boolean - - def isNumber(index: Int): Boolean - - def isString(index: Int): Boolean - - def isTable(index: Int): Boolean - - def isFunction(index: Int): Boolean - - def isThread(index: Int): Boolean - - def checkType(index: Int, what: jnlua.LuaType) - - def checkArg(index: Int, condition: Boolean, message: String) - - def checkInteger(index: Int): Int - - def checkString(index: Int): String - - def toBoolean(index: Int): Boolean - - def toNumber(index: Int): Double - - def toByteArray(index: Int): Array[Byte] - - def toString(index: Int): String - - def toMap(index: Int): java.util.Map[_, _] - - def insert(index: Int) - - def pushNil() - - def pushBoolean(value: Boolean) - - def pushInteger(value: Int) - - def pushNumber(value: Double) - - def pushByteArray(value: Array[Byte]) - - def pushString(value: String) - - def pushValue(index: Int) - - def pushScalaFunction(f: LuaState => Int) - - def load(stream: InputStream, name: String) - - def call(index: Int, argCount: Int) - - def resume(index: Int, argCount: Int): Int - - def status(index: Int): Int - - def gc(what: com.naef.jnlua.LuaState.GcAction, arg: Int) - - def newTable(arrayCount: Int = 0, recordCount: Int = 0) - - def newThread() - - def getTable(index: Int) - - def setTable(index: Int) - - def setField(index: Int, key: String) - - def getField(index: Int, key: String) - - def rawSet(index: Int) - - def rawSet(index: Int, key: Int) - - def getGlobal(key: String) - - def setGlobal(key: String) - - def next(index: Int): Boolean - - def getFreeMemory: Int - - def getTotalMemory: Int - - def setTotalMemory(value: Int) - - def close() -} - -object LuaState { - implicit def wrap(lua: jnlua.LuaState): LuaState = new LuaState { - def getTop = lua.getTop - - def setTop(n: Int) = lua.setTop(n) - - def pop(n: Int) = lua.pop(n) - - def `type`(index: Int) = lua.`type`(index) - - def isNil(index: Int) = lua.isNil(index) - - def isBoolean(index: Int) = lua.isBoolean(index) - - def isNumber(index: Int) = lua.isNumber(index) - - def isString(index: Int) = lua.isString(index) - - def isTable(index: Int) = lua.isTable(index) - - def isFunction(index: Int) = lua.isFunction(index) - - def isThread(index: Int) = lua.isThread(index) - - def checkType(index: Int, what: jnlua.LuaType) = lua.checkType(index, what) - - def checkArg(index: Int, condition: Boolean, message: String) = lua.checkArg(index, condition, message) - - def checkInteger(index: Int) = lua.checkInteger(index) - - def checkString(index: Int) = lua.checkString(index) - - def toBoolean(index: Int) = lua.toBoolean(index) - - def toNumber(index: Int) = lua.toNumber(index) - - def toByteArray(index: Int) = lua.toByteArray(index) - - def toString(index: Int) = lua.toString(index) - - def toMap(index: Int) = lua.toJavaObject(index, classOf[java.util.Map[_, _]]) - - def insert(index: Int) = lua.insert(index) - - def pushNil() = lua.pushNil() - - def pushBoolean(value: Boolean) = lua.pushBoolean(value) - - def pushInteger(value: Int) = lua.pushInteger(value) - - def pushNumber(value: Double) = lua.pushNumber(value) - - def pushByteArray(value: Array[Byte]) = lua.pushByteArray(value) - - def pushString(value: String) = lua.pushString(value) - - def pushValue(index: Int) = lua.pushValue(index) - - def pushScalaFunction(f: (LuaState) => Int) = lua.pushJavaFunction(new jnlua.JavaFunction { - def invoke(state: jnlua.LuaState) = f(LuaState.wrap(state)) - }) - - def load(stream: InputStream, name: String) = lua.load(stream, name, "t") - - def call(index: Int, argCount: Int) = lua.call(index, argCount) - - def resume(index: Int, argCount: Int) = lua.resume(index, argCount) - - def status(index: Int) = lua.status(index) - - def gc(what: jnlua.LuaState.GcAction, arg: Int) = lua.gc(what, arg) - - def newTable(arrayCount: Int, recordCount: Int) = lua.newTable(arrayCount, recordCount) - - def newThread() = lua.newThread() - - def getTable(index: Int) = lua.getTable(index) - - def setTable(index: Int) = lua.setTable(index) - - def setField(index: Int, key: String) = lua.setField(index, key) - - def getField(index: Int, key: String) = lua.getField(index, key) - - def rawSet(index: Int) = lua.rawSet(index) - - def rawSet(index: Int, key: Int) = lua.rawSet(index, key) - - def getGlobal(key: String) = lua.getGlobal(key) - - def setGlobal(key: String) = lua.setGlobal(key) - - def next(index: Int) = lua.next(index) - - def getFreeMemory = lua.getFreeMemory - - def getTotalMemory = lua.getTotalMemory - - def setTotalMemory(value: Int) = lua.setTotalMemory(value) - - def close() = lua.close() - } - - implicit def wrap(lua: luaj.vm2.Globals): LuaState = new LuaState { - def getTop = ??? - - def setTop(n: Int) = ??? - - def pop(n: Int) = ??? - - def `type`(index: Int) = ??? - - def isNil(index: Int) = ??? - - def isBoolean(index: Int) = ??? - - def isNumber(index: Int) = ??? - - def isString(index: Int) = ??? - - def isTable(index: Int) = ??? - - def isFunction(index: Int) = ??? - - def isThread(index: Int) = ??? - - def checkType(index: Int, what: LuaType) = ??? - - def checkArg(index: Int, condition: Boolean, message: String) = ??? - - def checkInteger(index: Int) = ??? - - def checkString(index: Int) = ??? - - def toBoolean(index: Int) = ??? - - def toNumber(index: Int) = ??? - - def toByteArray(index: Int) = ??? - - def toString(index: Int) = ??? - - def toMap(index: Int) = ??? - - def insert(index: Int) = ??? - - def pushNil() = ??? - - def pushBoolean(value: Boolean) = ??? - - def pushInteger(value: Int) = ??? - - def pushNumber(value: Double) = ??? - - def pushByteArray(value: Array[Byte]) = ??? - - def pushString(value: String) = ??? - - def pushValue(index: Int) = ??? - - def pushScalaFunction(f: (LuaState) => Int) = ??? - - def load(stream: InputStream, name: String) = ??? - - def call(index: Int, argCount: Int) = ??? - - def resume(index: Int, argCount: Int) = ??? - - def status(index: Int) = ??? - - def gc(what: GcAction, arg: Int) = ??? - - def newTable(arrayCount: Int, recordCount: Int) = ??? - - def newThread() = ??? - - def getTable(index: Int) = ??? - - def setTable(index: Int) = ??? - - def setField(index: Int, key: String) = ??? - - def getField(index: Int, key: String) = ??? - - def rawSet(index: Int) = ??? - - def rawSet(index: Int, key: Int) = ??? - - def getGlobal(key: String) = ??? - - def setGlobal(key: String) = ??? - - def next(index: Int) = ??? - - def getFreeMemory = ??? - - def getTotalMemory = ??? - - def setTotalMemory(value: Int) = ??? - - def close() = ??? - } -} \ No newline at end of file diff --git a/li/cil/oc/util/LuaStateFactory.scala b/li/cil/oc/util/LuaStateFactory.scala index 0ded83bb4..94f4dbdbb 100644 --- a/li/cil/oc/util/LuaStateFactory.scala +++ b/li/cil/oc/util/LuaStateFactory.scala @@ -1,13 +1,14 @@ package li.cil.oc.util -import LuaState._ import com.naef.jnlua +import com.naef.jnlua.LuaState import com.naef.jnlua.NativeSupport.Loader import java.io.File import java.io.FileOutputStream import java.nio.channels.Channels import java.util.logging.Level import li.cil.oc.server.component.Machine +import li.cil.oc.util.ExtendedLuaState._ import li.cil.oc.{OpenComputers, Settings} import org.apache.commons.lang3.SystemUtils import org.lwjgl.LWJGLUtil