Working on generalizing device bus to allow different device types.
Make bus elements source of identifiers in API, too, since they are also in implementation.
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
package li.cil.oc2.api;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import li.cil.oc2.api.bus.device.DeviceMethod;
|
||||
import li.cil.oc2.api.bus.device.object.Callback;
|
||||
import li.cil.oc2.api.provider.DeviceInterfaceProvider;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCMethod;
|
||||
import li.cil.oc2.api.imc.DeviceMethodParameterTypeAdapter;
|
||||
import li.cil.oc2.api.provider.DeviceProvider;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
@@ -12,7 +12,7 @@ public final class API {
|
||||
public static final String MOD_ID = "oc2";
|
||||
|
||||
/**
|
||||
* IMC message for registering a {@link DeviceInterfaceProvider}.
|
||||
* IMC message for registering a {@link DeviceProvider}.
|
||||
* <p>
|
||||
* Example:
|
||||
* <pre>
|
||||
@@ -27,11 +27,11 @@ public final class API {
|
||||
* <p>
|
||||
* Must be called with a supplier that provides an instance of {@link DeviceMethodParameterTypeAdapter}.
|
||||
* <p>
|
||||
* It can be necessary to register additional serializers when implementing {@link DeviceMethod}s
|
||||
* It can be necessary to register additional serializers when implementing {@link RPCMethod}s
|
||||
* that use custom parameter types.
|
||||
*
|
||||
* @see GsonBuilder#registerTypeAdapter(Type, Object)
|
||||
* @see DeviceMethod
|
||||
* @see RPCMethod
|
||||
* @see Callback
|
||||
*/
|
||||
public static final String IMC_ADD_DEVICE_METHOD_PARAMETER_TYPE_ADAPTER = "addDeviceMethodParameterTypeAdapter";
|
||||
|
||||
14
src/main/java/li/cil/oc2/api/bus/Device.java
Normal file
14
src/main/java/li/cil/oc2/api/bus/Device.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package li.cil.oc2.api.bus;
|
||||
|
||||
/**
|
||||
* Base interface for objects that can be registered as devices on a {@link DeviceBus}.
|
||||
* <p>
|
||||
* Which types are handled/supported by a bus depends on the {@link DeviceBusController}
|
||||
* managing the bus.
|
||||
* <p>
|
||||
* Note that it is strongly encouraged for implementations to provide an overloaded
|
||||
* {@link #equals(Object)} and {@link #hashCode()} so that identical devices can be
|
||||
* detected.
|
||||
*/
|
||||
public interface Device {
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
package li.cil.oc2.api.bus;
|
||||
|
||||
import li.cil.oc2.api.bus.device.Device;
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCDevice;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A device bus provides the interface by which {@link DeviceInterface} can be made available
|
||||
* A device bus provides the interface by which {@link RPCDevice} can be made available
|
||||
* to a {@link DeviceBusController}, which is usually used by VMs to access devices.
|
||||
*/
|
||||
public interface DeviceBus {
|
||||
@@ -58,10 +57,4 @@ public interface DeviceBus {
|
||||
* have to interact with this interface.
|
||||
*/
|
||||
void scheduleScan();
|
||||
|
||||
// long addDevice(MemoryMappedDevice device);
|
||||
//
|
||||
// void addDevice(final long address, MemoryMappedDevice device);
|
||||
//
|
||||
// void removeDevice(MemoryMappedDevice device);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
package li.cil.oc2.api.bus;
|
||||
|
||||
import li.cil.oc2.api.bus.device.Device;
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -13,7 +9,7 @@ import java.util.UUID;
|
||||
* {@link DeviceBusElement#addController(DeviceBusController)}.
|
||||
* <p>
|
||||
* This interface is usually provided by VM containers and used to collect connected
|
||||
* {@link DeviceInterface}s by aggregating the devices that were added to the device bus elements
|
||||
* {@link Device}s by aggregating the devices that were added to the device bus elements
|
||||
* via {@link DeviceBus#addDevice(Device)}.
|
||||
* <p>
|
||||
* The only way for {@link DeviceBusElement}s to be added to a bus is for a
|
||||
@@ -52,18 +48,23 @@ public interface DeviceBusController {
|
||||
/**
|
||||
* The list of all devices currently known to this controller.
|
||||
* <p>
|
||||
* This is the aggregation of all {@link DeviceInterface} added to all {@link DeviceBusElement}s known
|
||||
* This is the aggregation of all {@link Device}s 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();
|
||||
Set<Device> getDevices();
|
||||
|
||||
/**
|
||||
* Get the device with the specified unique identifier, if possible.
|
||||
* Obtain the unique identifiers for the specified device, if any, as
|
||||
* provided by the {@link DeviceBusElement}s that provided this device.
|
||||
* <p>
|
||||
* If the device was added to multiple {@link DeviceBusElement}s this
|
||||
* may return multiple {@link UUID}s.
|
||||
*
|
||||
* @param uuid the id of the device to get.
|
||||
* @return the device with the specified id, if possible.
|
||||
* @param device the device to get the identifiers for.
|
||||
* @return the identifiers for the device, if any.
|
||||
* @see DeviceBusElement#getDeviceIdentifier(Device)
|
||||
*/
|
||||
Optional<Device> getDevice(final UUID uuid);
|
||||
Set<UUID> getDeviceIdentifiers(Device device);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package li.cil.oc2.api.bus;
|
||||
|
||||
import li.cil.oc2.api.bus.device.Device;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCDevice;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Represents a single connection point on a device bus.
|
||||
@@ -33,14 +35,14 @@ public interface DeviceBusElement extends DeviceBus {
|
||||
*
|
||||
* @param controller the controller to add.
|
||||
*/
|
||||
void addController(final DeviceBusController controller);
|
||||
void addController(DeviceBusController controller);
|
||||
|
||||
/**
|
||||
* Unregisters a controller from this bus element.
|
||||
*
|
||||
* @param controller the controller to remove.
|
||||
*/
|
||||
void removeController(final DeviceBusController controller);
|
||||
void removeController(DeviceBusController controller);
|
||||
|
||||
/**
|
||||
* Returns the list of devices connected specifically by this element.
|
||||
@@ -56,4 +58,23 @@ public interface DeviceBusElement extends DeviceBus {
|
||||
* @return the devices that have been added to this element.
|
||||
*/
|
||||
Collection<Device> getLocalDevices();
|
||||
|
||||
/**
|
||||
* Returns an identifier unique to the specified device.
|
||||
* <p>
|
||||
* This id must persist over save/load to prevent code in a running VM losing
|
||||
* track of the device. Note that some device types (e.g. {@link RPCDevice}s)
|
||||
* require for an ID to be provided for them to work at all.
|
||||
* <p>
|
||||
* It is possible for multiple devices to have the same identifier. Typically
|
||||
* this means they represent a view on the same underlying object. How this is
|
||||
* handled depends on the device type and may or may not be supported.
|
||||
* <p>
|
||||
* Only devices retrieved by calling {@link #getLocalDevices()} should be passed
|
||||
* to this method.
|
||||
*
|
||||
* @param device the device to obtain the ID for.
|
||||
* @return the stable id for the specified device.
|
||||
*/
|
||||
Optional<UUID> getDeviceIdentifier(Device device);
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package li.cil.oc2.api.bus.device;
|
||||
|
||||
/**
|
||||
* Convenience base class for {@link DeviceMethod} implementations.
|
||||
*/
|
||||
public abstract class AbstractDeviceMethod implements DeviceMethod {
|
||||
protected final String name;
|
||||
protected final boolean synchronize;
|
||||
protected final Class<?> returnType;
|
||||
protected final DeviceMethodParameter[] parameters;
|
||||
|
||||
protected AbstractDeviceMethod(final String name, final boolean synchronize, final Class<?> returnType, final DeviceMethodParameter... parameters) {
|
||||
this.name = name;
|
||||
this.synchronize = synchronize;
|
||||
this.returnType = returnType;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
protected AbstractDeviceMethod(final String name, final Class<?> returnType, final DeviceMethodParameter... parameters) {
|
||||
this(name, false, returnType, parameters);
|
||||
}
|
||||
|
||||
protected AbstractDeviceMethod(final String name, final boolean synchronize, final DeviceMethodParameter... parameters) {
|
||||
this(name, synchronize, void.class, parameters);
|
||||
}
|
||||
|
||||
protected AbstractDeviceMethod(final String name, final DeviceMethodParameter... parameters) {
|
||||
this(name, false, void.class, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSynchronized() {
|
||||
return synchronize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceMethodParameter[] getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package li.cil.oc2.api.bus.device;
|
||||
|
||||
import li.cil.oc2.api.bus.DeviceBus;
|
||||
import li.cil.oc2.api.provider.DeviceInterfaceProvider;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Specialization of a device interface that can be referenced by a {@link UUID} and
|
||||
* represents a device as a whole.
|
||||
* <p>
|
||||
* Implementations will typically act as a top-level wrapper for one singular device
|
||||
* as viewed by a list of {@link DeviceInterface}s.
|
||||
* <p>
|
||||
* A unique ID is required when adding devices to a {@link DeviceBus} such that they
|
||||
* may then be referenced.
|
||||
* <p>
|
||||
* Note that {@link DeviceInterfaceProvider}s are <em>not</em> expected to return instances
|
||||
* implementing this interface, but return regular {@link DeviceInterface}s instead.
|
||||
*/
|
||||
public interface Device extends DeviceInterface {
|
||||
/**
|
||||
* 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 getUniqueIdentifier();
|
||||
|
||||
/**
|
||||
* Returns a possible underlying instance of a device.
|
||||
* <p>
|
||||
* Frequently some {@link DeviceInterface} obtained from a {@link DeviceInterfaceProvider} will be
|
||||
* wrapped by an instance of this interface. To prevent this leading to duplicated
|
||||
* device listings this allows accessing the device proper for equality checks.
|
||||
*
|
||||
* @return the underlying device. May be this device itself.
|
||||
*/
|
||||
default DeviceInterface getIdentifiedDevice() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package li.cil.oc2.api.bus.device.object;
|
||||
|
||||
import li.cil.oc2.api.bus.device.DeviceMethod;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCMethod;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
@@ -8,10 +8,10 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Utility annotation to allow generating lists of {@link DeviceMethod}s using
|
||||
* Utility annotation to allow generating lists of {@link RPCMethod}s using
|
||||
* {@link Callbacks#collectMethods(Object)}.
|
||||
* <p>
|
||||
* Intended to be used in classes instances of which are used as a target of {@link ObjectDeviceInterface}.
|
||||
* Intended to be used in classes instances of which are used as a target of {@link ObjectDevice}.
|
||||
* <p>
|
||||
* Method parameters are serialized and deserialized using Gson. When using custom
|
||||
* parameter types it may be necessary to register a custom type adapter for them
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
package li.cil.oc2.api.bus.device.object;
|
||||
|
||||
import li.cil.oc2.api.bus.device.DeviceMethod;
|
||||
import li.cil.oc2.api.bus.device.rpc.AbstractRPCMethod;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCMethod;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCParameter;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
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.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Provides automated extraction of {@link DeviceMethod}s from instances of
|
||||
* Provides automated extraction of {@link RPCMethod}s from instances of
|
||||
* class with methods annotated with the {@link Callback} annotation.
|
||||
* <p>
|
||||
* Prefer using {@link ObjectDeviceInterface} instead of using this class directly.
|
||||
* Prefer using {@link ObjectDevice} instead of using this class directly.
|
||||
*/
|
||||
public final class Callbacks {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
@@ -23,9 +26,9 @@ public final class Callbacks {
|
||||
|
||||
/**
|
||||
* Collects all methods annotated with {@link Callback} in the specified object
|
||||
* and generated {@link DeviceMethod}s for each one.
|
||||
* and generated {@link RPCMethod}s for each one.
|
||||
* <p>
|
||||
* The generated {@link DeviceMethod} will be bound to the passed object and
|
||||
* The generated {@link RPCMethod} will be bound to the passed object and
|
||||
* can be called without needing to pass the object.
|
||||
* <p>
|
||||
* For example:
|
||||
@@ -42,10 +45,10 @@ public final class Callbacks {
|
||||
* @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) {
|
||||
public static List<RPCMethod> collectMethods(final Object methodContainer) {
|
||||
final List<Method> reflectedMethods = getMethods(methodContainer.getClass());
|
||||
|
||||
final ArrayList<DeviceMethod> methods = new ArrayList<>();
|
||||
final ArrayList<RPCMethod> methods = new ArrayList<>();
|
||||
for (final Method method : reflectedMethods) {
|
||||
try {
|
||||
methods.add(new ObjectDeviceMethod(methodContainer, method));
|
||||
@@ -59,7 +62,7 @@ public final class Callbacks {
|
||||
|
||||
/**
|
||||
* Returns whether any {@link Callback} annotated methods are present on the specified
|
||||
* object without creating bound {@link DeviceMethod} instances.
|
||||
* object without creating bound {@link RPCMethod} instances.
|
||||
* <p>
|
||||
* The specified {@code object} can be an instance or a {@link Class}.
|
||||
*
|
||||
@@ -80,4 +83,94 @@ public final class Callbacks {
|
||||
.filter(m -> m.isAnnotationPresent(Callback.class))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private static final class ObjectDeviceMethod extends AbstractRPCMethod {
|
||||
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(),
|
||||
Objects.requireNonNull(method.getAnnotation(Callback.class), "Method without Callback annotation.").synchronize(),
|
||||
method.getReturnType(),
|
||||
getParameters(method));
|
||||
|
||||
final Callback annotation = method.getAnnotation(Callback.class);
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final ObjectDeviceMethod that = (ObjectDeviceMethod) o;
|
||||
return handle.equals(that.handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return handle.toString();
|
||||
}
|
||||
|
||||
private static RPCParameter[] getParameters(final Method method) {
|
||||
return Arrays.stream(method.getParameters())
|
||||
.map(ReflectionParameter::new)
|
||||
.toArray(RPCParameter[]::new);
|
||||
}
|
||||
|
||||
private static final class ReflectionParameter implements RPCParameter {
|
||||
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 Parameter annotation = parameter.getAnnotation(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,14 @@ import net.minecraft.tileentity.TileEntity;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* This interface is used to declare additional type names for a device on targets of an {@link ObjectDeviceInterface}.
|
||||
* This interface is used to declare additional type names for a device on targets of an {@link ObjectDevice}.
|
||||
* <p>
|
||||
* In particular {@link Block}s and {@link TileEntity}s that contain {@link Callback} methods may implement
|
||||
* For example: {@link Block}s and {@link TileEntity}s that contain {@link Callback} methods may implement
|
||||
* this interface to provide additional type names.
|
||||
*/
|
||||
public interface NamedDevice {
|
||||
/**
|
||||
* A list of additional type names to associate with any {@link ObjectDeviceInterface} used
|
||||
* A list of additional type names to associate with any {@link ObjectDevice} used
|
||||
* to make available methods in an instance of a class implementing this interface.
|
||||
*
|
||||
* @return the list of additional type names.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package li.cil.oc2.api.bus.device.object;
|
||||
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.api.bus.device.DeviceMethod;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCDevice;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCMethod;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
@@ -9,14 +9,14 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A reflection based implementation of {@link DeviceInterface} using the {@link Callback}
|
||||
* annotation to discover {@link DeviceMethod}s in a target object via
|
||||
* A reflection based implementation of {@link RPCDevice} using the {@link Callback}
|
||||
* annotation to discover {@link RPCMethod}s in a target object via
|
||||
* {@link Callbacks#collectMethods(Object)}.
|
||||
*/
|
||||
public final class ObjectDeviceInterface implements DeviceInterface {
|
||||
public final class ObjectDevice implements RPCDevice {
|
||||
private final Object object;
|
||||
private final ArrayList<String> typeNames;
|
||||
private final List<DeviceMethod> methods;
|
||||
private final List<RPCMethod> methods;
|
||||
private final String className;
|
||||
|
||||
/**
|
||||
@@ -26,7 +26,7 @@ public final class ObjectDeviceInterface implements DeviceInterface {
|
||||
* @param object the object containing methods provided by this device.
|
||||
* @param typeNames the type names of the device.
|
||||
*/
|
||||
public ObjectDeviceInterface(final Object object, final List<String> typeNames) {
|
||||
public ObjectDevice(final Object object, final List<String> typeNames) {
|
||||
this.object = object;
|
||||
this.typeNames = new ArrayList<>(typeNames);
|
||||
this.methods = Callbacks.collectMethods(object);
|
||||
@@ -40,12 +40,12 @@ public final class ObjectDeviceInterface implements DeviceInterface {
|
||||
/**
|
||||
* Creates a new object device with methods in the specified object and the specified
|
||||
* type name. For convenience, the type name may be {@code null}, in which case using
|
||||
* this constructor is equivalent to using {@link #ObjectDeviceInterface(Object)}.
|
||||
* this constructor is equivalent to using {@link #ObjectDevice(Object)}.
|
||||
*
|
||||
* @param object the object containing methods provided by this device.
|
||||
* @param typeName the type name of the device.
|
||||
*/
|
||||
public ObjectDeviceInterface(final Object object, @Nullable final String typeName) {
|
||||
public ObjectDevice(final Object object, @Nullable final String typeName) {
|
||||
this(object, typeName != null ? Collections.singletonList(typeName) : Collections.emptyList());
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public final class ObjectDeviceInterface implements DeviceInterface {
|
||||
*
|
||||
* @param object the object containing the methods provided by this device.
|
||||
*/
|
||||
public ObjectDeviceInterface(final Object object) {
|
||||
public ObjectDevice(final Object object) {
|
||||
this(object, Collections.emptyList());
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public final class ObjectDeviceInterface implements DeviceInterface {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeviceMethod> getMethods() {
|
||||
public List<RPCMethod> getMethods() {
|
||||
return methods;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ public final class ObjectDeviceInterface implements DeviceInterface {
|
||||
public boolean equals(@Nullable final Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final ObjectDeviceInterface that = (ObjectDeviceInterface) o;
|
||||
final ObjectDevice that = (ObjectDevice) o;
|
||||
return object.equals(that.object);
|
||||
}
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
package li.cil.oc2.api.bus.device.object;
|
||||
|
||||
import li.cil.oc2.api.bus.device.AbstractDeviceMethod;
|
||||
import li.cil.oc2.api.bus.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.Objects;
|
||||
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(),
|
||||
Objects.requireNonNull(method.getAnnotation(Callback.class), "Method without Callback annotation.").synchronize(),
|
||||
method.getReturnType(),
|
||||
getParameters(method));
|
||||
|
||||
final Callback annotation = method.getAnnotation(Callback.class);
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final ObjectDeviceMethod that = (ObjectDeviceMethod) o;
|
||||
return handle.equals(that.handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return handle.toString();
|
||||
}
|
||||
|
||||
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.bus.device.object.Parameter annotation = parameter.getAnnotation(li.cil.oc2.api.bus.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package li.cil.oc2.api.bus.device.rpc;
|
||||
|
||||
/**
|
||||
* Convenience base class for {@link RPCMethod} implementations.
|
||||
*/
|
||||
public abstract class AbstractRPCMethod implements RPCMethod {
|
||||
protected final String name;
|
||||
protected final boolean synchronize;
|
||||
protected final Class<?> returnType;
|
||||
protected final RPCParameter[] parameters;
|
||||
|
||||
protected AbstractRPCMethod(final String name, final boolean synchronize, final Class<?> returnType, final RPCParameter... parameters) {
|
||||
this.name = name;
|
||||
this.synchronize = synchronize;
|
||||
this.returnType = returnType;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
protected AbstractRPCMethod(final String name, final Class<?> returnType, final RPCParameter... parameters) {
|
||||
this(name, false, returnType, parameters);
|
||||
}
|
||||
|
||||
protected AbstractRPCMethod(final String name, final boolean synchronize, final RPCParameter... parameters) {
|
||||
this(name, synchronize, void.class, parameters);
|
||||
}
|
||||
|
||||
protected AbstractRPCMethod(final String name, final RPCParameter... parameters) {
|
||||
this(name, false, void.class, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSynchronized() {
|
||||
return synchronize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getReturnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RPCParameter[] getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,28 @@
|
||||
package li.cil.oc2.api.bus.device;
|
||||
package li.cil.oc2.api.bus.device.rpc;
|
||||
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDeviceInterface;
|
||||
import li.cil.oc2.api.provider.DeviceInterfaceProvider;
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDevice;
|
||||
import li.cil.oc2.api.provider.DeviceProvider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementations act as an interface for a device.
|
||||
* Provides an interface for an RPC device, describing the methods that can be
|
||||
* called on it and the type names it can be detected by/is compatible with.
|
||||
* <p>
|
||||
* A {@link DeviceInterface} represents a single view onto some device. One device
|
||||
* may have multiple {@code DeviceInterfaces} providing different methods for the
|
||||
* A {@link RPCDevice} may represent a single view onto some device or be a
|
||||
* collection of multiple aggregated {@link RPCDevice}s. One underlying device
|
||||
* may have multiple {@link RPCDevice}s providing different methods for the
|
||||
* device. This allows specifying general purpose interfaces which provide logic
|
||||
* for some aspect of an underlying device which may be shared with other devices.
|
||||
* <p>
|
||||
* The easiest and hence recommended way of implementing this interface is to use
|
||||
* the {@link ObjectDeviceInterface} class.
|
||||
* <p>
|
||||
* Note that it is strongly encouraged for implementations to provide an overloaded
|
||||
* {@link #equals(Object)} and {@link #hashCode()} so that identical devices can be
|
||||
* detected.
|
||||
* the {@link ObjectDevice} class.
|
||||
*
|
||||
* @see ObjectDeviceInterface
|
||||
* @see DeviceInterfaceProvider
|
||||
* @see ObjectDevice
|
||||
* @see DeviceProvider
|
||||
*/
|
||||
public interface DeviceInterface {
|
||||
public interface RPCDevice extends Device {
|
||||
/**
|
||||
* A list of type names identifying this interface.
|
||||
* <p>
|
||||
@@ -39,5 +38,5 @@ public interface DeviceInterface {
|
||||
/**
|
||||
* The list of methods provided by this interface.
|
||||
*/
|
||||
List<DeviceMethod> getMethods();
|
||||
List<RPCMethod> getMethods();
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
package li.cil.oc2.api.bus.device;
|
||||
package li.cil.oc2.api.bus.device.rpc;
|
||||
|
||||
import li.cil.oc2.api.bus.DeviceBusController;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDeviceInterface;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDevice;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents a single method that can be exposed by a {@link DeviceInterface}.
|
||||
* Represents a single method that can be exposed by a {@link RPCDevice}.
|
||||
* <p>
|
||||
* The easiest and hence recommended way of implementing this interface is to use
|
||||
* the {@link ObjectDeviceInterface} class.
|
||||
* the {@link ObjectDevice} class.
|
||||
* <p>
|
||||
* Method parameters are serialized and deserialized using Gson. When using custom
|
||||
* parameter types it may be necessary to register a custom type adapter for them
|
||||
* via {@link li.cil.oc2.api.API#IMC_ADD_DEVICE_METHOD_PARAMETER_TYPE_ADAPTER}.
|
||||
*
|
||||
* @see ObjectDeviceInterface
|
||||
* @see ObjectDevice
|
||||
*/
|
||||
public interface DeviceMethod {
|
||||
public interface RPCMethod {
|
||||
/**
|
||||
* The name of the method.
|
||||
* <p>
|
||||
@@ -41,7 +41,7 @@ public interface DeviceMethod {
|
||||
/**
|
||||
* The list of parameters this method accepts.
|
||||
*/
|
||||
DeviceMethodParameter[] getParameters();
|
||||
RPCParameter[] getParameters();
|
||||
|
||||
/**
|
||||
* Called to run this method.
|
||||
@@ -1,18 +1,18 @@
|
||||
package li.cil.oc2.api.bus.device;
|
||||
package li.cil.oc2.api.bus.device.rpc;
|
||||
|
||||
import li.cil.oc2.api.bus.DeviceBusController;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Describes a single parameter of a {@link DeviceMethod}.
|
||||
* Describes a single parameter of a {@link RPCMethod}.
|
||||
*/
|
||||
public interface DeviceMethodParameter {
|
||||
public interface RPCParameter {
|
||||
/**
|
||||
* 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...)}.
|
||||
* level representation before passing it to {@link RPCMethod#invoke(Object...)}.
|
||||
* As such, the types used must be kept simple. As a rule of thumb, only primitives
|
||||
* and POJOs should be used.
|
||||
*
|
||||
@@ -0,0 +1,7 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
package li.cil.oc2.api.bus.device.rpc;
|
||||
|
||||
import mcp.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -1,23 +1,24 @@
|
||||
package li.cil.oc2.api.provider;
|
||||
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDeviceInterface;
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDevice;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCDevice;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
|
||||
/**
|
||||
* Allows querying for device interfaces given some context.
|
||||
* Allows querying for devices given some context.
|
||||
* <p>
|
||||
* See the specializations of {@link DeviceQuery} for possible queries.
|
||||
* <p>
|
||||
* Returning a device interface does <em>not</em> transfer ownership of the device in terms
|
||||
* Returning a devices does <em>not</em> transfer ownership of the device in terms
|
||||
* of responsibility for persistence. Callers of this method will <em>not</em> attempt
|
||||
* to persist objects returned by this method. It is the responsibility of the provider
|
||||
* to ensure persistence where required.
|
||||
* <ul>
|
||||
* <li>Implementations <em>may</em> handle multiple query types and return various device
|
||||
* interface types depending on the query.</li>
|
||||
* types depending on the query.</li>
|
||||
* <li>
|
||||
* Implementations <em>should</em> return the same device interface for the same query.
|
||||
* Implementations <em>should</em> return the same device for the same query.
|
||||
* <p>
|
||||
* Failing that, implementations <em>should</em> return instances that are equal to each
|
||||
* other when compared using {@link #equals(Object)} and have equal {@link #hashCode()}s.
|
||||
@@ -30,18 +31,18 @@ import net.minecraftforge.common.util.LazyOptional;
|
||||
* <p>
|
||||
* Providers can be registered with the IMC message {@link li.cil.oc2.api.API#IMC_ADD_DEVICE_PROVIDER}.
|
||||
*
|
||||
* @see DeviceInterface
|
||||
* @see ObjectDeviceInterface
|
||||
* @see RPCDevice
|
||||
* @see ObjectDevice
|
||||
* @see DeviceQuery
|
||||
* @see BlockDeviceQuery
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface DeviceInterfaceProvider {
|
||||
public interface DeviceProvider {
|
||||
/**
|
||||
* Get a device for the specified query.
|
||||
*
|
||||
* @param query the query describing the object to get a {@link DeviceInterface} for.
|
||||
* @param query the query describing the object to get a {@link Device} for.
|
||||
* @return a device for the specified query, if available.
|
||||
*/
|
||||
LazyOptional<DeviceInterface> getDeviceInterface(DeviceQuery query);
|
||||
LazyOptional<Device> getDevice(DeviceQuery query);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package li.cil.oc2.api.provider;
|
||||
|
||||
/**
|
||||
* Base interface for all queries to {@link DeviceInterfaceProvider}s.
|
||||
* Base interface for all queries to {@link DeviceProvider}s.
|
||||
*
|
||||
* @see DeviceInterfaceProvider
|
||||
* @see DeviceProvider
|
||||
* @see BlockDeviceQuery
|
||||
*/
|
||||
public interface DeviceQuery {
|
||||
|
||||
9
src/main/java/li/cil/oc2/api/vm/InterruptAllocator.java
Normal file
9
src/main/java/li/cil/oc2/api/vm/InterruptAllocator.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package li.cil.oc2.api.vm;
|
||||
|
||||
import java.util.OptionalInt;
|
||||
|
||||
public interface InterruptAllocator {
|
||||
OptionalInt claimInterrupt(int interrupt);
|
||||
|
||||
OptionalInt claimInterrupt();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package li.cil.oc2.api.vm;
|
||||
|
||||
import li.cil.sedna.api.device.InterruptController;
|
||||
import li.cil.sedna.api.memory.MemoryMap;
|
||||
|
||||
public interface MemoryMappedDeviceReference {
|
||||
boolean load(final MemoryMap memoryMap, final InterruptController interruptController);
|
||||
|
||||
void unload();
|
||||
}
|
||||
12
src/main/java/li/cil/oc2/api/vm/VirtualMachineContext.java
Normal file
12
src/main/java/li/cil/oc2/api/vm/VirtualMachineContext.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package li.cil.oc2.api.vm;
|
||||
|
||||
import li.cil.sedna.api.device.InterruptController;
|
||||
import li.cil.sedna.api.memory.MemoryMap;
|
||||
|
||||
public interface VirtualMachineContext {
|
||||
MemoryMap getMemoryMap();
|
||||
|
||||
InterruptAllocator getInterruptAllocator();
|
||||
|
||||
InterruptController getInterruptController();
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package li.cil.oc2.common;
|
||||
|
||||
import li.cil.oc2.api.API;
|
||||
import li.cil.oc2.api.provider.DeviceInterfaceProvider;
|
||||
import li.cil.oc2.api.provider.DeviceProvider;
|
||||
import li.cil.oc2.api.imc.DeviceMethodParameterTypeAdapter;
|
||||
import li.cil.oc2.common.device.DeviceMethodParameterTypeAdapters;
|
||||
import li.cil.oc2.common.device.provider.Providers;
|
||||
@@ -39,7 +39,7 @@ public final class IMC {
|
||||
}
|
||||
|
||||
private static void addDeviceProvider(final InterModComms.IMCMessage message) {
|
||||
getMessageParameter(message, DeviceInterfaceProvider.class).ifPresent(Providers::addProvider);
|
||||
getMessageParameter(message, DeviceProvider.class).ifPresent(Providers::addProvider);
|
||||
}
|
||||
|
||||
private static void addDeviceMethodParameterTypeAdapter(final InterModComms.IMCMessage message) {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package li.cil.oc2.common.bus;
|
||||
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.bus.DeviceBusController;
|
||||
import li.cil.oc2.api.bus.DeviceBusElement;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class AbstractDeviceBusElement implements DeviceBusElement {
|
||||
protected final List<Device> devices = new ArrayList<>();
|
||||
protected final HashSet<DeviceBusController> controllers = new HashSet<>();
|
||||
|
||||
public void addController(final DeviceBusController controller) {
|
||||
controllers.add(controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeController(final DeviceBusController controller) {
|
||||
controllers.remove(controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Device> getLocalDevices() {
|
||||
return devices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<UUID> getDeviceIdentifier(final Device device) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDevice(final Device device) {
|
||||
devices.add(device);
|
||||
scanDevices();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDevice(final Device device) {
|
||||
devices.remove(device);
|
||||
scanDevices();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Device> getDevices() {
|
||||
if (!controllers.isEmpty()) {
|
||||
return controllers.stream().flatMap(controller -> getDevices().stream()).collect(Collectors.toList());
|
||||
} else {
|
||||
return getLocalDevices();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleScan() {
|
||||
// Controllers are expected to remove themselves when a scan is scheduled.
|
||||
final ArrayList<DeviceBusController> oldControllers = new ArrayList<>(controllers);
|
||||
for (final DeviceBusController controller : oldControllers) {
|
||||
controller.scheduleBusScan();
|
||||
}
|
||||
assert controllers.isEmpty();
|
||||
}
|
||||
|
||||
protected void scanDevices() {
|
||||
for (final DeviceBusController controller : controllers) {
|
||||
controller.scanDevices();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,17 @@ package li.cil.oc2.common.bus;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import li.cil.ceres.api.Serialized;
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.bus.DeviceBusController;
|
||||
import li.cil.oc2.api.bus.device.Device;
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.api.bus.device.DeviceMethod;
|
||||
import li.cil.oc2.api.bus.device.DeviceMethodParameter;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCDevice;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCMethod;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCParameter;
|
||||
import li.cil.oc2.common.device.DeviceMethodParameterTypeAdapters;
|
||||
import li.cil.oc2.serialization.serializers.DeviceJsonSerializer;
|
||||
import li.cil.oc2.serialization.serializers.DeviceMethodJsonSerializer;
|
||||
import li.cil.oc2.common.device.RPCDeviceList;
|
||||
import li.cil.oc2.serialization.serializers.MessageJsonDeserializer;
|
||||
import li.cil.oc2.serialization.serializers.MethodInvocationJsonDeserializer;
|
||||
import li.cil.oc2.serialization.serializers.RPCDeviceWithIdentifierJsonSerializer;
|
||||
import li.cil.oc2.serialization.serializers.RPCMethodJsonSerializer;
|
||||
import li.cil.sedna.api.device.Steppable;
|
||||
import li.cil.sedna.api.device.serial.SerialDevice;
|
||||
|
||||
@@ -20,9 +21,9 @@ import javax.annotation.Nullable;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public final class RPCAdapter implements Steppable {
|
||||
private static final int DEFAULT_MAX_MESSAGE_SIZE = 4 * 1024;
|
||||
@@ -35,10 +36,14 @@ public final class RPCAdapter implements Steppable {
|
||||
public static final String ERROR_INVALID_PARAMETER_SIGNATURE = "invalid parameter signature";
|
||||
|
||||
private final DeviceBusController controller;
|
||||
|
||||
private final SerialDevice serialDevice;
|
||||
private final Gson gson;
|
||||
|
||||
private final ArrayList<RPCDeviceWithIdentifier> devices = new ArrayList<>();
|
||||
private final HashMap<UUID, RPCDeviceList> devicesById = new HashMap<>();
|
||||
private final Lock pauseLock = new ReentrantLock();
|
||||
private boolean isPaused;
|
||||
|
||||
@Serialized private final ByteBuffer transmitBuffer; // for data written to device by VM
|
||||
@Serialized private ByteBuffer receiveBuffer; // for data written by device to VM
|
||||
@Serialized private MethodInvocation synchronizedInvocation; // pending main thread invocation
|
||||
@@ -54,18 +59,88 @@ public final class RPCAdapter implements Steppable {
|
||||
this.gson = DeviceMethodParameterTypeAdapters.beginBuildGson()
|
||||
.registerTypeAdapter(MethodInvocation.class, new MethodInvocationJsonDeserializer())
|
||||
.registerTypeAdapter(Message.class, new MessageJsonDeserializer())
|
||||
.registerTypeAdapter(DeviceInterface.class, new DeviceJsonSerializer())
|
||||
.registerTypeAdapter(DeviceMethod.class, new DeviceMethodJsonSerializer())
|
||||
.registerTypeAdapter(RPCDeviceWithIdentifier.class, new RPCDeviceWithIdentifierJsonSerializer())
|
||||
.registerTypeAdapter(RPCMethod.class, new RPCMethodJsonSerializer())
|
||||
.create();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
devices.clear();
|
||||
devicesById.clear();
|
||||
isPaused = false;
|
||||
transmitBuffer.clear();
|
||||
receiveBuffer = null;
|
||||
synchronizedInvocation = null;
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
if (isPaused) {
|
||||
return;
|
||||
}
|
||||
|
||||
pauseLock.lock();
|
||||
isPaused = true;
|
||||
pauseLock.unlock();
|
||||
|
||||
devices.clear();
|
||||
devicesById.clear();
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
if (!isPaused) {
|
||||
return;
|
||||
}
|
||||
|
||||
// How device grouping works:
|
||||
// Each device can have multiple UUIDs due to being attached to multiple bus elements.
|
||||
// There is no guarantee that for each device D1 present on bus elements E1 and E2,
|
||||
// where device D2 is present on E1 it will also be present on E2. This is completely
|
||||
// up to the device providers.
|
||||
// Therefore we must group all devices by their identifiers to then remove duplicate
|
||||
// groups. This is fragile because it will depend on the order the devices appear in
|
||||
// the list. However, since we add devices to bus elements in the order of their
|
||||
// providers, then add devices to the controller in the order of their elements, this
|
||||
// will work. And even if it does not, it only leads to duplicate devices popping up
|
||||
// in the VM, which, while annoying, is not breaking anything.
|
||||
// In a final step, when we know which devices are duplicates and what identifiers
|
||||
// they have, we pick a single identifier in a deterministic way, given the list of
|
||||
// identifiers is the same.
|
||||
|
||||
final HashMap<UUID, ArrayList<RPCDevice>> devicesByIdentifier = new HashMap<>();
|
||||
for (final Device device : controller.getDevices()) {
|
||||
if (device instanceof RPCDevice) {
|
||||
final RPCDevice rpcDevice = (RPCDevice) device;
|
||||
final Set<UUID> identifiers = controller.getDeviceIdentifiers(device);
|
||||
for (final UUID identifier : identifiers) {
|
||||
devicesByIdentifier
|
||||
.computeIfAbsent(identifier, unused -> new ArrayList<>())
|
||||
.add(rpcDevice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final HashMap<RPCDeviceList, ArrayList<UUID>> identifiersByDevice = new HashMap<>();
|
||||
devicesByIdentifier.forEach((identifier, devices) -> {
|
||||
final RPCDeviceList device = new RPCDeviceList(devices);
|
||||
identifiersByDevice
|
||||
.computeIfAbsent(device, unused -> new ArrayList<>())
|
||||
.add(identifier);
|
||||
});
|
||||
|
||||
identifiersByDevice.forEach((device, identifiers) -> {
|
||||
final UUID identifier = selectIdentifierDeterministically(identifiers);
|
||||
devices.add(new RPCDeviceWithIdentifier(identifier, device));
|
||||
devicesById.put(identifier, device);
|
||||
});
|
||||
|
||||
isPaused = false;
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (isPaused) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (synchronizedInvocation != null) {
|
||||
final MethodInvocation methodInvocation = synchronizedInvocation;
|
||||
synchronizedInvocation = null;
|
||||
@@ -74,8 +149,27 @@ public final class RPCAdapter implements Steppable {
|
||||
}
|
||||
|
||||
public void step(final int cycles) {
|
||||
readFromDevice();
|
||||
writeToDevice();
|
||||
if (isPaused || !pauseLock.tryLock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
readFromDevice();
|
||||
writeToDevice();
|
||||
} finally {
|
||||
pauseLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private UUID selectIdentifierDeterministically(final ArrayList<UUID> identifiers) {
|
||||
UUID lowestIdentifier = identifiers.get(0);
|
||||
for (int i = 1; i < identifiers.size(); i++) {
|
||||
final UUID identifier = identifiers.get(i);
|
||||
if (identifier.compareTo(lowestIdentifier) < 0) {
|
||||
lowestIdentifier = identifier;
|
||||
}
|
||||
}
|
||||
return lowestIdentifier;
|
||||
}
|
||||
|
||||
private void readFromDevice() {
|
||||
@@ -151,8 +245,8 @@ public final class RPCAdapter implements Steppable {
|
||||
}
|
||||
|
||||
private void processMethodInvocation(final MethodInvocation methodInvocation, final boolean isMainThread) {
|
||||
final Optional<Device> device = controller.getDevice(methodInvocation.deviceId);
|
||||
if (!device.isPresent()) {
|
||||
final RPCDevice device = devicesById.get(methodInvocation.deviceId);
|
||||
if (device == null) {
|
||||
writeError(ERROR_UNKNOWN_DEVICE);
|
||||
return;
|
||||
}
|
||||
@@ -163,12 +257,12 @@ public final class RPCAdapter implements Steppable {
|
||||
// flexibility for free (devices may dynamically change their methods).
|
||||
String error = ERROR_UNKNOWN_METHOD;
|
||||
outer:
|
||||
for (final DeviceMethod method : device.get().getMethods()) {
|
||||
for (final RPCMethod method : device.getMethods()) {
|
||||
if (!Objects.equals(method.getName(), methodInvocation.methodName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final DeviceMethodParameter[] parametersSpec = method.getParameters();
|
||||
final RPCParameter[] parametersSpec = method.getParameters();
|
||||
if (methodInvocation.parameters.size() != parametersSpec.length) {
|
||||
error = ERROR_INVALID_PARAMETER_SIGNATURE;
|
||||
continue; // There may be an overload with matching parameter count.
|
||||
@@ -176,7 +270,7 @@ public final class RPCAdapter implements Steppable {
|
||||
|
||||
final Object[] parameters = new Object[parametersSpec.length];
|
||||
for (int i = 0; i < parametersSpec.length; i++) {
|
||||
final DeviceMethodParameter parameterInfo = parametersSpec[i];
|
||||
final RPCParameter parameterInfo = parametersSpec[i];
|
||||
try {
|
||||
parameters[i] = gson.fromJson(methodInvocation.parameters.get(i), parameterInfo.getType());
|
||||
} catch (final Throwable e) {
|
||||
@@ -204,7 +298,7 @@ public final class RPCAdapter implements Steppable {
|
||||
}
|
||||
|
||||
private void writeStatus() {
|
||||
writeMessage(Message.MESSAGE_TYPE_STATUS, controller.getDevices().toArray(new DeviceInterface[0]));
|
||||
writeMessage(Message.MESSAGE_TYPE_STATUS, devices);
|
||||
}
|
||||
|
||||
private void writeError(final String message) {
|
||||
@@ -232,6 +326,16 @@ public final class RPCAdapter implements Steppable {
|
||||
receiveBuffer.flip();
|
||||
}
|
||||
|
||||
public static final class RPCDeviceWithIdentifier {
|
||||
public final UUID identifier;
|
||||
public final RPCDevice device;
|
||||
|
||||
private RPCDeviceWithIdentifier(final UUID identifier, final RPCDevice device) {
|
||||
this.identifier = identifier;
|
||||
this.device = device;
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Message {
|
||||
// Device -> VM
|
||||
public static final String MESSAGE_TYPE_STATUS = "status";
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package li.cil.oc2.common.bus;
|
||||
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.bus.DeviceBusController;
|
||||
import li.cil.oc2.api.bus.DeviceBusElement;
|
||||
import li.cil.oc2.api.bus.device.Device;
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.Direction;
|
||||
@@ -12,11 +11,9 @@ import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class TileEntityDeviceBusController implements DeviceBusController {
|
||||
public abstract class TileEntityDeviceBusController implements DeviceBusController {
|
||||
public enum State {
|
||||
SCAN_PENDING,
|
||||
TOO_COMPLEX,
|
||||
@@ -27,27 +24,26 @@ public final class TileEntityDeviceBusController implements DeviceBusController
|
||||
private static final int MAX_BUS_ELEMENT_COUNT = 128;
|
||||
|
||||
private final TileEntity tileEntity;
|
||||
@Nullable private final Runnable onBeforeClearDevices;
|
||||
|
||||
private final Set<DeviceBusElement> elements = new HashSet<>();
|
||||
private final ConcurrentHashMap<UUID, Device> devices = new ConcurrentHashMap<>();
|
||||
private final HashSet<Device> devices = new HashSet<>();
|
||||
private final HashMap<Device, Set<UUID>> deviceIds = new HashMap<>();
|
||||
|
||||
private int scanDelay;
|
||||
|
||||
public TileEntityDeviceBusController(final TileEntity tileEntity) {
|
||||
this(tileEntity, null);
|
||||
protected TileEntityDeviceBusController(final TileEntity tileEntity) {
|
||||
this.tileEntity = tileEntity;
|
||||
}
|
||||
|
||||
public TileEntityDeviceBusController(final TileEntity tileEntity, @Nullable final Runnable onBeforeBusScan) {
|
||||
this.tileEntity = tileEntity;
|
||||
this.onBeforeClearDevices = onBeforeBusScan;
|
||||
protected void onDevicesInvalid() {
|
||||
}
|
||||
|
||||
protected void onDevicesValid() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scheduleBusScan() {
|
||||
if (!devices.isEmpty() && onBeforeClearDevices != null) {
|
||||
onBeforeClearDevices.run();
|
||||
}
|
||||
onDevicesInvalid();
|
||||
|
||||
for (final DeviceBusElement element : elements) {
|
||||
element.removeController(this);
|
||||
@@ -55,36 +51,37 @@ public final class TileEntityDeviceBusController implements DeviceBusController
|
||||
|
||||
elements.clear();
|
||||
devices.clear();
|
||||
deviceIds.clear();
|
||||
|
||||
scanDelay = 0; // scan as soon as possible
|
||||
}
|
||||
|
||||
@Override
|
||||
public void scanDevices() {
|
||||
devices.clear();
|
||||
onDevicesInvalid();
|
||||
|
||||
final HashMap<DeviceInterface, ArrayList<Device>> groupedDevices = new HashMap<>();
|
||||
devices.clear();
|
||||
deviceIds.clear();
|
||||
|
||||
for (final DeviceBusElement element : elements) {
|
||||
for (final Device device : element.getLocalDevices()) {
|
||||
groupedDevices.computeIfAbsent(device.getIdentifiedDevice(), d -> new ArrayList<>()).add(device);
|
||||
devices.add(device);
|
||||
element.getDeviceIdentifier(device).ifPresent(identifier -> deviceIds
|
||||
.computeIfAbsent(device, unused -> new HashSet<>()).add(identifier));
|
||||
}
|
||||
}
|
||||
|
||||
for (final ArrayList<Device> group : groupedDevices.values()) {
|
||||
final Device device = selectDeviceDeterministically(group);
|
||||
devices.putIfAbsent(device.getUniqueIdentifier(), device);
|
||||
}
|
||||
onDevicesValid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Device> getDevices() {
|
||||
return devices.values();
|
||||
public Set<Device> getDevices() {
|
||||
return devices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Device> getDevice(final UUID uuid) {
|
||||
return Optional.ofNullable(devices.get(uuid));
|
||||
public Set<UUID> getDeviceIdentifiers(final Device device) {
|
||||
return deviceIds.getOrDefault(device, Collections.emptySet());
|
||||
}
|
||||
|
||||
public State scan() {
|
||||
@@ -189,18 +186,6 @@ public final class TileEntityDeviceBusController implements DeviceBusController
|
||||
return State.READY;
|
||||
}
|
||||
|
||||
private static Device selectDeviceDeterministically(final ArrayList<Device> devices) {
|
||||
Device deviceWithLowestUuid = devices.get(0);
|
||||
for (int i = 1; i < devices.size(); i++) {
|
||||
final Device device = devices.get(i);
|
||||
if (device.getUniqueIdentifier().compareTo(deviceWithLowestUuid.getUniqueIdentifier()) < 0) {
|
||||
deviceWithLowestUuid = device;
|
||||
}
|
||||
}
|
||||
|
||||
return deviceWithLowestUuid;
|
||||
}
|
||||
|
||||
private static final class ScanEdge {
|
||||
public final BlockPos position;
|
||||
public final Direction face;
|
||||
|
||||
@@ -1,47 +1,46 @@
|
||||
package li.cil.oc2.common.bus;
|
||||
|
||||
import li.cil.ceres.api.Serialized;
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.bus.DeviceBus;
|
||||
import li.cil.oc2.api.bus.DeviceBusElement;
|
||||
import li.cil.oc2.common.ServerScheduler;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
import li.cil.oc2.common.device.DeviceImpl;
|
||||
import li.cil.oc2.common.device.DeviceInterfaceCollection;
|
||||
import li.cil.oc2.common.device.provider.Providers;
|
||||
import li.cil.oc2.common.util.WorldUtils;
|
||||
import net.minecraft.nbt.CompoundNBT;
|
||||
import net.minecraft.nbt.ListNBT;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class TileEntityDeviceBusElement {
|
||||
private static final String DEVICE_IDS_NBT_TAG_NAME = "deviceIds";
|
||||
private static final String DEVICE_ID_NBT_TAG_NAME = "deviceId";
|
||||
import java.util.*;
|
||||
|
||||
public final class TileEntityDeviceBusElement extends AbstractDeviceBusElement {
|
||||
private static final int NEIGHBOR_COUNT = 6;
|
||||
|
||||
private final TileEntity tileEntity;
|
||||
|
||||
private final DeviceBusElement busElement = Objects.requireNonNull(Capabilities.DEVICE_BUS_ELEMENT_CAPABILITY.getDefaultInstance());
|
||||
private final DeviceImpl[] devices = new DeviceImpl[NEIGHBOR_COUNT];
|
||||
@Serialized private UUID[] deviceIds = new UUID[NEIGHBOR_COUNT];
|
||||
private final ArrayList<HashSet<Device>> sidedDevices = new ArrayList<>(6);
|
||||
@Serialized private UUID[] sidedDeviceIds = new UUID[NEIGHBOR_COUNT];
|
||||
|
||||
public TileEntityDeviceBusElement(final TileEntity tileEntity) {
|
||||
this.tileEntity = tileEntity;
|
||||
|
||||
for (int i = 0; i < NEIGHBOR_COUNT; i++) {
|
||||
deviceIds[i] = UUID.randomUUID();
|
||||
sidedDevices.add(new HashSet<>());
|
||||
sidedDeviceIds[i] = UUID.randomUUID();
|
||||
}
|
||||
}
|
||||
|
||||
public DeviceBusElement getBusElement() {
|
||||
return busElement;
|
||||
@Override
|
||||
public Optional<UUID> getDeviceIdentifier(final Device device) {
|
||||
for (int i = 0; i < NEIGHBOR_COUNT; i++) {
|
||||
if (sidedDevices.get(i).contains(device)) {
|
||||
return Optional.of(sidedDeviceIds[i]);
|
||||
}
|
||||
}
|
||||
return super.getDeviceIdentifier(device);
|
||||
}
|
||||
|
||||
public void handleNeighborChanged(final BlockPos pos) {
|
||||
@@ -58,30 +57,22 @@ public final class TileEntityDeviceBusElement {
|
||||
|
||||
final int index = direction.getIndex();
|
||||
|
||||
final LazyOptional<DeviceInterfaceCollection> device = Providers.getDevice(world, pos, direction);
|
||||
final DeviceImpl identifiableDevice;
|
||||
|
||||
if (device.isPresent()) {
|
||||
final String typeName = WorldUtils.getBlockName(world, pos);
|
||||
identifiableDevice = new DeviceImpl(device, deviceIds[index], typeName);
|
||||
device.addListener((ignored) -> handleNeighborChanged(pos));
|
||||
} else {
|
||||
identifiableDevice = null;
|
||||
final HashSet<Device> newDevices = new HashSet<>();
|
||||
for (final LazyOptional<Device> device : Providers.getDevices(world, pos, direction)) {
|
||||
device.ifPresent(newDevices::add);
|
||||
device.addListener(unused -> handleNeighborChanged(pos));
|
||||
}
|
||||
|
||||
if (Objects.equals(devices[index], identifiableDevice)) {
|
||||
final HashSet<Device> devicesOnSide = sidedDevices.get(index);
|
||||
if (Objects.equals(newDevices, devicesOnSide)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (devices[index] != null) {
|
||||
busElement.removeDevice(devices[index]);
|
||||
}
|
||||
|
||||
devices[index] = identifiableDevice;
|
||||
|
||||
if (devices[index] != null) {
|
||||
busElement.addDevice(devices[index]);
|
||||
}
|
||||
devices.removeAll(devicesOnSide);
|
||||
devicesOnSide.clear();
|
||||
devicesOnSide.addAll(newDevices);
|
||||
devices.addAll(devicesOnSide);
|
||||
scanDevices();
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
@@ -97,19 +88,7 @@ public final class TileEntityDeviceBusElement {
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
busElement.scheduleScan();
|
||||
}
|
||||
|
||||
public CompoundNBT write(final CompoundNBT compound) {
|
||||
final ListNBT deviceIdsNbt = new ListNBT();
|
||||
for (int i = 0; i < NEIGHBOR_COUNT; i++) {
|
||||
final CompoundNBT deviceIdNbt = new CompoundNBT();
|
||||
deviceIdNbt.putUniqueId(DEVICE_ID_NBT_TAG_NAME, deviceIds[i]);
|
||||
deviceIdsNbt.add(deviceIdNbt);
|
||||
}
|
||||
compound.put(DEVICE_IDS_NBT_TAG_NAME, deviceIdsNbt);
|
||||
|
||||
return compound;
|
||||
scheduleScan();
|
||||
}
|
||||
|
||||
private void scanNeighborsForDevices() {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package li.cil.oc2.common.capabilities;
|
||||
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.bus.DeviceBusController;
|
||||
import li.cil.oc2.api.bus.device.Device;
|
||||
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class DeviceBusControllerCapability {
|
||||
@@ -24,13 +23,13 @@ public final class DeviceBusControllerCapability {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Device> getDevices() {
|
||||
return Collections.emptyList();
|
||||
public Set<Device> getDevices() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Device> getDevice(final UUID uuid) {
|
||||
return Optional.empty();
|
||||
public Set<UUID> getDeviceIdentifiers(final Device device) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,10 @@ package li.cil.oc2.common.capabilities;
|
||||
|
||||
import li.cil.oc2.api.bus.DeviceBusController;
|
||||
import li.cil.oc2.api.bus.DeviceBusElement;
|
||||
import li.cil.oc2.api.bus.device.Device;
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import net.minecraftforge.common.capabilities.CapabilityManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class DeviceBusElementCapability {
|
||||
@@ -34,6 +31,11 @@ public final class DeviceBusElementCapability {
|
||||
return devices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<UUID> getDeviceIdentifier(final Device device) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDevice(final Device device) {
|
||||
devices.add(device);
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
package li.cil.oc2.common.device;
|
||||
|
||||
import li.cil.oc2.api.bus.device.Device;
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.api.bus.device.DeviceMethod;
|
||||
import li.cil.oc2.common.util.LazyOptionalUtils;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
|
||||
public final class DeviceImpl implements Device {
|
||||
private final LazyOptional<DeviceInterface> deviceInterface;
|
||||
private final UUID uuid;
|
||||
@Nullable private final String typeName;
|
||||
|
||||
public DeviceImpl(final LazyOptional<? extends DeviceInterface> deviceInterface, final UUID uuid) {
|
||||
this(deviceInterface, uuid, null);
|
||||
}
|
||||
|
||||
public DeviceImpl(final LazyOptional<? extends DeviceInterface> deviceInterface, final UUID uuid, @Nullable final String typeName) {
|
||||
this.deviceInterface = deviceInterface.cast();
|
||||
this.uuid = uuid;
|
||||
this.typeName = typeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUniqueIdentifier() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceInterface getIdentifiedDevice() {
|
||||
return deviceInterface.orElse(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTypeNames() {
|
||||
if (typeName != null) {
|
||||
final List<String> typeNames = new ArrayList<>(deviceInterface.map(DeviceInterface::getTypeNames).orElse(Collections.emptyList()));
|
||||
typeNames.add(typeName);
|
||||
return typeNames;
|
||||
} else {
|
||||
return deviceInterface.map(DeviceInterface::getTypeNames).orElse(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeviceMethod> getMethods() {
|
||||
return deviceInterface.map(DeviceInterface::getMethods).orElse(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final DeviceImpl that = (DeviceImpl) o;
|
||||
return uuid.equals(that.uuid) &&
|
||||
LazyOptionalUtils.equals(deviceInterface, that.deviceInterface) &&
|
||||
Objects.equals(typeName, that.typeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(uuid, LazyOptionalUtils.hashCode(deviceInterface), typeName);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package li.cil.oc2.common.device;
|
||||
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.api.bus.device.DeviceMethod;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCDevice;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCMethod;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -9,25 +9,25 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class DeviceInterfaceCollection implements DeviceInterface {
|
||||
private final ArrayList<DeviceInterface> deviceInterfaces;
|
||||
public final class RPCDeviceList implements RPCDevice {
|
||||
private final ArrayList<RPCDevice> deviceInterfaces;
|
||||
|
||||
public DeviceInterfaceCollection(final ArrayList<DeviceInterface> deviceInterfaces) {
|
||||
public RPCDeviceList(final ArrayList<RPCDevice> deviceInterfaces) {
|
||||
this.deviceInterfaces = deviceInterfaces;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTypeNames() {
|
||||
return deviceInterfaces.stream()
|
||||
.map(DeviceInterface::getTypeNames)
|
||||
.map(RPCDevice::getTypeNames)
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeviceMethod> getMethods() {
|
||||
public List<RPCMethod> getMethods() {
|
||||
return deviceInterfaces.stream()
|
||||
.map(DeviceInterface::getMethods)
|
||||
.map(RPCDevice::getMethods)
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public final class DeviceInterfaceCollection implements DeviceInterface {
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final DeviceInterfaceCollection that = (DeviceInterfaceCollection) o;
|
||||
final RPCDeviceList that = (RPCDeviceList) o;
|
||||
return deviceInterfaces.equals(that.deviceInterfaces);
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package li.cil.oc2.common.device.provider;
|
||||
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public abstract class AbstractCapabilityAnyTileEntityDeviceInterfaceProvider<TCapability> extends AbstractCapabilityTileEntityDeviceInterfaceProvider<TCapability, TileEntity> {
|
||||
public AbstractCapabilityAnyTileEntityDeviceInterfaceProvider(final Supplier<Capability<TCapability>> capabilitySupplier) {
|
||||
super(TileEntity.class, capabilitySupplier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package li.cil.oc2.common.device.provider;
|
||||
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public abstract class AbstractCapabilityAnyTileEntityDeviceProvider<TCapability> extends AbstractCapabilityTileEntityDeviceProvider<TCapability, TileEntity> {
|
||||
public AbstractCapabilityAnyTileEntityDeviceProvider(final Supplier<Capability<TCapability>> capabilitySupplier) {
|
||||
super(TileEntity.class, capabilitySupplier);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package li.cil.oc2.common.device.provider;
|
||||
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.provider.BlockDeviceQuery;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
@@ -8,16 +8,16 @@ import net.minecraftforge.common.util.LazyOptional;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public abstract class AbstractCapabilityTileEntityDeviceInterfaceProvider<TCapability, TTileEntity extends TileEntity> extends AbstractTileEntityDeviceInterfaceProvider<TTileEntity> {
|
||||
public abstract class AbstractCapabilityTileEntityDeviceProvider<TCapability, TTileEntity extends TileEntity> extends AbstractTileEntityDeviceProvider<TTileEntity> {
|
||||
private final Supplier<Capability<TCapability>> capabilitySupplier;
|
||||
|
||||
protected AbstractCapabilityTileEntityDeviceInterfaceProvider(final Class<TTileEntity> tileEntityType, final Supplier<Capability<TCapability>> capabilitySupplier) {
|
||||
protected AbstractCapabilityTileEntityDeviceProvider(final Class<TTileEntity> tileEntityType, final Supplier<Capability<TCapability>> capabilitySupplier) {
|
||||
super(tileEntityType);
|
||||
this.capabilitySupplier = capabilitySupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final LazyOptional<DeviceInterface> getDeviceInterface(final BlockDeviceQuery blockQuery, final TileEntity tileEntity) {
|
||||
protected final LazyOptional<Device> getDeviceInterface(final BlockDeviceQuery blockQuery, final TileEntity tileEntity) {
|
||||
final Capability<TCapability> capability = capabilitySupplier.get();
|
||||
if (capability == null) throw new IllegalStateException();
|
||||
final LazyOptional<TCapability> optional = tileEntity.getCapability(capability, blockQuery.getQuerySide());
|
||||
@@ -26,10 +26,10 @@ public abstract class AbstractCapabilityTileEntityDeviceInterfaceProvider<TCapab
|
||||
}
|
||||
|
||||
final TCapability value = optional.orElseThrow(AssertionError::new);
|
||||
final LazyOptional<DeviceInterface> device = getDeviceInterface(blockQuery, value);
|
||||
final LazyOptional<Device> device = getDeviceInterface(blockQuery, value);
|
||||
optional.addListener(ignored -> device.invalidate());
|
||||
return device;
|
||||
}
|
||||
|
||||
protected abstract LazyOptional<DeviceInterface> getDeviceInterface(final BlockDeviceQuery query, final TCapability value);
|
||||
protected abstract LazyOptional<Device> getDeviceInterface(final BlockDeviceQuery query, final TCapability value);
|
||||
}
|
||||
@@ -1,28 +1,29 @@
|
||||
package li.cil.oc2.common.device.provider;
|
||||
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.provider.BlockDeviceQuery;
|
||||
import li.cil.oc2.api.provider.DeviceInterfaceProvider;
|
||||
import li.cil.oc2.api.provider.DeviceProvider;
|
||||
import li.cil.oc2.api.provider.DeviceQuery;
|
||||
import li.cil.oc2.common.util.WorldUtils;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
|
||||
public abstract class AbstractTileEntityDeviceInterfaceProvider<T extends TileEntity> implements DeviceInterfaceProvider {
|
||||
public abstract class AbstractTileEntityDeviceProvider<T extends TileEntity> implements DeviceProvider {
|
||||
private final Class<T> tileEntityType;
|
||||
|
||||
protected AbstractTileEntityDeviceInterfaceProvider(final Class<T> tileEntityType) {
|
||||
protected AbstractTileEntityDeviceProvider(final Class<T> tileEntityType) {
|
||||
this.tileEntityType = tileEntityType;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public LazyOptional<DeviceInterface> getDeviceInterface(final DeviceQuery query) {
|
||||
public LazyOptional<Device> getDevice(final DeviceQuery query) {
|
||||
if (!(query instanceof BlockDeviceQuery)) {
|
||||
return LazyOptional.empty();
|
||||
}
|
||||
|
||||
final BlockDeviceQuery blockQuery = (BlockDeviceQuery) query;
|
||||
final TileEntity tileEntity = blockQuery.getWorld().getTileEntity(blockQuery.getQueryPosition());
|
||||
final TileEntity tileEntity = WorldUtils.getTileEntityIfChunkExists(blockQuery.getWorld(), blockQuery.getQueryPosition());
|
||||
if (!tileEntityType.isInstance(tileEntity)) {
|
||||
return LazyOptional.empty();
|
||||
}
|
||||
@@ -30,5 +31,5 @@ public abstract class AbstractTileEntityDeviceInterfaceProvider<T extends TileEn
|
||||
return getDeviceInterface(blockQuery, (T) tileEntity);
|
||||
}
|
||||
|
||||
protected abstract LazyOptional<DeviceInterface> getDeviceInterface(final BlockDeviceQuery query, final T tileEntity);
|
||||
protected abstract LazyOptional<Device> getDeviceInterface(final BlockDeviceQuery query, final T tileEntity);
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
package li.cil.oc2.common.device.provider;
|
||||
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.bus.device.object.Callbacks;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDeviceInterface;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDevice;
|
||||
import li.cil.oc2.api.provider.BlockDeviceQuery;
|
||||
import li.cil.oc2.api.provider.DeviceInterfaceProvider;
|
||||
import li.cil.oc2.api.provider.DeviceProvider;
|
||||
import li.cil.oc2.api.provider.DeviceQuery;
|
||||
import li.cil.oc2.common.util.WorldUtils;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
|
||||
public class BlockDeviceInterfaceProvider implements DeviceInterfaceProvider {
|
||||
public class BlockDeviceProvider implements DeviceProvider {
|
||||
@Override
|
||||
public LazyOptional<DeviceInterface> getDeviceInterface(final DeviceQuery query) {
|
||||
public LazyOptional<Device> getDevice(final DeviceQuery query) {
|
||||
if (!(query instanceof BlockDeviceQuery)) {
|
||||
return LazyOptional.empty();
|
||||
}
|
||||
@@ -30,6 +30,6 @@ public class BlockDeviceInterfaceProvider implements DeviceInterfaceProvider {
|
||||
}
|
||||
|
||||
final String typeName = WorldUtils.getBlockName(blockQuery.getWorld(), blockQuery.getQueryPosition());
|
||||
return LazyOptional.of(() -> new ObjectDeviceInterface(block, typeName));
|
||||
return LazyOptional.of(() -> new ObjectDevice(block, typeName));
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,23 @@
|
||||
package li.cil.oc2.common.device.provider;
|
||||
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.bus.device.object.Callback;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDeviceInterface;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDevice;
|
||||
import li.cil.oc2.api.provider.BlockDeviceQuery;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.energy.IEnergyStorage;
|
||||
|
||||
public class EnergyStorageDeviceInterfaceProvider extends AbstractCapabilityAnyTileEntityDeviceInterfaceProvider<IEnergyStorage> {
|
||||
public class EnergyStorageDeviceProvider extends AbstractCapabilityAnyTileEntityDeviceProvider<IEnergyStorage> {
|
||||
private static final String ENERGY_STORAGE_TYPE_NAME = "energyStorage";
|
||||
|
||||
public EnergyStorageDeviceInterfaceProvider() {
|
||||
public EnergyStorageDeviceProvider() {
|
||||
super(() -> Capabilities.ENERGY_STORAGE_CAPABILITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LazyOptional<DeviceInterface> getDeviceInterface(final BlockDeviceQuery query, final IEnergyStorage value) {
|
||||
return LazyOptional.of(() -> new ObjectDeviceInterface(new EnergyStorageDevice(value), ENERGY_STORAGE_TYPE_NAME));
|
||||
protected LazyOptional<Device> getDeviceInterface(final BlockDeviceQuery query, final IEnergyStorage value) {
|
||||
return LazyOptional.of(() -> new ObjectDevice(new EnergyStorageDevice(value), ENERGY_STORAGE_TYPE_NAME));
|
||||
}
|
||||
|
||||
public static final class EnergyStorageDevice extends AbstractObjectProxy<IEnergyStorage> {
|
||||
@@ -1,24 +1,24 @@
|
||||
package li.cil.oc2.common.device.provider;
|
||||
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.bus.device.object.Callback;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDeviceInterface;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDevice;
|
||||
import li.cil.oc2.api.provider.BlockDeviceQuery;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.fluids.FluidStack;
|
||||
import net.minecraftforge.fluids.capability.IFluidHandler;
|
||||
|
||||
public class FluidHandlerDeviceInterfaceProvider extends AbstractCapabilityAnyTileEntityDeviceInterfaceProvider<IFluidHandler> {
|
||||
public class FluidHandlerDeviceProvider extends AbstractCapabilityAnyTileEntityDeviceProvider<IFluidHandler> {
|
||||
private static final String FLUID_HANDLER_TYPE_NAME = "fluidHandler";
|
||||
|
||||
public FluidHandlerDeviceInterfaceProvider() {
|
||||
public FluidHandlerDeviceProvider() {
|
||||
super(() -> Capabilities.FLUID_HANDLER_CAPABILITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LazyOptional<DeviceInterface> getDeviceInterface(final BlockDeviceQuery query, final IFluidHandler value) {
|
||||
return LazyOptional.of(() -> new ObjectDeviceInterface(new FluidHandlerDevice(value), FLUID_HANDLER_TYPE_NAME));
|
||||
protected LazyOptional<Device> getDeviceInterface(final BlockDeviceQuery query, final IFluidHandler value) {
|
||||
return LazyOptional.of(() -> new ObjectDevice(new FluidHandlerDevice(value), FLUID_HANDLER_TYPE_NAME));
|
||||
}
|
||||
|
||||
public static final class FluidHandlerDevice extends AbstractObjectProxy<IFluidHandler> {
|
||||
@@ -1,24 +1,24 @@
|
||||
package li.cil.oc2.common.device.provider;
|
||||
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.bus.device.object.Callback;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDeviceInterface;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDevice;
|
||||
import li.cil.oc2.api.provider.BlockDeviceQuery;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import net.minecraftforge.items.IItemHandler;
|
||||
|
||||
public class ItemHandlerDeviceInterfaceProvider extends AbstractCapabilityAnyTileEntityDeviceInterfaceProvider<IItemHandler> {
|
||||
public class ItemHandlerDeviceProvider extends AbstractCapabilityAnyTileEntityDeviceProvider<IItemHandler> {
|
||||
private static final String ITEM_HANDLER_TYPE_NAME = "itemHandler";
|
||||
|
||||
public ItemHandlerDeviceInterfaceProvider() {
|
||||
public ItemHandlerDeviceProvider() {
|
||||
super(() -> Capabilities.ITEM_HANDLER_CAPABILITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LazyOptional<DeviceInterface> getDeviceInterface(final BlockDeviceQuery query, final IItemHandler value) {
|
||||
return LazyOptional.of(() -> new ObjectDeviceInterface(new ItemHandlerDevice(value), ITEM_HANDLER_TYPE_NAME));
|
||||
protected LazyOptional<Device> getDeviceInterface(final BlockDeviceQuery query, final IItemHandler value) {
|
||||
return LazyOptional.of(() -> new ObjectDevice(new ItemHandlerDevice(value), ITEM_HANDLER_TYPE_NAME));
|
||||
}
|
||||
|
||||
public static final class ItemHandlerDevice extends AbstractObjectProxy<IItemHandler> {
|
||||
@@ -1,10 +1,9 @@
|
||||
package li.cil.oc2.common.device.provider;
|
||||
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.api.provider.DeviceInterfaceProvider;
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.provider.DeviceProvider;
|
||||
import li.cil.oc2.api.provider.DeviceQuery;
|
||||
import li.cil.oc2.common.device.BlockDeviceQueryImpl;
|
||||
import li.cil.oc2.common.device.DeviceInterfaceCollection;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.Direction;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
@@ -12,56 +11,46 @@ import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class Providers {
|
||||
private static final ArrayList<DeviceInterfaceProvider> DEVICE_PROVIDERS = new ArrayList<>();
|
||||
private static final ArrayList<DeviceProvider> DEVICE_PROVIDERS = new ArrayList<>();
|
||||
|
||||
public static void initialize() {
|
||||
addProvider(new EnergyStorageDeviceInterfaceProvider());
|
||||
addProvider(new FluidHandlerDeviceInterfaceProvider());
|
||||
addProvider(new ItemHandlerDeviceInterfaceProvider());
|
||||
addProvider(new TileEntityDeviceInterfaceProvider());
|
||||
addProvider(new BlockDeviceInterfaceProvider());
|
||||
addProvider(new EnergyStorageDeviceProvider());
|
||||
addProvider(new FluidHandlerDeviceProvider());
|
||||
addProvider(new ItemHandlerDeviceProvider());
|
||||
addProvider(new TileEntityDeviceProvider());
|
||||
addProvider(new BlockDeviceProvider());
|
||||
}
|
||||
|
||||
public static void addProvider(final DeviceInterfaceProvider provider) {
|
||||
public static void addProvider(final DeviceProvider provider) {
|
||||
if (!DEVICE_PROVIDERS.contains(provider)) {
|
||||
DEVICE_PROVIDERS.add(provider);
|
||||
}
|
||||
}
|
||||
|
||||
public static LazyOptional<DeviceInterfaceCollection> getDevice(final TileEntity tileEntity, final Direction side) {
|
||||
public static List<LazyOptional<Device>> getDevices(final TileEntity tileEntity, final Direction side) {
|
||||
final World world = tileEntity.getWorld();
|
||||
final BlockPos pos = tileEntity.getPos();
|
||||
|
||||
if (world == null) throw new IllegalArgumentException();
|
||||
|
||||
return getDevice(world, pos, side);
|
||||
return getDevices(world, pos, side);
|
||||
}
|
||||
|
||||
public static LazyOptional<DeviceInterfaceCollection> getDevice(final World world, final BlockPos pos, final Direction side) {
|
||||
return getDevice(new BlockDeviceQueryImpl(world, pos, side));
|
||||
public static List<LazyOptional<Device>> getDevices(final World world, final BlockPos pos, final Direction side) {
|
||||
return getDevices(new BlockDeviceQueryImpl(world, pos, side));
|
||||
}
|
||||
|
||||
public static LazyOptional<DeviceInterfaceCollection> getDevice(final DeviceQuery query) {
|
||||
final ArrayList<DeviceInterface> deviceInterfaces = new ArrayList<>();
|
||||
final ArrayList<LazyOptional<DeviceInterface>> optionals = new ArrayList<>();
|
||||
for (final DeviceInterfaceProvider provider : DEVICE_PROVIDERS) {
|
||||
final LazyOptional<DeviceInterface> optional = provider.getDeviceInterface(query);
|
||||
optional.ifPresent((device) -> {
|
||||
deviceInterfaces.add(device);
|
||||
optionals.add(optional);
|
||||
});
|
||||
}
|
||||
|
||||
if (deviceInterfaces.isEmpty()) {
|
||||
return LazyOptional.empty();
|
||||
} else {
|
||||
final LazyOptional<DeviceInterfaceCollection> compoundOptional = LazyOptional.of(() -> new DeviceInterfaceCollection(deviceInterfaces));
|
||||
for (final LazyOptional<DeviceInterface> optional : optionals) {
|
||||
optional.addListener((ignored) -> compoundOptional.invalidate());
|
||||
public static List<LazyOptional<Device>> getDevices(final DeviceQuery query) {
|
||||
final ArrayList<LazyOptional<Device>> devices = new ArrayList<>();
|
||||
for (final DeviceProvider provider : DEVICE_PROVIDERS) {
|
||||
final LazyOptional<Device> device = provider.getDevice(query);
|
||||
if (device.isPresent()) {
|
||||
devices.add(device);
|
||||
}
|
||||
return compoundOptional;
|
||||
}
|
||||
return devices;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
package li.cil.oc2.common.device.provider;
|
||||
|
||||
import li.cil.oc2.api.bus.device.DeviceInterface;
|
||||
import li.cil.oc2.api.bus.Device;
|
||||
import li.cil.oc2.api.bus.device.object.Callbacks;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDeviceInterface;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDevice;
|
||||
import li.cil.oc2.api.provider.BlockDeviceQuery;
|
||||
import li.cil.oc2.common.util.WorldUtils;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
|
||||
public final class TileEntityDeviceInterfaceProvider extends AbstractTileEntityDeviceInterfaceProvider<TileEntity> {
|
||||
public TileEntityDeviceInterfaceProvider() {
|
||||
public final class TileEntityDeviceProvider extends AbstractTileEntityDeviceProvider<TileEntity> {
|
||||
public TileEntityDeviceProvider() {
|
||||
super(TileEntity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyOptional<DeviceInterface> getDeviceInterface(final BlockDeviceQuery query, final TileEntity tileEntity) {
|
||||
public LazyOptional<Device> getDeviceInterface(final BlockDeviceQuery query, final TileEntity tileEntity) {
|
||||
if (Callbacks.hasMethods(tileEntity)) {
|
||||
return LazyOptional.of(() -> {
|
||||
final String typeName = WorldUtils.getBlockName(query.getWorld(), query.getQueryPosition());
|
||||
return new ObjectDeviceInterface(tileEntity, typeName);
|
||||
return new ObjectDevice(tileEntity, typeName);
|
||||
});
|
||||
} else {
|
||||
return LazyOptional.empty();
|
||||
@@ -16,7 +16,7 @@ public class BusCableTileEntity extends AbstractTileEntity {
|
||||
super(OpenComputers.BUS_CABLE_TILE_ENTITY.get());
|
||||
|
||||
busElement = new TileEntityDeviceBusElement(this);
|
||||
setCapabilityIfAbsent(Capabilities.DEVICE_BUS_ELEMENT_CAPABILITY, busElement.getBusElement());
|
||||
setCapabilityIfAbsent(Capabilities.DEVICE_BUS_ELEMENT_CAPABILITY, busElement);
|
||||
}
|
||||
|
||||
public void handleNeighborChanged(final BlockPos pos) {
|
||||
|
||||
@@ -84,14 +84,14 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
super(OpenComputers.COMPUTER_TILE_ENTITY.get());
|
||||
|
||||
busElement = new TileEntityDeviceBusElement(this);
|
||||
busController = new TileEntityDeviceBusController(this, this::joinVirtualMachine);
|
||||
busController = new BusController();
|
||||
busState = TileEntityDeviceBusController.State.SCAN_PENDING;
|
||||
runState = RunState.STOPPED;
|
||||
|
||||
terminal = new Terminal();
|
||||
virtualMachine = new VirtualMachine(busController);
|
||||
|
||||
setCapabilityIfAbsent(Capabilities.DEVICE_BUS_ELEMENT_CAPABILITY, busElement.getBusElement());
|
||||
setCapabilityIfAbsent(Capabilities.DEVICE_BUS_ELEMENT_CAPABILITY, busElement);
|
||||
setCapabilityIfAbsent(Capabilities.DEVICE_BUS_CONTROLLER_CAPABILITY, busController);
|
||||
}
|
||||
|
||||
@@ -412,6 +412,22 @@ public final class ComputerTileEntity extends AbstractTileEntity implements ITic
|
||||
}
|
||||
}
|
||||
|
||||
private class BusController extends TileEntityDeviceBusController {
|
||||
private BusController() {
|
||||
super(ComputerTileEntity.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDevicesInvalid() {
|
||||
virtualMachine.rpcAdapter.pause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDevicesValid() {
|
||||
virtualMachine.rpcAdapter.resume();
|
||||
}
|
||||
}
|
||||
|
||||
private final class ConsoleRunner extends VirtualMachineRunner {
|
||||
// Thread-local buffers for lock-free read/writes in inner loop.
|
||||
private final ByteArrayFIFOQueue outputBuffer = new ByteArrayFIFOQueue(1024);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package li.cil.oc2.common.vm;
|
||||
|
||||
import li.cil.oc2.api.vm.InterruptAllocator;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
public final class InterruptAllocatorImpl implements InterruptAllocator {
|
||||
private final BitSet interrupts;
|
||||
private final int interruptCount;
|
||||
private boolean isValid = true;
|
||||
|
||||
public InterruptAllocatorImpl(final BitSet interrupts, final int interruptCount) {
|
||||
this.interrupts = new BitSet(interruptCount);
|
||||
this.interrupts.or(interrupts);
|
||||
this.interruptCount = interruptCount;
|
||||
}
|
||||
|
||||
public BitSet complete() {
|
||||
isValid = false;
|
||||
return interrupts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalInt claimInterrupt(final int interrupt) {
|
||||
if (!isValid) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
if (interrupts.get(interrupt)) {
|
||||
return claimInterrupt();
|
||||
} else {
|
||||
interrupts.set(interrupt);
|
||||
return OptionalInt.of(interrupt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalInt claimInterrupt() {
|
||||
if (!isValid) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
final int interrupt = interrupts.nextClearBit(0);
|
||||
if (interrupt >= interruptCount) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
interrupts.set(interrupt);
|
||||
return OptionalInt.of(interrupt);
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package li.cil.oc2.serialization.serializers;
|
||||
|
||||
import com.google.gson.*;
|
||||
import li.cil.oc2.api.bus.device.Device;
|
||||
import li.cil.oc2.api.bus.device.DeviceMethod;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public final class DeviceJsonSerializer implements JsonSerializer<Device> {
|
||||
@Override
|
||||
public JsonElement serialize(final Device src, final Type typeOfSrc, final JsonSerializationContext context) {
|
||||
if (src == null) {
|
||||
return JsonNull.INSTANCE;
|
||||
}
|
||||
|
||||
final JsonObject deviceJson = new JsonObject();
|
||||
deviceJson.add("deviceId", context.serialize(src.getUniqueIdentifier()));
|
||||
deviceJson.add("typeNames", context.serialize(src.getTypeNames()));
|
||||
|
||||
final JsonArray methodsJson = new JsonArray();
|
||||
deviceJson.add("methods", methodsJson);
|
||||
for (final DeviceMethod method : src.getMethods()) {
|
||||
methodsJson.add(context.serialize(method, DeviceMethod.class));
|
||||
}
|
||||
|
||||
return deviceJson;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package li.cil.oc2.serialization.serializers;
|
||||
|
||||
import com.google.gson.*;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCMethod;
|
||||
import li.cil.oc2.common.bus.RPCAdapter;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public final class RPCDeviceWithIdentifierJsonSerializer implements JsonSerializer<RPCAdapter.RPCDeviceWithIdentifier> {
|
||||
@Override
|
||||
public JsonElement serialize(final RPCAdapter.RPCDeviceWithIdentifier src, final Type typeOfSrc, final JsonSerializationContext context) {
|
||||
if (src == null) {
|
||||
return JsonNull.INSTANCE;
|
||||
}
|
||||
|
||||
final JsonObject deviceJson = new JsonObject();
|
||||
deviceJson.add("deviceId", context.serialize(src.identifier));
|
||||
deviceJson.add("typeNames", context.serialize(src.device.getTypeNames()));
|
||||
|
||||
final JsonArray methodsJson = new JsonArray();
|
||||
deviceJson.add("methods", methodsJson);
|
||||
for (final RPCMethod method : src.device.getMethods()) {
|
||||
methodsJson.add(context.serialize(method, RPCMethod.class));
|
||||
}
|
||||
|
||||
return deviceJson;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
package li.cil.oc2.serialization.serializers;
|
||||
|
||||
import com.google.gson.*;
|
||||
import li.cil.oc2.api.bus.device.DeviceMethod;
|
||||
import li.cil.oc2.api.bus.device.DeviceMethodParameter;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCMethod;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCParameter;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
public final class DeviceMethodJsonSerializer implements JsonSerializer<DeviceMethod> {
|
||||
public final class RPCMethodJsonSerializer implements JsonSerializer<RPCMethod> {
|
||||
@Override
|
||||
public JsonElement serialize(final DeviceMethod method, final Type typeOfMethod, final JsonSerializationContext context) {
|
||||
public JsonElement serialize(final RPCMethod method, final Type typeOfMethod, final JsonSerializationContext context) {
|
||||
if (method == null) {
|
||||
return JsonNull.INSTANCE;
|
||||
}
|
||||
@@ -23,8 +23,8 @@ public final class DeviceMethodJsonSerializer implements JsonSerializer<DeviceMe
|
||||
final JsonArray parametersJson = new JsonArray();
|
||||
methodJson.add("parameters", parametersJson);
|
||||
|
||||
final DeviceMethodParameter[] parameters = method.getParameters();
|
||||
for (final DeviceMethodParameter parameter : parameters) {
|
||||
final RPCParameter[] parameters = method.getParameters();
|
||||
for (final RPCParameter parameter : parameters) {
|
||||
final JsonObject parameterJson = new JsonObject();
|
||||
|
||||
parameter.getName().ifPresent(s -> parameterJson.addProperty("name", s));
|
||||
@@ -1,7 +1,7 @@
|
||||
package li.cil.oc2.bus;
|
||||
|
||||
import li.cil.oc2.api.bus.DeviceBusElement;
|
||||
import li.cil.oc2.api.bus.device.Device;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCDevice;
|
||||
import li.cil.oc2.common.bus.TileEntityDeviceBusController;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
@@ -16,7 +16,6 @@ import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
@@ -50,7 +49,7 @@ public class DeviceBusTests {
|
||||
.thenReturn(LazyOptional.of(() -> busControllerBusElement));
|
||||
when(busControllerBusElement.getLocalDevices()).thenReturn(Collections.emptyList());
|
||||
|
||||
busController = new TileEntityDeviceBusController(busControllerTileEntity);
|
||||
busController = new TestBusController();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -68,11 +67,9 @@ public class DeviceBusTests {
|
||||
public void scanSuccessfulWithLocalElement() {
|
||||
when(world.chunkExists(anyInt(), anyInt())).thenReturn(true);
|
||||
|
||||
final Device device = mock(Device.class);
|
||||
final RPCDevice device = mock(RPCDevice.class);
|
||||
when(busControllerBusElement.getLocalDevices()).thenReturn(Collections.singletonList(device));
|
||||
|
||||
when(device.getUniqueIdentifier()).thenReturn(UUID.randomUUID());
|
||||
|
||||
Assertions.assertEquals(TileEntityDeviceBusController.State.READY, busController.scan());
|
||||
|
||||
verify(busControllerBusElement).addController(busController);
|
||||
@@ -103,4 +100,10 @@ public class DeviceBusTests {
|
||||
|
||||
return busElement;
|
||||
}
|
||||
|
||||
private final class TestBusController extends TileEntityDeviceBusController {
|
||||
public TestBusController() {
|
||||
super(busControllerTileEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package li.cil.oc2.vm;
|
||||
|
||||
import li.cil.oc2.api.bus.device.DeviceMethod;
|
||||
import li.cil.oc2.api.bus.device.DeviceMethodParameter;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCMethod;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCParameter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Optional;
|
||||
|
||||
abstract class AbstractTestMethod implements DeviceMethod {
|
||||
abstract class AbstractTestMethod implements RPCMethod {
|
||||
private final Class<?> returnType;
|
||||
private final DeviceMethodParameter[] parameters;
|
||||
private final RPCParameter[] parameters;
|
||||
|
||||
protected AbstractTestMethod(final Class<?> returnType, final Class<?>... parameterTypes) {
|
||||
this.returnType = returnType;
|
||||
parameters = new DeviceMethodParameter[parameterTypes.length];
|
||||
parameters = new RPCParameter[parameterTypes.length];
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
parameters[i] = new TestParameter("arg" + i, parameterTypes[i]);
|
||||
}
|
||||
@@ -34,11 +34,11 @@ abstract class AbstractTestMethod implements DeviceMethod {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceMethodParameter[] getParameters() {
|
||||
public RPCParameter[] getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private static final class TestParameter implements DeviceMethodParameter {
|
||||
private static final class TestParameter implements RPCParameter {
|
||||
@Nullable private final String name;
|
||||
private final Class<?> type;
|
||||
|
||||
|
||||
@@ -3,15 +3,13 @@ package li.cil.oc2.vm;
|
||||
import com.google.gson.*;
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue;
|
||||
import li.cil.oc2.api.bus.DeviceBusController;
|
||||
import li.cil.oc2.api.bus.device.Device;
|
||||
import li.cil.oc2.api.bus.device.DeviceMethod;
|
||||
import li.cil.oc2.api.bus.device.object.Callback;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDeviceInterface;
|
||||
import li.cil.oc2.api.bus.device.object.ObjectDevice;
|
||||
import li.cil.oc2.api.bus.device.object.Parameter;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCDevice;
|
||||
import li.cil.oc2.api.bus.device.rpc.RPCMethod;
|
||||
import li.cil.oc2.common.bus.RPCAdapter;
|
||||
import li.cil.oc2.common.device.DeviceImpl;
|
||||
import li.cil.sedna.api.device.serial.SerialDevice;
|
||||
import net.minecraftforge.common.util.LazyOptional;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -20,13 +18,14 @@ import javax.annotation.Nullable;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class RPCAdapterTests {
|
||||
private static final UUID DEVICE_UUID = java.util.UUID.randomUUID();
|
||||
|
||||
private TestSerialDevice serialDevice;
|
||||
private DeviceBusController busController;
|
||||
private RPCAdapter rpcAdapter;
|
||||
@@ -41,8 +40,8 @@ public class RPCAdapterTests {
|
||||
@Test
|
||||
public void resetAndReadDescriptor() {
|
||||
final VoidIntMethod method = new VoidIntMethod();
|
||||
final TestDeviceInterface device = new TestDeviceInterface(method);
|
||||
setDevice(device);
|
||||
final TestRPCDeviceInterface device = new TestRPCDeviceInterface(method);
|
||||
setDevice(device, DEVICE_UUID);
|
||||
|
||||
final JsonObject request = new JsonObject();
|
||||
request.addProperty("type", "status");
|
||||
@@ -66,10 +65,10 @@ public class RPCAdapterTests {
|
||||
@Test
|
||||
public void simpleMethod() {
|
||||
final VoidIntMethod method = new VoidIntMethod();
|
||||
final TestDeviceInterface device = new TestDeviceInterface(method);
|
||||
setDevice(device);
|
||||
final TestRPCDeviceInterface device = new TestRPCDeviceInterface(method);
|
||||
setDevice(device, DEVICE_UUID);
|
||||
|
||||
invokeMethod(device, method.getName(), 0xdeadbeef);
|
||||
invokeMethod(DEVICE_UUID, method.getName(), 0xdeadbeef);
|
||||
|
||||
Assertions.assertEquals(0xdeadbeef, method.passedValue);
|
||||
}
|
||||
@@ -77,10 +76,10 @@ public class RPCAdapterTests {
|
||||
@Test
|
||||
public void returningMethod() {
|
||||
final IntLongMethod method = new IntLongMethod();
|
||||
final TestDeviceInterface device = new TestDeviceInterface(method);
|
||||
setDevice(device);
|
||||
final TestRPCDeviceInterface device = new TestRPCDeviceInterface(method);
|
||||
setDevice(device, DEVICE_UUID);
|
||||
|
||||
final JsonElement result = invokeMethod(device, method.getName(), 0xdeadbeefcafebabeL);
|
||||
final JsonElement result = invokeMethod(DEVICE_UUID, method.getName(), 0xdeadbeefcafebabeL);
|
||||
Assertions.assertNotNull(result);
|
||||
Assertions.assertTrue(result.isJsonPrimitive());
|
||||
Assertions.assertEquals(0xcafebabe, result.getAsInt());
|
||||
@@ -89,23 +88,26 @@ public class RPCAdapterTests {
|
||||
@Test
|
||||
public void annotatedObject() {
|
||||
final SimpleObject object = new SimpleObject();
|
||||
final ObjectDeviceInterface device = new ObjectDeviceInterface(object);
|
||||
final DeviceImpl identifiableDevice = new DeviceImpl(LazyOptional.of(() -> device), UUID.randomUUID());
|
||||
setDevice(identifiableDevice);
|
||||
final ObjectDevice device = new ObjectDevice(object);
|
||||
setDevice(device, DEVICE_UUID);
|
||||
|
||||
Assertions.assertEquals(42 + 23, invokeMethod(identifiableDevice, "add", 42, 23).getAsInt());
|
||||
Assertions.assertEquals(42 + 23, invokeMethod(DEVICE_UUID, "add", 42, 23).getAsInt());
|
||||
}
|
||||
|
||||
private void setDevice(final Device device) {
|
||||
when(busController.getDevices()).thenReturn(Collections.singletonList(device));
|
||||
when(busController.getDevice(device.getUniqueIdentifier())).thenReturn(Optional.of(device));
|
||||
private void setDevice(final RPCDevice device, final UUID deviceId) {
|
||||
when(busController.getDevices()).thenReturn(Collections.singleton(device));
|
||||
when(busController.getDeviceIdentifiers(device)).thenReturn(Collections.singleton(deviceId));
|
||||
|
||||
// trigger device cache rebuild
|
||||
rpcAdapter.pause();
|
||||
rpcAdapter.resume();
|
||||
}
|
||||
|
||||
private JsonElement invokeMethod(final Device device, final String name, final Object... parameters) {
|
||||
private JsonElement invokeMethod(final UUID deviceId, final String name, final Object... parameters) {
|
||||
final JsonObject request = new JsonObject();
|
||||
request.addProperty("type", "invoke");
|
||||
final JsonObject methodInvocation = new JsonObject();
|
||||
methodInvocation.addProperty("deviceId", device.getUniqueIdentifier().toString());
|
||||
methodInvocation.addProperty("deviceId", deviceId.toString());
|
||||
methodInvocation.addProperty("name", name);
|
||||
final JsonArray parametersJson = new JsonArray();
|
||||
methodInvocation.add("parameters", parametersJson);
|
||||
@@ -196,7 +198,7 @@ public class RPCAdapterTests {
|
||||
}
|
||||
|
||||
if (bytes.size() > 0) {
|
||||
return new String(bytes.toByteArray());
|
||||
return bytes.toString();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -218,12 +220,10 @@ public class RPCAdapterTests {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestDeviceInterface implements Device {
|
||||
private static final UUID UUID = java.util.UUID.randomUUID();
|
||||
private static final class TestRPCDeviceInterface implements RPCDevice {
|
||||
private final RPCMethod method;
|
||||
|
||||
private final DeviceMethod method;
|
||||
|
||||
public TestDeviceInterface(final DeviceMethod method) {
|
||||
public TestRPCDeviceInterface(final RPCMethod method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
@@ -233,13 +233,8 @@ public class RPCAdapterTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeviceMethod> getMethods() {
|
||||
public List<RPCMethod> getMethods() {
|
||||
return Collections.singletonList(method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUniqueIdentifier() {
|
||||
return UUID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user