From 88037bcb3d05b6b8678f01b5ff4a5c437be0eb9b Mon Sep 17 00:00:00 2001 From: Gareth Coles Date: Wed, 8 Jan 2025 19:57:29 +0000 Subject: [PATCH] Add autocomplete coroutine context, switch to less delicate API Cache an Arguments object per-command for autocomplete purposes --- .../kotlin/dev/kordex/core/ExtensibleBot.kt | 18 +++++++++++++++--- .../core/builders/ExtensibleBotBuilder.kt | 7 +++++++ .../DefaultApplicationCommandRegistry.kt | 3 ++- .../StorageAwareApplicationCommandRegistry.kt | 2 +- .../commands/application/slash/SlashCommand.kt | 5 +++++ 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/ExtensibleBot.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/ExtensibleBot.kt index 0b261a3c7c..b73044335c 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/ExtensibleBot.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/ExtensibleBot.kt @@ -53,6 +53,7 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromJsonElement import org.koin.core.component.inject import org.koin.dsl.bind +import java.util.concurrent.Executors import kotlin.Throws import kotlin.concurrent.thread @@ -75,9 +76,20 @@ public open class ExtensibleBot( override var mutex: Mutex? = Mutex() override var locking: Boolean = settings.membersBuilder.lockMemberRequests - @OptIn(DelicateCoroutinesApi::class) + protected var autoCompleteCoroutineThreads: Int = 0 + protected var interactionCoroutineThreads: Int = 0 + + public val autoCompleteCoroutineContext: CoroutineDispatcher = + Executors.newFixedThreadPool(settings.autoCompleteContextThreads) { r -> + autoCompleteCoroutineThreads++ + Thread(r, "kordex-interactions-${autoCompleteCoroutineThreads - 1}") + }.asCoroutineDispatcher() + public val interactionCoroutineContext: CoroutineDispatcher = - newFixedThreadPoolContext(settings.interactionContextThreads, "kord-extensions-interactions") + Executors.newFixedThreadPool(settings.interactionContextThreads) { r -> + interactionCoroutineThreads++ + Thread(r, "kordex-autocomplete-${interactionCoroutineThreads - 1}") + }.asCoroutineDispatcher() /** @suppress Meant for internal use by public inline function. **/ public val kordRef: Kord by inject() @@ -454,7 +466,7 @@ public open class ExtensibleBot( is AutoCompleteInteractionCreateEvent -> if (settings.applicationCommandsBuilder.enabled) { - kordRef.launch(interactionCoroutineContext) { + kordRef.launch(autoCompleteCoroutineContext) { try { getKoin().get().handle(event) } catch (e: Exception) { diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/builders/ExtensibleBotBuilder.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/builders/ExtensibleBotBuilder.kt index 36c4cf2ad4..88cb4b2cbf 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/builders/ExtensibleBotBuilder.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/builders/ExtensibleBotBuilder.kt @@ -84,6 +84,13 @@ public open class ExtensibleBotBuilder { /** Called to create an [ExtensibleBot], can be set to the constructor of your own subtype if needed. **/ public var constructor: (ExtensibleBotBuilder, String) -> ExtensibleBot = ::ExtensibleBot + /** + * The number of threads to use for autocomplete event coroutines. + * + * Defaults to the available CPU cores, as returned by `Runtime.getRuntime().availableProcessors()`. + */ + public var autoCompleteContextThreads: Int = Runtime.getRuntime().availableProcessors() + /** * The number of threads to use for interaction event coroutines. * diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/DefaultApplicationCommandRegistry.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/DefaultApplicationCommandRegistry.kt index 69eac7b353..c5e86e9ee5 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/DefaultApplicationCommandRegistry.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/DefaultApplicationCommandRegistry.kt @@ -383,7 +383,8 @@ public open class DefaultApplicationCommandRegistry : ApplicationCommandRegistry option ?: return logger.trace { "Autocomplete event for command $command doesn't have a focused option." } - val arguments = command.arguments!!() + val arguments = command.cachedArguments + ?: return logger.trace { "Command $command doesn't have a cached arguments object for some reason." } val arg = arguments.args.firstOrNull { it.getDefaultTranslatedDisplayName(translationsProvider, command) == option.first diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/StorageAwareApplicationCommandRegistry.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/StorageAwareApplicationCommandRegistry.kt index c902c28517..0cf0ad9738 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/StorageAwareApplicationCommandRegistry.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/StorageAwareApplicationCommandRegistry.kt @@ -117,7 +117,7 @@ public open class StorageAwareApplicationCommandRegistry( option ?: return logger.trace { "Autocomplete event for command $command doesn't have a focused option." } - val arguments = command.arguments!!() + val arguments = command.cachedArguments!! val arg = arguments.args.firstOrNull { it.getDefaultTranslatedDisplayName( diff --git a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/SlashCommand.kt b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/SlashCommand.kt index 293809a1e2..33316702ef 100644 --- a/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/SlashCommand.kt +++ b/kord-extensions/src/main/kotlin/dev/kordex/core/commands/application/slash/SlashCommand.kt @@ -49,6 +49,9 @@ public abstract class SlashCommand, A : Argumen public open val parentCommand: SlashCommand<*, *, *>? = null, public open val parentGroup: SlashGroup? = null, ) : ApplicationCommand(extension) { + /** @suppress Cached arguments object only used by the autocomplete event handler. **/ + public var cachedArguments: A? = null + /** @suppress This is only meant for use by code that extends the command system. **/ public val kxLogger: KLogger = KotlinLogging.logger {} @@ -171,6 +174,8 @@ public abstract class SlashCommand, A : Argumen "instead." ) } + + cachedArguments = arguments?.invoke() } /** Call this to supply a command [body], to be called when the command is executed. **/