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")
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 <reified T : Plugin<out Project>> Project.apply() =
(this as PluginAware).apply<T>()
allprojects {
apply {
plugin(JavaPlugin::class.java)
}
apply<JavaPlugin>()
repositories {
mavenCentral()
maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots")
@@ -34,12 +39,28 @@ allprojects {
}
project(":dicore3:dicore3-command") {
apply<KotlinPlatformJvmPlugin>()
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" }
}
}
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"
//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.Modifier;
final class ReflectiveCommand extends Command {
public final class ReflectiveCommand extends Command {
private final Cmd cmdAnnotation;
private final Method method;
private final Object instance;
private String[] parameterOrder;
@@ -20,6 +21,7 @@ final class ReflectiveCommand extends Command {
if (!method.isAnnotationPresent(Cmd.class)) {
throw new CommandParseException("No @Cmd present for the method " + method.toGenericString());
}
cmdAnnotation = method.getAnnotation(Cmd.class);
java.lang.reflect.Parameter[] parameters = method.getParameters();
@@ -46,6 +48,14 @@ final class ReflectiveCommand extends Command {
this.flags = ReflectiveRegistration.parseCommandAttributes(selector, method, this, parameters);
}
public Method getMethod() {
return method;
}
public Object getInstance() {
return instance;
}
void setParameterOrder(String[] parameterOrder) {
this.parameterOrder = parameterOrder;
}
@@ -54,7 +64,7 @@ final class ReflectiveCommand extends Command {
ChildCommandAddress result = new ChildCommandAddress();
result.setCommand(this);
Cmd cmd = method.getAnnotation(Cmd.class);
Cmd cmd = cmdAnnotation;
result.getNames().add(cmd.value());
for (String alias : cmd.aliases()) {
result.getNames().add(alias);
@@ -71,54 +81,86 @@ final class ReflectiveCommand extends Command {
@Override
public String execute(CommandSender sender, ExecutionContext context) throws CommandException {
//System.out.println("In ReflectiveCommand.execute()");
String[] parameterOrder = this.parameterOrder;
int start = Integer.bitCount(flags);
//System.out.println("start = " + start);
Object[] args = new Object[parameterOrder.length + start];
int i = 0;
if ((flags & 1) != 0) {
args[i++] = sender;
try {
args[i++] = ((ICommandReceiver.Factory) instance).getReceiver(context, method, cmdAnnotation.value());
} catch (Exception ex) {
handleException(ex);
return null; // unreachable
}
}
if ((flags & 2) != 0) {
args[i++] = sender;
}
if ((flags & 4) != 0) {
args[i++] = context;
}
//System.out.println("i = " + i);
//System.out.println("parameterOrder = " + Arrays.toString(parameterOrder));
for (int n = args.length; i < n; i++) {
//System.out.println("n = " + n);
args[i] = context.get(parameterOrder[i - start]);
//System.out.println("context.get(parameterOrder[i - start]) = " + context.get(parameterOrder[i - start]));
//System.out.println("context.get(parameterOrder[i - start]).getClass() = " + context.get(parameterOrder[i - start]).getClass());
}
//System.out.println("args = " + Arrays.toString(args));
if (!isSuspendFunction()) {
return callSynchronously(args);
}
Object result;
return callAsCoroutine(context, args);
}
private boolean isSuspendFunction() {
try {
result = method.invoke(instance, args);
} catch (InvocationTargetException ex) {
return KotlinReflectiveRegistrationKt.isSuspendFunction(method);
} catch (Throwable ex) {
return false;
}
}
public String callSynchronously(Object[] args) throws CommandException {
try {
return getResult(method.invoke(instance, args), null);
} catch (Exception ex) {
return getResult(null, ex);
}
}
public static String getResult(Object returned, Exception ex) throws CommandException {
if (ex != null) {
handleException(ex);
return null; // unreachable
}
if (returned instanceof String) {
return (String) returned;
}
if (returned instanceof CommandResult) {
return ((CommandResult) returned).getMessage();
}
return null;
}
public static void handleException(Exception ex) throws CommandException {
if (ex instanceof InvocationTargetException) {
if (ex.getCause() instanceof CommandException) {
throw (CommandException) ex.getCause();
}
ex.printStackTrace();
throw new CommandException("An internal error occurred while executing this command.", ex);
} catch (Exception ex) {
ex.printStackTrace();
throw new CommandException("An internal error occurred while executing this command.", ex);
}
if (ex instanceof CommandException) {
throw (CommandException) ex;
}
ex.printStackTrace();
throw new CommandException("An internal error occurred while executing this command.", ex);
}
if (result instanceof String) {
return (String) result;
}
if (result instanceof CommandResult) {
return ((CommandResult) result).getMessage();
}
return null;
private String callAsCoroutine(ExecutionContext context, Object[] args) {
return KotlinReflectiveRegistrationKt.callAsCoroutine(this, (ICommandReceiver.Factory) instance, context, args);
}
}

View File

@@ -197,10 +197,20 @@ public class ReflectiveRegistration {
static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command, java.lang.reflect.Parameter[] parameters) throws CommandParseException {
ParameterList list = command.getParameterList();
boolean hasReceiverParameter = false;
boolean hasSenderParameter = false;
int start = 0;
Class<?> firstParameterType = null;
if (parameters.length > start && CommandSender.class.isAssignableFrom(firstParameterType = parameters[0].getType())) {
Class<?> senderParameterType = null;
if (parameters.length > start
&& command.getInstance() instanceof ICommandReceiver.Factory
&& ICommandReceiver.class.isAssignableFrom(firstParameterType = parameters[start].getType())) {
hasReceiverParameter = true;
start++;
}
if (parameters.length > start && CommandSender.class.isAssignableFrom(senderParameterType = parameters[start].getType())) {
hasSenderParameter = true;
start++;
}
@@ -212,12 +222,17 @@ public class ReflectiveRegistration {
}
String[] parameterNames = lookupParameterNames(method, parameters, start);
command.setParameterOrder(parameterNames);
for (int i = start, n = parameters.length; i < n; i++) {
if (parameters[i].getType().getName().equals("kotlin.coroutines.experimental.Continuation")) {
List<String> temp = new ArrayList<>(Arrays.asList(parameterNames));
temp.remove(i - start);
parameterNames = temp.toArray(new String[0]);
continue;
}
Parameter<?, ?> parameter = parseParameter(selector, method, parameters[i], parameterNames[i - start]);
list.addParameter(parameter);
}
command.setParameterOrder(parameterNames);
RequirePermissions cmdPermissions = method.getAnnotation(RequirePermissions.class);
if (cmdPermissions != null) {
@@ -257,9 +272,9 @@ public class ReflectiveRegistration {
command.setDescription();
}
if (hasSenderParameter && Player.class.isAssignableFrom(firstParameterType)) {
if (hasSenderParameter && Player.class.isAssignableFrom(senderParameterType)) {
command.addContextFilter(IContextFilter.PLAYER_ONLY);
} else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(firstParameterType)) {
} else if (hasSenderParameter && ConsoleCommandSender.class.isAssignableFrom(senderParameterType)) {
command.addContextFilter(IContextFilter.CONSOLE_ONLY);
} else if (method.isAnnotationPresent(RequirePlayer.class)) {
command.addContextFilter(IContextFilter.PLAYER_ONLY);
@@ -269,7 +284,7 @@ public class ReflectiveRegistration {
list.setRepeatFinalParameter(parameters.length > start && parameters[parameters.length - 1].isVarArgs());
list.setFinalParameterMayBeFlag(true);
return (hasSenderParameter ? 1 : 0) | (hasContextParameter ? 2 : 0);
return (hasSenderParameter ? 2 : 0) | (hasContextParameter ? 4 : 0) | (hasReceiverParameter ? 1 : 0);
}
public static int parseCommandAttributes(IParameterTypeSelector selector, Method method, ReflectiveCommand command) throws CommandParseException {

View File

@@ -0,0 +1,72 @@
package io.dico.dicore.command.registration.reflect
import io.dico.dicore.command.CommandException
import io.dico.dicore.command.EMessageType
import io.dico.dicore.command.ExecutionContext
import io.dico.dicore.command.ICommandReceiver
import kotlinx.coroutines.experimental.CoroutineStart.UNDISPATCHED
import kotlinx.coroutines.experimental.Deferred
import kotlinx.coroutines.experimental.asCoroutineDispatcher
import kotlinx.coroutines.experimental.async
import java.lang.reflect.Method
import java.util.*
import java.util.concurrent.CancellationException
import java.util.concurrent.Executor
import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
import kotlin.reflect.jvm.kotlinFunction
fun isSuspendFunction(method: Method): Boolean {
val func = method.kotlinFunction ?: return false
return func.isSuspend
}
fun callAsCoroutine(command: ReflectiveCommand,
factory: ICommandReceiver.Factory,
context: ExecutionContext,
args: Array<Any?>): String? {
val dispatcher = Executor { task -> factory.plugin.server.scheduler.runTask(factory.plugin, task) }.asCoroutineDispatcher()
// UNDISPATCHED causes the handler to run until the first suspension point on the current thread
val job = async(context = dispatcher, start = UNDISPATCHED) { command.method.invokeSuspend(command.instance, args) }
if (job.isCompleted) {
return job.getResult()
}
job.invokeOnCompletion {
val cc = context.address.chatController
try {
val result = job.getResult()
cc.sendMessage(context.sender, EMessageType.RESULT, result)
} catch (ex: Throwable) {
cc.handleException(context.sender, context, ex)
}
}
return null
}
private suspend fun Method.invokeSuspend(instance: Any?, args: Array<Any?>): Any? {
return suspendCoroutineOrReturn { cont ->
println()
println("Calling command method suspendedly")
println(toGenericString())
println(Arrays.toString(arrayOf(instance, *args, cont)))
println()
invoke(instance, *args, cont)
}
}
@Throws(CommandException::class)
private fun Deferred<Any?>.getResult(): String? {
getCompletionExceptionOrNull()?.let { ex ->
if (ex is CancellationException) {
System.err.println("An asynchronously dispatched command was cancelled unexpectedly")
ex.printStackTrace()
throw CommandException("The command was cancelled unexpectedly (see console)")
}
if (ex is Exception) return ReflectiveCommand.getResult(null, ex)
throw ex
}
return ReflectiveCommand.getResult(getCompleted(), null)
}

View File

@@ -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<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? {
return world()?.parcelByID(pos)
}

View File

@@ -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 <T> HasWorlds.requireInWorld(player: Player,
admin: Boolean = false,
block: WorldOnlyScope.() -> T): T {
return WorldOnlyScope(worlds.getWorldRequired(player, admin = admin)).block()
}
val require = function.findAnnotation<ParcelRequire>()
val admin = require?.admin == true
val owner = require?.owner == true
inline fun <T> 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()
val player = context.sender as Player
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
}

View File

@@ -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()
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}")
awaitSynchronousTask {
val limit = player.parcelLimit
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!"
}
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) {
}
}

View File

@@ -7,9 +7,9 @@ interface StorageFactory {
companion object StorageFactories {
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 {
// have to write the code like this in kotlin.
@@ -20,7 +20,7 @@ interface StorageFactory {
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">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %magenta(%-8.-8(%thread)) %highlight(%-5level) %boldCyan(%32.-32logger{32}) - %msg</pattern>