cleaned up RIM reflection into a helper singleton; cleaned up and extended RIM component a bit; extended arguments class with type testing methods; documented arguments class; linearly increasing screen buffer size per tier (copy would fail for advanced screens...)

This commit is contained in:
Florian Nücke 2013-11-05 13:23:11 +01:00
parent 43d9365c17
commit 2a2785e077
7 changed files with 263 additions and 61 deletions

View File

@ -5,6 +5,10 @@ package li.cil.oc.api.network;
* <p/> * <p/>
* It allows checking for the presence of arguments in a uniform manner, taking * It allows checking for the presence of arguments in a uniform manner, taking
* care of proper type checking based on what can be passed along by Lua. * care of proper type checking based on what can be passed along by Lua.
* <p/>
* Note that integer values fetched this way are actually double values that
* have been truncated. So if a Lua program passes <tt>1.9</tt> and you do a
* <tt>checkInteger</tt> you'll get a <tt>1</tt>.
*/ */
public interface Arguments extends Iterable<Object> { public interface Arguments extends Iterable<Object> {
/** /**
@ -28,16 +32,125 @@ public interface Arguments extends Iterable<Object> {
* *
* @param index the index from which to get the argument. * @param index the index from which to get the argument.
* @return the raw value at that index. * @return the raw value at that index.
* @throws IllegalArgumentException if there is no argument at that index.
*/ */
Object checkAny(int index); Object checkAny(int index);
/**
* Try to get a boolean value at the specified index.
* <p/>
* Throws an error if there are too few arguments.
*
* @param index the index from which to get the argument.
* @return the boolean value at the specified index.
* @throws IllegalArgumentException if there is no argument at that index,
* or if the argument is not a boolean.
*/
boolean checkBoolean(int index); boolean checkBoolean(int index);
double checkDouble(int index); /**
* Try to get an integer value at the specified index.
* <p/>
* Throws an error if there are too few arguments.
*
* @param index the index from which to get the argument.
* @return the integer value at the specified index.
* @throws IllegalArgumentException if there is no argument at that index,
* or if the argument is not a number.
*/
int checkInteger(int index); int checkInteger(int index);
/**
* Try to get a double value at the specified index.
* <p/>
* Throws an error if there are too few arguments.
*
* @param index the index from which to get the argument.
* @return the double value at the specified index.
* @throws IllegalArgumentException if there is no argument at that index,
* or if the argument is not a number.
*/
double checkDouble(int index);
/**
* Try to get a string value at the specified index.
* <p/>
* Throws an error if there are too few arguments.
* <p/>
* This will actually check for a byte array and convert it to a string
* using UTF-8 encoding.
*
* @param index the index from which to get the argument.
* @return the boolean value at the specified index.
* @throws IllegalArgumentException if there is no argument at that index,
* or if the argument is not a string.
*/
String checkString(int index); String checkString(int index);
/**
* Try to get a byte array at the specified index.
* <p/>
* Throws an error if there are too few arguments.
*
* @param index the index from which to get the argument.
* @return the byte array at the specified index.
* @throws IllegalArgumentException if there is no argument at that index,
* or if the argument is not a byte array.
*/
byte[] checkByteArray(int index); byte[] checkByteArray(int index);
/**
* Tests whether the argument at the specified index is a boolean value.
* <p/>
* This will return true if there is <em>no</em> argument at the specified
* index, i.e. if there are too few arguments.
*
* @param index the index to check.
* @return true if the argument is a boolean; false otherwise.
*/
boolean isBoolean(int index);
/**
* Tests whether the argument at the specified index is an integer value.
* <p/>
* This will return true if there is <em>no</em> argument at the specified
* index, i.e. if there are too few arguments.
*
* @param index the index to check.
* @return true if the argument is an integer; false otherwise.
*/
boolean isInteger(int index);
/**
* Tests whether the argument at the specified index is a double value.
* <p/>
* This will return true if there is <em>no</em> argument at the specified
* index, i.e. if there are too few arguments.
*
* @param index the index to check.
* @return true if the argument is a double; false otherwise.
*/
boolean isDouble(int index);
/**
* Tests whether the argument at the specified index is a string value.
* <p/>
* This will return true if there is <em>no</em> argument at the specified
* index, i.e. if there are too few arguments.
*
* @param index the index to check.
* @return true if the argument is a string; false otherwise.
*/
boolean isString(int index);
/**
* Tests whether the argument at the specified index is a byte array.
* <p/>
* This will return true if there is <em>no</em> argument at the specified
* index, i.e. if there are too few arguments.
*
* @param index the index to check.
* @return true if the argument is a byte array; false otherwise.
*/
boolean isByteArray(int index);
} }

