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:
Florian Nücke
2020-12-04 23:44:00 +01:00
parent c777fe3e3a
commit 59b92be93f
50 changed files with 803 additions and 666 deletions

View File

@@ -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";

View 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 {
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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);
}
}
}
}

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}

View File

@@ -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.

View File

@@ -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.
*

View File

@@ -0,0 +1,7 @@
@ParametersAreNonnullByDefault
@MethodsReturnNonnullByDefault
package li.cil.oc2.api.bus.device.rpc;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -0,0 +1,9 @@
package li.cil.oc2.api.vm;
import java.util.OptionalInt;
public interface InterruptAllocator {
OptionalInt claimInterrupt(int interrupt);
OptionalInt claimInterrupt();
}

View File

@@ -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();
}

View 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();
}

View File

@@ -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) {

View File

@@ -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();
}
}
}

View File

@@ -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";

View File

@@ -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;

View File

@@ -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() {

View File

@@ -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();
}
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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));
}
}

View File

@@ -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> {

View File

@@ -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> {

View File

@@ -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> {

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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));

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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;
}
}
}