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

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