Improve async command approach - use coroutines correctly
This commit is contained in:
@@ -1,23 +1,28 @@
|
|||||||
@file:Suppress("UNUSED_VARIABLE")
|
@file:Suppress("UNUSED_VARIABLE")
|
||||||
|
|
||||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||||
import org.jetbrains.kotlin.gradle.dsl.Coroutines
|
import org.jetbrains.kotlin.gradle.dsl.Coroutines.ENABLE
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformJvmPlugin
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
|
|
||||||
plugins {
|
val stdout = PrintWriter(File("$rootDir/gradle-output.txt"))
|
||||||
kotlin("jvm") version "1.2.51"
|
|
||||||
id("com.github.johnrengelman.plugin-shadow") version "2.0.3"
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin.experimental.coroutines = Coroutines.ENABLE
|
buildscript {
|
||||||
|
dependencies {
|
||||||
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.51")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
group = "io.dico"
|
group = "io.dico"
|
||||||
version = "0.1"
|
version = "0.1"
|
||||||
|
|
||||||
|
inline fun <reified T : Plugin<out Project>> Project.apply() =
|
||||||
|
(this as PluginAware).apply<T>()
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
apply {
|
apply<JavaPlugin>()
|
||||||
plugin(JavaPlugin::class.java)
|
|
||||||
}
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots")
|
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots")
|
||||||
@@ -34,12 +39,28 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
project(":dicore3:dicore3-command") {
|
project(":dicore3:dicore3-command") {
|
||||||
|
apply<KotlinPlatformJvmPlugin>()
|
||||||
|
|
||||||
|
kotlin.experimental.coroutines = ENABLE
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// why the fuck does it need reflect explicitly?
|
||||||
|
compile("org.jetbrains.kotlinx:kotlinx-coroutines-core:0.23.4")
|
||||||
|
compile(kotlin("reflect", version = "1.2.50"))
|
||||||
|
compile(kotlin("stdlib-jdk8", version = "1.2.51"))
|
||||||
compile(project(":dicore3:dicore3-core"))
|
compile(project(":dicore3:dicore3-core"))
|
||||||
compile("com.thoughtworks.paranamer:paranamer:2.8")
|
compile("com.thoughtworks.paranamer:paranamer:2.8")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
kotlin("jvm") version "1.2.51"
|
||||||
|
id("com.github.johnrengelman.plugin-shadow") version "2.0.3"
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin.experimental.coroutines = ENABLE
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven("https://dl.bintray.com/kotlin/exposed")
|
maven("https://dl.bintray.com/kotlin/exposed")
|
||||||
}
|
}
|
||||||
@@ -65,14 +86,22 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
val compileKotlin by getting(KotlinCompile::class) {
|
||||||
|
//this.setupPlugins()
|
||||||
|
|
||||||
|
//serializedCompilerArguments.add("-java-parameters")
|
||||||
|
}
|
||||||
|
|
||||||
fun Jar.packageDependencies(vararg names: String) {
|
fun Jar.packageDependencies(vararg names: String) {
|
||||||
from(*project.configurations.compile.resolvedConfiguration.firstLevelModuleDependencies
|
//afterEvaluate {
|
||||||
.filter { it.moduleName in names }
|
from(*project.configurations.compile.resolvedConfiguration.firstLevelModuleDependencies
|
||||||
.flatMap { it.allModuleArtifacts }
|
.filter { it.moduleName in names }
|
||||||
.map { it.file }
|
.flatMap { it.allModuleArtifacts }
|
||||||
.map(::zipTree)
|
.map { it.file }
|
||||||
.toTypedArray()
|
.map(::zipTree)
|
||||||
)
|
.toTypedArray()
|
||||||
|
)
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Jar.packageDependency(name: String, configure: ModuleDependency.() -> Unit) {
|
fun Jar.packageDependency(name: String, configure: ModuleDependency.() -> Unit) {
|
||||||
@@ -92,18 +121,18 @@ tasks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Jar.packageArtifacts(vararg names: String) {
|
fun Jar.packageArtifacts(vararg names: String) {
|
||||||
val stream = PrintWriter(File("$rootDir/gradle-output.txt"))
|
//afterEvaluate {
|
||||||
from(*project.configurations.compile.resolvedConfiguration.resolvedArtifacts
|
from(*project.configurations.compile.resolvedConfiguration.resolvedArtifacts
|
||||||
.filter {
|
.filter {
|
||||||
val id = it.moduleVersion.id
|
val id = it.moduleVersion.id
|
||||||
(id.name in names).also {
|
(id.name in names).also {
|
||||||
if (!it) stream.println("Not including artifact: ${id.group}:${id.name}")
|
if (!it) stdout.println("Not including artifact: ${id.group}:${id.name}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.map { it.file }
|
||||||
.map { it.file }
|
.map(::zipTree)
|
||||||
.map(::zipTree)
|
.toTypedArray())
|
||||||
.toTypedArray())
|
//}
|
||||||
stream.flush()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val serverDir = "$rootDir/debug"
|
val serverDir = "$rootDir/debug"
|
||||||
@@ -151,7 +180,13 @@ tasks {
|
|||||||
|
|
||||||
"trove4j",
|
"trove4j",
|
||||||
"joda-time",
|
"joda-time",
|
||||||
"annotations"
|
|
||||||
|
"annotations",
|
||||||
|
"kotlin-stdlib-common",
|
||||||
|
"kotlin-stdlib",
|
||||||
|
"kotlin-stdlib-jdk7",
|
||||||
|
"kotlin-stdlib-jdk8",
|
||||||
|
"kotlin-reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
relocate("org.yaml.snakeyaml", "io.dico.parcels2.util.snakeyaml")
|
relocate("org.yaml.snakeyaml", "io.dico.parcels2.util.snakeyaml")
|
||||||
@@ -165,3 +200,5 @@ tasks {
|
|||||||
allprojects {
|
allprojects {
|
||||||
tasks.filter { it is Jar }.forEach { it.group = "artifacts" }
|
tasks.filter { it is Jar }.forEach { it.group = "artifacts" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stdout.flush()
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.dsl.Coroutines
|
||||||
|
import org.jetbrains.kotlin.js.translate.context.Namer.kotlin
|
||||||
|
|
||||||
group = "io.dico.dicore3"
|
group = "io.dico.dicore3"
|
||||||
//name = "dicore3-command"
|
//name = "dicore3-command"
|
||||||
|
|||||||
@@ -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.Method;
|
||||||
import java.lang.reflect.Modifier;
|
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 Method method;
|
||||||
private final Object instance;
|
private final Object instance;
|
||||||
private String[] parameterOrder;
|
private String[] parameterOrder;
|
||||||
@@ -20,6 +21,7 @@ final class ReflectiveCommand extends Command {
|
|||||||
if (!method.isAnnotationPresent(Cmd.class)) {
|
if (!method.isAnnotationPresent(Cmd.class)) {
|
||||||
throw new CommandParseException("No @Cmd present for the method " + method.toGenericString());
|
throw new CommandParseException("No @Cmd present for the method " + method.toGenericString());
|
||||||
}
|
}
|
||||||
|
cmdAnnotation = method.getAnnotation(Cmd.class);
|
||||||
|
|
||||||
java.lang.reflect.Parameter[] parameters = method.getParameters();
|
java.lang.reflect.Parameter[] parameters = method.getParameters();
|
||||||
|
|
||||||
@@ -46,6 +48,14 @@ final class ReflectiveCommand extends Command {
|
|||||||
this.flags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
|
this.flags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Method getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
void setParameterOrder(String[] parameterOrder) {
|
void setParameterOrder(String[] parameterOrder) {
|
||||||
this.parameterOrder = parameterOrder;
|
this.parameterOrder = parameterOrder;
|
||||||
}
|
}
|
||||||
@@ -54,7 +64,7 @@ final class ReflectiveCommand extends Command {
|
|||||||
ChildCommandAddress result = new ChildCommandAddress();
|
ChildCommandAddress result = new ChildCommandAddress();
|
||||||
result.setCommand(this);
|
result.setCommand(this);
|
||||||
|
|
||||||
Cmd cmd = method.getAnnotation(Cmd.class);
|
Cmd cmd = cmdAnnotation;
|
||||||
result.getNames().add(cmd.value());
|
result.getNames().add(cmd.value());
|
||||||
for (String alias : cmd.aliases()) {
|
for (String alias : cmd.aliases()) {
|
||||||
result.getNames().add(alias);
|
result.getNames().add(alias);
|
||||||
@@ -71,54 +81,86 @@ final class ReflectiveCommand extends Command {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String execute(CommandSender sender, ExecutionContext context) throws CommandException {
|
public String execute(CommandSender sender, ExecutionContext context) throws CommandException {
|
||||||
//System.out.println("In ReflectiveCommand.execute()");
|
|
||||||
|
|
||||||
String[] parameterOrder = this.parameterOrder;
|
String[] parameterOrder = this.parameterOrder;
|
||||||
int start = Integer.bitCount(flags);
|
int start = Integer.bitCount(flags);
|
||||||
//System.out.println("start = " + start);
|
|
||||||
Object[] args = new Object[parameterOrder.length + start];
|
Object[] args = new Object[parameterOrder.length + start];
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
if ((flags & 1) != 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) {
|
if ((flags & 2) != 0) {
|
||||||
|
args[i++] = sender;
|
||||||
|
}
|
||||||
|
if ((flags & 4) != 0) {
|
||||||
args[i++] = context;
|
args[i++] = context;
|
||||||
}
|
}
|
||||||
//System.out.println("i = " + i);
|
|
||||||
//System.out.println("parameterOrder = " + Arrays.toString(parameterOrder));
|
|
||||||
|
|
||||||
for (int n = args.length; i < n; i++) {
|
for (int n = args.length; i < n; i++) {
|
||||||
//System.out.println("n = " + n);
|
|
||||||
args[i] = context.get(parameterOrder[i - start]);
|
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 {
|
try {
|
||||||
result = method.invoke(instance, args);
|
return KotlinReflectiveRegistrationKt.isSuspendFunction(method);
|
||||||
} catch (InvocationTargetException ex) {
|
} 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) {
|
if (ex.getCause() instanceof CommandException) {
|
||||||
throw (CommandException) ex.getCause();
|
throw (CommandException) ex.getCause();
|
||||||
}
|
}
|
||||||
|
|
||||||
ex.printStackTrace();
|
|
||||||
throw new CommandException("An internal error occurred while executing this command.", ex);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
throw new CommandException("An internal error occurred while executing this command.", ex);
|
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) {
|
private String callAsCoroutine(ExecutionContext context, Object[] args) {
|
||||||
return (String) result;
|
return KotlinReflectiveRegistrationKt.callAsCoroutine(this, (ICommandReceiver.Factory) instance, context, args);
|
||||||
}
|
|
||||||
if (result instanceof CommandResult) {
|
|
||||||
return ((CommandResult) result).getMessage();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -197,10 +197,20 @@ public class ReflectiveRegistration {
|
|||||||
|
|
||||||
static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command, java.lang.reflect.Parameter[] parameters) throws CommandParseException {
|
static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command, java.lang.reflect.Parameter[] parameters) throws CommandParseException {
|
||||||
ParameterList list = command.getParameterList();
|
ParameterList list = command.getParameterList();
|
||||||
|
boolean hasReceiverParameter = false;
|
||||||
boolean hasSenderParameter = false;
|
boolean hasSenderParameter = false;
|
||||||
int start = 0;
|
int start = 0;
|
||||||
Class<?> firstParameterType = null;
|
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;
|
hasSenderParameter = true;
|
||||||
start++;
|
start++;
|
||||||
}
|
}
|
||||||
@@ -212,12 +222,17 @@ public class ReflectiveRegistration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String[] parameterNames = lookupParameterNames(method, parameters, start);
|
String[] parameterNames = lookupParameterNames(method, parameters, start);
|
||||||
command.setParameterOrder(parameterNames);
|
|
||||||
|
|
||||||
for (int i = start, n = parameters.length; i < n; i++) {
|
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]);
|
Parameter<?, ?> parameter = parseParameter(selector, method, parameters[i], parameterNames[i - start]);
|
||||||
list.addParameter(parameter);
|
list.addParameter(parameter);
|
||||||
}
|
}
|
||||||
|
command.setParameterOrder(parameterNames);
|
||||||
|
|
||||||
RequirePermissions cmdPermissions = method.getAnnotation(RequirePermissions.class);
|
RequirePermissions cmdPermissions = method.getAnnotation(RequirePermissions.class);
|
||||||
if (cmdPermissions != null) {
|
if (cmdPermissions != null) {
|
||||||
@@ -257,9 +272,9 @@ public class ReflectiveRegistration {
|
|||||||
command.setDescription();
|
command.setDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasSenderParameter && Player.class.isAssignableFrom(firstParameterType)) {
|
if (hasSenderParameter && Player.class.isAssignableFrom(senderParameterType)) {
|
||||||
command.addContextFilter(IContextFilter.PLAYER_ONLY);
|
command.addContextFilter(IContextFilter.PLAYER_ONLY);
|
||||||
} else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(firstParameterType)) {
|
} else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(senderParameterType)) {
|
||||||
command.addContextFilter(IContextFilter.CONSOLE_ONLY);
|
command.addContextFilter(IContextFilter.CONSOLE_ONLY);
|
||||||
} else if (method.isAnnotationPresent(RequirePlayer.class)) {
|
} else if (method.isAnnotationPresent(RequirePlayer.class)) {
|
||||||
command.addContextFilter(IContextFilter.PLAYER_ONLY);
|
command.addContextFilter(IContextFilter.PLAYER_ONLY);
|
||||||
@@ -269,7 +284,7 @@ public class ReflectiveRegistration {
|
|||||||
|
|
||||||
list.setRepeatFinalParameter(parameters.length > start && parameters[parameters.length - 1].isVarArgs());
|
list.setRepeatFinalParameter(parameters.length > start && parameters[parameters.length - 1].isVarArgs());
|
||||||
list.setFinalParameterMayBeFlag(true);
|
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 {
|
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)
|
||||||
|
}
|
||||||
@@ -16,6 +16,8 @@ import org.bukkit.entity.Entity
|
|||||||
import org.bukkit.entity.Player
|
import org.bukkit.entity.Player
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.coroutines.experimental.buildSequence
|
import kotlin.coroutines.experimental.buildSequence
|
||||||
|
import kotlin.reflect.jvm.javaMethod
|
||||||
|
import kotlin.reflect.jvm.kotlinFunction
|
||||||
|
|
||||||
class Worlds(private val plugin: ParcelsPlugin) {
|
class Worlds(private val plugin: ParcelsPlugin) {
|
||||||
val worlds: Map<String, ParcelWorld> get() = _worlds
|
val worlds: Map<String, ParcelWorld> get() = _worlds
|
||||||
@@ -39,6 +41,11 @@ class Worlds(private val plugin: ParcelsPlugin) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val function = ::loadWorlds
|
||||||
|
function.javaMethod!!.kotlinFunction
|
||||||
|
}
|
||||||
|
|
||||||
operator fun SerializableParcel.invoke(): Parcel? {
|
operator fun SerializableParcel.invoke(): Parcel? {
|
||||||
return world()?.parcelByID(pos)
|
return world()?.parcelByID(pos)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +1,57 @@
|
|||||||
@file:Suppress("NOTHING_TO_INLINE")
|
|
||||||
|
|
||||||
package io.dico.parcels2.command
|
package io.dico.parcels2.command
|
||||||
|
|
||||||
import io.dico.dicore.command.CommandException
|
import io.dico.dicore.command.*
|
||||||
import io.dico.dicore.command.Validate
|
|
||||||
import io.dico.parcels2.Parcel
|
import io.dico.parcels2.Parcel
|
||||||
import io.dico.parcels2.ParcelWorld
|
import io.dico.parcels2.ParcelWorld
|
||||||
import io.dico.parcels2.Worlds
|
import io.dico.parcels2.Worlds
|
||||||
|
import io.dico.parcels2.logger
|
||||||
import io.dico.parcels2.util.hasAdminManage
|
import io.dico.parcels2.util.hasAdminManage
|
||||||
import io.dico.parcels2.util.uuid
|
import io.dico.parcels2.util.uuid
|
||||||
import org.bukkit.entity.Player
|
import org.bukkit.entity.Player
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
import kotlin.reflect.full.extensionReceiverParameter
|
||||||
|
import kotlin.reflect.full.findAnnotation
|
||||||
|
import kotlin.reflect.jvm.javaType
|
||||||
|
import kotlin.reflect.jvm.jvmErasure
|
||||||
|
import kotlin.reflect.jvm.kotlinFunction
|
||||||
|
|
||||||
/*
|
@Target(AnnotationTarget.FUNCTION)
|
||||||
* Scope types for extension lambdas
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
*/
|
annotation class ParcelRequire(val admin: Boolean = false, val owner: Boolean = false)
|
||||||
sealed class BaseScope
|
|
||||||
|
|
||||||
class WorldOnlyScope(val world: ParcelWorld) : BaseScope()
|
sealed class BaseScope(private var _timeout: Int = 0) : ICommandSuspendReceiver {
|
||||||
|
override fun getTimeout() = _timeout
|
||||||
|
fun setTimeout(timeout: Int) {
|
||||||
|
_timeout = timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SuspendOnlyScope : BaseScope()
|
||||||
class ParcelScope(val world: ParcelWorld, val parcel: Parcel) : BaseScope()
|
class ParcelScope(val world: ParcelWorld, val parcel: Parcel) : BaseScope()
|
||||||
|
class WorldOnlyScope(val world: ParcelWorld) : BaseScope()
|
||||||
|
|
||||||
/*
|
fun getParcelCommandReceiver(worlds: Worlds, context: ExecutionContext, method: Method, cmdName: String): ICommandReceiver {
|
||||||
* Interface to implicitly access worlds object by creating extension functions for it
|
val function = method.kotlinFunction!!
|
||||||
*/
|
val receiverType = function.extensionReceiverParameter!!.type
|
||||||
interface HasWorlds {
|
logger.info("Receiver type: ${receiverType.javaType.typeName}")
|
||||||
val worlds: Worlds
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
val require = function.findAnnotation<ParcelRequire>()
|
||||||
* Functions to be used by command implementations
|
val admin = require?.admin == true
|
||||||
*/
|
val owner = require?.owner == true
|
||||||
inline fun <T> HasWorlds.requireInWorld(player: Player,
|
|
||||||
admin: Boolean = false,
|
|
||||||
block: WorldOnlyScope.() -> T): T {
|
|
||||||
return WorldOnlyScope(worlds.getWorldRequired(player, admin = admin)).block()
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> HasWorlds.requireInParcel(player: Player,
|
val player = context.sender as Player
|
||||||
admin: Boolean = false,
|
|
||||||
own: Boolean = false,
|
return when (receiverType.jvmErasure) {
|
||||||
block: ParcelScope.() -> T): T {
|
ParcelScope::class -> worlds.getParcelRequired(player, admin = admin, own = owner).let {
|
||||||
val parcel = worlds.getParcelRequired(player, admin = admin, own = own)
|
ParcelScope(it.world, it)
|
||||||
return ParcelScope(parcel.world, parcel).block()
|
}
|
||||||
|
WorldOnlyScope::class -> worlds.getWorldRequired(player, admin = admin).let {
|
||||||
|
WorldOnlyScope(it)
|
||||||
|
}
|
||||||
|
SuspendOnlyScope::class -> SuspendOnlyScope()
|
||||||
|
else -> throw InternalError("Invalid command receiver type")
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -60,4 +71,3 @@ fun Worlds.getParcelRequired(player: Player, admin: Boolean = false, own: Boolea
|
|||||||
return parcel
|
return parcel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,29 @@ package io.dico.parcels2.command
|
|||||||
|
|
||||||
import io.dico.dicore.command.CommandException
|
import io.dico.dicore.command.CommandException
|
||||||
import io.dico.dicore.command.ExecutionContext
|
import io.dico.dicore.command.ExecutionContext
|
||||||
|
import io.dico.dicore.command.ICommandReceiver
|
||||||
import io.dico.dicore.command.annotation.Cmd
|
import io.dico.dicore.command.annotation.Cmd
|
||||||
import io.dico.dicore.command.annotation.Desc
|
import io.dico.dicore.command.annotation.Desc
|
||||||
import io.dico.dicore.command.annotation.RequireParameters
|
import io.dico.dicore.command.annotation.RequireParameters
|
||||||
import io.dico.parcels2.ParcelOwner
|
import io.dico.parcels2.ParcelOwner
|
||||||
import io.dico.parcels2.ParcelsPlugin
|
import io.dico.parcels2.ParcelsPlugin
|
||||||
|
import io.dico.parcels2.logger
|
||||||
import io.dico.parcels2.storage.getParcelBySerializedValue
|
import io.dico.parcels2.storage.getParcelBySerializedValue
|
||||||
import io.dico.parcels2.util.hasParcelHomeOthers
|
import io.dico.parcels2.util.hasParcelHomeOthers
|
||||||
import io.dico.parcels2.util.parcelLimit
|
import io.dico.parcels2.util.parcelLimit
|
||||||
import io.dico.parcels2.util.uuid
|
import io.dico.parcels2.util.uuid
|
||||||
import org.bukkit.entity.Player
|
import org.bukkit.entity.Player
|
||||||
|
import org.bukkit.plugin.Plugin
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
@Suppress("unused")
|
//@Suppress("unused")
|
||||||
class ParcelCommands(override val plugin: ParcelsPlugin) : HasWorlds, HasPlugin {
|
class ParcelCommands(val plugin: ParcelsPlugin) : ICommandReceiver.Factory {
|
||||||
override val worlds = plugin.worlds
|
private inline val worlds get() = plugin.worlds
|
||||||
|
|
||||||
|
override fun getPlugin(): Plugin = plugin
|
||||||
|
override fun getReceiver(context: ExecutionContext, target: Method, cmdName: String): ICommandReceiver {
|
||||||
|
return getParcelCommandReceiver(plugin.worlds, context, target, cmdName)
|
||||||
|
}
|
||||||
|
|
||||||
private fun error(message: String): Nothing {
|
private fun error(message: String): Nothing {
|
||||||
throw CommandException(message)
|
throw CommandException(message)
|
||||||
@@ -25,31 +34,29 @@ class ParcelCommands(override val plugin: ParcelsPlugin) : HasWorlds, HasPlugin
|
|||||||
@Desc("Finds the unclaimed parcel nearest to origin,",
|
@Desc("Finds the unclaimed parcel nearest to origin,",
|
||||||
"and gives it to you",
|
"and gives it to you",
|
||||||
shortVersion = "sets you up with a fresh, unclaimed parcel")
|
shortVersion = "sets you up with a fresh, unclaimed parcel")
|
||||||
fun cmdAuto(player: Player, context: ExecutionContext) = requireInWorld(player) {
|
suspend fun WorldOnlyScope.cmdAuto(player: Player): Any? {
|
||||||
delegateCommandAsync(context) {
|
logger.info("cmdAuto thread before await: ${Thread.currentThread().name}")
|
||||||
val numOwnedParcels = plugin.storage.getNumParcels(ParcelOwner(uuid = player.uuid)).await()
|
val numOwnedParcels = plugin.storage.getNumParcels(ParcelOwner(uuid = player.uuid)).await()
|
||||||
|
logger.info("cmdAuto thread before await: ${Thread.currentThread().name}")
|
||||||
|
|
||||||
awaitSynchronousTask {
|
val limit = player.parcelLimit
|
||||||
val limit = player.parcelLimit
|
|
||||||
|
|
||||||
if (numOwnedParcels >= limit) {
|
if (numOwnedParcels >= limit) {
|
||||||
error("You have enough plots for now")
|
error("You have enough plots for now")
|
||||||
}
|
|
||||||
|
|
||||||
val parcel = world.nextEmptyParcel()
|
|
||||||
?: error("This world is full, please ask an admin to upsize it")
|
|
||||||
parcel.owner = ParcelOwner(uuid = player.uuid)
|
|
||||||
player.teleport(parcel.homeLocation)
|
|
||||||
"Enjoy your new parcel!"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val parcel = world.nextEmptyParcel()
|
||||||
|
?: error("This world is full, please ask an admin to upsize it")
|
||||||
|
parcel.owner = ParcelOwner(uuid = player.uuid)
|
||||||
|
player.teleport(parcel.homeLocation)
|
||||||
|
return "Enjoy your new parcel!"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Cmd("info", aliases = ["i"])
|
@Cmd("info", aliases = ["i"])
|
||||||
@Desc("Displays general information",
|
@Desc("Displays general information",
|
||||||
"about the parcel you're on",
|
"about the parcel you're on",
|
||||||
shortVersion = "displays information about this parcel")
|
shortVersion = "displays information about this parcel")
|
||||||
fun cmdInfo(player: Player) = requireInParcel(player) { parcel.infoString }
|
fun ParcelScope.cmdInfo(player: Player) = parcel.infoString
|
||||||
|
|
||||||
@Cmd("home", aliases = ["h"])
|
@Cmd("home", aliases = ["h"])
|
||||||
@Desc("Teleports you to your parcels,",
|
@Desc("Teleports you to your parcels,",
|
||||||
@@ -58,36 +65,33 @@ class ParcelCommands(override val plugin: ParcelsPlugin) : HasWorlds, HasPlugin
|
|||||||
"more than one parcel",
|
"more than one parcel",
|
||||||
shortVersion = "teleports you to parcels")
|
shortVersion = "teleports you to parcels")
|
||||||
@RequireParameters(0)
|
@RequireParameters(0)
|
||||||
fun cmdHome(player: Player, context: ExecutionContext, target: NamedParcelTarget) {
|
suspend fun SuspendOnlyScope.cmdHome(player: Player, context: ExecutionContext,
|
||||||
|
@NamedParcelDefault(NamedParcelDefaultValue.FIRST_OWNED) target: NamedParcelTarget): Any? {
|
||||||
if (player !== target.player && !player.hasParcelHomeOthers) {
|
if (player !== target.player && !player.hasParcelHomeOthers) {
|
||||||
error("You do not have permission to teleport to other people's parcels")
|
error("You do not have permission to teleport to other people's parcels")
|
||||||
}
|
}
|
||||||
|
|
||||||
return delegateCommandAsync(context) {
|
logger.info("cmdHome thread before await: ${Thread.currentThread().name}")
|
||||||
val ownedParcelsResult = plugin.storage.getOwnedParcels(ParcelOwner(uuid = target.player.uuid)).await()
|
val ownedParcelsResult = plugin.storage.getOwnedParcels(ParcelOwner(uuid = target.player.uuid)).await()
|
||||||
awaitSynchronousTask {
|
logger.info("cmdHome thread after await: ${Thread.currentThread().name}")
|
||||||
val uuid = target.player.uuid
|
|
||||||
val ownedParcels = ownedParcelsResult
|
|
||||||
.map { worlds.getParcelBySerializedValue(it) }
|
|
||||||
.filter { it != null && it.world == target.world && it.owner?.uuid == uuid }
|
|
||||||
|
|
||||||
val targetMatch = ownedParcels.getOrNull(target.index)
|
val uuid = target.player.uuid
|
||||||
?: error("The specified parcel could not be matched")
|
val ownedParcels = ownedParcelsResult
|
||||||
|
.map { worlds.getParcelBySerializedValue(it) }
|
||||||
|
.filter { it != null && it.world == target.world && it.owner?.uuid == uuid }
|
||||||
|
|
||||||
player.teleport(targetMatch.homeLocation)
|
val targetMatch = ownedParcels.getOrNull(target.index)
|
||||||
""
|
?: error("The specified parcel could not be matched")
|
||||||
}
|
|
||||||
}
|
player.teleport(targetMatch.homeLocation)
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@Cmd("claim")
|
@Cmd("claim")
|
||||||
@Desc("If this parcel is unowned, makes you the owner",
|
@Desc("If this parcel is unowned, makes you the owner",
|
||||||
shortVersion = "claims this parcel")
|
shortVersion = "claims this parcel")
|
||||||
fun cmdClaim(player: Player) {
|
fun ParcelScope.cmdClaim(player: Player) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,9 @@ interface StorageFactory {
|
|||||||
companion object StorageFactories {
|
companion object StorageFactories {
|
||||||
private val map: MutableMap<String, StorageFactory> = HashMap()
|
private val map: MutableMap<String, StorageFactory> = HashMap()
|
||||||
|
|
||||||
fun registerFactory(method: String, generator: StorageFactory): Boolean = map.putIfAbsent(method.toLowerCase(), generator) == null
|
fun registerFactory(dialect: String, generator: StorageFactory): Boolean = map.putIfAbsent(dialect.toLowerCase(), generator) == null
|
||||||
|
|
||||||
fun getFactory(method: String): StorageFactory? = map[method.toLowerCase()]
|
fun getFactory(dialect: String): StorageFactory? = map[dialect.toLowerCase()]
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// have to write the code like this in kotlin.
|
// have to write the code like this in kotlin.
|
||||||
@@ -20,7 +20,7 @@ interface StorageFactory {
|
|||||||
|
|
||||||
val optionsClass: KClass<out Any>
|
val optionsClass: KClass<out Any>
|
||||||
|
|
||||||
fun newStorageInstance(method: String, options: Any): Storage
|
fun newStorageInstance(dialect: String, options: Any): Storage
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<configuration>
|
<configuration debug="true">
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<encoder>
|
<encoder>
|
||||||
<pattern>%d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%32.-32logger{32}) - %msg</pattern>
|
<pattern>%d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%32.-32logger{32}) - %msg</pattern>
|
||||||
|
|||||||
Reference in New Issue
Block a user