diff --git a/src/main/java/li/cil/oc/api/network/Component.java b/src/main/java/li/cil/oc/api/network/Component.java index 62f4d0ed7..b471692c2 100644 --- a/src/main/java/li/cil/oc/api/network/Component.java +++ b/src/main/java/li/cil/oc/api/network/Component.java @@ -81,6 +81,5 @@ public interface Component extends Node { * @return the list of results, or null if there is no result. * @throws NoSuchMethodException if there is no method with that name. */ - Object[] invoke(String method, Context context, Object... arguments) - throws NoSuchMethodException; + Object[] invoke(String method, Context context, Object... arguments) throws Exception; } diff --git a/src/main/java/li/cil/oc/api/network/ManagedPeripheral.java b/src/main/java/li/cil/oc/api/network/ManagedPeripheral.java new file mode 100644 index 000000000..e81ea2bc1 --- /dev/null +++ b/src/main/java/li/cil/oc/api/network/ManagedPeripheral.java @@ -0,0 +1,35 @@ +package li.cil.oc.api.network; + +/** + * This specialization of the managed environment is intended to be used for + * environments wrapping a ComputerCraft peripheral, although it could be used + * for other purposes as well. It allows providing method names in addition to + * those defined via the {@link li.cil.oc.api.network.Callback} annotation, and + * invoking said methods. + */ +public interface ManagedPeripheral extends ManagedEnvironment { + /** + * Get the list of methods provided by this environment, in + * addition to methods marked as callbacks. + *

+ * Returning null has the same meaning as returning an empty array, + * that being that there are no methods - in which case you don't need this + * interface! + * + * @return the list of methods provided by the environment. + */ + String[] methods(); + + /** + * Calls a method from the list provided by {@link #methods()}. + *

+ * + * @param method the name of the method to call. + * @param context the context from which the method is called. + * @param args the arguments to pass to the method. + * @return the result of calling the method. Same as for callbacks. + * @throws java.lang.NoSuchMethodException if there is no method with the + * specified name. + */ + Object[] invoke(String method, Context context, Arguments args) throws Exception; +} diff --git a/src/main/java/li/cil/oc/api/prefab/ManagedPeripheral.java b/src/main/java/li/cil/oc/api/prefab/ManagedPeripheral.java new file mode 100644 index 000000000..872925ff4 --- /dev/null +++ b/src/main/java/li/cil/oc/api/prefab/ManagedPeripheral.java @@ -0,0 +1,156 @@ +package li.cil.oc.api.prefab; + +import com.google.common.collect.Iterables; +import dan200.computer.api.*; +import li.cil.oc.api.network.Arguments; +import li.cil.oc.api.network.Context; +import li.cil.oc.api.network.Node; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Implementation of the ManagedPeripheral interface for simple + * wrapping of ComputerCraft peripherals. + */ +public class ManagedPeripheral extends ManagedEnvironment implements li.cil.oc.api.network.ManagedPeripheral { + protected final IPeripheral peripheral; + + protected final List _methods; + + protected final Map accesses = new HashMap(); + + public ManagedPeripheral(final IPeripheral peripheral) { + this.peripheral = peripheral; + _methods = Arrays.asList(peripheral.getMethodNames()); + } + + @Override + public String[] methods() { + return peripheral.getMethodNames(); + } + + @Override + public Object[] invoke(final String method, final Context context, final Arguments args) throws Exception { + final int index = _methods.indexOf(method); + if (index < 0) { + throw new NoSuchMethodException(); + } + final Object[] argArray = Iterables.toArray(args, Object.class); + for (int i = 0; i < argArray.length; ++i) { + if (argArray[i] instanceof byte[]) { + argArray[i] = new String((byte[]) argArray[i], "UTF-8"); + } + } + final FakeComputerAccess access; + if (accesses.containsKey(context.address())) { + access = accesses.get(context.address()); + } else { + // The calling contexts is not visible to us, meaning we never got + // an onConnect for it. Create a temporary access. + access = new FakeComputerAccess(this, context); + } + return peripheral.callMethod(access, UnsupportedLuaContext.instance(), index, argArray); + } + + @Override + public void onConnect(final Node node) { + super.onConnect(node); + if (node.host() instanceof Context) { + final FakeComputerAccess access = new FakeComputerAccess(this, (Context) node.host()); + accesses.put(node.address(), access); + peripheral.attach(access); + } + } + + @Override + public void onDisconnect(final Node node) { + super.onDisconnect(node); + if (node.host() instanceof Context) { + final FakeComputerAccess access = accesses.remove(node.address()); + if (access != null) { + peripheral.detach(access); + } + } else if (node == this.node) { + for (FakeComputerAccess access : accesses.values()) { + peripheral.detach(access); + } + accesses.clear(); + } + } + + /** + * Map interaction with the computer to our format as good as we can. + */ + private static class FakeComputerAccess implements IComputerAccess { + protected final ManagedPeripheral owner; + protected final Context context; + + public FakeComputerAccess(final ManagedPeripheral owner, final Context context) { + this.owner = owner; + this.context = context; + } + + @Override + public String mount(final String desiredLocation, final IMount mount) { + throw new UnsupportedOperationException(); + } + + @Override + public String mountWritable(final String desiredLocation, final IWritableMount mount) { + throw new UnsupportedOperationException(); + } + + @Override + public void unmount(final String location) { + throw new UnsupportedOperationException(); + } + + @Override + public int getID() { + return context.address().hashCode(); + } + + @Override + public void queueEvent(final String event, final Object[] arguments) { + context.signal(event, arguments); + } + + @Override + public String getAttachmentName() { + return owner.node.address(); + } + } + + /** + * Since we abstract away anything language specific, we cannot support the + * Lua context specific operations ComputerCraft provides. + */ + private final static class UnsupportedLuaContext implements ILuaContext { + protected static final UnsupportedLuaContext Instance = new UnsupportedLuaContext(); + + private UnsupportedLuaContext() { + } + + public static UnsupportedLuaContext instance() { + return Instance; + } + + @Override + public Object[] pullEvent(final String filter) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + public Object[] pullEventRaw(final String filter) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public Object[] yield(final Object[] arguments) throws InterruptedException { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/main/scala/li/cil/oc/server/network/Component.scala b/src/main/scala/li/cil/oc/server/network/Component.scala index 7eeae35ad..84de1eeea 100644 --- a/src/main/scala/li/cil/oc/server/network/Component.scala +++ b/src/main/scala/li/cil/oc/server/network/Component.scala @@ -8,6 +8,7 @@ import li.cil.oc.api.network import li.cil.oc.api.network.{Node => ImmutableNode, _} import li.cil.oc.server.component.machine.Machine import li.cil.oc.server.driver.CompoundBlockEnvironment +import li.cil.oc.server.network.Component.{PeripheralCallback, ComponentCallback} import net.minecraft.nbt.NBTTagCompound import scala.Some import scala.collection.convert.WrapAsJava._ @@ -24,18 +25,26 @@ trait Component extends network.Component with Node { private lazy val hosts = host match { case multi: CompoundBlockEnvironment => callbacks.map { - case (method, callback) => - multi.environments.find { - case (_, environment) => environment.getClass == callback.method.getDeclaringClass - } match { - case Some((_, environment)) => method -> Some(environment) - case _ => method -> None - } - } - case _ => - callbacks.map { - case (method, callback) => method -> Some(host) + case (method, callback) => callback match { + case component: ComponentCallback => + multi.environments.find { + case (_, environment) => environment.getClass == component.method.getDeclaringClass + } match { + case Some((_, environment)) => method -> Some(environment) + case _ => method -> None + } + case peripheral: PeripheralCallback => + multi.environments.find { + case (_, environment: ManagedPeripheral) => environment.methods.contains(peripheral.name) + } match { + case Some((_, environment)) => method -> Some(environment) + case _ => method -> None + } + } } + case _ => callbacks.map { + case (method, callback) => method -> Some(host) + } } private var _visibility = Visibility.None @@ -130,6 +139,7 @@ object Component { def callbacks(host: Environment) = host match { case multi: CompoundBlockEnvironment => analyze(host) + case peripheral: ManagedPeripheral => analyze(host) case _ => cache.getOrElseUpdate(host.getClass, analyze(host)) } @@ -137,8 +147,20 @@ object Component { val callbacks = mutable.Map.empty[String, Callback] val seeds = host match { case multi: CompoundBlockEnvironment => multi.environments.map { - case (_, environment) => environment.getClass: Class[_] + case (_, environment) => + environment match { + case peripheral: ManagedPeripheral => for (name <- peripheral.methods() if !callbacks.contains(name)) { + callbacks += name -> new PeripheralCallback(name) + } + case _ => + } + environment.getClass: Class[_] } + case peripheral: ManagedPeripheral => + for (name <- peripheral.methods() if !callbacks.contains(name)) { + callbacks += name -> new PeripheralCallback(name) + } + Seq(host.getClass: Class[_]) case _ => Seq(host.getClass: Class[_]) } for (seed <- seeds) { @@ -162,7 +184,7 @@ object Component { val a = m.getAnnotation[network.Callback](classOf[network.Callback]) val name = if (a.value != null && a.value.trim != "") a.value else m.getName if (!callbacks.contains(name)) { - callbacks += name -> new Callback(m, a.direct, a.limit) + callbacks += name -> new ComponentCallback(m, a.direct, a.limit) } } ) @@ -175,14 +197,26 @@ object Component { // ----------------------------------------------------------------------- // - class Callback(val method: Method, val direct: Boolean, val limit: Int) { - def apply(instance: AnyRef, context: Context, args: Arguments): Array[AnyRef] = try { + abstract class Callback(val direct: Boolean, val limit: Int) { + def apply(instance: Environment, context: Context, args: Arguments): Array[AnyRef] + } + + class ComponentCallback(val method: Method, direct: Boolean, limit: Int) extends Callback(direct, limit) { + override def apply(instance: Environment, context: Context, args: Arguments) = try { method.invoke(instance, context, args).asInstanceOf[Array[AnyRef]] } catch { case e: InvocationTargetException => throw e.getCause } } + class PeripheralCallback(val name: String) extends Callback(true, 100) { + override def apply(instance: Environment, context: Context, args: Arguments) = + instance match { + case peripheral: ManagedPeripheral => peripheral.invoke(name, context, args) + case _ => throw new NoSuchMethodException() + } + } + class VarArgs(val args: Seq[AnyRef]) extends Arguments { def iterator() = args.iterator