From bf1da033703f1343cfc54c61f8ace1fea7dbac25 Mon Sep 17 00:00:00 2001 From: Dico200 Date: Thu, 26 Jul 2018 17:21:26 +0100 Subject: Improve async command approach - use coroutines correctly --- build.gradle.kts | 95 +++++++++++++++------- dicore3/command/build.gradle.kts | 2 + .../io/dico/dicore/command/ICommandReceiver.java | 17 ++++ .../dicore/command/ICommandSuspendReceiver.java | 7 ++ .../registration/reflect/ReflectiveCommand.java | 92 +++++++++++++++------ .../reflect/ReflectiveRegistration.java | 27 ++++-- .../reflect/KotlinReflectiveRegistration.kt | 72 ++++++++++++++++ src/main/kotlin/io/dico/parcels2/ParcelWorld.kt | 7 ++ .../io/dico/parcels2/command/CommandRequirement.kt | 70 +++++++++------- .../io/dico/parcels2/command/ParcelCommands.kt | 82 ++++++++++--------- .../io/dico/parcels2/storage/StorageFactory.kt | 6 +- src/main/resources/logback.xml | 2 +- 12 files changed, 346 insertions(+), 133 deletions(-) create mode 100644 dicore3/command/src/main/java/io/dico/dicore/command/ICommandReceiver.java create mode 100644 dicore3/command/src/main/java/io/dico/dicore/command/ICommandSuspendReceiver.java create mode 100644 dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt diff --git a/build.gradle.kts b/build.gradle.kts index ba041c6..f0cd548 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,23 +1,28 @@ @file:Suppress("UNUSED_VARIABLE") 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 -plugins { - kotlin("jvm") version "1.2.51" - id("com.github.johnrengelman.plugin-shadow") version "2.0.3" -} +val stdout = PrintWriter(File("$rootDir/gradle-output.txt")) -kotlin.experimental.coroutines = Coroutines.ENABLE +buildscript { + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.2.51") + } +} group = "io.dico" version = "0.1" +inline fun > Project.apply() = + (this as PluginAware).apply() + allprojects { - apply { - plugin(JavaPlugin::class.java) - } + apply() + repositories { mavenCentral() maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots") @@ -34,12 +39,28 @@ allprojects { } project(":dicore3:dicore3-command") { + apply() + + kotlin.experimental.coroutines = ENABLE + 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("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 { maven("https://dl.bintray.com/kotlin/exposed") } @@ -65,14 +86,22 @@ dependencies { } tasks { + val compileKotlin by getting(KotlinCompile::class) { + //this.setupPlugins() + + //serializedCompilerArguments.add("-java-parameters") + } + fun Jar.packageDependencies(vararg names: String) { - from(*project.configurations.compile.resolvedConfiguration.firstLevelModuleDependencies - .filter { it.moduleName in names } - .flatMap { it.allModuleArtifacts } - .map { it.file } - .map(::zipTree) - .toTypedArray() - ) + //afterEvaluate { + from(*project.configurations.compile.resolvedConfiguration.firstLevelModuleDependencies + .filter { it.moduleName in names } + .flatMap { it.allModuleArtifacts } + .map { it.file } + .map(::zipTree) + .toTypedArray() + ) + //} } fun Jar.packageDependency(name: String, configure: ModuleDependency.() -> Unit) { @@ -92,18 +121,18 @@ tasks { } fun Jar.packageArtifacts(vararg names: String) { - val stream = PrintWriter(File("$rootDir/gradle-output.txt")) - from(*project.configurations.compile.resolvedConfiguration.resolvedArtifacts - .filter { - val id = it.moduleVersion.id - (id.name in names).also { - if (!it) stream.println("Not including artifact: ${id.group}:${id.name}") + //afterEvaluate { + from(*project.configurations.compile.resolvedConfiguration.resolvedArtifacts + .filter { + val id = it.moduleVersion.id + (id.name in names).also { + if (!it) stdout.println("Not including artifact: ${id.group}:${id.name}") + } } - } - .map { it.file } - .map(::zipTree) - .toTypedArray()) - stream.flush() + .map { it.file } + .map(::zipTree) + .toTypedArray()) + //} } val serverDir = "$rootDir/debug" @@ -151,7 +180,13 @@ tasks { "trove4j", "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") @@ -164,4 +199,6 @@ tasks { allprojects { tasks.filter { it is Jar }.forEach { it.group = "artifacts" } -} \ No newline at end of file +} + +stdout.flush() \ No newline at end of file diff --git a/dicore3/command/build.gradle.kts b/dicore3/command/build.gradle.kts index 6077c91..b4e64bd 100644 --- a/dicore3/command/build.gradle.kts +++ b/dicore3/command/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.Coroutines +import org.jetbrains.kotlin.js.translate.context.Namer.kotlin group = "io.dico.dicore3" //name = "dicore3-command" diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/ICommandReceiver.java b/dicore3/command/src/main/java/io/dico/dicore/command/ICommandReceiver.java new file mode 100644 index 0000000..88b0d50 --- /dev/null +++ b/dicore3/command/src/main/java/io/dico/dicore/command/ICommandReceiver.java @@ -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(); + + } + +} diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/ICommandSuspendReceiver.java b/dicore3/command/src/main/java/io/dico/dicore/command/ICommandSuspendReceiver.java new file mode 100644 index 0000000..3f952d3 --- /dev/null +++ b/dicore3/command/src/main/java/io/dico/dicore/command/ICommandSuspendReceiver.java @@ -0,0 +1,7 @@ +package io.dico.dicore.command; + +public interface ICommandSuspendReceiver extends ICommandReceiver { + + int getTimeout(); + +} diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCommand.java b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCommand.java index f033d9e..591d824 100644 --- a/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCommand.java +++ b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveCommand.java @@ -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); + } + + return callAsCoroutine(context, args); + } - Object result; + 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 (result instanceof String) { - return (String) result; } - if (result instanceof CommandResult) { - return ((CommandResult) result).getMessage(); + if (ex instanceof CommandException) { + throw (CommandException) ex; } - return null; + ex.printStackTrace(); + throw new CommandException("An internal error occurred while executing this command.", ex); + } + + private String callAsCoroutine(ExecutionContext context, Object[] args) { + return KotlinReflectiveRegistrationKt.callAsCoroutine(this, (ICommandReceiver.Factory) instance, context, args); } } diff --git a/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java index 84bb10b..0003c1f 100644 --- a/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java +++ b/dicore3/command/src/main/java/io/dico/dicore/command/registration/reflect/ReflectiveRegistration.java @@ -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 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 { diff --git a/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt b/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt new file mode 100644 index 0000000..b31d091 --- /dev/null +++ b/dicore3/command/src/main/kotlin/io/dico/dicore/command/registration/reflect/KotlinReflectiveRegistration.kt @@ -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): 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? { + 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.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) +} \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt index c346a04..2868556 100644 --- a/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt +++ b/src/main/kotlin/io/dico/parcels2/ParcelWorld.kt @@ -16,6 +16,8 @@ import org.bukkit.entity.Entity import org.bukkit.entity.Player import java.util.* import kotlin.coroutines.experimental.buildSequence +import kotlin.reflect.jvm.javaMethod +import kotlin.reflect.jvm.kotlinFunction class Worlds(private val plugin: ParcelsPlugin) { val worlds: Map get() = _worlds @@ -39,6 +41,11 @@ class Worlds(private val plugin: ParcelsPlugin) { } } + init { + val function = ::loadWorlds + function.javaMethod!!.kotlinFunction + } + operator fun SerializableParcel.invoke(): Parcel? { return world()?.parcelByID(pos) } diff --git a/src/main/kotlin/io/dico/parcels2/command/CommandRequirement.kt b/src/main/kotlin/io/dico/parcels2/command/CommandRequirement.kt index b4eea83..a98ddc1 100644 --- a/src/main/kotlin/io/dico/parcels2/command/CommandRequirement.kt +++ b/src/main/kotlin/io/dico/parcels2/command/CommandRequirement.kt @@ -1,46 +1,57 @@ -@file:Suppress("NOTHING_TO_INLINE") - package io.dico.parcels2.command -import io.dico.dicore.command.CommandException -import io.dico.dicore.command.Validate +import io.dico.dicore.command.* import io.dico.parcels2.Parcel import io.dico.parcels2.ParcelWorld import io.dico.parcels2.Worlds +import io.dico.parcels2.logger import io.dico.parcels2.util.hasAdminManage import io.dico.parcels2.util.uuid 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 -/* - * Scope types for extension lambdas - */ -sealed class BaseScope +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class ParcelRequire(val admin: Boolean = false, val owner: Boolean = false) -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 WorldOnlyScope(val world: ParcelWorld) : BaseScope() -/* - * Interface to implicitly access worlds object by creating extension functions for it - */ -interface HasWorlds { - val worlds: Worlds -} +fun getParcelCommandReceiver(worlds: Worlds, context: ExecutionContext, method: Method, cmdName: String): ICommandReceiver { + val function = method.kotlinFunction!! + val receiverType = function.extensionReceiverParameter!!.type + logger.info("Receiver type: ${receiverType.javaType.typeName}") -/* - * Functions to be used by command implementations - */ -inline fun HasWorlds.requireInWorld(player: Player, - admin: Boolean = false, - block: WorldOnlyScope.() -> T): T { - return WorldOnlyScope(worlds.getWorldRequired(player, admin = admin)).block() -} + val require = function.findAnnotation() + val admin = require?.admin == true + val owner = require?.owner == true + + val player = context.sender as Player -inline fun HasWorlds.requireInParcel(player: Player, - admin: Boolean = false, - own: Boolean = false, - block: ParcelScope.() -> T): T { - val parcel = worlds.getParcelRequired(player, admin = admin, own = own) - return ParcelScope(parcel.world, parcel).block() + return when (receiverType.jvmErasure) { + ParcelScope::class -> worlds.getParcelRequired(player, admin = admin, own = owner).let { + ParcelScope(it.world, it) + } + 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 } - diff --git a/src/main/kotlin/io/dico/parcels2/command/ParcelCommands.kt b/src/main/kotlin/io/dico/parcels2/command/ParcelCommands.kt index b10c23e..0917cf9 100644 --- a/src/main/kotlin/io/dico/parcels2/command/ParcelCommands.kt +++ b/src/main/kotlin/io/dico/parcels2/command/ParcelCommands.kt @@ -2,20 +2,29 @@ package io.dico.parcels2.command import io.dico.dicore.command.CommandException 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.Desc import io.dico.dicore.command.annotation.RequireParameters import io.dico.parcels2.ParcelOwner import io.dico.parcels2.ParcelsPlugin +import io.dico.parcels2.logger import io.dico.parcels2.storage.getParcelBySerializedValue import io.dico.parcels2.util.hasParcelHomeOthers import io.dico.parcels2.util.parcelLimit import io.dico.parcels2.util.uuid import org.bukkit.entity.Player +import org.bukkit.plugin.Plugin +import java.lang.reflect.Method -@Suppress("unused") -class ParcelCommands(override val plugin: ParcelsPlugin) : HasWorlds, HasPlugin { - override val worlds = plugin.worlds +//@Suppress("unused") +class ParcelCommands(val plugin: ParcelsPlugin) : ICommandReceiver.Factory { + 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 { throw CommandException(message) @@ -25,31 +34,29 @@ class ParcelCommands(override val plugin: ParcelsPlugin) : HasWorlds, HasPlugin @Desc("Finds the unclaimed parcel nearest to origin,", "and gives it to you", shortVersion = "sets you up with a fresh, unclaimed parcel") - fun cmdAuto(player: Player, context: ExecutionContext) = requireInWorld(player) { - delegateCommandAsync(context) { - val numOwnedParcels = plugin.storage.getNumParcels(ParcelOwner(uuid = player.uuid)).await() - - awaitSynchronousTask { - val limit = player.parcelLimit - - if (numOwnedParcels >= limit) { - 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!" - } + suspend fun WorldOnlyScope.cmdAuto(player: Player): Any? { + logger.info("cmdAuto thread before await: ${Thread.currentThread().name}") + val numOwnedParcels = plugin.storage.getNumParcels(ParcelOwner(uuid = player.uuid)).await() + logger.info("cmdAuto thread before await: ${Thread.currentThread().name}") + + val limit = player.parcelLimit + + if (numOwnedParcels >= limit) { + 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) + return "Enjoy your new parcel!" } @Cmd("info", aliases = ["i"]) @Desc("Displays general information", "about the parcel you're on", 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"]) @Desc("Teleports you to your parcels,", @@ -58,36 +65,33 @@ class ParcelCommands(override val plugin: ParcelsPlugin) : HasWorlds, HasPlugin "more than one parcel", shortVersion = "teleports you to parcels") @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) { error("You do not have permission to teleport to other people's parcels") } - return delegateCommandAsync(context) { - val ownedParcelsResult = plugin.storage.getOwnedParcels(ParcelOwner(uuid = target.player.uuid)).await() - awaitSynchronousTask { - val uuid = target.player.uuid - val ownedParcels = ownedParcelsResult - .map { worlds.getParcelBySerializedValue(it) } - .filter { it != null && it.world == target.world && it.owner?.uuid == uuid } + logger.info("cmdHome thread before await: ${Thread.currentThread().name}") + val ownedParcelsResult = plugin.storage.getOwnedParcels(ParcelOwner(uuid = target.player.uuid)).await() + logger.info("cmdHome thread after await: ${Thread.currentThread().name}") - val targetMatch = ownedParcels.getOrNull(target.index) - ?: error("The specified parcel could not be matched") + val uuid = target.player.uuid + 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") @Desc("If this parcel is unowned, makes you the owner", shortVersion = "claims this parcel") - fun cmdClaim(player: Player) { + fun ParcelScope.cmdClaim(player: Player) { } - - - } \ No newline at end of file diff --git a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt index b0140f1..38c5f6d 100644 --- a/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt +++ b/src/main/kotlin/io/dico/parcels2/storage/StorageFactory.kt @@ -7,9 +7,9 @@ interface StorageFactory { companion object StorageFactories { private val map: MutableMap = 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 { // have to write the code like this in kotlin. @@ -20,7 +20,7 @@ interface StorageFactory { val optionsClass: KClass - fun newStorageInstance(method: String, options: Any): Storage + fun newStorageInstance(dialect: String, options: Any): Storage } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 6617645..58426c6 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,4 +1,4 @@ - + %d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%32.-32logger{32}) - %msg -- cgit v1.2.3