View File

@ -74,12 +74,12 @@ object Screen {
trait Environment extends tileentity.Environment with util.Persistable { trait Environment extends tileentity.Environment with util.Persistable {
val node = api.Network.newNode(this, Visibility.Network). val node = api.Network.newNode(this, Visibility.Network).
withComponent("screen"). withComponent("screen").
withConnector(Config.bufferScreen). withConnector(Config.bufferScreen * (tier + 1)).
create() create()
final val instance = new component.Screen(this, maxResolution) final val instance = new component.Screen(this, Config.screenResolutionsByTier(tier))
protected def maxResolution: (Int, Int) protected def tier: Int
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //

View File

@ -14,15 +14,15 @@ import scala.collection.mutable
import net.minecraft.client.Minecraft import net.minecraft.client.Minecraft
class ScreenTier1 extends Screen { class ScreenTier1 extends Screen {
protected def maxResolution = Config.screenResolutionsByTier(0) protected def tier = 0
} }
class ScreenTier2 extends Screen { class ScreenTier2 extends Screen {
protected def maxResolution = Config.screenResolutionsByTier(1) protected def tier = 1
} }
class ScreenTier3 extends Screen { class ScreenTier3 extends Screen {
protected def maxResolution = Config.screenResolutionsByTier(2) protected def tier = 2
} }
abstract class Screen extends Rotatable with ScreenEnvironment { abstract class Screen extends Rotatable with ScreenEnvironment {
@ -165,7 +165,7 @@ abstract class Screen extends Rotatable with ScreenEnvironment {
def tryMergeTowards(dx: Int, dy: Int) = { def tryMergeTowards(dx: Int, dy: Int) = {
val (nx, ny, nz) = unproject(x + dx, y + dy, z) val (nx, ny, nz) = unproject(x + dx, y + dy, z)
worldObj.getBlockTileEntity(nx, ny, nz) match { worldObj.getBlockTileEntity(nx, ny, nz) match {
case s: Screen if s.maxResolution == maxResolution && s.pitch == pitch && s.yaw == yaw && !screens.contains(s) => case s: Screen if s.tier == tier && s.pitch == pitch && s.yaw == yaw && !screens.contains(s) =>
val (sx, sy, _) = project(s.origin) val (sx, sy, _) = project(s.origin)
val canMergeAlongX = sy == y && s.height == height && s.width + width <= Config.maxScreenWidth val canMergeAlongX = sy == y && s.height == height && s.width + width <= Config.maxScreenWidth
val canMergeAlongY = sx == x && s.width == width && s.height + height <= Config.maxScreenHeight val canMergeAlongY = sx == x && s.width == width && s.height + height <= Config.maxScreenHeight

View File

@ -1,49 +1,71 @@
package li.cil.oc.server.component package li.cil.oc.server.component
import java.lang.reflect.InvocationTargetException
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.util.RedstoneInMotion
import net.minecraft.nbt.NBTTagCompound import net.minecraft.nbt.NBTTagCompound
import scala.Some
import scala.language.existentials
class Carriage(controller: Object) extends ManagedComponent { class Carriage(controller: AnyRef) extends ManagedComponent {
val node = api.Network.newNode(this, Visibility.Network). val node = api.Network.newNode(this, Visibility.Network).
withComponent("carriage"). withComponent("carriage").
create() create()
private val (directions, setup, move) = try { private val names = Map(
val directions = Class.forName("JAKJ.RedstoneInMotion.Directions").getEnumConstants "negy" -> 0, "posy" -> 1, "negz" -> 2, "posz" -> 3, "negx" -> 4, "posx" -> 5,
val clazz = Class.forName("JAKJ.RedstoneInMotion.CarriageControllerEntity") "down" -> 0, "up" -> 1, "north" -> 2, "south" -> 3, "west" -> 4, "east" -> 5)
val methods = clazz.getDeclaredMethods
val setup = methods.find(_.getName == "SetupMotion").orNull
val move = methods.find(_.getName == "Move").orNull
(directions, setup, move)
} catch {
case _: Throwable => (null, null, null)
}
private var shouldMove = false private var anchored = false
private var direction = 0 private var direction = 0
private var simulating = false private var simulating = false
private var anchored = false
private var shouldMove = false
private var moving = false private var moving = false
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
@LuaCallback("move") @LuaCallback("move")
def move(context: Context, args: Arguments): Array[Object] = { def move(context: Context, args: Arguments): Array[Object] = {
if (directions == null || setup == null || move == null) direction = checkDirection(args)
simulating = if (args.count > 1) args.checkBoolean(1) else false
shouldMove = true
result(true)
}
@LuaCallback("simulate")
def simulate(context: Context, args: Arguments): Array[Object] = {
direction = checkDirection(args)
simulating = true
shouldMove = true
result(true)
}
@LuaCallback(value = "getAnchored", direct = true)
def getAnchored(context: Context, args: Arguments): Array[Object] =
result(anchored)
@LuaCallback("setAnchored")
def setAnchored(context: Context, args: Arguments): Array[Object] = {
anchored = args.checkBoolean(0)
result(anchored)
}
private def checkDirection(args: Arguments) = {
if (!RedstoneInMotion.available)
throw new Exception("Redstone in Motion not found") throw new Exception("Redstone in Motion not found")
if (shouldMove || moving) if (shouldMove || moving)
throw new Exception("already moving") throw new Exception("already moving")
direction = args.checkInteger(0) if (args.isString(0)) {
if (direction < 0 || direction > directions.length) val name = args.checkString(0).toLowerCase
if (!names.contains(name))
throw new IllegalArgumentException("invalid direction")
names(name)
}
else {
val index = args.checkInteger(0)
if (index < 0 || index > 5)
throw new ArrayIndexOutOfBoundsException("invalid direction") throw new ArrayIndexOutOfBoundsException("invalid direction")
simulating = args.checkBoolean(1) index
anchored = args.checkBoolean(2) }
shouldMove = true
result(true)
} }
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
@ -53,18 +75,19 @@ class Carriage(controller: Object) extends ManagedComponent {
if (shouldMove) { if (shouldMove) {
shouldMove = false shouldMove = false
moving = true moving = true
var error: Option[Throwable] = None
try { try {
setup.invoke(controller, directions(direction), Boolean.box(simulating), Boolean.box(anchored)) RedstoneInMotion.move(controller, direction, simulating, anchored)
move.invoke(controller) if (simulating || anchored) {
} catch { // We won't get re-connected, so we won't send in onConnect. Do it here.
case e: InvocationTargetException => error = Some(e.getCause) node.sendToReachable("computer.signal", "carriage_moved", Boolean.box(true))
case e: Throwable => error = Some(e)
} }
}
catch {
case e: Throwable =>
node.sendToReachable("computer.signal", "carriage_moved", Unit, Option(e.getMessage).getOrElse(e.toString))
}
finally {
moving = false moving = false
error match {
case Some(e) => node.sendToReachable("computer.signal", "carriage_moved", Unit, Option(e.getMessage).getOrElse(e.toString))
case _ => if (simulating || anchored) node.sendToReachable("computer.signal", "carriage_moved", Boolean.box(true))
} }
} }
} }
@ -84,10 +107,12 @@ class Carriage(controller: Object) extends ManagedComponent {
override def save(nbt: NBTTagCompound) { override def save(nbt: NBTTagCompound) {
super.save(nbt) super.save(nbt)
nbt.setBoolean("moving", moving) nbt.setBoolean("moving", moving)
nbt.setBoolean("anchored", anchored)
} }
override def load(nbt: NBTTagCompound) { override def load(nbt: NBTTagCompound) {
super.load(nbt) super.load(nbt)
moving = nbt.getBoolean("moving") moving = nbt.getBoolean("moving")
anchored = nbt.getBoolean("anchored")
} }
} }

View File

@ -2,28 +2,19 @@ package li.cil.oc.server.driver
import li.cil.oc.api.driver import li.cil.oc.api.driver
import li.cil.oc.server.component import li.cil.oc.server.component
import li.cil.oc.util.RedstoneInMotion
import net.minecraft.world.World import net.minecraft.world.World
object Carriage extends driver.Block { object Carriage extends driver.Block {
private val (carriageControllerClass) = try {
Class.forName("JAKJ.RedstoneInMotion.CarriageControllerEntity")
} catch {
case _: Throwable => null
}
def worksWith(world: World, x: Int, y: Int, z: Int) = def worksWith(world: World, x: Int, y: Int, z: Int) =
Option(world.getBlockTileEntity(x, y, z)) match { Option(world.getBlockTileEntity(x, y, z)) match {
case Some(entity) if checkClass(entity) => true case Some(entity) if RedstoneInMotion.isCarriageController(entity) => true
case _ => false case _ => false
} }
def createEnvironment(world: World, x: Int, y: Int, z: Int) = def createEnvironment(world: World, x: Int, y: Int, z: Int) =
world.getBlockTileEntity(x, y, z) match { world.getBlockTileEntity(x, y, z) match {
case controller if checkClass(controller) => case entity if RedstoneInMotion.isCarriageController(entity) => new component.Carriage(entity)
new component.Carriage(controller)
case _ => null case _ => null
} }
private def checkClass(value: Object) =
carriageControllerClass != null && carriageControllerClass.isAssignableFrom(value.getClass)
} }

View File

@ -146,7 +146,7 @@ object Component {
args(index) args(index)
} }
def checkBoolean(index: Int): Boolean = { def checkBoolean(index: Int) = {
checkIndex(index, "boolean") checkIndex(index, "boolean")
args(index) match { args(index) match {
case value: java.lang.Boolean => value case value: java.lang.Boolean => value
@ -154,7 +154,7 @@ object Component {
} }
} }
def checkDouble(index: Int): Double = { def checkDouble(index: Int) = {
checkIndex(index, "number") checkIndex(index, "number")
args(index) match { args(index) match {
case value: java.lang.Double => value case value: java.lang.Double => value
@ -162,7 +162,7 @@ object Component {
} }
} }
def checkInteger(index: Int): Int = { def checkInteger(index: Int) = {
checkIndex(index, "number") checkIndex(index, "number")
args(index) match { args(index) match {
case value: java.lang.Double => value.intValue case value: java.lang.Double => value.intValue
@ -170,10 +170,16 @@ object Component {
} }
} }
def checkString(index: Int) = def checkString(index: Int) = {
new String(checkByteArray(index), "UTF-8") checkIndex(index, "string")
args(index) match {
case value: java.lang.String => value
case value: Array[Byte] => new String(value, "UTF-8")
case value => throw typeError(index, value, "string")
}
}
def checkByteArray(index: Int): Array[Byte] = { def checkByteArray(index: Int) = {
checkIndex(index, "string") checkIndex(index, "string")
args(index) match { args(index) match {
case value: Array[Byte] => value case value: Array[Byte] => value
@ -181,6 +187,37 @@ object Component {
} }
} }
def isBoolean(index: Int) =
index >= 0 && index < count && (args(index) match {
case value: java.lang.Boolean => true
case _ => false
})
def isDouble(index: Int) =
index >= 0 && index < count && (args(index) match {
case value: java.lang.Double => true
case _ => false
})
def isInteger(index: Int) =
index >= 0 && index < count && (args(index) match {
case value: java.lang.Integer => true
case _ => false
})
def isString(index: Int) =
index >= 0 && index < count && (args(index) match {
case value: java.lang.String => true
case value: Array[Byte] => true
case _ => false
})
def isByteArray(index: Int) =
index >= 0 && index < count && (args(index) match {
case value: Array[Byte] => true
case _ => false
})
private def checkIndex(index: Int, name: String) = private def checkIndex(index: Int, name: String) =
if (index < 0) throw new IndexOutOfBoundsException() if (index < 0) throw new IndexOutOfBoundsException()
else if (args.length <= index) throw new IllegalArgumentException( else if (args.length <= index) throw new IllegalArgumentException(

View File

@ -0,0 +1,36 @@
package li.cil.oc.util
import java.lang.reflect.InvocationTargetException
import scala.language.existentials
object RedstoneInMotion {
private val (controller, setup, move, directions) = try {
val controller = Class.forName("JAKJ.RedstoneInMotion.CarriageControllerEntity")
val methods = controller.getDeclaredMethods
val setup = methods.find(_.getName == "SetupMotion").get
val move = methods.find(_.getName == "Move").get
val directions = Class.forName("JAKJ.RedstoneInMotion.Directions").getEnumConstants
(Option(controller), setup, move, directions)
} catch {
case _: Throwable => (None, null, null, null)
}
def available = controller.isDefined
def isCarriageController(value: AnyRef) = controller match {
case Some(clazz) => clazz.isAssignableFrom(value.getClass)
case _ => false
}
def move(controller: AnyRef, direction: Int, simulating: Boolean, anchored: Boolean) {
if (!isCarriageController(controller))
throw new IllegalArgumentException("Not a carriage controller.")
try {
setup.invoke(controller, directions(direction), Boolean.box(simulating), Boolean.box(anchored))
move.invoke(controller)
} catch {
case e: InvocationTargetException => throw e.getCause
}
}
}