Archived
0

Improve async command approach - use coroutines correctly

This commit is contained in:
Dico200
2018-07-26 17:21:26 +01:00
parent c0e4ab728e
commit bf1da03370
12 changed files with 344 additions and 131 deletions

View File

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

View File

@@ -0,0 +1,7 @@
package io.dico.dicore.command;
public interface ICommandSuspendReceiver extends ICommandReceiver {
int getTimeout();
}

View File

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

View File

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

View File

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