blob: 6b8c5e68adf5464352aa754cec703116d5b796f5 [file] [log] [blame] [raw]
package li.cil.oc.server.network
import cpw.mods.fml.common.FMLCommonHandler
import cpw.mods.fml.relauncher.Side
import java.lang.reflect.InvocationTargetException
import li.cil.oc.api
import li.cil.oc.api.network.environment.{Environment, LuaCallback}
import li.cil.oc.api.network.{Message, Visibility}
import net.minecraft.nbt.NBTTagCompound
import scala.collection.convert.WrapAsScala._
import scala.collection.{immutable, mutable}
class Component(host: Environment, name: String, reachability: Visibility) extends Node(host, name, reachability) with api.network.Component {
private val luaCallbacks = Component.callbacks(host.getClass)
private var visibility_ = reachability
def visibility = visibility_
def visibility(value: Visibility) = {
if (value.ordinal() > visibility.ordinal()) {
throw new IllegalArgumentException("Trying to set computer visibility to '" + value + "' on a '" + name +
"' node with reachability '" + visibility + "'. It will be limited to the node's reachability.")
}
if (value != visibility_ && FMLCommonHandler.instance.getEffectiveSide == Side.SERVER) {
if (network != null) visibility_ match {
case Visibility.Neighbors => value match {
case Visibility.Network =>
val neighbors = network.neighbors(this).toSet
val visible = network.nodes(this)
val delta = visible.filterNot(neighbors.contains)
delta.foreach(node => network.sendToAddress(this, node.address, "computer.signal", "component_added"))
case Visibility.None => network.sendToNeighbors(this, "computer.signal", "component_removed")
case _ => // Cannot happen, but avoids compiler warnings.
}
case Visibility.Network => value match {
case Visibility.Neighbors =>
val neighbors = network.neighbors(this).toSet
val visible = network.nodes(this)
val delta = visible.filterNot(neighbors.contains)
delta.foreach(node => network.sendToAddress(this, node.address, "computer.signal", "component_removed"))
case Visibility.None => network.sendToVisible(this, "computer.signal", "component_removed")
case _ => // Cannot happen, but avoids compiler warnings.
}
case Visibility.None => value match {
case Visibility.Neighbors => network.sendToNeighbors(this, "computer.signal", "component_added")
case Visibility.Network => network.sendToVisible(this, "computer.signal", "component_added")
case _ => // Cannot happen, but avoids compiler warnings.
}
}
visibility_ = value
}
}
def canBeSeenBy(other: api.network.Node) = network == null || (visibility match {
case Visibility.None => false
case Visibility.Network => true
case Visibility.Neighbors => other.network.neighbors(other).exists(_ == this)
})
// ----------------------------------------------------------------------- //
override def receive(message: Message) = {
if (message.name == "component.methods")
if (canBeSeenBy(message.source))
Array(Array(luaCallbacks.keys.toSeq: _*))
else null
else if (message.name == "component.invoke") {
luaCallbacks.get(message.data()(0).asInstanceOf[String]) match {
case Some(callback) => callback(host, message)
case _ => throw new NoSuchMethodException()
}
}
else super.receive(message)
}
// ----------------------------------------------------------------------- //
override def save(nbt: NBTTagCompound) {
super.load(nbt)
if (nbt.hasKey("visibility"))
visibility_ = Visibility.values()(nbt.getInteger("visibility"))
}
override def load(nbt: NBTTagCompound) {
super.save(nbt)
nbt.setInteger("visibility", visibility_.ordinal())
}
}
object Component {
private val cache = mutable.Map.empty[Class[_], immutable.Map[String, (Object, api.network.Message) => Array[Object]]]
def callbacks(clazz: Class[_]) = cache.getOrElseUpdate(clazz, analyze(clazz))
private def analyze(clazz: Class[_]) = {
val callbacks = mutable.Map.empty[String, (Object, api.network.Message) => Array[Object]]
var c = clazz
while (c != classOf[Object]) {
val ms = c.getDeclaredMethods
ms.filter(_.isAnnotationPresent(classOf[LuaCallback])).foreach(m =>
if (m.getParameterTypes.size != 1 || m.getParameterTypes.head != classOf[api.network.Message]) {
throw new IllegalArgumentException("Invalid use of LuaCallback annotation (method must take exactly one Message parameter).")
}
else if (m.getReturnType != classOf[Array[Object]]) {
throw new IllegalArgumentException("Invalid use of LuaCallback annotation (method must return an array of objects).")
}
else {
val a = m.getAnnotation[LuaCallback](classOf[LuaCallback])
if (a.value == null || a.value == "") {
throw new IllegalArgumentException("Invalid use of LuaCallback annotation (name must not be null or empty).")
}
else if (!callbacks.contains(a.value)) {
callbacks += a.value -> ((o, e) => try {
m.invoke(o, e).asInstanceOf[Array[Object]]
} catch {
case e: InvocationTargetException => throw e.getCause
})
}
}
)
c = c.getSuperclass
}
callbacks.toMap
}
}