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/>
* 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.
* <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> {
/**
@ -28,16 +32,125 @@ public interface Arguments extends Iterable<Object> {
*
* @param index the index from which to get the argument.
* @return the raw value at that index.
* @throws IllegalArgumentException if there is no argument at that 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);
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);
/**
* 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);
/**
* 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);
/**
* 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 {
val node = api.Network.newNode(this, Visibility.Network).
withComponent("screen").
withConnector(Config.bufferScreen).
withConnector(Config.bufferScreen * (tier + 1)).
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
class ScreenTier1 extends Screen {
protected def maxResolution = Config.screenResolutionsByTier(0)
protected def tier = 0
}
class ScreenTier2 extends Screen {
protected def maxResolution = Config.screenResolutionsByTier(1)
protected def tier = 1
}
class ScreenTier3 extends Screen {
protected def maxResolution = Config.screenResolutionsByTier(2)
protected def tier = 2
}
abstract class Screen extends Rotatable with ScreenEnvironment {
@ -165,7 +165,7 @@ abstract class Screen extends Rotatable with ScreenEnvironment {
def tryMergeTowards(dx: Int, dy: Int) = {
val (nx, ny, nz) = unproject(x + dx, y + dy, z)
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 canMergeAlongX = sy == y && s.height == height && s.width + width <= Config.maxScreenWidth
val canMergeAlongY = sx == x && s.width == width && s.height + height <= Config.maxScreenHeight

View File

@ -1,49 +1,71 @@
package li.cil.oc.server.component
import java.lang.reflect.InvocationTargetException
import li.cil.oc.api
import li.cil.oc.api.network._
import li.cil.oc.util.RedstoneInMotion
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).
withComponent("carriage").
create()
private val (directions, setup, move) = try {
val directions = Class.forName("JAKJ.RedstoneInMotion.Directions").getEnumConstants
val clazz = Class.forName("JAKJ.RedstoneInMotion.CarriageControllerEntity")
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 val names = Map(
"negy" -> 0, "posy" -> 1, "negz" -> 2, "posz" -> 3, "negx" -> 4, "posx" -> 5,
"down" -> 0, "up" -> 1, "north" -> 2, "south" -> 3, "west" -> 4, "east" -> 5)
private var shouldMove = false
private var anchored = false
private var direction = 0
private var simulating = false
private var anchored = false
private var shouldMove = false
private var moving = false
// ----------------------------------------------------------------------- //
@LuaCallback("move")
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")
if (shouldMove || moving)
throw new Exception("already moving")
direction = args.checkInteger(0)
if (direction < 0 || direction > directions.length)
throw new ArrayIndexOutOfBoundsException("invalid direction")
simulating = args.checkBoolean(1)
anchored = args.checkBoolean(2)
shouldMove = true
result(true)
if (args.isString(0)) {
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")
index
}
}
// ----------------------------------------------------------------------- //
@ -53,18 +75,19 @@ class Carriage(controller: Object) extends ManagedComponent {
if (shouldMove) {
shouldMove = false
moving = true
var error: Option[Throwable] = None
try {
setup.invoke(controller, directions(direction), Boolean.box(simulating), Boolean.box(anchored))
move.invoke(controller)
} catch {
case e: InvocationTargetException => error = Some(e.getCause)
case e: Throwable => error = Some(e)
RedstoneInMotion.move(controller, direction, simulating, anchored)
if (simulating || anchored) {
// We won't get re-connected, so we won't send in onConnect. Do it here.
node.sendToReachable("computer.signal", "carriage_moved", Boolean.box(true))
}
}
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))
catch {
case e: Throwable =>
node.sendToReachable("computer.signal", "carriage_moved", Unit, Option(e.getMessage).getOrElse(e.toString))
}
finally {
moving = false
}
}
}
@ -84,10 +107,12 @@ class Carriage(controller: Object) extends ManagedComponent {
override def save(nbt: NBTTagCompound) {
super.save(nbt)
nbt.setBoolean("moving", moving)
nbt.setBoolean("anchored", anchored)
}
override def load(nbt: NBTTagCompound) {
super.load(nbt)
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.server.component
import li.cil.oc.util.RedstoneInMotion
import net.minecraft.world.World
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) =
Option(world.getBlockTileEntity(x, y, z)) match {
case Some(entity) if checkClass(entity) => true
case Some(entity) if RedstoneInMotion.isCarriageController(entity) => true
case _ => false
}
def createEnvironment(world: World, x: Int, y: Int, z: Int) =
world.getBlockTileEntity(x, y, z) match {
case controller if checkClass(controller) =>
new component.Carriage(controller)
case entity if RedstoneInMotion.isCarriageController(entity) => new component.Carriage(entity)
case _ => null
}
private def checkClass(value: Object) =
carriageControllerClass != null && carriageControllerClass.isAssignableFrom(value.getClass)
}

View File

@ -146,7 +146,7 @@ object Component {
args(index)
}
def checkBoolean(index: Int): Boolean = {
def checkBoolean(index: Int) = {
checkIndex(index, "boolean")
args(index) match {
case value: java.lang.Boolean => value
@ -154,7 +154,7 @@ object Component {
}
}
def checkDouble(index: Int): Double = {
def checkDouble(index: Int) = {
checkIndex(index, "number")
args(index) match {
case value: java.lang.Double => value
@ -162,7 +162,7 @@ object Component {
}
}
def checkInteger(index: Int): Int = {
def checkInteger(index: Int) = {
checkIndex(index, "number")
args(index) match {
case value: java.lang.Double => value.intValue
@ -170,10 +170,16 @@ object Component {
}
}
def checkString(index: Int) =
new String(checkByteArray(index), "UTF-8")
def checkString(index: Int) = {
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")
args(index) match {
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) =
if (index < 0) throw new IndexOutOfBoundsException()
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
}
}
}