Improve async command approach - use coroutines correctly
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
package io.dico.dicore.command;
|
||||
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public interface ICommandReceiver {
|
||||
|
||||
interface Factory {
|
||||
|
||||
ICommandReceiver getReceiver(ExecutionContext context, Method target, String cmdName);
|
||||
|
||||
Plugin getPlugin();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.dico.dicore.command;
|
||||
|
||||
public interface ICommandSuspendReceiver extends ICommandReceiver {
|
||||
|
||||
int getTimeout();
|
||||
|
||||
}
|
||||
@@ -10,7 +10,8 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
final class ReflectiveCommand extends Command {
|
||||
public final class ReflectiveCommand extends Command {
|
||||
private final Cmd cmdAnnotation;
|
||||
private final Method method;
|
||||
private final Object instance;
|
||||
private String[] parameterOrder;
|
||||
@@ -20,6 +21,7 @@ final class ReflectiveCommand extends Command {
|
||||
if (!method.isAnnotationPresent(Cmd.class)) {
|
||||
throw new CommandParseException("No @Cmd present for the method " + method.toGenericString());
|
||||
}
|
||||
cmdAnnotation = method.getAnnotation(Cmd.class);
|
||||
|
||||
java.lang.reflect.Parameter[] parameters = method.getParameters();
|
||||
|
||||
@@ -46,6 +48,14 @@ final class ReflectiveCommand extends Command {
|
||||
this.flags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public Object getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
void setParameterOrder(String[] parameterOrder) {
|
||||
this.parameterOrder = parameterOrder;
|
||||
}
|
||||
@@ -54,7 +64,7 @@ final class ReflectiveCommand extends Command {
|
||||
ChildCommandAddress result = new ChildCommandAddress();
|
||||
result.setCommand(this);
|
||||
|
||||
Cmd cmd = method.getAnnotation(Cmd.class);
|
||||
Cmd cmd = cmdAnnotation;
|
||||
result.getNames().add(cmd.value());
|
||||
for (String alias : cmd.aliases()) {
|
||||
result.getNames().add(alias);
|
||||
@@ -71,54 +81,86 @@ final class ReflectiveCommand extends Command {
|
||||
|
||||
@Override
|
||||
public String execute(CommandSender sender, ExecutionContext context) throws CommandException {
|
||||
//System.out.println("In ReflectiveCommand.execute()");
|
||||
|
||||
String[] parameterOrder = this.parameterOrder;
|
||||
int start = Integer.bitCount(flags);
|
||||
//System.out.println("start = " + start);
|
||||
Object[] args = new Object[parameterOrder.length + start];
|
||||
|
||||
int i = 0;
|
||||
if ((flags & 1) != 0) {
|
||||
args[i++] = sender;
|
||||
try {
|
||||
args[i++] = ((ICommandReceiver.Factory) instance).getReceiver(context, method, cmdAnnotation.value());
|
||||
} catch (Exception ex) {
|
||||
handleException(ex);
|
||||
return null; // unreachable
|
||||
}
|
||||
}
|
||||
if ((flags & 2) != 0) {
|
||||
args[i++] = sender;
|
||||
}
|
||||
if ((flags & 4) != 0) {
|
||||
args[i++] = context;
|
||||
}
|
||||
//System.out.println("i = " + i);
|
||||
//System.out.println("parameterOrder = " + Arrays.toString(parameterOrder));
|
||||
|
||||
for (int n = args.length; i < n; i++) {
|
||||
//System.out.println("n = " + n);
|
||||
args[i] = context.get(parameterOrder[i - start]);
|
||||
//System.out.println("context.get(parameterOrder[i - start]) = " + context.get(parameterOrder[i - start]));
|
||||
//System.out.println("context.get(parameterOrder[i - start]).getClass() = " + context.get(parameterOrder[i - start]).getClass());
|
||||
}
|
||||
|
||||
//System.out.println("args = " + Arrays.toString(args));
|
||||
if (!isSuspendFunction()) {
|
||||
return callSynchronously(args);
|
||||
}
|
||||
|
||||
Object result;
|
||||
return callAsCoroutine(context, args);
|
||||
}
|
||||
|
||||
private boolean isSuspendFunction() {
|
||||
try {
|
||||
result = method.invoke(instance, args);
|
||||
} catch (InvocationTargetException ex) {
|
||||
return KotlinReflectiveRegistrationKt.isSuspendFunction(method);
|
||||
} catch (Throwable ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String callSynchronously(Object[] args) throws CommandException {
|
||||
try {
|
||||
return getResult(method.invoke(instance, args), null);
|
||||
} catch (Exception ex) {
|
||||
return getResult(null, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getResult(Object returned, Exception ex) throws CommandException {
|
||||
if (ex != null) {
|
||||
handleException(ex);
|
||||
return null; // unreachable
|
||||
}
|
||||
|
||||
if (returned instanceof String) {
|
||||
return (String) returned;
|
||||
}
|
||||
if (returned instanceof CommandResult) {
|
||||
return ((CommandResult) returned).getMessage();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void handleException(Exception ex) throws CommandException {
|
||||
if (ex instanceof InvocationTargetException) {
|
||||
if (ex.getCause() instanceof CommandException) {
|
||||
throw (CommandException) ex.getCause();
|
||||
}
|
||||
|
||||
ex.printStackTrace();
|
||||
throw new CommandException("An internal error occurred while executing this command.", ex);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
throw new CommandException("An internal error occurred while executing this command.", ex);
|
||||
}
|
||||
if (ex instanceof CommandException) {
|
||||
throw (CommandException) ex;
|
||||
}
|
||||
ex.printStackTrace();
|
||||
throw new CommandException("An internal error occurred while executing this command.", ex);
|
||||
}
|
||||
|
||||
if (result instanceof String) {
|
||||
return (String) result;
|
||||
}
|
||||
if (result instanceof CommandResult) {
|
||||
return ((CommandResult) result).getMessage();
|
||||
}
|
||||
return null;
|
||||
private String callAsCoroutine(ExecutionContext context, Object[] args) {
|
||||
return KotlinReflectiveRegistrationKt.callAsCoroutine(this, (ICommandReceiver.Factory) instance, context, args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -197,10 +197,20 @@ public class ReflectiveRegistration {
|
||||
|
||||
static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command, java.lang.reflect.Parameter[] parameters) throws CommandParseException {
|
||||
ParameterList list = command.getParameterList();
|
||||
boolean hasReceiverParameter = false;
|
||||
boolean hasSenderParameter = false;
|
||||
int start = 0;
|
||||
Class<?> firstParameterType = null;
|
||||
if (parameters.length > start && CommandSender.class.isAssignableFrom(firstParameterType = parameters[0].getType())) {
|
||||
Class<?> senderParameterType = null;
|
||||
|
||||
if (parameters.length > start
|
||||
&& command.getInstance() instanceof ICommandReceiver.Factory
|
||||
&& ICommandReceiver.class.isAssignableFrom(firstParameterType = parameters[start].getType())) {
|
||||
hasReceiverParameter = true;
|
||||
start++;
|
||||
}
|
||||
|
||||
if (parameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = parameters[start].getType())) {
|
||||
hasSenderParameter = true;
|
||||
start++;
|
||||
}
|
||||
@@ -212,12 +222,17 @@ public class ReflectiveRegistration {
|
||||
}
|
||||
|
||||
String[] parameterNames = lookupParameterNames(method, parameters, start);
|
||||
command.setParameterOrder(parameterNames);
|
||||
|
||||
for (int i = start, n = parameters.length; i < n; i++) {
|
||||
if (parameters[i].getType().getName().equals("kotlin.coroutines.experimental.Continuation")) {
|
||||
List<String> temp = new ArrayList<>(Arrays.asList(parameterNames));
|
||||
temp.remove(i - start);
|
||||
parameterNames = temp.toArray(new String[0]);
|
||||
continue;
|
||||
}
|
||||
Parameter<?, ?> parameter = parseParameter(selector, method, parameters[i], parameterNames[i - start]);
|
||||
list.addParameter(parameter);
|
||||
}
|
||||
command.setParameterOrder(parameterNames);
|
||||
|
||||
RequirePermissions cmdPermissions = method.getAnnotation(RequirePermissions.class);
|
||||
if (cmdPermissions != null) {
|
||||
@@ -257,9 +272,9 @@ public class ReflectiveRegistration {
|
||||
command.setDescription();
|
||||
}
|
||||
|
||||
if (hasSenderParameter && Player.class.isAssignableFrom(firstParameterType)) {
|
||||
if (hasSenderParameter && Player.class.isAssignableFrom(senderParameterType)) {
|
||||
command.addContextFilter(IContextFilter.PLAYER_ONLY);
|
||||
} else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(firstParameterType)) {
|
||||
} else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(senderParameterType)) {
|
||||
command.addContextFilter(IContextFilter.CONSOLE_ONLY);
|
||||
} else if (method.isAnnotationPresent(RequirePlayer.class)) {
|
||||
command.addContextFilter(IContextFilter.PLAYER_ONLY);
|
||||
@@ -269,7 +284,7 @@ public class ReflectiveRegistration {
|
||||
|
||||
list.setRepeatFinalParameter(parameters.length > start && parameters[parameters.length - 1].isVarArgs());
|
||||
list.setFinalParameterMayBeFlag(true);
|
||||
return (hasSenderParameter ? 1 : 0) | (hasContextParameter ? 2 : 0);
|
||||
return (hasSenderParameter ? 2 : 0) | (hasContextParameter ? 4 : 0) | (hasReceiverParameter ? 1 : 0);
|
||||
}
|
||||
|
||||
public static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command) throws CommandParseException {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package io.dico.dicore.command.registration.reflect
|
||||
|
||||
import io.dico.dicore.command.CommandException
|
||||
import io.dico.dicore.command.EMessageType
|
||||
import io.dico.dicore.command.ExecutionContext
|
||||
import io.dico.dicore.command.ICommandReceiver
|
||||
import kotlinx.coroutines.experimental.CoroutineStart.UNDISPATCHED
|
||||
import kotlinx.coroutines.experimental.Deferred
|
||||
import kotlinx.coroutines.experimental.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.experimental.async
|
||||
import java.lang.reflect.Method
|
||||
import java.util.*
|
||||
import java.util.concurrent.CancellationException
|
||||
import java.util.concurrent.Executor
|
||||
import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
|
||||
import kotlin.reflect.jvm.kotlinFunction
|
||||
|
||||
fun isSuspendFunction(method: Method): Boolean {
|
||||
val func = method.kotlinFunction ?: return false
|
||||
return func.isSuspend
|
||||
}
|
||||
|
||||
fun callAsCoroutine(command: ReflectiveCommand,
|
||||
factory: ICommandReceiver.Factory,
|
||||
context: ExecutionContext,
|
||||
args: Array<Any?>): String? {
|
||||
val dispatcher = Executor { task -> factory.plugin.server.scheduler.runTask(factory.plugin, task) }.asCoroutineDispatcher()
|
||||
|
||||
// UNDISPATCHED causes the handler to run until the first suspension point on the current thread
|
||||
val job = async(context = dispatcher, start = UNDISPATCHED) { command.method.invokeSuspend(command.instance, args) }
|
||||
|
||||
if (job.isCompleted) {
|
||||
return job.getResult()
|
||||
}
|
||||
|
||||
job.invokeOnCompletion {
|
||||
val cc = context.address.chatController
|
||||
try {
|
||||
val result = job.getResult()
|
||||
cc.sendMessage(context.sender, EMessageType.RESULT, result)
|
||||
} catch (ex: Throwable) {
|
||||
cc.handleException(context.sender, context, ex)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private suspend fun Method.invokeSuspend(instance: Any?, args: Array<Any?>): Any? {
|
||||
return suspendCoroutineOrReturn { cont ->
|
||||
println()
|
||||
println("Calling command method suspendedly")
|
||||
println(toGenericString())
|
||||
println(Arrays.toString(arrayOf(instance, *args, cont)))
|
||||
println()
|
||||
invoke(instance, *args, cont)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(CommandException::class)
|
||||
private fun Deferred<Any?>.getResult(): String? {
|
||||
getCompletionExceptionOrNull()?.let { ex ->
|
||||
if (ex is CancellationException) {
|
||||
System.err.println("An asynchronously dispatched command was cancelled unexpectedly")
|
||||
ex.printStackTrace()
|
||||
throw CommandException("The command was cancelled unexpectedly (see console)")
|
||||
}
|
||||
if (ex is Exception) return ReflectiveCommand.getResult(null, ex)
|
||||
throw ex
|
||||
}
|
||||
return ReflectiveCommand.getResult(getCompleted(), null)
|
||||
}
|
||||
Reference in New Issue
Block a user