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

@@ -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")
@@ -164,4 +199,6 @@ tasks {
allprojects { allprojects {
tasks.filter { it is Jar }.forEach { it.group = "artifacts" } tasks.filter { it is Jar }.forEach { it.group = "artifacts" }
} }
stdout.flush()

View File

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

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.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;
} }
} }

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

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

View File

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

View File

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

View File

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

View File

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

View File

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