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