mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-15 10:21:45 -04:00
added setting to allow logging of tracebacks for exceptions that occurred in Lua callbacks (disabled per default to avoid spamming the log with InvalidArgumentExceptions and the like); moved argument parsing and result pushing to extended lua state and added capability to push maps as tables, meaning callbacks can now return maps, too
This commit is contained in:
parent
9478907a58
commit
3b59b53d5c
@ -43,6 +43,7 @@ class Settings(config: Config) {
|
||||
val maxUsers = config.getInt("computer.maxUsers") max 0
|
||||
val maxUsernameLength = config.getInt("computer.maxUsernameLength") max 0
|
||||
val allowBytecode = config.getBoolean("computer.allowBytecode")
|
||||
val logLuaCallbackErrors = config.getBoolean("computer.logCallbackErrors")
|
||||
|
||||
// ----------------------------------------------------------------------- //
|
||||
// robot
|
||||
|
@ -20,8 +20,6 @@ import scala.Array.canBuildFrom
|
||||
import scala.Some
|
||||
import scala.collection.convert.WrapAsScala._
|
||||
import scala.collection.mutable
|
||||
import scala.math.ScalaNumber
|
||||
import scala.runtime.BoxedUnit
|
||||
|
||||
class Computer(val owner: tileentity.Computer) extends ManagedComponent with Context with Runnable {
|
||||
val node = api.Network.newNode(this, Visibility.Network).
|
||||
@ -665,57 +663,6 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con
|
||||
// ----------------------------------------------------------------------- //
|
||||
|
||||
private def init(): Boolean = {
|
||||
// Utility functions for varargs callbacks.
|
||||
def parseArgument(lua: LuaState, index: Int): AnyRef = lua.`type`(index) match {
|
||||
case LuaType.BOOLEAN => Boolean.box(lua.toBoolean(index))
|
||||
case LuaType.NUMBER => Double.box(lua.toNumber(index))
|
||||
case LuaType.STRING => lua.toByteArray(index)
|
||||
case LuaType.TABLE => lua.toJavaObject(index, classOf[java.util.Map[_, _]])
|
||||
case _ => Unit
|
||||
}
|
||||
|
||||
def parseArguments(lua: LuaState, start: Int) =
|
||||
for (index <- start to lua.getTop) yield parseArgument(lua, index)
|
||||
|
||||
def pushList(value: Iterator[(Any, Int)]) {
|
||||
lua.newTable()
|
||||
var count = 0
|
||||
value.foreach {
|
||||
case (x, index) => x match {
|
||||
case (entry: ScalaNumber) =>
|
||||
pushResult(lua, entry.underlying())
|
||||
case (entry) =>
|
||||
pushResult(lua, entry.asInstanceOf[AnyRef])
|
||||
}
|
||||
lua.rawSet(-2, index + 1)
|
||||
count = count + 1
|
||||
}
|
||||
lua.pushString("n")
|
||||
lua.pushInteger(count)
|
||||
lua.rawSet(-3)
|
||||
}
|
||||
|
||||
def pushResult(lua: LuaState, value: AnyRef): Unit = value match {
|
||||
case null | Unit | _: BoxedUnit => lua.pushNil()
|
||||
case value: java.lang.Boolean => lua.pushBoolean(value.booleanValue)
|
||||
case value: java.lang.Byte => lua.pushNumber(value.byteValue)
|
||||
case value: java.lang.Character => lua.pushString(String.valueOf(value))
|
||||
case value: java.lang.Short => lua.pushNumber(value.shortValue)
|
||||
case value: java.lang.Integer => lua.pushNumber(value.intValue)
|
||||
case value: java.lang.Long => lua.pushNumber(value.longValue)
|
||||
case value: java.lang.Float => lua.pushNumber(value.floatValue)
|
||||
case value: java.lang.Double => lua.pushNumber(value.doubleValue)
|
||||
case value: java.lang.String => lua.pushString(value)
|
||||
case value: Array[Byte] => lua.pushByteArray(value)
|
||||
case value: Array[_] => pushList(value.zipWithIndex.iterator)
|
||||
case value: Product => pushList(value.productIterator.zipWithIndex)
|
||||
case value: Seq[_] => pushList(value.zipWithIndex.iterator)
|
||||
// TODO maps?
|
||||
case _ =>
|
||||
OpenComputers.log.warning("A component callback tried to return an unsupported value of type " + value.getClass.getName + ".")
|
||||
lua.pushNil()
|
||||
}
|
||||
|
||||
// Reset error state.
|
||||
message = None
|
||||
|
||||
@ -862,7 +809,7 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con
|
||||
lua.setField(-2, "totalMemory")
|
||||
|
||||
lua.pushScalaFunction(lua => {
|
||||
lua.pushBoolean(signal(lua.checkString(1), parseArguments(lua, 2): _*))
|
||||
lua.pushBoolean(signal(lua.checkString(1), lua.toSimpleJavaObjects(2): _*))
|
||||
1
|
||||
})
|
||||
lua.setField(-2, "pushSignal")
|
||||
@ -1029,7 +976,7 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con
|
||||
lua.pushScalaFunction(lua => {
|
||||
val address = lua.checkString(1)
|
||||
val method = lua.checkString(2)
|
||||
val args = parseArguments(lua, 3)
|
||||
val args = lua.toSimpleJavaObjects(3)
|
||||
try {
|
||||
(Option(node.network.node(address)) match {
|
||||
case Some(component: server.network.Component) if component.canBeSeenFrom(node) || component == node =>
|
||||
@ -1048,57 +995,68 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con
|
||||
}) match {
|
||||
case results: Array[_] =>
|
||||
lua.pushBoolean(true)
|
||||
results.foreach(pushResult(lua, _))
|
||||
results.foreach(result => lua.pushValue(result))
|
||||
1 + results.length
|
||||
case _ =>
|
||||
lua.pushBoolean(true)
|
||||
1
|
||||
}
|
||||
} catch {
|
||||
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)
|
||||
3
|
||||
case _: ArrayIndexOutOfBoundsException =>
|
||||
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
|
||||
}
|
||||
catch {
|
||||
case e: Throwable =>
|
||||
OpenComputers.log.log(Level.WARNING, "Unexpected error in Lua callback.", e)
|
||||
lua.pushBoolean(true)
|
||||
lua.pushNil()
|
||||
lua.pushString("unknown error")
|
||||
3
|
||||
if (Settings.get.logLuaCallbackErrors) {
|
||||
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 (true) {
|
||||
lua.pushString(e.getStackTraceString)
|
||||
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")
|
||||
@ -1293,19 +1251,7 @@ class Computer(val owner: tileentity.Computer) extends ManagedComponent with Con
|
||||
else (signals.synchronized(if (signals.isEmpty) None else Some(signals.dequeue())) match {
|
||||
case Some(signal) =>
|
||||
lua.pushString(signal.name)
|
||||
signal.args.foreach {
|
||||
case Unit => lua.pushNil()
|
||||
case arg: Boolean => lua.pushBoolean(arg)
|
||||
case arg: Double => lua.pushNumber(arg)
|
||||
case arg: String => lua.pushString(arg)
|
||||
case arg: Array[Byte] => lua.pushByteArray(arg)
|
||||
case arg: Map[String, String] =>
|
||||
lua.newTable(0, arg.size)
|
||||
for ((key, value) <- arg if key != null && value != null) {
|
||||
lua.pushString(value)
|
||||
lua.setField(-2, key)
|
||||
}
|
||||
}
|
||||
signal.args.foreach(arg => lua.pushValue(arg))
|
||||
lua.resume(1, 1 + signal.args.length)
|
||||
case _ =>
|
||||
lua.resume(1, 0)
|
||||
|
@ -3,7 +3,6 @@ package li.cil.oc.server.network
|
||||
import cpw.mods.fml.common.FMLCommonHandler
|
||||
import cpw.mods.fml.relauncher.Side
|
||||
import java.lang.reflect.{Method, InvocationTargetException}
|
||||
import java.util
|
||||
import li.cil.oc.api
|
||||
import li.cil.oc.api.network._
|
||||
import li.cil.oc.server.component
|
||||
@ -249,7 +248,7 @@ object Component {
|
||||
|
||||
def isTable(index: Int) =
|
||||
index >= 0 && index < count && (args(index) match {
|
||||
case value: util.Map[_, _] => true
|
||||
case value: java.util.Map[_, _] => true
|
||||
case value: Map[_, _] => true
|
||||
case value: mutable.Map[_, _] => true
|
||||
case _ => false
|
||||
@ -272,7 +271,7 @@ object Component {
|
||||
case _: java.lang.Double => "double"
|
||||
case _: java.lang.String => "string"
|
||||
case _: Array[Byte] => "string"
|
||||
case value: util.Map[_, _] => "table"
|
||||
case value: java.util.Map[_, _] => "table"
|
||||
case value: Map[_, _] => "table"
|
||||
case value: mutable.Map[_, _] => "table"
|
||||
case _ => value.getClass.getSimpleName
|
||||
|
@ -1,16 +1,87 @@
|
||||
package li.cil.oc.util
|
||||
|
||||
import com.naef.jnlua.{JavaFunction, LuaState}
|
||||
import com.naef.jnlua.{LuaType, JavaFunction, LuaState}
|
||||
import li.cil.oc.OpenComputers
|
||||
import scala.collection.convert.WrapAsScala._
|
||||
import scala.collection.mutable
|
||||
import scala.language.implicitConversions
|
||||
import scala.math.ScalaNumber
|
||||
import scala.runtime.BoxedUnit
|
||||
|
||||
object ExtendedLuaState {
|
||||
|
||||
implicit def extendLuaState(state: LuaState) = new ExtendedLuaState(state)
|
||||
|
||||
class ExtendedLuaState(val state: LuaState) {
|
||||
def pushScalaFunction(f: (LuaState) => Int) = state.pushJavaFunction(new JavaFunction {
|
||||
class ExtendedLuaState(val lua: LuaState) {
|
||||
def pushScalaFunction(f: (LuaState) => Int) = lua.pushJavaFunction(new JavaFunction {
|
||||
override def invoke(state: LuaState) = f(state)
|
||||
})
|
||||
|
||||
def pushValue(value: Any) {
|
||||
(value match {
|
||||
case number: ScalaNumber => number.underlying
|
||||
case reference: AnyRef => reference
|
||||
case null => null
|
||||
case primitive => primitive.asInstanceOf[AnyRef]
|
||||
}) match {
|
||||
case null | Unit | _: BoxedUnit => lua.pushNil()
|
||||
case value: java.lang.Boolean => lua.pushBoolean(value.booleanValue)
|
||||
case value: java.lang.Byte => lua.pushNumber(value.byteValue)
|
||||
case value: java.lang.Character => lua.pushString(String.valueOf(value))
|
||||
case value: java.lang.Short => lua.pushNumber(value.shortValue)
|
||||
case value: java.lang.Integer => lua.pushNumber(value.intValue)
|
||||
case value: java.lang.Long => lua.pushNumber(value.longValue)
|
||||
case value: java.lang.Float => lua.pushNumber(value.floatValue)
|
||||
case value: java.lang.Double => lua.pushNumber(value.doubleValue)
|
||||
case value: java.lang.String => lua.pushString(value)
|
||||
case value: Array[Byte] => lua.pushByteArray(value)
|
||||
case value: Array[_] => pushList(value.zipWithIndex.iterator)
|
||||
case value: Product => pushList(value.productIterator.zipWithIndex)
|
||||
case value: Seq[_] => pushList(value.zipWithIndex.iterator)
|
||||
case value: java.util.Map[_, _] => pushTable(value.toMap)
|
||||
case value: Map[_, _] => pushTable(value)
|
||||
case value: mutable.Map[_, _] => pushTable(value.toMap)
|
||||
case _ =>
|
||||
OpenComputers.log.warning("Tried to push an unsupported value of type to Lua: " + value.getClass.getName + ".")
|
||||
lua.pushNil()
|
||||
}
|
||||
}
|
||||
|
||||
def pushList(list: Iterator[(Any, Int)]) {
|
||||
lua.newTable()
|
||||
var count = 0
|
||||
list.foreach {
|
||||
case (value, index) =>
|
||||
pushValue(value)
|
||||
lua.rawSet(-2, index + 1)
|
||||
count = count + 1
|
||||
}
|
||||
lua.pushString("n")
|
||||
lua.pushInteger(count)
|
||||
lua.rawSet(-3)
|
||||
}
|
||||
|
||||
def pushTable(map: Map[_, _]) {
|
||||
lua.newTable(0, map.size)
|
||||
for ((key: AnyRef, value: AnyRef) <- map) {
|
||||
if (key != null && key != Unit && !key.isInstanceOf[BoxedUnit]) {
|
||||
pushValue(key)
|
||||
pushValue(value)
|
||||
lua.setTable(-3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def toSimpleJavaObject(index: Int): AnyRef = lua.`type`(index) match {
|
||||
case LuaType.BOOLEAN => Boolean.box(lua.toBoolean(index))
|
||||
case LuaType.NUMBER => Double.box(lua.toNumber(index))
|
||||
case LuaType.STRING => lua.toByteArray(index)
|
||||
case LuaType.TABLE => lua.toJavaObject(index, classOf[java.util.Map[_, _]])
|
||||
case _ => null
|
||||
}
|
||||
|
||||
def toSimpleJavaObjects(start: Int) =
|
||||
for (index <- start to lua.getTop) yield toSimpleJavaObject(index)
|
||||
}
|
||||
|
||||
}
|
@ -150,6 +150,15 @@ opencomputers {
|
||||
# list of registered users on the Lua side.
|
||||
# See also: `canComputersBeOwned`.
|
||||
maxUsernameLength: 32
|
||||
|
||||
# This setting is meant for debugging errors that occur in Lua callbacks.
|
||||
# Per default, if an error occurs and it has a message set, only the
|
||||
# message is pushed back to Lua, and that's it. If you encounter weird
|
||||
# errors or are developing an addon you'll want the stacktrace for those
|
||||
# errors. Enabling this setting will log them to the game log. This is
|
||||
# disabled per default to avoid spamming the log with inconsequentual
|
||||
# exceptions such as IllegalArgumentExceptions and the like.
|
||||
logCallbackErrors: false
|
||||
}
|
||||
|
||||
# Robot related settings, what they may do and general balancing.
|
||||
|
Loading…
x
Reference in New Issue
Block a user