mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-19 04:06:43 -04:00
refactored computer class to extract any language specific logic (Lua) from the main running logic. this makes it a little less massive and should make it easier to add other language implementations, such as a Java implementation of Lua, or even altogether different languages such as an assembly emulator, for example
This commit is contained in:
parent
775236dc9d
commit
e560a9ee47
@ -1,16 +1,14 @@
|
|||||||
package li.cil.oc.server.component
|
package li.cil.oc.server.component
|
||||||
|
|
||||||
import com.naef.jnlua._
|
|
||||||
import java.io.{FileNotFoundException, IOException}
|
|
||||||
import java.util.logging.Level
|
import java.util.logging.Level
|
||||||
import li.cil.oc.api
|
import li.cil.oc.api
|
||||||
import li.cil.oc.api.network._
|
import li.cil.oc.api.network._
|
||||||
import li.cil.oc.common.tileentity
|
import li.cil.oc.common.tileentity
|
||||||
import li.cil.oc.server
|
import li.cil.oc.server
|
||||||
import li.cil.oc.server.PacketSender
|
import li.cil.oc.server.PacketSender
|
||||||
import li.cil.oc.util.ExtendedLuaState.extendLuaState
|
import li.cil.oc.server.component.machine.{ExecutionResult, LuaArchitecture}
|
||||||
import li.cil.oc.util.ExtendedNBT._
|
import li.cil.oc.util.ExtendedNBT._
|
||||||
import li.cil.oc.util.{ThreadPoolFactory, GameTimeFormatter, LuaStateFactory}
|
import li.cil.oc.util.ThreadPoolFactory
|
||||||
import li.cil.oc.{OpenComputers, Settings}
|
import li.cil.oc.{OpenComputers, Settings}
|
||||||
import net.minecraft.entity.player.EntityPlayer
|
import net.minecraft.entity.player.EntityPlayer
|
||||||
import net.minecraft.nbt._
|
import net.minecraft.nbt._
|
||||||
@ -19,7 +17,6 @@ import net.minecraft.server.integrated.IntegratedServer
|
|||||||
import net.minecraft.world.World
|
import net.minecraft.world.World
|
||||||
import scala.Array.canBuildFrom
|
import scala.Array.canBuildFrom
|
||||||
import scala.Some
|
import scala.Some
|
||||||
import scala.collection.convert.WrapAsScala._
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
||||||
class Machine(val owner: Machine.Owner) extends ManagedComponent with Context with Runnable {
|
class Machine(val owner: Machine.Owner) extends ManagedComponent with Context with Runnable {
|
||||||
@ -36,13 +33,11 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
fromMemory(Settings.get.tmpSize * 1024), "tmpfs"))
|
fromMemory(Settings.get.tmpSize * 1024), "tmpfs"))
|
||||||
} else None
|
} else None
|
||||||
|
|
||||||
private val state = mutable.Stack(Machine.State.Stopped)
|
private val architecture = new LuaArchitecture(this)
|
||||||
|
|
||||||
private var lua: LuaState = null
|
private[component] val state = mutable.Stack(Machine.State.Stopped)
|
||||||
|
|
||||||
private var kernelMemory = 0
|
private[component] val components = mutable.Map.empty[String, String]
|
||||||
|
|
||||||
private val components = mutable.Map.empty[String, String]
|
|
||||||
|
|
||||||
private val addedComponents = mutable.Set.empty[Component]
|
private val addedComponents = mutable.Set.empty[Component]
|
||||||
|
|
||||||
@ -52,17 +47,15 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
|
|
||||||
private val callCounts = mutable.Map.empty[String, mutable.Map[String, Int]]
|
private val callCounts = mutable.Map.empty[String, mutable.Map[String, Int]]
|
||||||
|
|
||||||
private val ramScale = if (LuaStateFactory.is64Bit) Settings.get.ramScaleFor64Bit else 1.0
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------- //
|
// ----------------------------------------------------------------------- //
|
||||||
|
|
||||||
private var timeStarted = 0L // Game-world time [ms] for os.uptime().
|
private[component] var timeStarted = 0L // Game-world time [ms] for os.uptime().
|
||||||
|
|
||||||
private var worldTime = 0L // Game-world time for os.time().
|
private[component] var worldTime = 0L // Game-world time for os.time().
|
||||||
|
|
||||||
private var cpuTime = 0L // Pseudo-real-world time [ns] for os.clock().
|
private[component] var cpuTime = 0L // Pseudo-real-world time [ns] for os.clock().
|
||||||
|
|
||||||
private var cpuStart = 0L // Pseudo-real-world time [ns] for os.clock().
|
private[component] var cpuStart = 0L // Pseudo-real-world time [ns] for os.clock().
|
||||||
|
|
||||||
private var remainIdle = 0 // Ticks left to sleep before resuming.
|
private var remainIdle = 0 // Ticks left to sleep before resuming.
|
||||||
|
|
||||||
@ -70,19 +63,11 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
|
|
||||||
private var usersChanged = false // Send updated users list to clients?
|
private var usersChanged = false // Send updated users list to clients?
|
||||||
|
|
||||||
private var message: Option[String] = None // For error messages.
|
private[component] var message: Option[String] = None // For error messages.
|
||||||
|
|
||||||
// ----------------------------------------------------------------------- //
|
// ----------------------------------------------------------------------- //
|
||||||
|
|
||||||
def recomputeMemory() = Option(lua) match {
|
def recomputeMemory() = architecture.recomputeMemory()
|
||||||
case Some(l) =>
|
|
||||||
l.setTotalMemory(Int.MaxValue)
|
|
||||||
l.gc(LuaState.GcAction.COLLECT, 0)
|
|
||||||
if (kernelMemory > 0) {
|
|
||||||
l.setTotalMemory(kernelMemory + math.ceil(owner.installedMemory * ramScale).toInt)
|
|
||||||
}
|
|
||||||
case _ =>
|
|
||||||
}
|
|
||||||
|
|
||||||
def lastError = message
|
def lastError = message
|
||||||
|
|
||||||
@ -202,6 +187,50 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
private[component] def popSignal(): Option[Machine.Signal] = signals.synchronized(if (signals.isEmpty) None else Some(signals.dequeue()))
|
||||||
|
|
||||||
|
private[component] def invoke(address: String, method: String, args: Seq[AnyRef]) =
|
||||||
|
Option(node.network.node(address)) match {
|
||||||
|
case Some(component: server.network.Component) if component.canBeSeenFrom(node) || component == node =>
|
||||||
|
val direct = component.isDirect(method)
|
||||||
|
if (direct) callCounts.synchronized {
|
||||||
|
val limit = component.limit(method)
|
||||||
|
val counts = callCounts.getOrElseUpdate(component.address, mutable.Map.empty[String, Int])
|
||||||
|
val count = counts.getOrElseUpdate(method, 0)
|
||||||
|
if (count >= limit) {
|
||||||
|
throw new Machine.LimitReachedException()
|
||||||
|
}
|
||||||
|
counts(method) += 1
|
||||||
|
}
|
||||||
|
component.invoke(method, this, args: _*)
|
||||||
|
case _ => throw new Exception("no such component")
|
||||||
|
}
|
||||||
|
|
||||||
|
private[component] def addUser(name: String) {
|
||||||
|
if (_users.size >= Settings.get.maxUsers)
|
||||||
|
throw new Exception("too many users")
|
||||||
|
|
||||||
|
if (_users.contains(name))
|
||||||
|
throw new Exception("user exists")
|
||||||
|
if (name.length > Settings.get.maxUsernameLength)
|
||||||
|
throw new Exception("username too long")
|
||||||
|
if (!MinecraftServer.getServer.getConfigurationManager.getAllUsernames.contains(name))
|
||||||
|
throw new Exception("player must be online")
|
||||||
|
|
||||||
|
_users.synchronized {
|
||||||
|
_users += name
|
||||||
|
usersChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private[component] def removeUser(name: String) = _users.synchronized {
|
||||||
|
val success = _users.remove(name)
|
||||||
|
if (success) {
|
||||||
|
usersChanged = true
|
||||||
|
}
|
||||||
|
success
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------- //
|
// ----------------------------------------------------------------------- //
|
||||||
|
|
||||||
@LuaCallback("start")
|
@LuaCallback("start")
|
||||||
@ -302,10 +331,6 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
}
|
}
|
||||||
// Perform a synchronized call (message sending).
|
// Perform a synchronized call (message sending).
|
||||||
case Machine.State.SynchronizedCall =>
|
case Machine.State.SynchronizedCall =>
|
||||||
// These three asserts are all guaranteed by run().
|
|
||||||
assert(lua.getTop == 2)
|
|
||||||
assert(lua.isThread(1))
|
|
||||||
assert(lua.isFunction(2))
|
|
||||||
// Clear direct call limits again, just to be on the safe side...
|
// Clear direct call limits again, just to be on the safe side...
|
||||||
// Theoretically it'd be possible for the executor to do some direct
|
// Theoretically it'd be possible for the executor to do some direct
|
||||||
// calls between the clear and the state check, which could in turn
|
// calls between the clear and the state check, which could in turn
|
||||||
@ -315,11 +340,7 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
// were performed from our executor thread.
|
// were performed from our executor thread.
|
||||||
switchTo(Machine.State.Running)
|
switchTo(Machine.State.Running)
|
||||||
try {
|
try {
|
||||||
// Synchronized call protocol requires the called function to return
|
architecture.runSynchronized()
|
||||||
// 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)
|
|
||||||
// Check if the callback called pause() or stop().
|
// Check if the callback called pause() or stop().
|
||||||
state.top match {
|
state.top match {
|
||||||
case Machine.State.Running =>
|
case Machine.State.Running =>
|
||||||
@ -333,15 +354,10 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
case _ => throw new AssertionError()
|
case _ => throw new AssertionError()
|
||||||
}
|
}
|
||||||
} catch {
|
} 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).
|
|
||||||
crash("not enough memory")
|
|
||||||
case e: java.lang.Error if e.getMessage == "not enough memory" =>
|
case e: java.lang.Error if e.getMessage == "not enough memory" =>
|
||||||
crash("not enough memory")
|
crash("not enough memory")
|
||||||
case e: Throwable =>
|
case e: Throwable =>
|
||||||
OpenComputers.log.log(Level.WARNING, "Faulty Lua implementation for synchronized calls.", e)
|
OpenComputers.log.log(Level.WARNING, "Faulty architecture implementation for synchronized calls.", e)
|
||||||
crash("protocol error")
|
crash("protocol error")
|
||||||
}
|
}
|
||||||
case _ => // Nothing special to do, just avoid match errors.
|
case _ => // Nothing special to do, just avoid match errors.
|
||||||
@ -430,7 +446,7 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
components.synchronized(components += component.address -> component.name)
|
components.synchronized(components += component.address -> component.name)
|
||||||
// Skip the signal if we're not initialized yet, since we'd generate a
|
// Skip the signal if we're not initialized yet, since we'd generate a
|
||||||
// duplicate in the startup script otherwise.
|
// duplicate in the startup script otherwise.
|
||||||
if (kernelMemory > 0) {
|
if (architecture.isInitialized) {
|
||||||
signal("component_added", component.address, component.name)
|
signal("component_added", component.address, component.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -468,28 +484,7 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
nbt.getTagList("users").foreach[NBTTagString](u => _users += u.data)
|
nbt.getTagList("users").foreach[NBTTagString](u => _users += u.data)
|
||||||
|
|
||||||
if (state.size > 0 && state.top != Machine.State.Stopped && init()) {
|
if (state.size > 0 && state.top != Machine.State.Stopped && init()) {
|
||||||
// Unlimit memory use while unpersisting.
|
architecture.load(nbt)
|
||||||
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.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
components ++= nbt.getTagList("components").iterator[NBTTagCompound].map(c =>
|
components ++= nbt.getTagList("components").iterator[NBTTagCompound].map(c =>
|
||||||
c.getString("address") -> c.getString("name"))
|
c.getString("address") -> c.getString("name"))
|
||||||
@ -519,7 +514,6 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
|
|
||||||
rom.foreach(rom => rom.load(nbt.getCompoundTag("rom")))
|
rom.foreach(rom => rom.load(nbt.getCompoundTag("rom")))
|
||||||
tmp.foreach(tmp => tmp.load(nbt.getCompoundTag("tmp")))
|
tmp.foreach(tmp => tmp.load(nbt.getCompoundTag("tmp")))
|
||||||
kernelMemory = (nbt.getInteger("kernelMemory") * ramScale).toInt
|
|
||||||
timeStarted = nbt.getLong("timeStarted")
|
timeStarted = nbt.getLong("timeStarted")
|
||||||
cpuTime = nbt.getLong("cpuTime")
|
cpuTime = nbt.getLong("cpuTime")
|
||||||
remainingPause = nbt.getInteger("remainingPause")
|
remainingPause = nbt.getInteger("remainingPause")
|
||||||
@ -527,16 +521,8 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
message = Some(nbt.getString("message"))
|
message = Some(nbt.getString("message"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limit memory again.
|
|
||||||
recomputeMemory()
|
|
||||||
|
|
||||||
// Delay execution for a second to allow the world around us to settle.
|
// Delay execution for a second to allow the world around us to settle.
|
||||||
pause(Settings.get.startupDelay)
|
pause(Settings.get.startupDelay)
|
||||||
} 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else close() // Clean up in case we got a weird state stack.
|
else close() // Clean up in case we got a weird state stack.
|
||||||
}
|
}
|
||||||
@ -556,20 +542,7 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
nbt.setNewTagList("users", _users)
|
nbt.setNewTagList("users", _users)
|
||||||
|
|
||||||
if (state.top != Machine.State.Stopped) {
|
if (state.top != Machine.State.Stopped) {
|
||||||
// Unlimit memory while persisting.
|
architecture.save(nbt)
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
val componentsNbt = new NBTTagList()
|
val componentsNbt = new NBTTagList()
|
||||||
for ((address, name) <- components) {
|
for ((address, name) <- components) {
|
||||||
@ -609,59 +582,11 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
rom.foreach(rom => nbt.setNewCompoundTag("rom", rom.save))
|
rom.foreach(rom => nbt.setNewCompoundTag("rom", rom.save))
|
||||||
tmp.foreach(tmp => nbt.setNewCompoundTag("tmp", tmp.save))
|
tmp.foreach(tmp => nbt.setNewCompoundTag("tmp", tmp.save))
|
||||||
|
|
||||||
nbt.setInteger("kernelMemory", math.ceil(kernelMemory / ramScale).toInt)
|
|
||||||
nbt.setLong("timeStarted", timeStarted)
|
nbt.setLong("timeStarted", timeStarted)
|
||||||
nbt.setLong("cpuTime", cpuTime)
|
nbt.setLong("cpuTime", cpuTime)
|
||||||
nbt.setInteger("remainingPause", remainingPause)
|
nbt.setInteger("remainingPause", remainingPause)
|
||||||
message.foreach(nbt.setString("message", _))
|
message.foreach(nbt.setString("message", _))
|
||||||
} 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 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------- //
|
// ----------------------------------------------------------------------- //
|
||||||
@ -670,16 +595,8 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
// Reset error state.
|
// Reset error state.
|
||||||
message = None
|
message = None
|
||||||
|
|
||||||
// Creates a new state with all base libraries and the persistence library
|
// Clear any left-over signals from a previous run.
|
||||||
// loaded into it. This means the state has much more power than it
|
signals.clear()
|
||||||
// rightfully should have, so we sandbox it a bit in the following.
|
|
||||||
LuaStateFactory.createState() match {
|
|
||||||
case None =>
|
|
||||||
lua = null
|
|
||||||
message = Some("native libraries not available")
|
|
||||||
return false
|
|
||||||
case Some(value) => lua = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect the ROM and `/tmp` node to our owner. We're not in a network in
|
// Connect the ROM and `/tmp` node to our owner. We're not in a network in
|
||||||
// case we're loading, which is why we have to check it here.
|
// case we're loading, which is why we have to check it here.
|
||||||
@ -689,393 +606,7 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Push a couple of functions that override original Lua API functions or
|
return architecture.init()
|
||||||
// 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((cpuTime + (System.nanoTime() - 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 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((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((worldTime - 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(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(signal(lua.checkString(1), lua.toSimpleJavaObjects(2): _*))
|
|
||||||
1
|
|
||||||
})
|
|
||||||
lua.setField(-2, "pushSignal")
|
|
||||||
|
|
||||||
// And its ROM address.
|
|
||||||
lua.pushScalaFunction(lua => {
|
|
||||||
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 => {
|
|
||||||
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 => {
|
|
||||||
_users.foreach(lua.pushString)
|
|
||||||
_users.size
|
|
||||||
})
|
|
||||||
lua.setField(-2, "users")
|
|
||||||
|
|
||||||
lua.pushScalaFunction(lua => try {
|
|
||||||
if (_users.size >= Settings.get.maxUsers)
|
|
||||||
throw new Exception("too many users")
|
|
||||||
|
|
||||||
val name = lua.checkString(1)
|
|
||||||
|
|
||||||
if (_users.contains(name))
|
|
||||||
throw new Exception("user exists")
|
|
||||||
if (name.length > Settings.get.maxUsernameLength)
|
|
||||||
throw new Exception("username too long")
|
|
||||||
if (!MinecraftServer.getServer.getConfigurationManager.getAllUsernames.contains(name))
|
|
||||||
throw new Exception("player must be online")
|
|
||||||
|
|
||||||
_users.synchronized {
|
|
||||||
_users += name
|
|
||||||
usersChanged = true
|
|
||||||
}
|
|
||||||
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 => {
|
|
||||||
val name = lua.checkString(1)
|
|
||||||
_users.synchronized {
|
|
||||||
val success = _users.remove(name)
|
|
||||||
if (success) {
|
|
||||||
usersChanged = true
|
|
||||||
}
|
|
||||||
lua.pushBoolean(success)
|
|
||||||
}
|
|
||||||
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")
|
|
||||||
|
|
||||||
class LimitReachedException extends Exception
|
|
||||||
|
|
||||||
lua.pushScalaFunction(lua => {
|
|
||||||
val address = lua.checkString(1)
|
|
||||||
val method = lua.checkString(2)
|
|
||||||
val args = lua.toSimpleJavaObjects(3)
|
|
||||||
try {
|
|
||||||
(Option(node.network.node(address)) match {
|
|
||||||
case Some(component: server.network.Component) if component.canBeSeenFrom(node) || component == node =>
|
|
||||||
val direct = component.isDirect(method)
|
|
||||||
if (direct) callCounts.synchronized {
|
|
||||||
val limit = component.limit(method)
|
|
||||||
val counts = callCounts.getOrElseUpdate(component.address, mutable.Map.empty[String, Int])
|
|
||||||
val count = counts.getOrElseUpdate(method, 0)
|
|
||||||
if (count >= limit) {
|
|
||||||
throw new LimitReachedException()
|
|
||||||
}
|
|
||||||
counts(method) += 1
|
|
||||||
}
|
|
||||||
component.invoke(method, this, args: _*)
|
|
||||||
case _ => throw new Exception("no such component")
|
|
||||||
}) 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[LimitReachedException]) {
|
|
||||||
OpenComputers.log.log(Level.WARNING, "Exception in Lua callback.", e)
|
|
||||||
}
|
|
||||||
e match {
|
|
||||||
case _: 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.
|
|
||||||
|
|
||||||
// Clear any left-over signals from a previous run.
|
|
||||||
signals.clear()
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
case ex: Throwable =>
|
case ex: Throwable =>
|
||||||
@ -1085,85 +616,11 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
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 close() = state.synchronized(
|
private def close() = state.synchronized(
|
||||||
if (state.size == 0 || state.top != Machine.State.Stopped) {
|
if (state.size == 0 || state.top != Machine.State.Stopped) {
|
||||||
state.clear()
|
state.clear()
|
||||||
state.push(Machine.State.Stopped)
|
state.push(Machine.State.Stopped)
|
||||||
if (lua != null) {
|
architecture.close()
|
||||||
lua.setTotalMemory(Integer.MAX_VALUE)
|
|
||||||
lua.close()
|
|
||||||
}
|
|
||||||
lua = null
|
|
||||||
kernelMemory = 0
|
|
||||||
signals.clear()
|
signals.clear()
|
||||||
timeStarted = 0
|
timeStarted = 0
|
||||||
cpuTime = 0
|
cpuTime = 0
|
||||||
@ -1211,99 +668,36 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
switchTo(Machine.State.Running)
|
switchTo(Machine.State.Running)
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
|
||||||
cpuStart = System.nanoTime()
|
cpuStart = System.nanoTime()
|
||||||
val (results, runtime) = 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), System.nanoTime() - cpuStart)
|
|
||||||
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, 0L)
|
|
||||||
}
|
|
||||||
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.
|
try {
|
||||||
lua.pushInteger(0)
|
val result = architecture.runThreaded(enterState)
|
||||||
(1, 0L)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else (signals.synchronized(if (signals.isEmpty) None else Some(signals.dequeue())) 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)
|
|
||||||
}, System.nanoTime() - cpuStart)
|
|
||||||
case s => throw new AssertionError("Running computer from invalid state " + s.toString)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep track of time spent executing the computer.
|
|
||||||
cpuTime += runtime
|
|
||||||
|
|
||||||
// Check if the kernel is still alive.
|
|
||||||
state.synchronized(if (lua.status(1) == LuaState.YIELD) {
|
|
||||||
// Check if someone called pause() or stop() in the meantime.
|
// Check if someone called pause() or stop() in the meantime.
|
||||||
state.top match {
|
state.synchronized(state.top match {
|
||||||
case Machine.State.Running =>
|
case Machine.State.Running =>
|
||||||
// If we get one function it must be a wrapper for a synchronized
|
result match {
|
||||||
// call. The protocol is that a closure is pushed that is then called
|
case result: ExecutionResult.Sleep =>
|
||||||
// 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)) {
|
|
||||||
switchTo(Machine.State.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)) {
|
|
||||||
if (lua.toBoolean(2)) switchTo(Machine.State.Restarting)
|
|
||||||
else switchTo(Machine.State.Stopping)
|
|
||||||
}
|
|
||||||
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 sleep =
|
|
||||||
if (results == 1 && lua.isNumber(2)) (lua.toNumber(2) * 20).toInt
|
|
||||||
else Int.MaxValue
|
|
||||||
lua.pop(results)
|
|
||||||
signals.synchronized {
|
signals.synchronized {
|
||||||
// Immediately check for signals to allow processing more than one
|
// Immediately check for signals to allow processing more than one
|
||||||
// signal per game tick.
|
// signal per game tick.
|
||||||
if (signals.isEmpty && sleep > 0) {
|
if (signals.isEmpty && result.ticks > 0) {
|
||||||
switchTo(Machine.State.Sleeping)
|
switchTo(Machine.State.Sleeping)
|
||||||
remainIdle = sleep
|
remainIdle = result.ticks
|
||||||
} else {
|
} else {
|
||||||
switchTo(Machine.State.Yielded)
|
switchTo(Machine.State.Yielded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case result: ExecutionResult.SynchronizedCall =>
|
||||||
|
switchTo(Machine.State.SynchronizedCall)
|
||||||
|
case result: ExecutionResult.Shutdown =>
|
||||||
|
if (result.reboot) {
|
||||||
|
switchTo(Machine.State.Restarting)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
switchTo(Machine.State.Stopping)
|
||||||
|
}
|
||||||
|
case result: ExecutionResult.Error =>
|
||||||
}
|
}
|
||||||
case Machine.State.Paused =>
|
case Machine.State.Paused =>
|
||||||
state.pop() // Paused
|
state.pop() // Paused
|
||||||
@ -1313,53 +707,23 @@ class Machine(val owner: Machine.Owner) extends ManagedComponent with Context wi
|
|||||||
case Machine.State.Stopping => // Nothing to do, we'll die anyway.
|
case Machine.State.Stopping => // Nothing to do, we'll die anyway.
|
||||||
case _ => throw new AssertionError(
|
case _ => throw new AssertionError(
|
||||||
"Invalid state in executor post-processing.")
|
"Invalid state in executor post-processing.")
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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.")
|
|
||||||
stop()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
lua.setTotalMemory(Int.MaxValue)
|
|
||||||
val error = lua.toString(3)
|
|
||||||
if (error != null) crash(error)
|
|
||||||
else crash("unknown error")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
case e: LuaRuntimeException =>
|
case e: Throwable => OpenComputers.log.log(Level.WARNING, "Architecture's runThreaded threw an error. This should never happen!", e)
|
||||||
OpenComputers.log.warning("Kernel crashed. This is a bug!\n" + e.toString + "\tat " + e.getLuaStackTrace.mkString("\n\tat "))
|
|
||||||
crash("kernel panic: this is a bug, check your log file and report it")
|
|
||||||
case e: LuaGcMetamethodException =>
|
|
||||||
if (e.getMessage != null) crash("kernel panic:\n" + e.getMessage)
|
|
||||||
else crash("kernel panic:\nerror in garbage collection metamethod")
|
|
||||||
case e: LuaMemoryAllocationException =>
|
|
||||||
crash("not enough memory")
|
|
||||||
case e: java.lang.Error if e.getMessage == "not enough memory" =>
|
|
||||||
crash("not enough memory")
|
|
||||||
case e: Throwable =>
|
|
||||||
OpenComputers.log.log(Level.WARNING, "Unexpected error in kernel. This is a bug!\n", e)
|
|
||||||
crash("kernel panic: this is a bug, check your log file and report it")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep track of time spent executing the computer.
|
||||||
|
cpuTime += System.nanoTime() - cpuStart
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Machine {
|
object Machine {
|
||||||
|
|
||||||
/** Signals are messages sent to the Lua state from Java asynchronously. */
|
private[component] class LimitReachedException extends Exception
|
||||||
private class Signal(val name: String, val args: Array[Any])
|
|
||||||
|
|
||||||
/** Possible states of the computer, and in particular its executor. */
|
/** Possible states of the computer, and in particular its executor. */
|
||||||
private object State extends Enumeration {
|
private[component] object State extends Enumeration {
|
||||||
/** The computer is not running right now and there is no Lua state. */
|
/** The computer is not running right now and there is no Lua state. */
|
||||||
val Stopped = Value("Stopped")
|
val Stopped = Value("Stopped")
|
||||||
|
|
||||||
@ -1391,7 +755,10 @@ object Machine {
|
|||||||
val Running = Value("Running")
|
val Running = Value("Running")
|
||||||
}
|
}
|
||||||
|
|
||||||
private val threadPool = ThreadPoolFactory.create("Lua", Settings.get.threads)
|
/** Signals are messages sent to the Lua state from Java asynchronously. */
|
||||||
|
private[component] class Signal(val name: String, val args: Array[Any])
|
||||||
|
|
||||||
|
private val threadPool = ThreadPoolFactory.create("Computer", Settings.get.threads)
|
||||||
|
|
||||||
trait Owner {
|
trait Owner {
|
||||||
def installedMemory: Int
|
def installedMemory: Int
|
||||||
|
93
li/cil/oc/server/component/machine/Architecture.scala
Normal file
93
li/cil/oc/server/component/machine/Architecture.scala
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package li.cil.oc.server.component.machine
|
||||||
|
|
||||||
|
import li.cil.oc.server.component.Machine
|
||||||
|
import net.minecraft.nbt.NBTTagCompound
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This trait abstracts away any language specific details for the Machine.
|
||||||
|
*
|
||||||
|
* At some point in the future this may allow us to introduce other languages,
|
||||||
|
* e.g. computers that run assembly or non-persistent computers that use a pure
|
||||||
|
* Java implementation of Lua.
|
||||||
|
*/
|
||||||
|
trait Architecture {
|
||||||
|
/**
|
||||||
|
* Used to check if the machine is fully initialized. If this is false no
|
||||||
|
* signals for detected components will be generated. Avoids duplicate signals
|
||||||
|
* if component_added signals are generated in the language's startup script,
|
||||||
|
* for already present components (see Lua's init.lua script).
|
||||||
|
*
|
||||||
|
* @return whether the machine is fully initialized.
|
||||||
|
*/
|
||||||
|
def isInitialized: Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is called when the amount of memory in the computer may have changed.
|
||||||
|
* It is triggered by the tile entity's onInventoryChanged.
|
||||||
|
*/
|
||||||
|
def recomputeMemory()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a synchronized call initialized in a previous call to runThreaded.
|
||||||
|
*/
|
||||||
|
def runSynchronized()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continues execution of the machine. The first call may be used to
|
||||||
|
* initialize the machine (e.g. for Lua we load the libraries in the first
|
||||||
|
* call so that the computers boot faster).
|
||||||
|
*
|
||||||
|
* The resumed state is either Machine.State.SynchronizedReturn, when a
|
||||||
|
* synchronized call has been completed (via runSynchronized), or
|
||||||
|
* Machine.State.Yielded in all other cases (sleep, interrupt, boot, ...).
|
||||||
|
*
|
||||||
|
* This is expected to return within a very short time, usually. For example,
|
||||||
|
* in Lua this returns as soon as the state yields, and returns at the latest
|
||||||
|
* when the Settings.timeout is reached (in which case it forces the state
|
||||||
|
* to crash).
|
||||||
|
*
|
||||||
|
* This is expected to consume a single signal if one is present and return.
|
||||||
|
* If returning from a synchronized call this should consume no signal.
|
||||||
|
*
|
||||||
|
* @param enterState the state that is being resumed.
|
||||||
|
* @return the result of the execution. Used to determine the new state.
|
||||||
|
*/
|
||||||
|
def runThreaded(enterState: Machine.State.Value): ExecutionResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a computer starts up. Used to (re-)initialize the underlying
|
||||||
|
* architecture logic. For example, for Lua the creates a new Lua state.
|
||||||
|
*
|
||||||
|
* This also sets up any built-in APIs for the underlying language, such as
|
||||||
|
* querying available memory, listing and interacting with components and so
|
||||||
|
* on. If this returns false the computer fails to start.
|
||||||
|
*
|
||||||
|
* @return whether the architecture was initialized successfully.
|
||||||
|
*/
|
||||||
|
def init(): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a computer stopped. Used to clean up any handles, memory and
|
||||||
|
* so on. For example, for Lua this destroys the Lua state.
|
||||||
|
*/
|
||||||
|
def close()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores the state of this architecture as previously saved in save().
|
||||||
|
*
|
||||||
|
* @param nbt the tag compound to save to.
|
||||||
|
*/
|
||||||
|
def load(nbt: NBTTagCompound)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the architecture for later restoration, e.g. across games or chunk
|
||||||
|
* unloads. Used to persist a computer's executions state. For native Lua this
|
||||||
|
* uses the Eris library to persist the main coroutine.
|
||||||
|
*
|
||||||
|
* Note that the tag compound is shared with the Machine, so collisions have
|
||||||
|
* to be avoided (see Machine.save for used keys).
|
||||||
|
*
|
||||||
|
* @param nbt the tag compound to save to.
|
||||||
|
*/
|
||||||
|
def save(nbt: NBTTagCompound)
|
||||||
|
}
|
45
li/cil/oc/server/component/machine/ExecutionResult.scala
Normal file
45
li/cil/oc/server/component/machine/ExecutionResult.scala
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package li.cil.oc.server.component.machine
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by the Machine to determine the result of a call to runThreaded.
|
||||||
|
*
|
||||||
|
* Do not implement this interface, only use the predefined classes below.
|
||||||
|
*/
|
||||||
|
trait ExecutionResult
|
||||||
|
|
||||||
|
object ExecutionResult {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the machine may sleep for the specified number of ticks. This is
|
||||||
|
* merely considered a suggestion. If signals are in the queue or are pushed
|
||||||
|
* to the queue while sleeping, the sleep will be interrupted and runThreaded
|
||||||
|
* will be called so that the next signal is pushed.
|
||||||
|
*
|
||||||
|
* @param ticks the number of ticks to sleep.
|
||||||
|
*/
|
||||||
|
class Sleep(val ticks: Int) extends ExecutionResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates tha the computer should shutdown or reboot.
|
||||||
|
*
|
||||||
|
* @param reboot whether to reboot. If false the computer will stop.
|
||||||
|
*/
|
||||||
|
class Shutdown(val reboot: Boolean) extends ExecutionResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that a synchronized call should be performed. The architecture
|
||||||
|
* is expected to be in a state that allows the next call to be to
|
||||||
|
* runSynchronized instead of runThreaded. This is used to perform calls from
|
||||||
|
* the server's main thread, to avoid threading issues when interacting with
|
||||||
|
* other objects in the world.
|
||||||
|
*/
|
||||||
|
class SynchronizedCall extends ExecutionResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that an error occurred and the computer should crash.
|
||||||
|
*
|
||||||
|
* @param message the error message.
|
||||||
|
*/
|
||||||
|
class Error(val message: String) extends ExecutionResult
|
||||||
|
|
||||||
|
}
|
715
li/cil/oc/server/component/machine/LuaArchitecture.scala
Normal file
715
li/cil/oc/server/component/machine/LuaArchitecture.scala
Normal file
@ -0,0 +1,715 @@
|
|||||||
|
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 scala.Some
|
||||||
|
import scala.collection.convert.WrapAsScala._
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
class LuaArchitecture(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
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user