|  | 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.util.ExtendedLuaState.extendLuaState | 
|  | import li.cil.oc.util.{GameTimeFormatter, LuaStateFactory} | 
|  | import li.cil.oc.{OpenComputers, server, Settings} | 
|  | import net.minecraft.nbt.NBTTagCompound | 
|  | import scala.Some | 
|  | import scala.collection.convert.WrapAsScala._ | 
|  | import scala.collection.mutable | 
|  |  | 
|  | 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(LuaState.GcAction.COLLECT, 0) | 
|  | if (kernelMemory > 0) { | 
|  | l.setTotalMemory(kernelMemory + math.ceil(machine.owner.installedMemory * ramScale).toInt) | 
|  | } | 
|  | case _ => | 
|  | } | 
|  |  | 
|  | // ----------------------------------------------------------------------- // | 
|  |  | 
|  | 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") | 
|  | } | 
|  | } | 
|  |  | 
|  | 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 | 
|  | kernelMemory = 0 | 
|  | } | 
|  |  | 
|  | // ----------------------------------------------------------------------- // | 
|  |  | 
|  | def load(nbt: NBTTagCompound) { | 
|  | // Unlimit memory use while unpersisting. | 
|  | lua.setTotalMemory(Integer.MAX_VALUE) | 
|  |  | 
|  | try { | 
|  | // Try unpersisting Lua, because that's what all of the rest depends | 
|  | // on. First, clear the stack, meaning the current kernel. | 
|  | lua.setTop(0) | 
|  |  | 
|  | unpersist(nbt.getByteArray("kernel")) | 
|  | if (!lua.isThread(1)) { | 
|  | // This shouldn't really happen, but there's a chance it does if | 
|  | // the save was corrupt (maybe someone modified the Lua files). | 
|  | throw new IllegalArgumentException("Invalid kernel.") | 
|  | } | 
|  | if (state.contains(Machine.State.SynchronizedCall) || state.contains(Machine.State.SynchronizedReturn)) { | 
|  | unpersist(nbt.getByteArray("stack")) | 
|  | if (!(if (state.contains(Machine.State.SynchronizedCall)) lua.isFunction(2) else lua.isTable(2))) { | 
|  | // Same as with the above, should not really happen normally, but | 
|  | // could for the same reasons. | 
|  | throw new IllegalArgumentException("Invalid stack.") | 
|  | } | 
|  | } | 
|  |  | 
|  | kernelMemory = (nbt.getInteger("kernelMemory") * ramScale).toInt | 
|  | } catch { | 
|  | case e: LuaRuntimeException => | 
|  | OpenComputers.log.warning("Could not unpersist computer.\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat ")) | 
|  | state.push(Machine.State.Stopping) | 
|  | } | 
|  |  | 
|  | // Limit memory again. | 
|  | recomputeMemory() | 
|  | } | 
|  |  | 
|  | def save(nbt: NBTTagCompound) { | 
|  | // Unlimit memory while persisting. | 
|  | lua.setTotalMemory(Integer.MAX_VALUE) | 
|  |  | 
|  | try { | 
|  | // Try persisting Lua, because that's what all of the rest depends on. | 
|  | // Save the kernel state (which is always at stack index one). | 
|  | assert(lua.isThread(1)) | 
|  | nbt.setByteArray("kernel", persist(1)) | 
|  | // While in a driver call we have one object on the global stack: either | 
|  | // the function to call the driver with, or the result of the call. | 
|  | if (state.contains(Machine.State.SynchronizedCall) || state.contains(Machine.State.SynchronizedReturn)) { | 
|  | assert(if (state.contains(Machine.State.SynchronizedCall)) lua.isFunction(2) else lua.isTable(2)) | 
|  | nbt.setByteArray("stack", persist(2)) | 
|  | } | 
|  |  | 
|  | nbt.setInteger("kernelMemory", math.ceil(kernelMemory / ramScale).toInt) | 
|  | } catch { | 
|  | case e: LuaRuntimeException => | 
|  | OpenComputers.log.warning("Could not persist computer.\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat ")) | 
|  | nbt.removeTag("state") | 
|  | } | 
|  |  | 
|  | // Limit memory again. | 
|  | recomputeMemory() | 
|  | } | 
|  |  | 
|  | private def initPerms() { | 
|  | // These tables must contain all java callbacks (i.e. C functions, since | 
|  | // they are wrapped on the native side using a C function, of course). | 
|  | // They are used when persisting/unpersisting the state so that the | 
|  | // persistence library knows which values it doesn't have to serialize | 
|  | // (since it cannot persist C functions). | 
|  | lua.newTable() /* ... perms */ | 
|  | lua.newTable() /* ... uperms */ | 
|  |  | 
|  | val perms = lua.getTop - 1 | 
|  | val uperms = lua.getTop | 
|  |  | 
|  | def flattenAndStore() { | 
|  | /* ... k v */ | 
|  | // We only care for tables and functions, any value types are safe. | 
|  | if (lua.isFunction(-1) || lua.isTable(-1)) { | 
|  | lua.pushValue(-2) /* ... k v k */ | 
|  | lua.getTable(uperms) /* ... k v uperms[k] */ | 
|  | assert(lua.isNil(-1), "duplicate permanent value named " + lua.toString(-3)) | 
|  | lua.pop(1) /* ... k v */ | 
|  | // If we have aliases its enough to store the value once. | 
|  | lua.pushValue(-1) /* ... k v v */ | 
|  | lua.getTable(perms) /* ... k v perms[v] */ | 
|  | val isNew = lua.isNil(-1) | 
|  | lua.pop(1) /* ... k v */ | 
|  | if (isNew) { | 
|  | lua.pushValue(-1) /* ... k v v */ | 
|  | lua.pushValue(-3) /* ... k v v k */ | 
|  | lua.rawSet(perms) /* ... k v ; perms[v] = k */ | 
|  | lua.pushValue(-2) /* ... k v k */ | 
|  | lua.pushValue(-2) /* ... k v k v */ | 
|  | lua.rawSet(uperms) /* ... k v ; uperms[k] = v */ | 
|  | // Recurse into tables. | 
|  | if (lua.isTable(-1)) { | 
|  | // Enforce a deterministic order when determining the keys, to ensure | 
|  | // the keys are the same when unpersisting again. | 
|  | val key = lua.toString(-2) | 
|  | val childKeys = mutable.ArrayBuffer.empty[String] | 
|  | lua.pushNil() /* ... k v nil */ | 
|  | while (lua.next(-2)) { | 
|  | /* ... k v ck cv */ | 
|  | lua.pop(1) /* ... k v ck */ | 
|  | childKeys += lua.toString(-1) | 
|  | } | 
|  | /* ... k v */ | 
|  | childKeys.sortWith((a, b) => a.compareTo(b) < 0) | 
|  | for (childKey <- childKeys) { | 
|  | lua.pushString(key + "." + childKey) /* ... k v ck */ | 
|  | lua.getField(-2, childKey) /* ... k v ck cv */ | 
|  | flattenAndStore() /* ... k v */ | 
|  | } | 
|  | /* ... k v */ | 
|  | } | 
|  | /* ... k v */ | 
|  | } | 
|  | /* ... k v */ | 
|  | } | 
|  | lua.pop(2) /* ... */ | 
|  | } | 
|  |  | 
|  | // Mark everything that's globally reachable at this point as permanent. | 
|  | lua.pushString("_G") /* ... perms uperms k */ | 
|  | lua.getGlobal("_G") /* ... perms uperms k v */ | 
|  |  | 
|  | flattenAndStore() /* ... perms uperms */ | 
|  | 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(LuaState.REGISTRYINDEX, "perms") /* ... eris persist perms */ | 
|  | lua.pushValue(index) // ... eris persist perms obj | 
|  | try { | 
|  | lua.call(2, 1) // ... eris str? | 
|  | } catch { | 
|  | case e: Throwable => | 
|  | lua.pop(1) | 
|  | throw e | 
|  | } | 
|  | if (lua.isString(-1)) { | 
|  | // ... eris str | 
|  | val result = lua.toByteArray(-1) | 
|  | lua.pop(2) // ... | 
|  | return result | 
|  | } // ... eris :( | 
|  | } // ... eris :( | 
|  | lua.pop(2) // ... | 
|  | Array[Byte]() | 
|  | } | 
|  |  | 
|  | private def unpersist(value: Array[Byte]): Boolean = { | 
|  | lua.getGlobal("eris") // ... eris | 
|  | lua.getField(-1, "unpersist") // ... eris unpersist | 
|  | if (lua.isFunction(-1)) { | 
|  | 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 | 
|  | lua.pop(1) | 
|  | return true | 
|  | } // ... :( | 
|  | lua.pop(1) | 
|  | false | 
|  | } | 
|  | } |