Improve async command approach - use coroutines correctly
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user