Skip to content

Commit

Permalink
Kotlin coroutines
Browse files Browse the repository at this point in the history
  • Loading branch information
ITesserakt committed Mar 25, 2019
1 parent 8acc376 commit 84e4e7f
Show file tree
Hide file tree
Showing 53 changed files with 698 additions and 645 deletions.
2 changes: 2 additions & 0 deletions api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ dependencies {

compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$courutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-reactive:$courutines_version"

implementation "com.discord4j:discord4j-core:$discord_version"

Expand Down
4 changes: 2 additions & 2 deletions api/src/main/kotlin/command/CommandLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import util.getSubTypesOf
class CommandLoader(private val pathToPackage: String) {
private val logger = Loggers.getLogger<CommandLoader>()

fun load() {
fun load(registry: CommandRegistry) {
var count = 0
logger.info("Начата загрузка модулей...")

Reflections(pathToPackage)
.getSubTypesOf<ModuleBase<ICommandContext>>()
.forEach {
CommandRegistry.register(it.kotlin)
registry.register(it.kotlin)
count++
}.run { logger.info("Загружено $count модулей") }
}
Expand Down
65 changes: 29 additions & 36 deletions api/src/main/kotlin/command/CommandRegistry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,47 +7,40 @@ import kotlin.reflect.KClass
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.declaredMemberFunctions

object CommandRegistry {
private val providers = mutableListOf<CommandProvider<out ICommandContext>>()

fun register(instance: KClass<out ModuleBase<ICommandContext>>) {
class CommandRegistry(private vararg val providers: CommandProvider<out ICommandContext>) {
internal fun register(instance: KClass<out ModuleBase<ICommandContext>>) {
val instanceOfInstance = instance.createInstance()

instance.declaredMemberFunctions
.filter { it.hasAnnotation<Command>() }
.filter { it.hasAnnotation<Command>() }
.filter { FunctionAnnotationProcessor(it).process() }
.forEach { func ->
val provider = providers.find { it.type == instanceOfInstance.contextType }
?: throw NoSuchElementException("Нет подходящего провайдера для контекста ${instanceOfInstance.contextType.simpleName}")
.forEach { func ->
val provider = providers.find { it.type == instanceOfInstance.contextType }
?: throw NoSuchElementException("Нет подходящего провайдера для контекста ${instanceOfInstance.contextType.simpleName}")

provider.addCommand(
CommandInfo(
name = NameAnnotationProcessor(func)
.setAdditionalProcessor(GroupAnnotationProcessor(instance))
.process(),
description = DescriptionAnnotationProcessor(func)
.process(),
functionPointer = func,
modulePointer = instanceOfInstance,
aliases = AliasesAnnotationProcessor(func)
.process(),
permissions = PermissionsAnnotationProcessor(func)
.process(),
isHidden = HiddenAnnotationProcessor(func)
.process(),
isRequiringDeveloper = RequiredDeveloperAnnotationProcessor(func)
.process() || RequiredDeveloperAnnotationProcessor(instance)
.process(),
isRequiringOwner = RequiredOwnerAnnotationProcessor(func)
.process() || RequiredOwnerAnnotationProcessor(instance)
.process()
)
provider.addCommand(
CommandInfo(
name = NameAnnotationProcessor(func)
.setAdditionalProcessor(GroupAnnotationProcessor(instance))
.process(),
description = DescriptionAnnotationProcessor(func)
.process(),
functionPointer = func,
modulePointer = instanceOfInstance,
aliases = AliasesAnnotationProcessor(func)
.process(),
permissions = PermissionsAnnotationProcessor(func)
.process(),
isHidden = HiddenAnnotationProcessor(func)
.process(),
isRequiringDeveloper = RequiredDeveloperAnnotationProcessor(func)
.process() || RequiredDeveloperAnnotationProcessor(instance)
.process(),
isRequiringOwner = RequiredOwnerAnnotationProcessor(func)
.process() || RequiredOwnerAnnotationProcessor(instance)
.process()
)
}
}

fun addProvider(provider: CommandProvider<out ICommandContext>): CommandRegistry {
providers.add(provider)
return this
)
}
}
}
20 changes: 12 additions & 8 deletions api/src/main/kotlin/command/ModuleBase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,30 @@ package command

import context.ICommandContext
import discord4j.core.spec.MessageCreateSpec
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlin.reflect.KClass

abstract class ModuleBase<T : ICommandContext>(val contextType: KClass<T>) {
protected lateinit var context: T
private set

protected val scope = CoroutineScope(Dispatchers.Default)

internal fun setContext(context: ICommandContext) {
@Suppress("UNCHECKED_CAST")
this.context = context as T
}

protected fun T.reply(message: String) {
this.message.channel.subscribe {
it.createMessage(message).subscribe()
}
protected suspend fun T.reply(message: String) {
this.channel.await()
.createMessage(message)
.subscribe()
}

protected fun T.reply(messageSpec: MessageCreateSpec.() -> Unit) {
message.channel.subscribe {
it.createMessage(messageSpec).subscribe()
}
protected suspend fun T.reply(messageSpec: MessageCreateSpec.() -> Unit) {
this.channel.await()
.createMessage(messageSpec)
.subscribe()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ internal inline class FunctionAnnotationProcessor(override val elem: KAnnotatedE
or elem.isInline
or elem.isInfix
or elem.isExternal
or elem.isSuspend
or elem.isAbstract
) {
logger.error("", IllegalStateException("Неверный тип функции"))
Expand Down
12 changes: 7 additions & 5 deletions api/src/main/kotlin/context/ICommandContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package context

import discord4j.core.DiscordClient
import discord4j.core.`object`.entity.Message
import discord4j.core.`object`.entity.MessageChannel
import discord4j.core.`object`.entity.User
import reactor.core.publisher.Mono
import kotlinx.coroutines.Deferred

interface ICommandContext {
val client : DiscordClient
val message : Message
val author: Mono<User>
val commandArgs : Array<String>
val client: DiscordClient
val message: Message
val author: User
val commandArgs: Array<String>
val channel: Deferred<MessageChannel>
}
63 changes: 32 additions & 31 deletions api/src/main/kotlin/handler/CommandHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,49 @@ import command.CommandInfo
import command.Continuous
import context.ICommandContext
import discord4j.core.event.domain.message.MessageCreateEvent
import reactor.core.publisher.Mono
import reactor.core.publisher.toFlux
import reactor.util.function.component1
import reactor.util.function.component2
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import util.hasAnnotation
import util.zipWith
import kotlin.reflect.KClass
import kotlin.reflect.KParameter
import kotlin.reflect.full.callSuspendBy

abstract class CommandHandler : Handler<MessageCreateEvent>() {
private lateinit var parser: Parser

protected fun execute(command: CommandInfo, context: ICommandContext): Mono<Any?> =
Mono.fromCallable {
command.modulePointer.setContext(context)
parser = Parser(context)
}
.flatMap { context.author }
.map { it.id.asLong() }
.filter { !command.isRequiringDeveloper || it == 316249690092077065 }
.flatMap { parseParameters(command) }
.zipWith(command)
.map { (params, command) ->
command.functionPointer.callBy(params)
}
protected fun executeAsync(command: CommandInfo, context: ICommandContext) = GlobalScope.async {
command.modulePointer.setContext(context)
parser = Parser(context)
val authorId = context.author.id
if (command.isRequiringDeveloper && authorId.asLong() != 316249690092077065) return@async null

val ps = parseParameters(command)
command.functionPointer.callSuspendBy(ps)
}

protected tailrec fun getError(error: Throwable?): String =
if (error?.message.isNullOrEmpty())
getError(error?.cause)
else error?.message ?: "неизвестно"

private fun parseParameters(command: CommandInfo): Mono<Map<KParameter, Any?>> =
command.parameters.toFlux()
.collectMap({ it }, {
val type = it.type.classifier as KClass<*>
val index = it.index - 1
val isContinuous = it.hasAnnotation<Continuous>()

when {
it.isOptional -> parser.parseOptional(index, type, isContinuous).block()
type == command.modulePointer::class -> command.modulePointer
else -> parser.parse(index, type, isContinuous).block()
}
}).map { it.filterValues { value -> value != null } }
private suspend fun parseParameters(command: CommandInfo): MutableMap<KParameter, Any?> {
val params = mutableMapOf<KParameter, Any?>()

for (p in command.parameters) {
val isContinuous = p.hasAnnotation<Continuous>()
val type = p.type.classifier as KClass<*>

if (type == command.modulePointer::class) {
params[p] = command.modulePointer
continue
}

params[p] = if (p.isOptional)
parser.parseOptional(p.index - 1, type, isContinuous) ?: continue
else
parser.parse(p.index - 1, type, isContinuous)
}

return params
}
}
3 changes: 2 additions & 1 deletion api/src/main/kotlin/handler/Handler.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package handler

import discord4j.core.event.domain.Event
import kotlinx.coroutines.Job

abstract class Handler <T : Event> {
abstract fun handle(event : T)
abstract fun handle(event: T): Job
}
12 changes: 6 additions & 6 deletions api/src/main/kotlin/handler/Parser.kt
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
package handler

import context.ICommandContext
import reactor.core.publisher.Mono
import types.ITypeResolver
import types.ResolverProvider
import kotlin.reflect.KClass

internal class Parser(private val context: ICommandContext) {
internal fun <T : Any> parseOptional(index: Int, type: KClass<T>, isContinuous: Boolean): Mono<T> = when {
internal suspend fun <T : Any> parseOptional(index: Int, type: KClass<T>, isContinuous: Boolean): T? = when {
context.commandArgs.size > index -> parse(index, type, isContinuous)
else -> Mono.empty()
else -> null
}

internal fun <T : Any> parse(index: Int, type: KClass<T>, isContinuous: Boolean): Mono<T> {
internal suspend fun <T : Any> parse(index: Int, type: KClass<T>, isContinuous: Boolean): T {
val resolver: ITypeResolver<T> = ResolverProvider[type.java]
val args = context.commandArgs

val neededArg = args.getOrNull(index)
?: return Mono.error(IllegalArgumentException("Пропущен параметр на ${index + 1} месте"))
val neededArg = args.getOrElse(index) {
throw NoSuchElementException("Пропущен параметр на ${it + 1} месте")
}

return if (isContinuous)
resolver.readToEnd(context, args.drop(index))
Expand Down
3 changes: 1 addition & 2 deletions api/src/main/kotlin/types/BooleanResolver.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package types

import context.ICommandContext
import reactor.core.publisher.toMono

class BooleanResolver : ITypeResolver<Boolean> {
override fun read(context: ICommandContext, input: String) = input.toBoolean().toMono()
override suspend fun read(context: ICommandContext, input: String) = input.toBoolean()
}
4 changes: 1 addition & 3 deletions api/src/main/kotlin/types/CharResolver.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package types

import context.ICommandContext
import reactor.core.publisher.Mono
import reactor.core.publisher.toMono

class CharResolver : ITypeResolver<Char> {
override fun read(context: ICommandContext, input: String): Mono<Char> = input[0].toMono()
override suspend fun read(context: ICommandContext, input: String): Char = input[0]
}
5 changes: 2 additions & 3 deletions api/src/main/kotlin/types/ITypeResolver.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package types

import context.ICommandContext
import reactor.core.publisher.Mono

interface ITypeResolver <T> {
fun read(context : ICommandContext, input : String) : Mono<T>
suspend fun read(context: ICommandContext, input: String): T

fun readToEnd(context: ICommandContext, input: List<String>): Mono<T> {
suspend fun readToEnd(context: ICommandContext, input: List<String>): T {
val stringArgs = input.joinToString(" ")
return read(context, stringArgs)
}
Expand Down
22 changes: 12 additions & 10 deletions api/src/main/kotlin/types/MentionableResolver.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
package types

import context.ICommandContext
import reactor.core.publisher.Mono
import reactor.core.publisher.switchIfEmpty
import kotlinx.coroutines.Deferred
import util.toOptional

abstract class MentionableResolver<T> : ITypeResolver<T> {
abstract fun mentionMatch(context: ICommandContext, input: String): Mono<T>
abstract fun idMatch(context: ICommandContext, input: String): Mono<T>
abstract fun elseMatch(context: ICommandContext, input: String): Mono<T>
abstract fun mentionMatchAsync(context: ICommandContext, input: String): Deferred<T?>
abstract fun idMatchAsync(context: ICommandContext, input: String): Deferred<T?>
abstract fun elseMatchAsync(context: ICommandContext, input: String): Deferred<T?>
abstract val exceptionMessage : String

override fun read(context: ICommandContext, input: String): Mono<T> {
override suspend fun read(context: ICommandContext, input: String): T {
return when {
Regex("""^<.\d{18}>$""").matches(input) -> mentionMatch(context, input)
Regex("""^\d{18}$""").matches(input) -> idMatch(context, input)
else -> elseMatch(context, input)
}.switchIfEmpty { throw NoSuchElementException(exceptionMessage) }
Regex("""^<.\d{18}>$""").matches(input) -> mentionMatchAsync(context, input)
Regex("""^\d{18}$""").matches(input) -> idMatchAsync(context, input)
else -> elseMatchAsync(context, input)
}.await()
.toOptional()
.orElseThrow { NoSuchElementException(exceptionMessage) }
}
}
15 changes: 6 additions & 9 deletions api/src/main/kotlin/types/Number.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
package types

import context.ICommandContext
import reactor.core.publisher.Mono
import reactor.core.publisher.switchIfEmpty

abstract class NumberResolver <T : Number> : ITypeResolver<T> {
override fun read(context: ICommandContext, input: String): Mono<T> =
Mono.justOrEmpty(readWithNulls(input))
.switchIfEmpty {
throw ClassCastException("Введенное значение не является числом!")
}.map { it!! }
import util.toOptional

abstract class NumberResolver<T : Number> : ITypeResolver<T> {
override suspend fun read(context: ICommandContext, input: String): T =
readWithNulls(input).toOptional()
.orElseThrow { IllegalArgumentException("Введенное значение не является числом!") }

abstract fun readWithNulls(input: String): T?
}
Expand Down
Loading

0 comments on commit 84e4e7f

Please sign in to comment.