First device API draft.
This commit is contained in:
67
src/main/java/li/cil/oc2/api/bus/DeviceBus.java
Normal file
67
src/main/java/li/cil/oc2/api/bus/DeviceBus.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package li.cil.oc2.api.bus;
|
||||
|
||||
import li.cil.oc2.api.device.Device;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A device bus provides the interface by which {@link Device} can be made available
|
||||
* to a {@link DeviceBusController}, which is usually used by VMs to access devices.
|
||||
* <p>
|
||||
*/
|
||||
public interface DeviceBus {
|
||||
/**
|
||||
* Adds a device to this device bus.
|
||||
* <p>
|
||||
* Adding a device to the bus does <em>not</em> transfer ownership. In particular,
|
||||
* the bus will not handle persisting devices that have been added to it. Also,
|
||||
* the bus does not persist the list of devices (that would imply it persisting
|
||||
* the devices, as well). Instead, all devices that have been added to the bus
|
||||
* must be added again after a load, at the latest during the first update/tick
|
||||
* after the load.
|
||||
*
|
||||
* @param device the device to add to the bus.
|
||||
*/
|
||||
void addDevice(Device device);
|
||||
|
||||
/**
|
||||
* Removes a device from this device bus.
|
||||
* <p>
|
||||
* If the device has not been added with {@link #addDevice(Device)} before calling
|
||||
* this method, this method is a no-op.
|
||||
*
|
||||
* @param device the device to remove from the bus.
|
||||
*/
|
||||
void removeDevice(Device device);
|
||||
|
||||
/**
|
||||
* The list of all devices currently registered with this device bus.
|
||||
*
|
||||
* @return the list of all devices that are currently on this bus.
|
||||
*/
|
||||
Collection<Device> getDevices();
|
||||
|
||||
/**
|
||||
* Schedules a rescan of the device bus.
|
||||
* <p>
|
||||
* This will cause the internal device bus controller to discard the current bus
|
||||
* state and scan for connected bus segments at an unspecified time in the future
|
||||
* (typically during the next tick).
|
||||
* <p>
|
||||
* This should be called on all neighboring {@link DeviceBus} instances when a
|
||||
* {@link DeviceBus} is created, typically when a block is placed/runs its first
|
||||
* update after a load.
|
||||
* <p>
|
||||
* Technically this is a convenience method. It is equivalent to querying for a
|
||||
* {@link DeviceBusElement}, checking if a controller is set and then scheduling
|
||||
* a scan on the controller, if present. This way regular code will only ever
|
||||
* have to interact with this interface.
|
||||
*/
|
||||
void scheduleScan();
|
||||
|
||||
// long addDevice(MemoryMappedDevice device);
|
||||
//
|
||||
// void addDevice(final long address, MemoryMappedDevice device);
|
||||
//
|
||||
// void removeDevice(MemoryMappedDevice device);
|
||||
}
|
||||
58
src/main/java/li/cil/oc2/api/bus/DeviceBusController.java
Normal file
58
src/main/java/li/cil/oc2/api/bus/DeviceBusController.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package li.cil.oc2.api.bus;
|
||||
|
||||
import li.cil.oc2.api.device.Device;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* For each device bus there can be exactly one controller. The controller performs the
|
||||
* actual scan for adjacent {@link DeviceBusElement}s and registers itself with them via
|
||||
* {@link DeviceBusElement#setController(DeviceBusController)}.
|
||||
* <p>
|
||||
* This interface is usually provided by VM containers and used to collect connected
|
||||
* {@link Device}s by aggregating the devices that were added to the device bus elements
|
||||
* via {@link DeviceBusElement#addDevice(Device)}.
|
||||
* <p>
|
||||
* The only way for {@link DeviceBusElement}s to be added to a bus is for a
|
||||
* {@link DeviceBusController} to detect them during a scan.
|
||||
* <p>
|
||||
* This interface is only of relevance when implementing a VM container or a bus element,
|
||||
* i.e. something that acts as a "cable" or otherwise extends the bus itself.
|
||||
*
|
||||
* @see DeviceBusElement
|
||||
*/
|
||||
public interface DeviceBusController {
|
||||
/**
|
||||
* Schedules a scan.
|
||||
* <p>
|
||||
* This will immediately invalidate the current bus, i.e. all {@link DeviceBusElement}s
|
||||
* will be removed from the controller and {@link #getDevices()} will return an empty
|
||||
* list after this call.
|
||||
* <p>
|
||||
* Multiple sequential calls to this method do nothing, the actual scan will be performed
|
||||
* in the next update.
|
||||
*/
|
||||
void scheduleBusScan();
|
||||
|
||||
/**
|
||||
* Forces a device map rebuild.
|
||||
* <p>
|
||||
* This causes the controller to query all registered {@link DeviceBusElement}s for their
|
||||
* current devices and update the aggregated list of devices. Unlike {@link #scheduleBusScan()}
|
||||
* this operation runs synchronously. The list of devices known to the controller will be
|
||||
* updated when this method returns.
|
||||
* <p>
|
||||
* This should be called when the list of devices of a {@link DeviceBusElement} changes.
|
||||
*/
|
||||
void scanDevices();
|
||||
|
||||
/**
|
||||
* The list of all devices currently known to this controller.
|
||||
* <p>
|
||||
* This is the aggregation of all {@link Device} added to all {@link DeviceBusElement}s known
|
||||
* to the controller as found during the last scan scheduled via {@link #scheduleBusScan()}.
|
||||
*
|
||||
* @return the list of all devices on the bus managed by this controller.
|
||||
*/
|
||||
Collection<Device> getDevices();
|
||||
}
|
||||
52
src/main/java/li/cil/oc2/api/bus/DeviceBusElement.java
Normal file
52
src/main/java/li/cil/oc2/api/bus/DeviceBusElement.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package li.cil.oc2.api.bus;
|
||||
|
||||
import li.cil.oc2.api.device.Device;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents a single connection point on a device bus.
|
||||
* <p>
|
||||
* The only way for {@link DeviceBusElement}s to be added to a bus is for a
|
||||
* {@link DeviceBusController} to detect them during a scan.
|
||||
* <p>
|
||||
* When discovered during a scan, the controller will then use the devices
|
||||
* connected to this element.
|
||||
* <p>
|
||||
* This interface is only relevant when implementing it, e.g. to provide a custom
|
||||
* "cable" or other means to extend a bus.
|
||||
*/
|
||||
public interface DeviceBusElement extends DeviceBus {
|
||||
/**
|
||||
* The controller this bus element is currently registered with, if any.
|
||||
*
|
||||
* @return the current controller.
|
||||
*/
|
||||
Optional<DeviceBusController> getController();
|
||||
|
||||
/**
|
||||
* Sets the controller this bus element is now registered with, if any.
|
||||
* <p>
|
||||
* This will be called by {@link DeviceBusController}s when scanning.
|
||||
*
|
||||
* @param controller the new controller.
|
||||
*/
|
||||
void setController(@Nullable final DeviceBusController controller);
|
||||
|
||||
/**
|
||||
* Returns the list of devices connected specifically by this element.
|
||||
* <p>
|
||||
* This differs from {@link #getDevices()} in such that {@link #getDevices()} will
|
||||
* return all devices connected to the controller, if this element is registered
|
||||
* with a controller.
|
||||
* <p>
|
||||
* This method is called by the {@link DeviceBusController} the element is registered
|
||||
* with when the global list of devices is rebuilt, e.g. after a call to
|
||||
* {@link DeviceBusController#scanDevices()}.
|
||||
*
|
||||
* @return the devices that have been added to this element.
|
||||
*/
|
||||
Collection<Device> getLocalDevices();
|
||||
}
|
||||
7
src/main/java/li/cil/oc2/api/bus/package-info.java
Normal file
7
src/main/java/li/cil/oc2/api/bus/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
package li.cil.oc2.api.bus;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
56
src/main/java/li/cil/oc2/api/device/AbstractDevice.java
Normal file
56
src/main/java/li/cil/oc2/api/device/AbstractDevice.java
Normal file
@@ -0,0 +1,56 @@
|
||||
package li.cil.oc2.api.device;
|
||||
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraftforge.common.util.INBTSerializable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Convenience base class for {@link Device} implementations.
|
||||
* <p>
|
||||
* In particular, this implements the logic needed for generating and
|
||||
* storing the unique ID for this device.
|
||||
*/
|
||||
public abstract class AbstractDevice implements Device, INBTSerializable<CompoundNBT> {
|
||||
protected static final String UUID_NBT_TAG_NAME = "uuid";
|
||||
|
||||
protected final List<String> typeNames;
|
||||
protected UUID uuid;
|
||||
|
||||
protected AbstractDevice() {
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
protected AbstractDevice(final Collection<String> typeNames) {
|
||||
this.typeNames = new ArrayList<>(typeNames);
|
||||
this.uuid = java.util.UUID.randomUUID();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUniqueId() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUniqueId(final UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTypeNames() {
|
||||
return typeNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundNBT serializeNBT() {
|
||||
final CompoundNBT nbt = new CompoundNBT();
|
||||
nbt.putUniqueId(UUID_NBT_TAG_NAME, uuid);
|
||||
return nbt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeNBT(final CompoundNBT nbt) {
|
||||
if (nbt.hasUniqueId(UUID_NBT_TAG_NAME)) {
|
||||
uuid = nbt.getUniqueId(UUID_NBT_TAG_NAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package li.cil.oc2.api.device;
|
||||
|
||||
/**
|
||||
* Convenience base class for {@link DeviceMethod} implementations.
|
||||
*/
|
||||
public abstract class AbstractDeviceMethod implements DeviceMethod {
|
||||
protected final String name;
|
||||
protected final Class<?> returnType;
|
||||
protected final DeviceMethodParameter[] parameters;
|
||||
|
||||
protected AbstractDeviceMethod(final String name, final Class<?> returnType, final DeviceMethodParameter... parameters) {
|
||||
this.name = name;
|
||||
this.returnType = returnType;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
protected AbstractDeviceMethod(final String name, final DeviceMethodParameter... parameters) {
|
||||
this(name, void.class, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceMethodParameter[] getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
95
src/main/java/li/cil/oc2/api/device/CompoundDevice.java
Normal file
95
src/main/java/li/cil/oc2/api/device/CompoundDevice.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package li.cil.oc2.api.device;
|
||||
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.nbt.INBT;
|
||||
import net.minecraftforge.common.util.INBTSerializable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A utility device type that allows grouping multiple {@link Device} instances.
|
||||
* <p>
|
||||
* Serialization of contained devices requires the added devices' unique id to
|
||||
* have been restored prior to calling {@link #deserializeNBT(CompoundNBT)}.
|
||||
*/
|
||||
public class CompoundDevice extends AbstractDevice {
|
||||
private final ArrayList<Device> devices;
|
||||
|
||||
public CompoundDevice(final Collection<Device> devices) {
|
||||
this.devices = new ArrayList<>(devices);
|
||||
}
|
||||
|
||||
public CompoundDevice(final Device... devices) {
|
||||
this(Arrays.asList(devices));
|
||||
}
|
||||
|
||||
public CompoundDevice() {
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of devices grouped in this device.
|
||||
* <p>
|
||||
* Use this in case you need to inspect the current list of devices, add new
|
||||
* devices or remove existing devices.
|
||||
*/
|
||||
public List<Device> getDevices() {
|
||||
return devices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTypeNames() {
|
||||
return devices.stream()
|
||||
.map(Device::getTypeNames)
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeviceMethod> getMethods() {
|
||||
return devices.stream()
|
||||
.map(Device::getMethods)
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public CompoundNBT serializeNBT() {
|
||||
final CompoundNBT nbt = super.serializeNBT();
|
||||
|
||||
final CompoundNBT devicesNbt = new CompoundNBT();
|
||||
for (final Device device : devices) {
|
||||
if (device instanceof INBTSerializable) {
|
||||
final INBTSerializable serializable = (INBTSerializable) device;
|
||||
final String uuid = device.getUniqueId().toString();
|
||||
final INBT deviceNbt = serializable.serializeNBT();
|
||||
devicesNbt.put(uuid, deviceNbt);
|
||||
}
|
||||
}
|
||||
nbt.put("devices", devicesNbt);
|
||||
|
||||
return nbt;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Override
|
||||
public void deserializeNBT(final CompoundNBT nbt) {
|
||||
super.deserializeNBT(nbt);
|
||||
|
||||
final CompoundNBT devicesNbt = nbt.getCompound("devices");
|
||||
for (final Device device : devices) {
|
||||
final String uuid = device.getUniqueId().toString();
|
||||
if (!devicesNbt.contains(uuid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (device instanceof INBTSerializable) {
|
||||
final INBTSerializable serializable = (INBTSerializable) device;
|
||||
final INBT deviceNbt = devicesNbt.get(uuid);
|
||||
serializable.deserializeNBT(deviceNbt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/main/java/li/cil/oc2/api/device/Device.java
Normal file
43
src/main/java/li/cil/oc2/api/device/Device.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package li.cil.oc2.api.device;
|
||||
|
||||
import li.cil.oc2.api.bus.DeviceBus;
|
||||
import li.cil.oc2.api.device.object.ObjectDevice;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Defines a device that may be added to a {@link DeviceBus}.
|
||||
* <p>
|
||||
* The easiest and hence recommended way of implementing this interface is to use
|
||||
* the {@link ObjectDevice} class.
|
||||
*
|
||||
* @see ObjectDevice
|
||||
*/
|
||||
public interface Device {
|
||||
/**
|
||||
* An id unique to this device.
|
||||
* <p>
|
||||
* This id must persist over save/load to prevent code in a running VM losing
|
||||
* track of the device.
|
||||
*/
|
||||
UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* A list of device type names for this device.
|
||||
* <p>
|
||||
* Devices may be identified by multiple type names. Although every atomic
|
||||
* implementation will usually only have one, when compounding such modular
|
||||
* devices into a {@link CompoundDevice} all the underlying type names can
|
||||
* thus be retained.
|
||||
* <p>
|
||||
* In a more general sense, these can be considered tags the device can be
|
||||
* referenced by inside a VM.
|
||||
*/
|
||||
List<String> getTypeNames();
|
||||
|
||||
/**
|
||||
* The list of methods implemented by this device.
|
||||
*/
|
||||
List<DeviceMethod> getMethods();
|
||||
}
|
||||
76
src/main/java/li/cil/oc2/api/device/DeviceMethod.java
Normal file
76
src/main/java/li/cil/oc2/api/device/DeviceMethod.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package li.cil.oc2.api.device;
|
||||
|
||||
import li.cil.oc2.api.bus.DeviceBusController;
|
||||
import li.cil.oc2.api.device.object.ObjectDevice;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents a single method that can be exposed by a {@link Device}.
|
||||
* <p>
|
||||
* The easiest and hence recommended way of implementing this interface is to use
|
||||
* the {@link ObjectDevice} class.
|
||||
*
|
||||
* @see ObjectDevice
|
||||
*/
|
||||
public interface DeviceMethod {
|
||||
/**
|
||||
* The name of the method.
|
||||
* <p>
|
||||
* When invoked through a {@link DeviceBusController} this is what the method
|
||||
* will be referenced by, so the name should be unlikely to be duplicated in
|
||||
* another device to avoid ambiguity when devices are combined, e.g. in a
|
||||
* {@link CompoundDevice}.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* The type of the values returned by this method.
|
||||
*/
|
||||
Class<?> getReturnType();
|
||||
|
||||
/**
|
||||
* The list of parameters this method accepts.
|
||||
*/
|
||||
DeviceMethodParameter[] getParameters();
|
||||
|
||||
/**
|
||||
* Called to run this method.
|
||||
* <p>
|
||||
* Implementations should expect the passed {@code parameters} to match the
|
||||
* declared parameters returned by {@link #getParameters()}. If the parameters
|
||||
* do not match, and exception should be raised.
|
||||
* <p>
|
||||
* <b>Important:</b> methods are expected to not irrevocably corrupt internal
|
||||
* state, even when they throw an exception. As such, implementations should
|
||||
* perform internal error handling to prevent state corruption and only throw
|
||||
* exceptions to communicate that an error happened during the invocation.
|
||||
*
|
||||
* @param parameters the parameters for the method.
|
||||
* @return the return value, or {@code null} if none.
|
||||
* @throws Throwable if the parameters did not match or something inside the
|
||||
* method caused an exception. The caller is responsible for
|
||||
* catching these and passing them on appropriately.
|
||||
*/
|
||||
@Nullable
|
||||
Object invoke(Object... parameters) throws Throwable;
|
||||
|
||||
/**
|
||||
* An optional description of the method.
|
||||
* <p>
|
||||
* May be used inside VMs to generate documentation.
|
||||
*/
|
||||
default Optional<String> getDescription() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* An optional description of the return value of this method.
|
||||
* <p>
|
||||
* May be used inside VMs to generate documentation.
|
||||
*/
|
||||
default Optional<String> getReturnValueDescription() {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package li.cil.oc2.api.device;
|
||||
|
||||
import li.cil.oc2.api.bus.DeviceBusController;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Describes a single parameter of a {@link DeviceMethod}.
|
||||
*/
|
||||
public interface DeviceMethodParameter {
|
||||
/**
|
||||
* The type of this parameter.
|
||||
* <p>
|
||||
* This is used by {@link DeviceBusController}s to convert parameters from a lower
|
||||
* level representation before passing it to {@link DeviceMethod#invoke(Object...)}.
|
||||
* As such, the types used must be kept simple. As a rule of thumb, only primitives
|
||||
* and POJOs should be used.
|
||||
*
|
||||
* @return the type of the parameter.
|
||||
*/
|
||||
Class<?> getType();
|
||||
|
||||
/**
|
||||
* An optional name of the parameter.
|
||||
* <p>
|
||||
* May be used inside VMs to generate documentation.
|
||||
*/
|
||||
default Optional<String> getName() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* An optional description of the parameter.
|
||||
* <p>
|
||||
* May be used inside VMs to generate documentation.
|
||||
*/
|
||||
default Optional<String> getDescription() {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
31
src/main/java/li/cil/oc2/api/device/object/Callback.java
Normal file
31
src/main/java/li/cil/oc2/api/device/object/Callback.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package li.cil.oc2.api.device.object;
|
||||
|
||||
import li.cil.oc2.api.device.DeviceMethod;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Utility annotation to allow generating lists of {@link DeviceMethod}s using
|
||||
* {@link Callbacks#collectMethods(Object)}.
|
||||
* <p>
|
||||
* Intended to be used in classes instances of which are used in combination with
|
||||
* {@link ObjectDevice} and subclasses of {@link ObjectDevice}.
|
||||
* <p>
|
||||
* For
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Callback {
|
||||
/**
|
||||
* Option VM visible documentation of this method.
|
||||
*/
|
||||
String description() default "";
|
||||
|
||||
/**
|
||||
* Optional VM visible documentation of the values returned by this method.
|
||||
*/
|
||||
String returnValueDescription() default "";
|
||||
}
|
||||
61
src/main/java/li/cil/oc2/api/device/object/Callbacks.java
Normal file
61
src/main/java/li/cil/oc2/api/device/object/Callbacks.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package li.cil.oc2.api.device.object;
|
||||
|
||||
import li.cil.oc2.api.device.DeviceMethod;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Provides automated extraction of {@link DeviceMethod}s from instances of
|
||||
* class with methods annotated with the {@link Callback} annotation.
|
||||
* <p>
|
||||
* Prefer using {@link ObjectDevice} instead of using this class directly.
|
||||
*/
|
||||
public final class Callbacks {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final HashMap<Class<?>, List<Method>> METHOD_BY_TYPE = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Collects all methods annotated with {@link Callback} in the specified object
|
||||
* and generated {@link DeviceMethod}s for each one.
|
||||
* <p>
|
||||
* The generated {@link DeviceMethod} will be bound to the passed object and
|
||||
* can be called without needing to pass the object.
|
||||
* <p>
|
||||
* For example:
|
||||
* <pre>
|
||||
* class Example {
|
||||
* @Callback
|
||||
* public void f(String a) { }
|
||||
* }
|
||||
*
|
||||
* List<DeviceMethod> methods = Callbacks.collectMethods(new Example());
|
||||
* methods.get(0).invoke("argument");
|
||||
* </pre>
|
||||
*
|
||||
* @param methodContainer an instance of a class with annotated methods.
|
||||
* @return the list of methods extracted from the specified object.
|
||||
*/
|
||||
public static List<DeviceMethod> collectMethods(final Object methodContainer) {
|
||||
final List<Method> reflectedMethods = METHOD_BY_TYPE.computeIfAbsent(methodContainer.getClass(), c -> Arrays.stream(c.getMethods())
|
||||
.filter(m -> m.isAnnotationPresent(Callback.class))
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
final List<DeviceMethod> methods = new ArrayList<>();
|
||||
for (final Method method : reflectedMethods) {
|
||||
try {
|
||||
methods.add(new ObjectDeviceMethod(methodContainer, method));
|
||||
} catch (final IllegalAccessException e) {
|
||||
LOGGER.error("Failed accessing method [{}].", method);
|
||||
}
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
}
|
||||
57
src/main/java/li/cil/oc2/api/device/object/ObjectDevice.java
Normal file
57
src/main/java/li/cil/oc2/api/device/object/ObjectDevice.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package li.cil.oc2.api.device.object;
|
||||
|
||||
import li.cil.oc2.api.device.AbstractDevice;
|
||||
import li.cil.oc2.api.device.Device;
|
||||
import li.cil.oc2.api.device.DeviceMethod;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A reflection based implementation of {@link Device} using the {@link Callback}
|
||||
* annotation to discover {@link DeviceMethod} in a target object via
|
||||
* {@link Callbacks#collectMethods(Object)}.
|
||||
* <p>
|
||||
* This class was designed targeting two possible use-cases:
|
||||
* <ul>
|
||||
* <li>Wrapping some separate object containing the annotated method.</li>
|
||||
* <li>Subclassing this type and implementing annotated methods in the subclass.</li>
|
||||
* </ul>
|
||||
* The two sets of constructors are designed for these use cases, with the constructors
|
||||
* targeting the workflow using an external object being {@code public}, the ones targeting
|
||||
* subclassing being {@code protected}.
|
||||
*/
|
||||
public class ObjectDevice extends AbstractDevice {
|
||||
private final List<DeviceMethod> methods;
|
||||
|
||||
public ObjectDevice(final Object object, final List<String> typeNames) {
|
||||
super(typeNames);
|
||||
this.methods = Callbacks.collectMethods(object);
|
||||
}
|
||||
|
||||
public ObjectDevice(final Object object, final String typeName) {
|
||||
this(object, Collections.singletonList(typeName));
|
||||
}
|
||||
|
||||
public ObjectDevice(final Object object) {
|
||||
this(object, Collections.emptyList());
|
||||
}
|
||||
|
||||
protected ObjectDevice(final List<String> typeNames) {
|
||||
super(typeNames);
|
||||
this.methods = Callbacks.collectMethods(this);
|
||||
}
|
||||
|
||||
protected ObjectDevice(final String typeName) {
|
||||
this(Collections.singletonList(typeName));
|
||||
}
|
||||
|
||||
protected ObjectDevice() {
|
||||
this(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeviceMethod> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package li.cil.oc2.api.device.object;
|
||||
|
||||
import li.cil.oc2.api.device.AbstractDeviceMethod;
|
||||
import li.cil.oc2.api.device.DeviceMethodParameter;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Not intended to be instantiated directly, see {@link Callbacks}.
|
||||
*
|
||||
* @see Callbacks
|
||||
*/
|
||||
public final class ObjectDeviceMethod extends AbstractDeviceMethod {
|
||||
private final MethodHandle handle;
|
||||
private final String description;
|
||||
private final String returnValueDescription;
|
||||
|
||||
public ObjectDeviceMethod(final Object target, final Method method) throws IllegalAccessException {
|
||||
super(method.getName(), method.getReturnType(), getParameters(method));
|
||||
|
||||
final Callback annotation = method.getAnnotation(Callback.class);
|
||||
if (annotation == null) {
|
||||
throw new IllegalArgumentException("Method without Callback annotation.");
|
||||
}
|
||||
|
||||
this.handle = MethodHandles.lookup().unreflect(method).bindTo(target);
|
||||
this.description = Strings.isNotBlank(annotation.description()) ? annotation.description() : null;
|
||||
this.returnValueDescription = Strings.isNotBlank(annotation.returnValueDescription()) ? annotation.returnValueDescription() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object invoke(final Object... parameters) throws Throwable {
|
||||
return handle.invokeWithArguments(parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getDescription() {
|
||||
return Optional.ofNullable(description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getReturnValueDescription() {
|
||||
return Optional.ofNullable(returnValueDescription);
|
||||
}
|
||||
|
||||
private static DeviceMethodParameter[] getParameters(final Method method) {
|
||||
return Arrays.stream(method.getParameters())
|
||||
.map(ReflectionParameter::new)
|
||||
.toArray(DeviceMethodParameter[]::new);
|
||||
}
|
||||
|
||||
private static final class ReflectionParameter implements DeviceMethodParameter {
|
||||
private final Class<?> type;
|
||||
@Nullable private final String name;
|
||||
@Nullable private final String description;
|
||||
|
||||
public ReflectionParameter(final java.lang.reflect.Parameter parameter) {
|
||||
this.type = parameter.getType();
|
||||
|
||||
final li.cil.oc2.api.device.object.Parameter annotation = parameter.getAnnotation(li.cil.oc2.api.device.object.Parameter.class);
|
||||
final boolean hasName = annotation != null && Strings.isNotBlank(annotation.value());
|
||||
final boolean hasDescription = annotation != null && Strings.isNotBlank(annotation.description());
|
||||
|
||||
this.name = hasName ? annotation.value() : null;
|
||||
this.description = hasDescription ? annotation.description() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getName() {
|
||||
return Optional.ofNullable(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getDescription() {
|
||||
return Optional.ofNullable(description);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
src/main/java/li/cil/oc2/api/device/object/Parameter.java
Normal file
30
src/main/java/li/cil/oc2/api/device/object/Parameter.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package li.cil.oc2.api.device.object;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* This annotation may be used to propagate the name of parameters and to
|
||||
* provide VM visible documentation of this parameter for methods annotated
|
||||
* with the {@link Callback} annotation.
|
||||
* <p>
|
||||
* Java strips parameter names in non-debug builds, so the actual method
|
||||
* parameter names cannot be retrieved directly.
|
||||
* <p>
|
||||
* If this is not present, parameters will appear unnamed to the VM.
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Parameter {
|
||||
/**
|
||||
* The name of the parameter as seen by the VM.
|
||||
*/
|
||||
String value();
|
||||
|
||||
/**
|
||||
* Optional VM visible documentation of this parameter.
|
||||
*/
|
||||
String description() default "";
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
package li.cil.oc2.api.device.object;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
7
src/main/java/li/cil/oc2/api/device/package-info.java
Normal file
7
src/main/java/li/cil/oc2/api/device/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
package li.cil.oc2.api.device;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -1,5 +1,6 @@
|
||||
package li.cil.oc2.common;
|
||||
|
||||
import li.cil.oc2.common.capabilities.DeviceBusElementCapability;
|
||||
import li.cil.oc2.common.network.Network;
|
||||
import li.cil.oc2.common.vm.Allocator;
|
||||
import li.cil.oc2.serialization.BlobStorage;
|
||||
@@ -10,6 +11,8 @@ import net.minecraftforge.fml.event.server.FMLServerStoppedEvent;
|
||||
|
||||
public final class CommonSetup {
|
||||
public static void run(final FMLCommonSetupEvent event) {
|
||||
DeviceBusElementCapability.register();
|
||||
|
||||
Network.setup();
|
||||
|
||||
MinecraftForge.EVENT_BUS.addListener(CommonSetup::handleServerAboutToStart);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package li.cil.oc2.common.capabilities;
|
||||
|
||||
import li.cil.oc2.api.bus.DeviceBus;
|
||||
import li.cil.oc2.api.bus.DeviceBusElement;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.capabilities.CapabilityInject;
|
||||
|
||||
public final class Capabilities {
|
||||
@CapabilityInject(DeviceBus.class) @SuppressWarnings("FieldMayBeFinal")
|
||||
public static Capability<DeviceBusElement> DEVICE_BUS_ELEMENT_CAPABILITY = null;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package li.cil.oc2.common.capabilities;
|
||||
|
||||
import li.cil.oc2.api.bus.DeviceBusElement;
|
||||
import li.cil.oc2.common.vm.DeviceBusElementImpl;
|
||||
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||
|
||||
public final class DeviceBusElementCapability {
|
||||
public static void register() {
|
||||
CapabilityManager.INSTANCE.register(DeviceBusElement.class, new NullStorage<>(), DeviceBusElementImpl::new);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package li.cil.oc2.common.capabilities;
|
||||
|
||||
import net.minecraft.nbt.INBT;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class NullStorage<T> implements Capability.IStorage<T> {
|
||||
@Nullable
|
||||
@Override
|
||||
public INBT writeNBT(final Capability<T> capability, final T instance, final Direction side) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readNBT(final Capability<T> capability, final T instance, final Direction side, final INBT nbt) {
|
||||
}
|
||||
}
|
||||
79
src/test/java/li/cil/oc2/bus/DeviceBusTests.java
Normal file
79
src/test/java/li/cil/oc2/bus/DeviceBusTests.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package li.cil.oc2.bus;
|
||||
|
||||
import li.cil.oc2.api.bus.DeviceBusElement;
|
||||
import li.cil.oc2.api.device.Device;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
import li.cil.oc2.common.vm.DeviceBusControllerImpl;
|
||||
import li.cil.sedna.api.device.serial.SerialDevice;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class DeviceBusTests {
|
||||
@Mock
|
||||
private static Capability<DeviceBusElement> busElementCapability;
|
||||
|
||||
private World world;
|
||||
private SerialDevice serialDevice;
|
||||
private DeviceBusControllerImpl controller;
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() {
|
||||
Capabilities.DEVICE_BUS_ELEMENT_CAPABILITY = busElementCapability;
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setupEach() {
|
||||
world = mock(World.class);
|
||||
serialDevice = mock(SerialDevice.class);
|
||||
controller = new DeviceBusControllerImpl(serialDevice);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scanPendingWhenTileEntityNotLoaded() {
|
||||
Assertions.assertEquals(DeviceBusControllerImpl.State.SCAN_PENDING,
|
||||
controller.scan(world, new BlockPos(0, 0, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scanCompletesWhenNoNeighbors() {
|
||||
when(world.chunkExists(anyInt(), anyInt())).thenReturn(true);
|
||||
Assertions.assertEquals(DeviceBusControllerImpl.State.READY,
|
||||
controller.scan(world, new BlockPos(0, 0, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scanSuccessfulWithLocalElement() {
|
||||
when(world.chunkExists(anyInt(), anyInt())).thenReturn(true);
|
||||
|
||||
final TileEntity tileEntity = mock(TileEntity.class);
|
||||
when(world.getTileEntity(eq(new BlockPos(0, 0, 0)))).thenReturn(tileEntity);
|
||||
|
||||
final DeviceBusElement busElement = mock(DeviceBusElement.class);
|
||||
when(tileEntity.getCapability(eq(busElementCapability), any())).thenReturn(LazyOptional.of(() -> busElement));
|
||||
|
||||
final Device device = mock(Device.class);
|
||||
when(busElement.getDevices()).thenReturn(Collections.singletonList(device));
|
||||
|
||||
when(device.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||
|
||||
Assertions.assertEquals(DeviceBusControllerImpl.State.READY,
|
||||
controller.scan(world, new BlockPos(0, 0, 0)));
|
||||
|
||||
verify(busElement).setController(controller);
|
||||
Assertions.assertTrue(controller.getDevices().contains(device));
|
||||
}
|
||||
}
|
||||
54
src/test/java/li/cil/oc2/vm/AbstractTestMethod.java
Normal file
54
src/test/java/li/cil/oc2/vm/AbstractTestMethod.java
Normal file
@@ -0,0 +1,54 @@
|
||||
package li.cil.oc2.vm;
|
||||
|
||||
import li.cil.oc2.api.device.DeviceMethod;
|
||||
import li.cil.oc2.api.device.DeviceMethodParameter;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
abstract class AbstractTestMethod implements DeviceMethod {
|
||||
private final Class<?> returnType;
|
||||
private final DeviceMethodParameter[] parameters;
|
||||
|
||||
protected AbstractTestMethod(final Class<?> returnType, final Class<?>... parameterTypes) {
|
||||
this.returnType = returnType;
|
||||
parameters = new DeviceMethodParameter[parameterTypes.length];
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
parameters[i] = new TestParameter("arg" + i, parameterTypes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceMethodParameter[] getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private static final class TestParameter implements DeviceMethodParameter {
|
||||
private final String name;
|
||||
private final Class<?> type;
|
||||
|
||||
public TestParameter(final String name, final Class<?> type) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<String> getName() {
|
||||
return Optional.ofNullable(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
256
src/test/java/li/cil/oc2/vm/ObjectDeviceProtocolTests.java
Normal file
256
src/test/java/li/cil/oc2/vm/ObjectDeviceProtocolTests.java
Normal file
@@ -0,0 +1,256 @@
|
||||
package li.cil.oc2.vm;
|
||||
|
||||
import com.google.gson.*;
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue;
|
||||
import li.cil.oc2.api.bus.DeviceBusElement;
|
||||
import li.cil.oc2.api.device.AbstractDevice;
|
||||
import li.cil.oc2.api.device.Device;
|
||||
import li.cil.oc2.api.device.DeviceMethod;
|
||||
import li.cil.oc2.api.device.object.Callback;
|
||||
import li.cil.oc2.api.device.object.ObjectDevice;
|
||||
import li.cil.oc2.api.device.object.Parameter;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
import li.cil.oc2.common.vm.DeviceBusControllerImpl;
|
||||
import li.cil.oc2.common.vm.DeviceBusElementImpl;
|
||||
import li.cil.sedna.api.device.serial.SerialDevice;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ObjectDeviceProtocolTests {
|
||||
private static final BlockPos CONTROLLER_POS = new BlockPos(0, 0, 0);
|
||||
|
||||
@Mock private Capability<DeviceBusElement> busElementCapability;
|
||||
private World world;
|
||||
private TestSerialDevice serialDevice;
|
||||
private DeviceBusControllerImpl controller;
|
||||
private DeviceBusElement busElement;
|
||||
|
||||
@BeforeEach
|
||||
public void setupEach() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
Capabilities.DEVICE_BUS_ELEMENT_CAPABILITY = busElementCapability;
|
||||
|
||||
serialDevice = new TestSerialDevice();
|
||||
controller = new DeviceBusControllerImpl(serialDevice);
|
||||
busElement = new DeviceBusElementImpl();
|
||||
|
||||
world = mock(World.class);
|
||||
when(world.chunkExists(anyInt(), anyInt())).thenReturn(true);
|
||||
|
||||
final TileEntity tileEntity = mock(TileEntity.class);
|
||||
when(world.getTileEntity(any())).thenReturn(tileEntity);
|
||||
|
||||
when(tileEntity.getCapability(eq(busElementCapability), any())).thenReturn(LazyOptional.of(() -> busElement));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resetAndReadDescriptor() {
|
||||
final VoidIntMethod method = new VoidIntMethod();
|
||||
|
||||
busElement.addDevice(new TestDevice(method));
|
||||
controller.scan(world, CONTROLLER_POS);
|
||||
|
||||
final JsonObject request = new JsonObject();
|
||||
request.addProperty("type", "status");
|
||||
serialDevice.putAsVM(request.toString());
|
||||
controller.step(0); // process message
|
||||
|
||||
final String message = serialDevice.readMessageAsVM();
|
||||
Assertions.assertNotNull(message);
|
||||
|
||||
final JsonObject json = new JsonParser().parse(message).getAsJsonObject();
|
||||
|
||||
final JsonArray devices = json.getAsJsonArray("data");
|
||||
Assertions.assertEquals(1, devices.size());
|
||||
|
||||
final JsonObject device = devices.get(0).getAsJsonObject();
|
||||
|
||||
final JsonArray methods = device.getAsJsonArray("methods");
|
||||
Assertions.assertEquals(1, methods.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleMethod() {
|
||||
final VoidIntMethod method = new VoidIntMethod();
|
||||
final TestDevice device = new TestDevice(method);
|
||||
|
||||
busElement.addDevice(device);
|
||||
controller.scan(world, CONTROLLER_POS);
|
||||
|
||||
invokeMethod(device, method.getName(), 0xdeadbeef);
|
||||
|
||||
Assertions.assertEquals(0xdeadbeef, method.passedValue);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returningMethod() {
|
||||
final IntLongMethod method = new IntLongMethod();
|
||||
final TestDevice device = new TestDevice(method);
|
||||
|
||||
busElement.addDevice(device);
|
||||
controller.scan(world, CONTROLLER_POS);
|
||||
|
||||
final JsonElement result = invokeMethod(device, method.getName(), 0xdeadbeefcafebabeL);
|
||||
Assertions.assertNotNull(result);
|
||||
Assertions.assertTrue(result.isJsonPrimitive());
|
||||
Assertions.assertEquals(0xcafebabe, result.getAsInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void annotatedObject() {
|
||||
final SimpleObject object = new SimpleObject();
|
||||
final ObjectDevice device = new ObjectDevice(object);
|
||||
|
||||
busElement.addDevice(device);
|
||||
controller.scan(world, CONTROLLER_POS);
|
||||
|
||||
Assertions.assertEquals(42 + 23, invokeMethod(device, "add", 42, 23).getAsInt());
|
||||
}
|
||||
|
||||
private JsonElement invokeMethod(final Device device, final String name, final Object... parameters) {
|
||||
final JsonObject request = new JsonObject();
|
||||
request.addProperty("type", "invoke");
|
||||
final JsonObject methodInvocation = new JsonObject();
|
||||
methodInvocation.addProperty("deviceId", device.getUniqueId().toString());
|
||||
methodInvocation.addProperty("name", name);
|
||||
final JsonArray parametersJson = new JsonArray();
|
||||
methodInvocation.add("parameters", parametersJson);
|
||||
for (final Object parameter : parameters) {
|
||||
parametersJson.add(new Gson().toJson(parameter));
|
||||
}
|
||||
request.add("data", methodInvocation);
|
||||
serialDevice.putAsVM(request.toString());
|
||||
|
||||
controller.step(0);
|
||||
|
||||
final String result = serialDevice.readMessageAsVM();
|
||||
Assertions.assertNotNull(result);
|
||||
final JsonObject resultJson = new JsonParser().parse(result).getAsJsonObject();
|
||||
Assertions.assertEquals("result", resultJson.get("type").getAsString());
|
||||
return resultJson.get("data");
|
||||
}
|
||||
|
||||
private static final class VoidIntMethod extends AbstractTestMethod {
|
||||
public int passedValue;
|
||||
|
||||
VoidIntMethod() {
|
||||
super(void.class, int.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(final Object... parameters) {
|
||||
passedValue = (int) parameters[0];
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class IntLongMethod extends AbstractTestMethod {
|
||||
public long passedValue;
|
||||
|
||||
IntLongMethod() {
|
||||
super(int.class, long.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(final Object... parameters) {
|
||||
passedValue = (long) parameters[0];
|
||||
return (int) passedValue;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class SimpleObject {
|
||||
@Callback
|
||||
public int add(@Parameter("a") final int a,
|
||||
@Parameter("b") final int b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
@Callback
|
||||
public int div(@Parameter("a") final long a,
|
||||
@Parameter("b") final long b) {
|
||||
return (int) (a / b);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestSerialDevice implements SerialDevice {
|
||||
private final ByteArrayFIFOQueue transmit = new ByteArrayFIFOQueue();
|
||||
private final ByteArrayFIFOQueue receive = new ByteArrayFIFOQueue();
|
||||
|
||||
public void putAsVM(final String data) {
|
||||
final byte[] bytes = data.getBytes();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
transmit.enqueue(bytes[i]);
|
||||
}
|
||||
transmit.enqueue((byte) 0);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String readMessageAsVM() {
|
||||
final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
while (!receive.isEmpty()) {
|
||||
final byte value = receive.dequeueByte();
|
||||
|
||||
if (value == 0) {
|
||||
if (bytes.size() == 0) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bytes.write(value);
|
||||
}
|
||||
|
||||
if (bytes.size() > 0) {
|
||||
return new String(bytes.toByteArray());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
return transmit.isEmpty() ? -1 : transmit.dequeueByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPutByte() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putByte(final byte value) {
|
||||
receive.enqueue(value);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestDevice extends AbstractDevice {
|
||||
private final DeviceMethod method;
|
||||
|
||||
public TestDevice(final DeviceMethod method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeviceMethod> getMethods() {
|
||||
return Collections.singletonList(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
src/test/java/li/cil/oc2/vm/package-info.java
Normal file
7
src/test/java/li/cil/oc2/vm/package-info.java
Normal file
@@ -0,0 +1,7 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
package li.cil.oc2.vm;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
Reference in New Issue
Block a user