blob: 31411cd824e0698c100a6db08f54573052e029d2 [file] [log] [blame] [raw]
package li.cil.oc.server.computer
import java.lang.reflect.Method
import scala.Array.canBuildFrom
import com.naef.jnlua.JavaFunction
import com.naef.jnlua.LuaState
import li.cil.oc.api.Callback
import li.cil.oc.api.IDriver
class Driver(val driver: IDriver) {
def injectInto(context: IComputerContext) {
// Check if the component actually provides an API.
val api = driver.apiName
if (api == null || api.isEmpty()) return
val lua = context.luaState
// Get or create registry entry holding API tables.
lua.getField(LuaState.REGISTRYINDEX, ComputerRegistry.driverApis)
if (lua.isNil(-1)) {
lua.pop(1)
lua.newTable()
lua.pushValue(-1)
lua.setField(LuaState.REGISTRYINDEX, ComputerRegistry.driverApis)
}
// Get or create API table.
lua.getField(-1, api)
if (lua.isNil(-1)) {
lua.pop(1)
lua.newTable()
lua.pushValue(-1)
lua.setField(-3, api)
}
for (method <- driver.getClass().getMethods()) {
val annotation = method.getAnnotation(classOf[Callback])
if (annotation != null) {
val name = annotation.name
lua.getField(-1, name)
if (lua.isNil(-1)) {
// Entry already exists, skip it.
lua.pop(1)
// TODO Log warning properly via a logger.
println("WARNING: Duplicate API entry, ignoring: " + api + "." + name)
}
else {
// No such entry yet. Pop the nil and build our callback wrapper.
lua.pop(1)
lua.pushJavaFunction(new MethodWrapper(context, method))
lua.setField(-2, name)
}
}
}
// Pop the API table and the table holding all APIs.
lua.pop(2)
}
/**
* This installs the driver on the computer, providing an API to interact
* with the device.
*
* This copies an existing API table from the registry and executes any
* initialization code provided by the driver.
*/
def install(context: IComputerContext) {
copyAPI(context)
// Do we have custom initialization code?
val code = driver.apiCode
if (code != null && !code.isEmpty()) {
val lua = context.luaState
lua.load(code, driver.componentName)
// TODO Set environment so that variables not explicitly added to globals
// table won't accidentally clutter it.
lua.call(0, 0)
}
}
private def copyAPI(context: IComputerContext) {
// Check if the component actually provides an API.
val api = driver.apiName
if (api == null && api.isEmpty()) return
// Get the Lua state and check if the API table already exists.
val lua = context.luaState
lua.getField(LuaState.REGISTRYINDEX, ComputerRegistry.driverApis)
if (lua.isNil(-1)) {
// We don't have any APIs at all.
lua.pop(1)
return
}
lua.getField(-1, api)
if (lua.isNil(-1)) {
// No such API. Which is kind of weird, but hey.
lua.pop(2)
return
}
// OK, we have our registry table. Create a new table to copy into.
val registryTable = lua.getTop()
lua.newTable()
val globalTable = lua.getTop()
// Copy all keys (which are the API functions).
lua.pushNil()
while (lua.next(registryTable)) {
val key = lua.toString(-2)
lua.setField(globalTable, key)
}
// Push our globals table into the global name space.
lua.setGlobal(api)
// Pop the registry API table and registry table holding all API tables.
lua.pop(2)
}
private class MethodWrapper(val context: IComputerContext, val method: Method) extends JavaFunction {
private val classOfBoolean = classOf[Boolean]
private val classOfByte = classOf[Byte]
private val classOfShort = classOf[Short]
private val classOfInteger = classOf[Int]
private val classOfLong = classOf[Long]
private val classOfFloat = classOf[Float]
private val classOfDouble = classOf[Double]
private val classOfString = classOf[String]
private val parameterTypes = method.getParameterTypes.zipWithIndex
private val parameterCount = parameterTypes.size
private val returnType = method.getReturnType
private val returnsTuple = returnType.isInstanceOf[Array[Object]]
private val returnsNothing = returnType.equals(Void.TYPE)
def invoke(state: LuaState): Int = {
// Parse the parameters, convert them to Java types.
val parameters = Array(context) ++ parameterTypes.map {
case (classOfBoolean, i) => boolean2Boolean(state.checkBoolean(i + 1))
case (classOfByte, i) => java.lang.Byte.valueOf(state.checkInteger(i + 1).toByte)
case (classOfShort, i) => java.lang.Short.valueOf(state.checkInteger(i + 1).toShort)
case (classOfInteger, i) => java.lang.Integer.valueOf(state.checkInteger(i + 1))
case (classOfLong, i) => java.lang.Long.valueOf(state.checkInteger(i + 1).toLong)
case (classOfFloat, i) => java.lang.Float.valueOf(state.checkNumber(i + 1).toFloat)
case (classOfDouble, i) => java.lang.Double.valueOf(state.checkNumber(i + 1))
case (classOfString, i) => state.checkString(i + 1)
case _ => null
}
// Call the actual function, grab the result, if any.
val result = method.invoke(driver, parameters: _*)
// Check the result, convert it to Lua.
if (returnsTuple) {
val array = result.asInstanceOf[Array[Object]]
array.foreach(v => push(state, v, v.getClass()))
return array.length
}
else if (returnsNothing) {
return 0
}
else {
push(state, result, returnType)
return 1
}
}
private def push(state: LuaState, value: Object, clazz: Class[_]) = clazz match {
case classOfBoolean => state.pushBoolean(value.asInstanceOf[Boolean])
case classOfInteger => state.pushNumber(value.asInstanceOf[Int])
case classOfDouble => state.pushNumber(value.asInstanceOf[Double])
case classOfString => state.pushString(value.asInstanceOf[String])
case _ => state.pushNil()
}
}
}