Add logic to allow synchronizing methods to main thread.
This commit is contained in:
@@ -5,17 +5,27 @@ package li.cil.oc2.api.device;
|
||||
*/
|
||||
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 Class<?> returnType, 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, void.class, parameters);
|
||||
this(name, false, void.class, parameters);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -23,6 +33,11 @@ public abstract class AbstractDeviceMethod implements DeviceMethod {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSynchronized() {
|
||||
return synchronize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getReturnType() {
|
||||
return returnType;
|
||||
|
||||
@@ -24,6 +24,11 @@ public interface DeviceMethod {
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* When {@code true}, invocations of this method will be synchronized to the main thread.
|
||||
*/
|
||||
boolean isSynchronized();
|
||||
|
||||
/**
|
||||
* The type of the values returned by this method.
|
||||
*/
|
||||
|
||||
@@ -13,12 +13,22 @@ import java.lang.annotation.Target;
|
||||
* <p>
|
||||
* Intended to be used in classes instances of which are used in combination with
|
||||
* {@link ObjectDevice} and subclasses of {@link ObjectDevice}.
|
||||
* <p>
|
||||
* For
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Callback {
|
||||
/**
|
||||
* Allows automatically moving method invocation into the main thread.
|
||||
* <p>
|
||||
* Note that this will lead to dramatically slower method calls as viewed from
|
||||
* the caller as each call will take at least one tick (50ms).
|
||||
* <p>
|
||||
* Use this when the targeted method interacts with data that is not thread
|
||||
* safe, for example the world or any objects inside the world, such as
|
||||
* tile entities and entities.
|
||||
*/
|
||||
boolean synchronize() default false;
|
||||
|
||||
/**
|
||||
* Option VM visible documentation of this method.
|
||||
*/
|
||||
|
||||
@@ -23,13 +23,12 @@ public final class ObjectDeviceMethod extends AbstractDeviceMethod {
|
||||
private final String returnValueDescription;
|
||||
|
||||
public ObjectDeviceMethod(final Object target, final Method method) throws IllegalAccessException {
|
||||
super(method.getName(), method.getReturnType(), getParameters(method));
|
||||
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);
|
||||
if (annotation == null) {
|
||||
throw new IllegalArgumentException("Method without Callback annotation.");
|
||||
}
|
||||
|
||||
this.handle = MethodHandles.lookup().unreflect(method).bindTo(target);
|
||||
this.description = Strings.isNotBlank(annotation.description()) ? annotation.description() : null;
|
||||
this.returnValueDescription = Strings.isNotBlank(annotation.returnValueDescription()) ? annotation.returnValueDescription() : null;
|
||||
|
||||
@@ -59,6 +59,7 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable {
|
||||
|
||||
@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
|
||||
|
||||
public DeviceBusControllerImpl(final SerialDevice serialDevice) {
|
||||
this(serialDevice, DEFAULT_MAX_MESSAGE_SIZE);
|
||||
@@ -189,6 +190,14 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable {
|
||||
return State.READY;
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (synchronizedInvocation != null) {
|
||||
final MethodInvocation methodInvocation = synchronizedInvocation;
|
||||
synchronizedInvocation = null;
|
||||
processMethodInvocation(methodInvocation, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void step(final int cycles) {
|
||||
readFromDevice();
|
||||
writeToDevice();
|
||||
@@ -200,7 +209,7 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable {
|
||||
// method of limiting the write queue size would work, but this is
|
||||
// the most simple and easy to maintain one I could think of.
|
||||
int value;
|
||||
while (receiveBuffer == null && (value = serialDevice.read()) >= 0) {
|
||||
while (receiveBuffer == null && synchronizedInvocation == null && (value = serialDevice.read()) >= 0) {
|
||||
if (value == 0) {
|
||||
if (transmitBuffer.limit() > 0) {
|
||||
transmitBuffer.flip();
|
||||
@@ -253,7 +262,7 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable {
|
||||
}
|
||||
case MESSAGE_TYPE_INVOKE_METHOD: {
|
||||
assert message.data != null : "MethodInvocation deserializer produced null data.";
|
||||
processMethodInvocation((MethodInvocation) message.data);
|
||||
processMethodInvocation((MethodInvocation) message.data, false);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
@@ -266,7 +275,7 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable {
|
||||
}
|
||||
}
|
||||
|
||||
private void processMethodInvocation(final MethodInvocation methodInvocation) {
|
||||
private void processMethodInvocation(final MethodInvocation methodInvocation, final boolean isMainThread) {
|
||||
final Device device = devices.get(methodInvocation.deviceId);
|
||||
if (device == null) {
|
||||
writeError(ERROR_UNKNOWN_DEVICE);
|
||||
@@ -301,6 +310,11 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable {
|
||||
}
|
||||
}
|
||||
|
||||
if (method.isSynchronized() && !isMainThread) {
|
||||
synchronizedInvocation = methodInvocation;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final Object result = method.invoke(parameters);
|
||||
writeMessage(MESSAGE_TYPE_RESULT, result);
|
||||
@@ -377,10 +391,11 @@ public class DeviceBusControllerImpl implements DeviceBusController, Steppable {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MethodInvocation {
|
||||
public final UUID deviceId;
|
||||
public final String methodName;
|
||||
public final JsonArray parameters;
|
||||
@Serialized
|
||||
public static final class MethodInvocation {
|
||||
public UUID deviceId;
|
||||
public String methodName;
|
||||
public JsonArray parameters;
|
||||
|
||||
public MethodInvocation(final UUID deviceId, final String methodName, final JsonArray parameters) {
|
||||
this.deviceId = deviceId;
|
||||
|
||||
@@ -3,6 +3,7 @@ package li.cil.oc2.vm;
|
||||
import li.cil.oc2.api.device.DeviceMethod;
|
||||
import li.cil.oc2.api.device.DeviceMethodParameter;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Optional;
|
||||
|
||||
abstract class AbstractTestMethod implements DeviceMethod {
|
||||
@@ -22,6 +23,11 @@ abstract class AbstractTestMethod implements DeviceMethod {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSynchronized() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getReturnType() {
|
||||
return returnType;
|
||||
@@ -33,10 +39,10 @@ abstract class AbstractTestMethod implements DeviceMethod {
|
||||
}
|
||||
|
||||
private static final class TestParameter implements DeviceMethodParameter {
|
||||
private final String name;
|
||||
@Nullable private final String name;
|
||||
private final Class<?> type;
|
||||
|
||||
public TestParameter(final String name, final Class<?> type) {
|
||||
public TestParameter(@Nullable final String name, final Class<?> type) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user