From a087c3063b2caf1b37f06bcdc367c8e5a10f60ff Mon Sep 17 00:00:00 2001 From: Shynixn CI Actions Date: Sun, 5 May 2024 10:29:06 +0000 Subject: [PATCH] Automatic CI Documentation. --- docs/wiki/site/coroutine/index.html | 16 ++++++++-------- docs/wiki/site/search/search_index.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/wiki/site/coroutine/index.html b/docs/wiki/site/coroutine/index.html index 2be4e05..1743d94 100644 --- a/docs/wiki/site/coroutine/index.html +++ b/docs/wiki/site/coroutine/index.html @@ -852,7 +852,7 @@

Starting a coroutine

-

As Folia brings multithreading to Paper based servers, threading becomes a lore more complicated for plugin developers.

+

As Folia brings multithreading to Paper based servers, threading becomes a lot more complicated for plugin developers.

Important

You can run mccoroutine-folia in standard Bukkit servers as well. MCCoroutine automatically falls back to the standard Bukkit @@ -864,13 +864,13 @@

Starting a coroutine

plugin.launch {} works differently in Folia compared to Bukkit.

First, it is important to understand that Folia does not have a server main thread. In order to access minecraft resources you need to use the correct thread for -a given resource. For an entity, you need to use the currently assigned thread for that entity. MCCoroutine provides dispatchers for each of these usecases and -automatically falls back to the matching dispatchers if you are on a Bukkit server instead of a Folia server.

-

However, this does not solve the problem of accessing our own data in our plugins. We do not have a main thread, so we could try accessing our data on the incoming +a given resource. For an entity, you need to use the currently assigned thread for that entity. MCCoroutine provides dispatchers for each of these usecases in Folia +and automatically falls back to the Bukkit dispatchers when you launch your Folia plugin on a standard Bukkit server.

+

However, this does not solve the problem of accessing our own data in our plugins. We do not have a main thread, so we default on accessing our data on the incoming thread. However, sometimes you have to make sure only 1 thread is accessing a resource at a time. This is important for ordering events and avoiding concurrency exceptions. Concurrent collections can help with that but you may still need synchronize access in other places.

As a solution, MCCoroutine proposes that each plugin gets their own "main thread" and corresponding "mainDispatcher". It is intended to execute all the stuff the plugin is going to do. -For minecraft actions, like teleporting a player or manipulating an entity. You simply excute them in a sub context and return back to your personal main thread. This +Examples for this are retrieving and matching data like having a List<Game> or List<Arena> in minigame plugins. For minecraft actions, like teleporting a player, you start a sub context, computate the result and return it back to your personal main thread. This concepts result into the following code.

import com.github.shynixn.mccoroutine.folia.launch
 import org.bukkit.plugin.Plugin
@@ -1079,7 +1079,7 @@ 

Plugin launch Execution order

TBD

-

In Folia, MCCoroutine offers 4 custom dispatchers.

+

In Folia, MCCoroutine offers 5 custom dispatchers.

  • mainDispatcher (Your personal plugin main thread, allows to execute coroutines on it)
  • globalRegionDispatcher (Allows to execute coroutines on the global region. e.g. Global Game Rules)
  • @@ -1090,11 +1090,11 @@

    Plugin launch Execution order

    An example how this works is shown below:

    fun foo(location: Location)) {
         plugin.launch {
    -        // Always make your you are on your personal plugin main thread.
    +        // Ensures that you are now on your plugin thread.
     
             val resultBlockType = withContext(plugin.regionDispatcher(location)) {
                 // In Folia, this will be the correct thread for the given location
    -            // In Bukkit, this will be the main thread.
    +            // In Bukkit, this will be the minecraft thread.
                 getTypeOfBlock()
             }
     
    diff --git a/docs/wiki/site/search/search_index.json b/docs/wiki/site/search/search_index.json
    index 3c35221..31c0a3f 100644
    --- a/docs/wiki/site/search/search_index.json
    +++ b/docs/wiki/site/search/search_index.json
    @@ -1 +1 @@
    -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Introduction","text":"

    MCCoroutine is a library, which adds extensive support for Kotlin Coroutines for Minecraft Server environments.

    Plugins for game servers and proxy servers often need to perform asynchronous operations (e.g. accessing databases) to be scalable for a large amount of players. MCCoroutine brings the full power of Kotlin Coroutines to them by extending the existing APIs with suspendable commands, events and schedules.

    Kotlin Coroutines

    Asynchronous or non-blocking programming is the new reality. Whether we're creating server-side, desktop or mobile applications, it's important that we provide an experience that is not only fluid from the user's perspective, but scalable when needed. There are many approaches to this problem, and in Kotlin we take a very flexible one by providing Coroutine support at the language level and delegating most of the functionality to libraries, much in line with Kotlin's philosophy. Source: (https://github.com/JetBrains/kotlin-web-site/blob/master/pages/docs/reference/coroutines-overview.md, Date: [09/11/2018], Licence copied to LICENCE).

    Supported Game Servers:

    • CraftBukkit
    • Fabric
    • Folia
    • Minestom
    • Paper
    • Spigot
    • SpongeVanilla
    • SpongeForge

    Supported Proxies:

    • BungeeCord
    • Velocity
    • Waterfall
    "},{"location":"#features","title":"Features","text":"
    • Full implementation of Kotlin Coroutines for Minecraft Servers and Minecraft Proxies
    • Extension functions for already established functions
    • Connection to events, commands, schedulers
    • Coroutine LifeCycle scope for plugins (supports plugin reloading)
    • No NMS
    • Support for Minecraft 1.7 - Latest
    • Support for Java 8 - Latest
    "},{"location":"caching/","title":"Suspending Caches, Background Repeating Tasks","text":"

    This page explains how you can create a lazy-loading cache using Kotlin Coroutines.

    In minecraft plugins, players can perform many actions in a short time period. If plugins want to keep track of them and store every action in the database, creating a new database call for every single action may cause performance problems. Therefore, caches are often implemented, which is a lot easier when using coroutines.

    Important

    The following code examples are for Bukkit, but work in a similar way in other mccoroutine implementations.

    "},{"location":"caching/#implementing-a-cache","title":"Implementing a Cache","text":"

    When taking a look at the Database implementation below, we can observe quite a lot of redundant database accesses when a player rejoins a server in a very short timeframe.

    For this, we put a lazy-loading cache in front of the Database implementation.

    class Database() {\n    fun createDbIfNotExist() {\n        // ... SQL calls\n    }\n\n    fun getDataFromPlayer(player : Player) : PlayerData {\n        // ... SQL calls\n    }\n\n    fun saveData(player : Player, playerData : PlayerData) {\n        // ... SQL calls\n    }\n}\n
    import kotlinx.coroutines.Deferred\nimport org.bukkit.entity.Player\n\nclass DatabaseCache(private val database: Database) {\n    private val cache = HashMap<Player, Deferred<PlayerData>>()\n\n    suspend fun getDataFromPlayer(player: Player): PlayerData {\n    }\n}\n
    "},{"location":"caching/#deferred-playerdata","title":"Deferred PlayerData","text":"

    Instead of using the type PlayerData directly, we use the type Deferred, which is the representation of a non-blocking job which has got PlayerData as result. This means we essentially store the job of retrieving data from the database into the cache.

    import kotlinx.coroutines.*\nimport org.bukkit.entity.Player\nimport org.bukkit.plugin.Plugin\n\nclass DatabaseCache(private val database: Database, private val plugin: Plugin) {\n    private val cache = HashMap<Player, Deferred<PlayerData>>()\n\n    suspend fun getDataFromPlayer(player: Player): PlayerData {\n        return coroutineScope {\n            if (!cache.containsKey(player)) {\n                // Cache miss, create a new job\n                cache[player] = async(Dispatchers.IO) {\n                    database.getDataFromPlayer(player)\n                }\n            }\n\n            // Await suspends the current context until the value of the Deferred job is ready.\n            cache[player]!!.await()\n        }\n    }\n}\n
    "},{"location":"caching/#implementing-cache-clearing","title":"Implementing cache clearing","text":"

    Clearing the cache is as simple as adding a clear method.

    import kotlinx.coroutines.*\nimport org.bukkit.entity.Player\nimport org.bukkit.plugin.Plugin\n\nclass DatabaseCache(private val database: Database, private val plugin: Plugin) {\n    private val cache = HashMap<Player, Deferred<PlayerData>>()\n\n    fun clear() {\n        cache.clear()\n    }\n\n    suspend fun getDataFromPlayer(player: Player): PlayerData {\n        return coroutineScope {\n            if (!cache.containsKey(player)) {\n                // Cache miss, create a new job\n                cache[player] = async(Dispatchers.IO) {\n                    database.getDataFromPlayer(player)\n                }\n            }\n\n            // Await suspends the current context until the value of the ``Deferred`` job is ready.\n            cache[player]!!.await()\n        }\n    }\n}\n
    "},{"location":"caching/#background-repeating-tasks","title":"Background Repeating Tasks","text":"

    After introducing a cache, we can implement a new suspendable background task to save the cached data every 10 minutes.

    import com.github.shynixn.mccoroutine.bukkit.launch\nimport kotlinx.coroutines.*\nimport org.bukkit.entity.Player\nimport org.bukkit.plugin.Plugin\n\nclass DatabaseCache(private val database: Database, private val plugin: Plugin) {\n    private val cache = HashMap<Player, Deferred<PlayerData>>()\n\n    init {\n        // This plugin.launch launches a new scope in the minecraft server context which can be understood\n        // to be a background task and behaves in a similar way to Bukkit.getScheduler().runTask(plugin, Runnable {  })\n        plugin.launch {\n            // This background task is a repeatable task which in this case is an endless loop. The endless loop\n            // is automatically stopped by MCCoroutine once you reload your plugin.\n            while (true) {\n                // Save all cached player data every 10 minutes.\n                for (player in cache.keys.toTypedArray()) {\n                    database.saveData(player, cache[player]!!.await())\n\n                    // Remove player when no longer online\n                    if (!player.isOnline) {\n                        cache.remove(player)\n                    }\n                }\n\n                // Suspending the current context is important in this case otherwise the minecraft thread will only execute this\n                // endless loop as it does not have time to execute other things. Delay gives the thread time to execute other things.\n                delay(10 * 60 * 1000) // 10 minutes\n            }\n        }\n    }\n\n    fun clear() {\n        cache.clear()\n    }\n\n    suspend fun getDataFromPlayer(player: Player): PlayerData {\n        return coroutineScope {\n            if (!cache.containsKey(player)) {\n                // Cache miss, create a new job\n                cache[player] = async(Dispatchers.IO) {\n                    database.getDataFromPlayer(player)\n                }\n            }\n\n            // Await suspends the current context until the value of the ``Deferred`` job is ready.\n            cache[player]!!.await()\n        }\n    }\n}\n
    "},{"location":"commandexecutor/","title":"Suspending Commandexecutors","text":"

    This page explains how you can use Kotlin Coroutines using the suspend key word for command executors in minecraft plugins.

    "},{"location":"commandexecutor/#create-the-commandexecutor","title":"Create the CommandExecutor","text":"BukkitBungeeCordFabricFoliaMinestomSpongeVelocity

    Create a traditional command executor but implement SuspendingCommandExecutor instead of CommandExecutor. Please consider, that the return value true is automatically assumed, if the function is suspended in one branch.

    import com.github.shynixn.mccoroutine.bukkit.SuspendingCommandExecutor\nimport org.bukkit.command.Command\nimport org.bukkit.command.CommandSender\nimport org.bukkit.entity.Player\n\nclass PlayerDataCommandExecutor(private val database: Database) : SuspendingCommandExecutor {\n    override suspend fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {\n        if (sender !is Player) {\n            return false\n        }\n\n        if (args.size == 2 && args[0].equals(\"rename\", true)) {\n            val name = args[1]\n            val playerData = database.getDataFromPlayer(sender)\n            playerData.name = name\n            database.saveData(sender, playerData)\n            return true\n        }\n\n        return false\n    }\n}\n

    Create a traditional command executor but extend from SuspendingCommand instead of Command.

    import com.github.shynixn.mccoroutine.bungeecord.SuspendingCommand\nimport net.md_5.bungee.api.CommandSender\nimport net.md_5.bungee.api.connection.ProxiedPlayer\n\nclass PlayerDataCommandExecutor(private val database: Database) : SuspendingCommand(\"playerdata\") {\n    override suspend fun execute(sender: CommandSender, args: Array<out String>) {\n        if (sender !is ProxiedPlayer) {\n            return\n        }\n\n        if (args.size == 2 && args[0].equals(\"rename\", true)) {\n            val name = args[1]\n            val playerData = database.getDataFromPlayer(sender)\n            playerData.name = name\n            database.saveData(sender, playerData)\n            return\n        }\n    }\n}\n

    Create a traditional command executor but extend from SuspendingCommand instead of SuspendingCommand.

    import com.github.shynixn.mccoroutine.fabric.SuspendingCommand\nimport com.mojang.brigadier.context.CommandContext\nimport net.minecraft.entity.player.PlayerEntity\nimport net.minecraft.server.command.ServerCommandSource\n\nclass PlayerDataCommandExecutor : SuspendingCommand<ServerCommandSource> {\n    override suspend fun run(context: CommandContext<ServerCommandSource>): Int {\n        if (context.source.entity is PlayerEntity) {\n            val sender = context.source.entity as PlayerEntity\n            println(\"[PlayerDataCommandExecutor] Is starting on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}\")\n        }\n\n        return 1\n    }\n}\n

    Folia schedules threads on the region of the entity who executed this command. For the console (globalregion) and command blocks (region) this rule applies as well. Other than that, usage is almost identical to Bukkit.

    import com.github.shynixn.mccoroutine.folia.SuspendingCommandExecutor\nimport org.bukkit.command.Command\nimport org.bukkit.command.CommandSender\nimport org.bukkit.entity.Player\n\nclass PlayerDataCommandExecutor(private val database: Database) : SuspendingCommandExecutor {\n    override suspend fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {\n        // In Folia, this will be the global region thread, or entity execution thread.\n        // In Bukkit, this will be the main thread.\n\n        if (sender !is Player) {\n            return false\n        }\n\n        if (args.size == 2 && args[0].equals(\"rename\", true)) {\n            val name = args[1]\n            withContext(plugin.mainDispatcher) {\n                // Make sure you switch to your plugin main thread before you do anything in your plugin.\n                val playerData = database.getDataFromPlayer(sender)\n                playerData.name = name\n                database.saveData(sender, playerData)\n            }\n\n            return true\n        }\n\n        return false\n    }\n}\n

    Create a traditional command and user server.launch or extension.launch in the addSyntax handler.

    import com.github.shynixn.mccoroutine.minestom.launch\nimport net.minestom.server.MinecraftServer\nimport net.minestom.server.command.builder.Command\nimport net.minestom.server.command.builder.arguments.ArgumentType\nimport net.minestom.server.entity.Player\n\nclass PlayerDataCommandExecutor(private val server: MinecraftServer, private val database: Database) : Command(\"mycommand\") {\n    init {\n        val nameArgument = ArgumentType.String(\"name\")\n        addSyntax({ sender, context ->\n            server.launch {\n                if (sender is Player) {\n                    val name : String = context.get(nameArgument)\n                    val playerData = database.getDataFromPlayer(sender)\n                    playerData.name = name\n                    database.saveData(sender, playerData)\n                }\n            }\n        })\n    }\n}\n

    Create a traditional command executor but extend from SuspendingCommandExecutor instead of CommandExecutor. Please consider, that the return value CommandResult.success() is automatically assumed, if the function is suspended in one branch.

    import com.github.shynixn.mccoroutine.sponge.SuspendingCommandExecutor\nimport org.spongepowered.api.command.CommandResult\nimport org.spongepowered.api.command.CommandSource\nimport org.spongepowered.api.command.args.CommandContext\nimport org.spongepowered.api.entity.living.player.Player\n\nclass PlayerDataCommandExecutor(private val database: Database) : SuspendingCommandExecutor {\n    override suspend fun execute(src: CommandSource, args: CommandContext): CommandResult {\n        if (src !is Player) {\n            return CommandResult.empty()\n        }\n\n        if (args.hasAny(\"name\")) {\n            val name = args.getOne<String>(\"name\").get()\n            val playerData = database.getDataFromPlayer(src)\n            playerData.name = name\n            database.saveData(src, playerData)\n            return CommandResult.success()\n        }\n\n        return CommandResult.empty()\n    }\n}\n

    There are multiple ways to create command executors in Velocity. MCCoroutine provides extensions for both the SimpleCommand and the BrigadierCommand to allow flexibility.

    A SimpleCommand can be created by implementing SuspendingSimpleCommand instead of SimpleCommand

    import com.github.shynixn.mccoroutine.velocity.SuspendingSimpleCommand\nimport com.velocitypowered.api.command.SimpleCommand\nimport com.velocitypowered.api.proxy.Player\n\nclass PlayerDataCommandExecutor(private val database: Database) : SuspendingSimpleCommand {\n    override suspend fun execute(invocation: SimpleCommand.Invocation) {\n        val source = invocation.source()\n\n        if (source !is Player) {\n            return\n        }\n\n        val args = invocation.arguments()\n\n        if (args.size == 2 && args[0].equals(\"rename\", true)) {\n            val name = args[1]\n            val playerData = database.getDataFromPlayer(source)\n            playerData.name = name\n            database.saveData(source, playerData)\n            return\n        }\n    }\n}\n

    A BrigadierCommand can be executed asynchronously using the executesSuspend extension function. More details below.

    "},{"location":"commandexecutor/#register-the-commandexecutor","title":"Register the CommandExecutor","text":"BukkitBungeeCordFabricFoliaMinestomSpongeVelocity

    Instead of using setExecutor, use the provided extension method setSuspendingExecutor to register a command executor.

    Important

    Do not forget to declare the playerdata command in your plugin.yml.

    import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin\nimport com.github.shynixn.mccoroutine.bukkit.registerSuspendingEvents\nimport com.github.shynixn.mccoroutine.bukkit.setSuspendingExecutor\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n        server.pluginManager.registerSuspendingEvents(PlayerDataListener(database), this)\n        getCommand(\"playerdata\")!!.setSuspendingExecutor(PlayerDataCommandExecutor(database))\n    }\n\n    override suspend fun onDisableAsync() {\n        // Minecraft Main Thread\n    }\n}\n

    Instead of using registerCommand, use the provided extension method registerSuspendingCommand to register a command executor.

    Important

    Do not forget to declare the playerdata command in your plugin.yml.

    import com.github.shynixn.mccoroutine.bungeecord.SuspendingPlugin\nimport com.github.shynixn.mccoroutine.bungeecord.registerSuspendingCommand\nimport com.github.shynixn.mccoroutine.bungeecord.registerSuspendingListener\n\nclass MCCoroutineSamplePlugin : SuspendingPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // BungeeCord Startup Thread\n        database.createDbIfNotExist()\n        proxy.pluginManager.registerSuspendingListener(this, PlayerDataListener(database))\n        proxy.pluginManager.registerSuspendingCommand(this, PlayerDataCommandExecutor(database))\n    }\n\n    override suspend fun onDisableAsync() {\n        // BungeeCord Shutdown Thread (Not the same as the startup thread)\n    }\n}\n
    class MCCoroutineSampleServerMod : DedicatedServerModInitializer {\n    override fun onInitializeServer() {\n        ServerLifecycleEvents.SERVER_STARTING.register(ServerLifecycleEvents.ServerStarting { server ->\n            // Connect Native Minecraft Scheduler and MCCoroutine.\n            mcCoroutineConfiguration.minecraftExecutor = Executor { r ->\n                server.submitAndJoin(r)\n            }\n            launch {\n                onServerStarting(server)\n            }\n        })\n\n        ServerLifecycleEvents.SERVER_STOPPING.register { server ->\n            mcCoroutineConfiguration.disposePluginSession()\n        }\n    }\n\n    /**\n     * MCCoroutine is ready after the server has started.\n     */\n    private suspend fun onServerStarting(server : MinecraftServer) {\n        // Register command\n        val command = PlayerDataCommandExecutor()\n        server.commandManager.dispatcher.register(CommandManager.literal(\"mccor\").executesSuspend(this, command))\n    }\n}\n

    Instead of using setExecutor, use the provided extension method setSuspendingExecutor to register a command executor.

    Important

    Do not forget to declare the playerdata command in your plugin.yml.

    import com.github.shynixn.mccoroutine.folia.SuspendingJavaPlugin\nimport com.github.shynixn.mccoroutine.folia.registerSuspendingEvents\nimport com.github.shynixn.mccoroutine.folia.setSuspendingExecutor\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // Global Region Thread.\n        database.createDbIfNotExist()\n        server.pluginManager.registerSuspendingEvents(PlayerDataListener(database), this)\n        getCommand(\"playerdata\")!!.setSuspendingExecutor(PlayerDataCommandExecutor(database))\n    }\n\n    override suspend fun onDisableAsync() {\n         // Global Region Thread.\n    }\n}\n

    Register the command in the same way as a traditional command.

    Instead of using executor, use the provided extension method suspendingExecutor to register a command executor.

    import com.github.shynixn.mccoroutine.sponge.SuspendingPluginContainer\nimport com.github.shynixn.mccoroutine.sponge.registerSuspendingListeners\nimport com.github.shynixn.mccoroutine.sponge.suspendingExecutor\nimport com.google.inject.Inject\nimport org.spongepowered.api.Sponge\nimport org.spongepowered.api.command.args.GenericArguments\nimport org.spongepowered.api.command.spec.CommandSpec\nimport org.spongepowered.api.event.Listener\nimport org.spongepowered.api.event.game.state.GameStartedServerEvent\nimport org.spongepowered.api.event.game.state.GameStoppingServerEvent\nimport org.spongepowered.api.plugin.Plugin\nimport org.spongepowered.api.plugin.PluginContainer\nimport org.spongepowered.api.text.Text\n\n@Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Sponge.\"\n)\nclass MCCoroutineSamplePlugin {\n    private val database = Database()\n\n    @Inject\n    private lateinit var suspendingPluginContainer: SuspendingPluginContainer\n\n    @Inject\n    private lateinit var pluginContainer: PluginContainer\n\n    @Listener\n    suspend fun onEnable(event: GameStartedServerEvent) {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n        Sponge.getEventManager().registerSuspendingListeners(pluginContainer, PlayerDataListener(database))\n        val commandSpec = CommandSpec.builder()\n            .description(Text.of(\"Command for operations.\"))\n            .permission(\"mccoroutine.sample\")\n            .arguments(\n                GenericArguments.onlyOne(GenericArguments.string(Text.of(\"name\")))\n            )\n            .suspendingExecutor(pluginContainer, PlayerDataCommandExecutor(database))\n        Sponge.getCommandManager().register(pluginContainer, commandSpec.build(), listOf(\"playerdata\"))\n    }\n\n    @Listener\n    suspend fun onDisable(event: GameStoppingServerEvent) {\n        // Minecraft Main Thread\n    }\n}\n

    Instead of using register, use the provided extension method registerSuspend to register a simple command executor.

    @Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Velocity.\"\n)\nclass MCCoroutineSamplePlugin {\n    private val database = Database()\n\n    @Inject\n    lateinit var proxyServer: ProxyServer\n\n    @Inject\n    constructor(suspendingPluginContainer: SuspendingPluginContainer) {\n        suspendingPluginContainer.initialize(this)\n    }\n\n    @Subscribe\n    suspend fun onProxyInitialization(event: ProxyInitializeEvent) {\n        // Velocity Thread Pool\n        database.createDbIfNotExist()\n        proxyServer.eventManager.registerSuspend(this, PlayerDataListener(database))\n        val meta = proxyServer.commandManager.metaBuilder(\"playerdata\").build()\n\n        // Register SimpleCommand\n        proxyServer.commandManager.registerSuspend(meta, PlayerDataCommandExecutor(database), this)\n\n        // Register BrigadierCommand\n        val helloCommand =\n            LiteralArgumentBuilder.literal<CommandSource>(\"test\")\n                .executesSuspend(this, { context: CommandContext<CommandSource> ->\n                    val message = Component.text(\"Hello World\", NamedTextColor.AQUA)\n                    context.getSource().sendMessage(message)\n                    1 // indicates success\n                })\n                .build()\n        proxyServer.commandManager.register(BrigadierCommand(helloCommand))\n    }\n}\n
    "},{"location":"commandexecutor/#test-the-commandexecutor","title":"Test the CommandExecutor","text":"

    Join your server and use the playerData command to observe getDataFromPlayer and saveData messages getting printed to your server log.

    "},{"location":"coroutine/","title":"Kotlin Coroutines and Minecraft Plugins","text":"

    When starting with Coroutines in Kotlin, you may wonder how you can use them for minecraft plugins and mods. This guide introduces concepts and a production ready API you can use, to start adding coroutines to your project.

    Important

    Make sure you have already installed MCCoroutine. See Installation for details.

    "},{"location":"coroutine/#starting-a-coroutine","title":"Starting a coroutine","text":"

    In order to start a coroutine, you can use the provided plugin.launch {} extension method. This is safe to be called anywhere in your plugin except in onDisable where you need to use runBlocking. However, keep in mind to avoid using runblocking anywhere else in any of your plugins.

    • To enter a coroutine anywhere in your code at any time:
    BukkitBungeeCordFabricFoliaSpongeVelocityMinestom
    import com.github.shynixn.mccoroutine.bukkit.launch\nimport org.bukkit.plugin.Plugin\n\nfun foo() {\n    plugin.launch {\n        // This will always be on the minecraft main thread.\n        // If you have been on the minecraft main thread before calling plugin.launch, this scope is entered immediately without any delay.\n    }\n}\n
    import com.github.shynixn.mccoroutine.bungeecord.launch\nimport net.md_5.bungee.api.plugin.Plugin\n\nfun foo() {\n    plugin.launch {\n        // This a random thread on the bungeeCord threadPool.\n        // If you have been on the bungeeCord threadPool before calling plugin.launch, this scope is executed in the next scheduler tick.\n        // If you pass CoroutineStart.UNDISPATCHED, you can enter this scope in the current tick. This is shown in a code example below.\n    }\n}\n

    Fabric has got 3 lifecycle scopes, the ModInitializer (both client and server) ClientModInitializer (client) and DedicatedServerModInitializer scope. This guide gives only DedicatedServerModInitializer examples but it works in the same way for the other scopes.

    import com.github.shynixn.mccoroutine.fabric.launch\nimport net.fabricmc.api.DedicatedServerModInitializer\n\nfun foo(){\n    mod.launch {\n        // This will always be on the minecraft main thread.\n    }\n}\n

    As Folia brings multithreading to Paper based servers, threading becomes a lore more complicated for plugin developers.

    Important

    You can run mccoroutine-folia in standard Bukkit servers as well. MCCoroutine automatically falls back to the standard Bukkit scheduler if the Folia schedulers are not found and the rules for mccoroutine-bukkit start to apply.

    Important

    If you have been using mccoroutine for Bukkit before, you have to perform some restructuring in your plugin. Simply changing the imports is not enough. plugin.launch {} works differently in Folia compared to Bukkit.

    First, it is important to understand that Folia does not have a server main thread. In order to access minecraft resources you need to use the correct thread for a given resource. For an entity, you need to use the currently assigned thread for that entity. MCCoroutine provides dispatchers for each of these usecases and automatically falls back to the matching dispatchers if you are on a Bukkit server instead of a Folia server.

    However, this does not solve the problem of accessing our own data in our plugins. We do not have a main thread, so we could try accessing our data on the incoming thread. However, sometimes you have to make sure only 1 thread is accessing a resource at a time. This is important for ordering events and avoiding concurrency exceptions. Concurrent collections can help with that but you may still need synchronize access in other places.

    As a solution, MCCoroutine proposes that each plugin gets their own \"main thread\" and corresponding \"mainDispatcher\". It is intended to execute all the stuff the plugin is going to do. For minecraft actions, like teleporting a player or manipulating an entity. You simply excute them in a sub context and return back to your personal main thread. This concepts result into the following code.

    import com.github.shynixn.mccoroutine.folia.launch\nimport org.bukkit.plugin.Plugin\n\nfun foo(entity : Entity) {\n    plugin.launch { // or plugin.launch(plugin.mainDispatcher) {}\n        // Your plugin main thread. If you have already been on your plugin main thread, this scope is entered immidiatly.\n        // Regardless if your are on bukkit or on folia, this is your personal thread and you must not call bukkit methods on it.\n        // Now perform some data access on your plugin data like accessing a repository.\n        val storedEntityDataInDatabase = database.get()\n\n        // Apply the data on the entity thread using the entityDispatcher.\n        // The plugin.entityDispatcher(entity) parameter ensures, that we end up on the scheduler for the entity in the specific region.\n        withContext(plugin.entityDispatcher(entity)) {\n              // In Folia, this will be the correct thread for the given entity.\n              // In Bukkit, this will be the main thread.\n        }\n    }\n}\n
    import com.github.shynixn.mccoroutine.sponge.launch\nimport org.spongepowered.api.plugin.PluginContainer\n\nfun foo() {\n    plugin.launch {\n        // This will always be on the minecraft main thread.\n    }\n}\n
    import com.github.shynixn.mccoroutine.velocity.launch\nimport com.velocitypowered.api.plugin.PluginContainer\n\nfun foo() {\n    plugin.launch {\n        // This will be a random thread on the Velocity threadpool\n    }\n}\n

    Minestom has got 2 lifecycle scopes, the server scope and the extension scope. When this guide talks about a plugin, the corresponding class in Minestom is Extension or MinecraftServer depending on your usecase.

    Server level (if you are developing a new server):

    import com.github.shynixn.mccoroutine.minestom.launch\nimport net.minestom.server.MinecraftServer\n\nfun foo() {\n    server.launch {\n        // This will always be on the minecraft main thread.\n    }\n}\n

    Extension level (if you are developing a new extension):

    import com.github.shynixn.mccoroutine.minestom.launch\nimport net.minestom.server.extensions.Extension\n\nfun foo() {\n    extension.launch {\n        // This will always be on the minecraft main thread.\n    }\n}\n
    "},{"location":"coroutine/#switching-coroutine-context","title":"Switching coroutine context","text":"

    Later in the Coroutines in Kotlin guide, the terms coroutine-context and dispatchers are explained. A dispatcher determines what thread or threads the corresponding coroutine uses for its execution.

    BukkitBungeeCordFabricFoliaSpongeVelocityMinestom

    In Bukkit, MCCoroutine offers 2 custom dispatchers.

    • minecraftDispatcher (Allows to execute coroutines on the main minecraft thread)
    • asyncDispatcher (Allows to execute coroutines on the async minecraft threadpool)

    Important

    You may also use Dispatchers.IO instead of asyncDispatcher, to reduce the dependency on mccoroutine in your code.

    An example how this works is shown below:

    fun foo() {\n    plugin.launch {\n        // This will always be on the minecraft main thread.\n        // If you have been on the minecraft main thread before calling plugin.launch, this scope is entered immediately without any delay.\n\n        val result1 = withContext(plugin.minecraftDispatcher) {\n            // Perform operations on the minecraft main thread.\n            \"Player is \" // Optionally, return a result.\n        }\n\n        // Here we are automatically back on the main thread again.\n\n        // Prefer using Dispatchers.IO instead of asyncDispatcher \n        val result2 = withContext(Dispatchers.IO) {\n            // Perform operations asynchronously.\n            \" Max\"\n        }\n\n        // Here we are automatically back on the main thread again.\n\n        println(result1 + result2) // Prints 'Player is Max'\n    }\n}\n

    Normally, you do not need to call plugin.minecraftDispatcher in your code. Instead, you are guaranteed to be always on the minecraft main thread in the plugin.launch{} scope and use sub coroutines (e.g. withContext) to perform asynchronous operations. Such a case can be found below:

    // This is a Bukkit example, but it works in the same way in every other framework.\n@EventHandler\nfun onPlayerJoinEvent(event: PlayerJoinEvent) {\n    plugin.launch {\n        // This will always be on the minecraft main thread.\n        // A PlayerJoinEvent arrives on the main thread, therefore this scope is entered immediately without any delay.\n\n        val name = event.player.name\n        val listOfFriends = withContext(Dispatchers.IO) {\n            // IO Thread\n            val friendNames = Files.readAllLines(Paths.get(\"$name.txt\"))\n            friendNames\n        }\n\n        // Main Thread\n        val friendText = listOfFriends.joinToString(\", \")\n        event.player.sendMessage(\"My friends are: $friendText\")\n    }\n}\n

    In BungeeCord, MCCoroutine offers 1 custom dispatcher.

    • bungeeCordDispatcher (Allows to execute coroutines on the bungeeCord threadpool)

    An example how this works is shown below:

    fun foo() {\n    plugin.launch {\n        // This a random thread on the bungeeCord threadPool.\n        // If you have been on the bungeeCord threadPool before calling plugin.launch, this scope is executed in the next scheduler tick.\n        // If you pass CoroutineStart.UNDISPATCHED, you can enter this scope in the current tick. This is shown in a code example below.\n\n        val result = withContext(Dispatchers.IO) {\n              // Perform operations asynchronously.\n            \"Playxer is Max\"\n        }\n\n        // Here we are automatically back on a new random thread on the bungeeCord threadPool.\n        println(result) // Prints 'Player is Max'\n    }\n}\n
    fun foo() {\n    plugin.launch(start = CoroutineStart.UNDISPATCHED) {\n        // This is the same thread before calling plugin.launch\n\n        val result = withContext(Dispatchers.IO) {\n            // Perform operations asynchronously.\n            \"Playxer is Max\"\n        }\n\n        // Here we are automatically back on a new random thread on the bungeeCord threadPool.\n        println(result) // Prints 'Player is Max'\n    }\n}\n

    TBD

    In Folia, MCCoroutine offers 4 custom dispatchers.

    • mainDispatcher (Your personal plugin main thread, allows to execute coroutines on it)
    • globalRegionDispatcher (Allows to execute coroutines on the global region. e.g. Global Game Rules)
    • regionDispatcher (Allows to execute coroutines on a specific location in a world)
    • entityDispatcher (Allows to execute coroutines on a specific entity)
    • asyncDispatcher (Allows to execute coroutines on the async thread pool)

    An example how this works is shown below:

    fun foo(location: Location)) {\n    plugin.launch {\n        // Always make your you are on your personal plugin main thread.\n\n        val resultBlockType = withContext(plugin.regionDispatcher(location)) {\n            // In Folia, this will be the correct thread for the given location\n            // In Bukkit, this will be the main thread.\n            getTypeOfBlock()\n        }\n\n        myBlockTypeList.add(resultBlockType)\n\n        withContext(plugin.asyncDispatcher) {\n            // save myBlockTypeList to file.\n        }\n    }\n}\n

    TBD

    TBD

    TBD

    "},{"location":"coroutine/#plugin-launch-execution-order","title":"Plugin launch Execution order","text":"

    If you use plugin.launch, it is important to understand the execution order.

    // This is a Bukkit example, but it works in the same way in every other framework.\nclass Foo(private val plugin: Plugin) {\n\n    fun bar() {\n        // Main Thread\n        // If you have been on the minecraft main thread before calling plugin.launch, this scope is entered immediately without any delay.\n        println(\"I am first\")\n\n        val job = plugin.launch {\n            println(\"I am second\") // The context is not suspended when switching to the same suspendable context.\n            delay(1000)\n            println(\"I am fourth\") // The context is given back after 1000 milliseconds and continuous here.\n            bob()\n        }\n\n        // When calling delay the suspendable context is suspended and the original context immediately continuous here.\n        println(\"I am third\")\n    }\n\n    private suspend fun bob() {\n        println(\"I am fifth\")\n    }\n}\n
    \"I am first\"\n\"I am second\"\n\"I am third\"\n\"I am fourth\"\n\"I am fifth\"\n
    "},{"location":"coroutine/#coroutines-everywhere","title":"Coroutines everywhere","text":"

    Using plugin.launch{}is valuable if you migrate existing plugins to use coroutines. However, if you write a new plugin from scratch, you may consider using convenience integrations provided by MCCoroutine such as:

    • Suspending Plugin
    • Suspending Listeners
    • Suspending CommandExecutors
    "},{"location":"exception/","title":"Exception Handling","text":"

    MCCoroutine implements exception handling as explained by the official Coroutine docs.

    If an exception is not caught (e.g. an exception is thrown in a suspendable commandexecutor or listener), the exception is propagated upwards to MCCoroutine.

    "},{"location":"exception/#default-exception-behaviour","title":"Default Exception Behaviour","text":"

    By default, MCCoroutine logs every exception except CoroutineCancellation, which is thrown when a job is cancelled.

    logger.log(\n    Level.SEVERE,\n    \"This is not an error of MCCoroutine! See sub exception for details.\",\n    exception\n)\n
    "},{"location":"exception/#custom-exception-behaviour","title":"Custom Exception Behaviour","text":"

    You can handle exceptions by yourself by listening to the MCCoroutineExceptionEvent. This event is sent to the event bus of the minecraft frame work (e.g. Bukkit, Sponge, BungeeCord) and can be used for logging. The following points should be considered:

    • The event arrives at the main thread in Bukkit, Sponge, Minestom. In Folia, it arrives on the globalRegionThread.
    • The event is also called for CoroutineCancellation
    • Exceptions arrive for every plugin using MCCoroutine. Check if event.plugin equals your plugin.
    • You can cancel the event to disable logging the event with the default exception behaviour
    • You can make this event a suspend function, however put a try-catch over the entire function. Otherwise, any exception which occur while logging the original exception could stack indefinitely which eventually causes a OutOfMemoryException
    "},{"location":"faq/","title":"FAQ","text":"

    This page explains the most common questions regarding MCCoroutine.

    "},{"location":"faq/#how-is-mccoroutine-implemented","title":"How is MCCoroutine implemented?","text":"

    MCCoroutine simply wraps the existing schedulers of the minecraft frameworks. For example, when you suspend a function using withContext, MCCoroutine sends new tasks to the Bukkit scheduler if necessary. Every consideration about Bukkit schedulers applies to MCCoroutine as well.

    "},{"location":"faq/#does-mccoroutine-need-more-ram","title":"Does MCCoroutine need more RAM?","text":"

    MCCoroutine does not create any resources like threads or threadPools. This means MCCoroutine does not have any overhead. However, Kotlin Coroutines contains additional thread pools which may increase memory usage slightly. Take a look the the official Kotlin Coroutine docs for details.

    "},{"location":"faq/#are-suspendable-listenerscommand-executors-slower","title":"Are Suspendable Listeners/Command Executors slower?","text":"

    No, they are as fast as ordinary listeners and command executors. The registration of them is slightly slower because reflection calls are used to create them. Once players join the server and events arrive, they are the same speed.

    "},{"location":"faq/#how-to-cancel-all-running-jobs","title":"How to cancel all running jobs?","text":"

    You can control the behaviour of the coroutine using plugin.scope.

    plugin.scope.coroutineContext.cancelChildren()\n
    "},{"location":"faq/#how-to-cancel-suspendable-events","title":"How to cancel suspendable events?","text":"

    The following example is not possible. You cannot cancel events after you have suspended the context for the very first time. The event has already happened, and the outcome has already been decided.

    @EventHandler\nsuspend fun onPlayerInteractEvent(event: PlayerInteractEvent) {\n    withContext(Dispatchers.IO){\n        // e.g. read file/database\n        delay(50)\n    }\n    // Cancellation is not possible at this point.\n    event.isCancelled = true;\n}\n

    Cancelling events before the first suspension is still possible.

    @EventHandler\nsuspend fun onPlayerInteractEvent(event: PlayerInteractEvent) {\n    // Cancellation is possible at this point.\n    event.isCancelled = true;\n\n    withContext(Dispatchers.IO){\n        // e.g. read file/database\n        delay(50)\n    }\n}\n
    "},{"location":"installation/","title":"Getting Started","text":"

    In order to use the MCCoroutine Kotlin API, you need to include the following libraries into your project.

    "},{"location":"installation/#add-mccoroutine-libraries","title":"Add MCCoroutine Libraries","text":"BukkitBungeeCordFabricFoliaMinestomSpongeVelocity
    dependencies {\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-bukkit-api:2.16.0\")\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-bukkit-core:2.16.0\")\n}\n
    dependencies {\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-bungeecord-api:2.16.0\")\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-bungeecord-core:2.16.0\")\n}\n
    dependencies {\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-fabric-api:2.16.0\")\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-fabric-core:2.16.0\")\n}\n
    dependencies {\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-folia-api:2.16.0\")\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-folia-core:2.16.0\")\n}\n
    dependencies {\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-minestom-api:2.16.0\")\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-minestom-core:2.16.0\")\n}\n
    dependencies {\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-sponge-api:2.16.0\")\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-sponge-core:2.16.0\")\n}\n
    dependencies {\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-velocity-api:2.16.0\")\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-velocity-core:2.16.0\")\n}\n
    "},{"location":"installation/#add-kotlin-coroutines-libraries","title":"Add Kotlin Coroutines Libraries","text":"

    MCCoroutine builds against Kotlin 1.3.x, however it does not distribute the Kotlin Runtime or Kotlin Coroutines Runtime. This means, you can use any Kotlin version in your plugins. It is even encouraged to always use the latest version.

    Replace 1.x.x with the actual versions.

    dependencies {\n    implementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.x.x\")\n}\n
    "},{"location":"installation/#shade-dependencies","title":"Shade Dependencies","text":"Bukkit Server 1.17 - LatestFoliaOther Server

    plugin.yml

    libraries:\n  - com.github.shynixn.mccoroutine:mccoroutine-bukkit-api:2.16.0\n  - com.github.shynixn.mccoroutine:mccoroutine-bukkit-core:2.16.0\n

    plugin.yml

    libraries:\n  - com.github.shynixn.mccoroutine:mccoroutine-folia-api:2.16.0\n  - com.github.shynixn.mccoroutine:mccoroutine-folia-core:2.16.0\n

    Shade the libraries into your plugin.jar file using gradle or maven.

    "},{"location":"installation/#test-the-plugin","title":"Test the Plugin","text":"

    Try to call launch{} in your onEnable() function in your Plugin class.

    Further help

    Please take a look at the sample plugins (e.g. mccoroutine-bukkit-sample or mccoroutine-sponge-sample) which can be found on Github. A real production plugin using MCCoroutine can be found here.

    "},{"location":"listener/","title":"Suspending Listeners","text":"

    This page explains how you can use Kotlin Coroutines using the suspend key word for listeners in minecraft plugins.

    "},{"location":"listener/#create-the-listener","title":"Create the Listener","text":"

    Create a listener and add suspend to all functions where you perform suspendable operations (e.g. calling other suspendable functions). You can mix suspendable and non suspendable functions in listeners.

    BukkitBungeeCordFabricFoliaMinestomSpongeVelocity
    import org.bukkit.event.EventHandler\nimport org.bukkit.event.Listener\nimport org.bukkit.event.player.PlayerJoinEvent\nimport org.bukkit.event.player.PlayerQuitEvent\nimport java.util.*\n\nclass PlayerDataListener(private val database: Database) : Listener {\n    @EventHandler\n    suspend fun onPlayerJoinEvent(event: PlayerJoinEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.name\n        playerData.lastJoinDate = Date()\n        database.saveData(player, playerData)\n    }\n\n    @EventHandler\n    suspend fun onPlayerQuitEvent(event: PlayerQuitEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.name\n        playerData.lastQuitDate = Date()\n        database.saveData(player, playerData)\n    }\n}\n

    In BungeeCord some events can be handled asynchronously. This allows full control over consuming, processing and resuming events when performing long running operations. When you create a suspend function using MCCoroutine, they automatically handle registerIntent and completeIntent. You do not have to do anything yourself, all suspend functions are automatically processed asynchronously.

    import net.md_5.bungee.api.event.PostLoginEvent\nimport net.md_5.bungee.api.event.ServerDisconnectEvent\nimport net.md_5.bungee.api.plugin.Listener\nimport net.md_5.bungee.event.EventHandler\nimport java.util.*\n\nclass PlayerDataListener(private val database: Database) : Listener {\n    @EventHandler\n    suspend fun onPlayerJoinEvent(event: PostLoginEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.name\n        playerData.lastJoinDate = Date()\n        database.saveData(player, playerData)\n    }\n\n    @EventHandler\n    suspend fun onPlayerQuitEvent(event: ServerDisconnectEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.name\n        playerData.lastQuitDate = Date()\n        database.saveData(player, playerData)\n    }\n}\n
    import net.minecraft.entity.Entity\nimport net.minecraft.entity.player.PlayerEntity\nimport net.minecraft.util.Hand\nimport net.minecraft.util.hit.EntityHitResult\nimport net.minecraft.world.World\nimport java.util.*\n\nclass PlayerDataListener(private val database: Database) {\n      suspend fun onPlayerAttackEvent(\n        player: PlayerEntity,\n        world: World,\n        hand: Hand,\n        entity: Entity,\n        hitResult: EntityHitResult?\n    ) {\n       val playerData = database.getDataFromPlayer(player)\n       playerData.name = player.name.toString()\n       playerData.lastJoinDate = Date()\n       database.saveData(player, playerData)\n    }\n}\n
    import org.bukkit.event.EventHandler\nimport org.bukkit.event.Listener\nimport org.bukkit.event.player.PlayerJoinEvent\nimport org.bukkit.event.player.PlayerQuitEvent\nimport java.util.*\n\nclass PlayerDataListener(private val database: Database) : Listener {\n    @EventHandler\n    suspend fun onPlayerJoinEvent(event: PlayerJoinEvent) {\n        // In Folia, this will be entity thread of the player.\n        // In Bukkit, this will be the main thread.\n        withContext(plugin.mainDispatcher) {\n            // Make sure you switch to your plugin main thread before you do anything in your plugin.\n            val player = event.player\n            val playerData = database.getDataFromPlayer(player)\n            playerData.name = player.name\n            playerData.lastJoinDate = Date()\n            database.saveData(player, playerData)\n        }\n    }\n\n    @EventHandler\n    fun onPlayerQuitEvent(event: PlayerQuitEvent) {\n        plugin.launch {\n            // Make sure you switch to your plugin main thread before you do anything in your plugin.\n            val player = event.player\n            val playerData = database.getDataFromPlayer(player)\n            playerData.name = player.name\n            playerData.lastQuitDate = Date()\n            database.saveData(player, playerData)\n        }\n    }\n}\n
    import net.minestom.server.event.player.PlayerDisconnectEvent\nimport net.minestom.server.event.player.PlayerLoginEvent\nimport java.util.*\n\nclass PlayerDataListener(private val database: Database) {\n    suspend fun onPlayerJoinEvent(event: PlayerLoginEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.username\n        playerData.lastJoinDate = Date()\n        database.saveData(player, playerData)\n    }\n\n    suspend fun onPlayerQuitEvent(event: PlayerDisconnectEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.username\n        playerData.lastQuitDate = Date()\n        database.saveData(player, playerData)\n    }\n}\n
    import org.spongepowered.api.event.Listener\nimport org.spongepowered.api.event.network.ClientConnectionEvent\nimport java.util.*\n\nclass PlayerDataListener(private val database: Database) {\n    @Listener\n    suspend fun onPlayerJoinEvent(event: ClientConnectionEvent.Join) {\n        val player = event.targetEntity\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.name\n        playerData.lastJoinDate = Date()\n        database.saveData(player, playerData)\n    }\n\n    @Listener\n    suspend fun onPlayerQuitEvent(event: ClientConnectionEvent.Disconnect) {\n        val player = event.targetEntity\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.name\n        playerData.lastQuitDate = Date()\n        database.saveData(player, playerData)\n    }\n}\n

    In Velocity events can be handled asynchronously. This allows full control over consuming, processing and resuming events when performing long running operations. When you create a suspend function using MCCoroutine, they automatically handle Continuation and EventTask. You do not have to do anything yourself, all suspend functions are automatically processed asynchronously.

    import com.velocitypowered.api.event.Subscribe\nimport com.velocitypowered.api.event.connection.DisconnectEvent\nimport com.velocitypowered.api.event.connection.PostLoginEvent\nimport java.util.*\n\nclass PlayerDataListener(private val database: Database) {\n    @Subscribe\n    suspend fun onPlayerJoinEvent(event: PostLoginEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.username\n        playerData.lastJoinDate = Date()\n        database.saveData(player, playerData)\n    }\n\n    @Subscribe\n    suspend fun onPlayerQuitEvent(event: DisconnectEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.username\n        playerData.lastQuitDate = Date()\n        database.saveData(player, playerData)\n    }\n}\n
    "},{"location":"listener/#register-the-listener","title":"Register the Listener","text":"BukkitBungeeCordFabricFoliaMinestomSpongeVelocity

    Instead of using registerEvents, use the provided extension method registerSuspendingEvents to allow suspendable functions in your listener. Please notice, that timing measurements are no longer accurate for suspendable functions.

    import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin\nimport com.github.shynixn.mccoroutine.bukkit.registerSuspendingEvents\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n        server.pluginManager.registerSuspendingEvents(PlayerDataListener(database), this)\n    }\n\n    override suspend fun onDisableAsync() {\n        // Minecraft Main Thread\n    }\n}\n

    Instead of using registerListener, use the provided extension method registerSuspendingListener to allow suspendable functions in your listener. Please notice, that timing measurements are no longer accurate for suspendable functions.

    import com.github.shynixn.mccoroutine.bungeecord.SuspendingPlugin\nimport com.github.shynixn.mccoroutine.bungeecord.registerSuspendingListener\n\nclass MCCoroutineSamplePlugin : SuspendingPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // BungeeCord Startup Thread\n        database.createDbIfNotExist()\n        proxy.pluginManager.registerSuspendingListener(this, PlayerDataListener(database))\n    }\n\n    override suspend fun onDisableAsync() {\n        // BungeeCord Shutdown Thread (Not the same as the startup thread)\n    }\n}\n
    class MCCoroutineSampleServerMod : DedicatedServerModInitializer {\n    override fun onInitializeServer() {\n        ServerLifecycleEvents.SERVER_STARTING.register(ServerLifecycleEvents.ServerStarting { server ->\n            // Connect Native Minecraft Scheduler and MCCoroutine.\n            mcCoroutineConfiguration.minecraftExecutor = Executor { r ->\n                server.submitAndJoin(r)\n            }\n            launch {\n                onServerStarting(server)\n            }\n        })\n\n        ServerLifecycleEvents.SERVER_STOPPING.register { server ->\n            mcCoroutineConfiguration.disposePluginSession()\n        }\n    }\n\n    /**\n     * MCCoroutine is ready after the server has started.\n     */\n    private suspend fun onServerStarting(server : MinecraftServer) {\n        // Minecraft Main Thread\n        val database = Database()\n        database.createDbIfNotExist()\n\n        val listener = PlayerDataListener(database)\n        val mod = this\n        AttackEntityCallback.EVENT.register(AttackEntityCallback { player, world, hand, entity, hitResult ->\n            mod.launch {\n                listener.onPlayerAttackEvent(player, world, hand, entity, hitResult)\n            }\n            ActionResult.PASS\n        })\n    }\n}\n

    Instead of using registerEvents, use the provided extension method registerSuspendingEvents to allow suspendable functions in your listener. Please notice, that timing measurements are no longer accurate for suspendable functions.

    import com.github.shynixn.mccoroutine.folia.SuspendingJavaPlugin\nimport com.github.shynixn.mccoroutine.folia.registerSuspendingEvents\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n        val plugin = this\n        // MCCoroutine for Folia cannot assume the correct dispatcher per event. You need to define how each event\n        // should find its correct dispatcher in MCCoroutine.\n        val eventDispatcher = mapOf<Class<out Event>, (event: Event) -> CoroutineContext>(\n            Pair(PlayerJoinEvent::class.java) {\n                require(it is PlayerJoinEvent)\n                plugin.entityDispatcher(it.player) // For a player event, the dispatcher is always player related.\n            },\n            Pair(PlayerQuitEvent::class.java) {\n                require(it is PlayerQuitEvent)\n                plugin.entityDispatcher(it.player)\n            }\n        )\n        server.pluginManager.registerSuspendingEvents(PlayerDataListener(database), this, eventDispatcher)\n    }\n\n    override suspend fun onDisableAsync() {\n        // Minecraft Main Thread\n    }\n}\n

    Instead of using addListener, use the provided extension method addSuspendingListener to allow suspendable functions in your listener. Please notice, that timing measurements are no longer accurate for suspendable functions.

    import com.github.shynixn.mccoroutine.minestom.addSuspendingListener\nimport com.github.shynixn.mccoroutine.minestom.launch\nimport com.github.shynixn.mccoroutine.minestom.sample.extension.impl.Database\nimport com.github.shynixn.mccoroutine.minestom.sample.extension.impl.PlayerDataListener\nimport net.minestom.server.MinecraftServer\nimport net.minestom.server.event.player.PlayerLoginEvent\n\nfun main(args: Array<String>) {\n    val minecraftServer = MinecraftServer.init() \n    minecraftServer.launch {\n        val database = Database()\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n\n        val listener = PlayerDataListener(database)\n        MinecraftServer.getGlobalEventHandler()\n            .addSuspendingListener(minecraftServer, PlayerLoginEvent::class.java) { e ->\n                listener.onPlayerJoinEvent(e)\n            }\n    }\n\n    minecraftServer.start(\"0.0.0.0\", 25565)\n}\n

    Instead of using registerListeners, use the provided extension method registerSuspendingListeners to allow suspendable functions in your listener. Please notice, that timing measurements are no longer accurate for suspendable functions.

    import com.github.shynixn.mccoroutine.sponge.SuspendingPluginContainer\nimport com.github.shynixn.mccoroutine.sponge.registerSuspendingListeners\nimport com.google.inject.Inject\nimport org.spongepowered.api.Sponge\nimport org.spongepowered.api.event.Listener\nimport org.spongepowered.api.event.game.state.GameStartedServerEvent\nimport org.spongepowered.api.event.game.state.GameStoppingServerEvent\nimport org.spongepowered.api.plugin.Plugin\nimport org.spongepowered.api.plugin.PluginContainer\n\n@Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Sponge.\"\n)\nclass MCCoroutineSamplePlugin {\n    private val database = Database()\n    @Inject\n    private lateinit var suspendingPluginContainer: SuspendingPluginContainer\n    @Inject\n    private lateinit var pluginContainer : PluginContainer\n\n    @Listener\n    suspend fun onEnable(event: GameStartedServerEvent) {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n        Sponge.getEventManager().registerSuspendingListeners(pluginContainer, PlayerDataListener(database))\n    }\n\n    @Listener\n    suspend fun onDisable(event: GameStoppingServerEvent) {\n        // Minecraft Main Thread\n    }\n}\n

    Instead of using register, use the provided extension method registerSuspend to allow suspendable functions in your listener. There are also method overloads for functional style listeners.

    import com.github.shynixn.mccoroutine.velocity.SuspendingPluginContainer\nimport com.github.shynixn.mccoroutine.velocity.registerSuspend\nimport com.google.inject.Inject\nimport com.velocitypowered.api.event.Subscribe\nimport com.velocitypowered.api.event.proxy.ProxyInitializeEvent\nimport com.velocitypowered.api.plugin.Plugin\nimport com.velocitypowered.api.proxy.ProxyServer\n\n@Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Velocity.\"\n)\nclass MCCoroutineSamplePlugin {\n    private val database = Database()\n\n    @Inject\n    lateinit var proxyServer: ProxyServer\n\n    @Inject\n    constructor(suspendingPluginContainer: SuspendingPluginContainer) {\n        suspendingPluginContainer.initialize(this)\n    }\n\n    @Subscribe\n    suspend fun onProxyInitialization(event: ProxyInitializeEvent) {\n        // Velocity Thread Pool\n        database.createDbIfNotExist()\n        proxyServer.eventManager.registerSuspend(this, PlayerDataListener(database))\n    }\n}\n
    "},{"location":"listener/#test-the-listener","title":"Test the Listener","text":"

    Join and leave your server to observe getDataFromPlayer and saveData messages getting printed to your server log.

    "},{"location":"plugin/","title":"Suspending Plugin","text":"

    This guide explains how Kotlin Coroutines can be used in minecraft plugins in various ways using MCCoroutine. For this, a new plugin is developed from scratch to handle asynchronous and synchronous code.

    Important

    Make sure you have already installed MCCoroutine. See Installation for details.

    "},{"location":"plugin/#plugin-main-class","title":"Plugin Main class","text":"

    MCCoroutine does not need to be called explicitly in your plugin main class. It is started implicitly when you use it for the first time and disposed automatically when you reload your plugin.

    BukkitBungeeCordFabricFoliaMinestomSpongeVelocity

    The first decision for Bukkit API based plugins is to decide between JavaPlugin or SuspendingJavaPlugin, which is a new base class extending JavaPlugin.

    If you want to perform async operations or call other suspending functions from your plugin class, go with the newly available type SuspendingJavaPlugin otherwise use JavaPlugin.

    import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    override suspend fun onEnableAsync() {\n        // Minecraft Main Thread\n    }\n\n    override suspend fun onDisableAsync() {\n        // Minecraft Main Thread\n    }\n}\n

    How onEnableAsync works

    The implementation which calls the onEnableAsync function manipulates the Bukkit Server implementation in the following way: If a context switch is made, it blocks the entire minecraft main thread until the context is given back. This means, in this method, you can switch contexts as you like but the plugin is not considered enabled until the context is given back. It allows for a clean startup as the plugin is not considered \"enabled\" until the context is given back. Other plugins which are already enabled, may or may not already perform work in the background. Plugins, which may get enabled in the future, wait until this plugin is enabled.

    The first decision for BungeeCord API based plugins is to decide between Plugin or SuspendingPlugin, which is a new base class extending Plugin.

    If you want to perform async operations or call other suspending functions from your plugin class, go with the newly available type SuspendingPlugin otherwise use Plugin.

    import com.github.shynixn.mccoroutine.bungeecord.SuspendingPlugin\n\nclass MCCoroutineSamplePlugin : SuspendingPlugin() {\n    override suspend fun onEnableAsync() {\n        // BungeeCord Startup Thread\n    }\n\n    override suspend fun onDisableAsync() {\n        // BungeeCord Shutdown Thread (Not the same as the startup thread)\n    }\n}\n

    How onEnableAsync works

    The implementation which calls the onEnableAsync function manipulates the BungeeCord Server implementation in the following way: If a context switch is made, it blocks the entire bungeecord startup thread until the context is given back. This means, in this method, you can switch contexts as you like but the plugin is not considered enabled until the context is given back. It allows for a clean startup as the plugin is not considered \"enabled\" until the context is given back. Other plugins which are already enabled, may or may not already perform work in the background. Plugins, which may get enabled in the future, wait until this plugin is enabled.

    MCCoroutine for Fabric does not have an dependency on Minecraft itself, therefore it is version independent from Minecraft. It only depends on the Fabric Api. This however means, we need to manually setup and dispose MCCoroutine. Register the SERVER_STARTING event and connect the native Minecraft Scheduler with MCCoroutine using an Executor. Dispose MCCoroutine in SERVER_STOPPING.

    class MCCoroutineSampleServerMod : DedicatedServerModInitializer {\n    override fun onInitializeServer() {\n        ServerLifecycleEvents.SERVER_STARTING.register(ServerLifecycleEvents.ServerStarting { server ->\n            // Connect Native Minecraft Scheduler and MCCoroutine.\n            mcCoroutineConfiguration.minecraftExecutor = Executor { r ->\n                server.submitAndJoin(r)\n            }\n            launch {\n                onServerStarting(server)\n            }\n        })\n\n        ServerLifecycleEvents.SERVER_STOPPING.register { server ->\n            mcCoroutineConfiguration.disposePluginSession()\n        }\n    }\n    /**\n     * MCCoroutine is ready after the server has started.\n     */\n    private suspend fun onServerStarting(server : MinecraftServer) {\n        // Minecraft Main Thread\n        // Your startup code with suspend support\n\n        this.launch {\n            // Launch new corroutines\n        }\n    }\n}\n

    The first decision for Bukkit API based plugins is to decide between JavaPlugin or SuspendingJavaPlugin, which is a new base class extending JavaPlugin.

    If you want to perform async operations or call other suspending functions from your plugin class, go with the newly available type SuspendingJavaPlugin otherwise use JavaPlugin.

    import com.github.shynixn.mccoroutine.folia.SuspendingJavaPlugin\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    override suspend fun onEnableAsync() {\n        // Global Region Thread\n    }\n\n    override suspend fun onDisableAsync() {\n        // Global Region Thread\n    }\n}\n

    How onEnableAsync works

    The implementation which calls the onEnableAsync function manipulates the Bukkit Server implementation in the following way: If a context switch is made, it blocks the entire global region thread until the context is given back. This means, in this method, you can switch contexts as you like but the plugin is not considered enabled until the context is given back. It allows for a clean startup as the plugin is not considered \"enabled\" until the context is given back. Other plugins which are already enabled, may or may not already perform work in the background. Plugins, which may get enabled in the future, wait until this plugin is enabled.

    MCCoroutine can be used on server or on extension level. The example below shows using MCCoroutine on server level. If you are developing an extension, you can use the instance of your Extension instead of the MinecraftServer

    import com.github.shynixn.mccoroutine.minestom.launch\nimport net.minestom.server.MinecraftServer\n\nfun main(args: Array<String>) {\n    val minecraftServer = MinecraftServer.init() \n    minecraftServer.launch {\n        // Suspendable operations   \n    }\n    minecraftServer.start(\"0.0.0.0\", 25565)\n}\n

    The first decision for Sponge API based plugins is to decide, if you want to call other suspending functions from your plugin class. If so, add a field which injects the type SuspendingPluginContainer. This turns your main class into a suspendable listener.

    import com.github.shynixn.mccoroutine.sponge.SuspendingPluginContainer\n@Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Sponge.\"\n)\nclass MCCoroutineSamplePlugin {\n    @Inject\n    private lateinit var suspendingPluginContainer: SuspendingPluginContainer\n\n    @Listener\n    suspend fun onEnable(event: GameStartedServerEvent) {\n        // Minecraft Main Thread\n    }\n\n    @Listener\n    suspend fun onDisable(event: GameStoppingServerEvent) {\n        // Minecraft Main Thread\n    }\n}\n

    MCCoroutine requires to initialize the plugin coroutine scope manually in your plugin main class. This also allows to call suspending functions in your plugin main class.

    import com.github.shynixn.mccoroutine.velocity.SuspendingPluginContainer\n@Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Velocity.\"\n)\nclass MCCoroutineSamplePlugin {\n     @Inject\n    constructor(suspendingPluginContainer: SuspendingPluginContainer) {\n        suspendingPluginContainer.initialize(this)\n    }\n\n    @Subscribe\n    suspend fun onProxyInitialization(event: ProxyInitializeEvent) {\n        // Velocity Thread Pool\n    }\n}\n
    "},{"location":"plugin/#calling-a-database-from-plugin-main-class","title":"Calling a Database from Plugin Main class","text":"

    Create a class containing properties of data, which we want to store into a database.

    class PlayerData(var uuid: UUID, var name: String, var lastJoinDate: Date, var lastQuitDate: Date) {\n}\n

    Create a class Database, which is responsible to store/retrieve this data into/from a database. Here, it is important that we perform all IO calls on async threads and returns on the minecraft main thread.

    BukkitBungeeCordFabricFoliaMinestomSpongeVelocity
    import kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport org.bukkit.entity.Player\nimport java.util.*\n\nclass Database() {\n    suspend fun createDbIfNotExist() {\n        println(\"[createDbIfNotExist] Start on minecraft thread \" + Thread.currentThread().id)\n        withContext(Dispatchers.IO){\n            println(\"[createDbIfNotExist] Creating database on database io thread \" + Thread.currentThread().id)\n            // ... create tables\n        }\n        println(\"[createDbIfNotExist] End on minecraft thread \" + Thread.currentThread().id)\n    }\n\n    suspend fun getDataFromPlayer(player : Player) : PlayerData {\n        println(\"[getDataFromPlayer] Start on minecraft thread \" + Thread.currentThread().id)\n        val playerData = withContext(Dispatchers.IO) {\n            println(\"[getDataFromPlayer] Retrieving player data on database io thread \" + Thread.currentThread().id)\n            // ... get from database by player uuid or create new playerData instance.\n            PlayerData(player.uniqueId, player.name, Date(), Date())\n        }\n\n        println(\"[getDataFromPlayer] End on minecraft thread \" + Thread.currentThread().id)\n        return playerData;\n    }\n\n    suspend fun saveData(player : Player, playerData : PlayerData) {\n        println(\"[saveData] Start on minecraft thread \" + Thread.currentThread().id)\n\n        withContext(Dispatchers.IO){\n            println(\"[saveData] Saving player data on database io thread \" + Thread.currentThread().id)\n            // insert or update playerData\n        }\n\n        println(\"[saveData] End on minecraft thread \" + Thread.currentThread().id)\n    }\n}\n

    Important

    BungeeCord does not have a main thread or minecraft thread. Instead it operates on different types of thread pools. This means, the thread id is not always the same if we suspend an operation. Therefore, it is recommend to print the name of the thread instead of the id to see which threadpool you are currently on.

    import kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport net.md_5.bungee.api.connection.ProxiedPlayer\nimport java.util.*\n\nclass Database() {\n    suspend fun createDbIfNotExist() {\n        println(\"[createDbIfNotExist] Start on any thread \" + Thread.currentThread().name)\n        withContext(Dispatchers.IO){\n            println(\"[createDbIfNotExist] Creating database on database io thread \" + Thread.currentThread().name)\n            // ... create tables\n        }\n        println(\"[createDbIfNotExist] End on bungeecord plugin threadpool \" + Thread.currentThread().name)\n    }   \n\n    suspend fun getDataFromPlayer(player : ProxiedPlayer) : PlayerData {\n        println(\"[getDataFromPlayer] Start on any thread \" + Thread.currentThread().name)\n        val playerData = withContext(Dispatchers.IO) {\n            println(\"[getDataFromPlayer] Retrieving player data on database io thread \" + Thread.currentThread().name)\n            // ... get from database by player uuid or create new playerData instance.\n            PlayerData(player.uniqueId, player.name, Date(), Date())\n        }\n\n        println(\"[getDataFromPlayer] End on bungeecord plugin threadpool \" + Thread.currentThread().name)\n        return playerData;\n    }\n\n    suspend fun saveData(player : ProxiedPlayer, playerData : PlayerData) {\n        println(\"[saveData] Start on any thread \" + Thread.currentThread().name)\n\n        withContext(Dispatchers.IO){\n            println(\"[saveData] Saving player data on database io thread \" + Thread.currentThread().name)\n            // insert or update playerData\n        }\n\n        println(\"[saveData] End on bungeecord plugin threadpool \" + Thread.currentThread().name)\n    }\n}\n
    import kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport net.minecraft.entity.player.PlayerEntity\nimport java.util.*\n\nclass Database() {\n    suspend fun createDbIfNotExist() {\n        println(\"[createDbIfNotExist] Start on minecraft thread \" + Thread.currentThread().id)\n        withContext(Dispatchers.IO){\n            println(\"[createDbIfNotExist] Creating database on database io thread \" + Thread.currentThread().id)\n            // ... create tables\n        }\n        println(\"[createDbIfNotExist] End on minecraft thread \" + Thread.currentThread().id)\n    }\n\n    suspend fun getDataFromPlayer(player: PlayerEntity) : PlayerData {\n        println(\"[getDataFromPlayer] Start on minecraft thread \" + Thread.currentThread().id)\n        val playerData = withContext(Dispatchers.IO) {\n            println(\"[getDataFromPlayer] Retrieving player data on database io thread \" + Thread.currentThread().id)\n            // ... get from database by player uuid or create new playerData instance.\n            PlayerData(player.uuid, player.name.toString(), Date(), Date())\n        }\n\n        println(\"[getDataFromPlayer] End on minecraft thread \" + Thread.currentThread().id)\n        return playerData;\n    }\n\n    suspend fun saveData(player: PlayerEntity, playerData : PlayerData) {\n        println(\"[saveData] Start on minecraft thread \" + Thread.currentThread().id)\n\n        withContext(Dispatchers.IO){\n            println(\"[saveData] Saving player data on database io thread \" + Thread.currentThread().id)\n            // insert or update playerData\n        }\n\n        println(\"[saveData] End on minecraft thread \" + Thread.currentThread().id)\n    }\n}\n
    import kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport org.bukkit.entity.Player\nimport java.util.*\n\nclass Database() {\n    suspend fun createDbIfNotExist() {\n        println(\"[createDbIfNotExist] Start on the caller thread \" + Thread.currentThread().id)\n        withContext(Dispatchers.IO){\n            println(\"[createDbIfNotExist] Creating database on database io thread \" + Thread.currentThread().id)\n            // ... create tables\n        }\n        println(\"[createDbIfNotExist] End on the caller thread \" + Thread.currentThread().id)\n    }\n\n    suspend fun getDataFromPlayer(player : Player) : PlayerData {\n        println(\"[getDataFromPlayer] Start on the caller thread \" + Thread.currentThread().id)\n        val playerData = withContext(Dispatchers.IO) {\n            println(\"[getDataFromPlayer] Retrieving player data on database io thread \" + Thread.currentThread().id)\n            // ... get from database by player uuid or create new playerData instance.\n            PlayerData(player.uniqueId, player.name, Date(), Date())\n        }\n\n        println(\"[getDataFromPlayer] End on the caller thread  \" + Thread.currentThread().id)\n        return playerData;\n    }\n\n    suspend fun saveData(player : Player, playerData : PlayerData) {\n        println(\"[saveData] Start on the caller thread  \" + Thread.currentThread().id)\n\n        withContext(Dispatchers.IO){\n            println(\"[saveData] Saving player data on database io thread \" + Thread.currentThread().id)\n            // insert or update playerData\n        }\n\n        println(\"[saveData] End on the caller thread  \" + Thread.currentThread().id)\n    }\n}\n
    import kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport net.minestom.server.entity.Player\nimport java.util.*\n\nclass Database() {\n    suspend fun createDbIfNotExist() {\n        println(\"[createDbIfNotExist] Start on minecraft thread \" + Thread.currentThread().id)\n        withContext(Dispatchers.IO){\n            println(\"[createDbIfNotExist] Creating database on database io thread \" + Thread.currentThread().id)\n            // ... create tables\n        }\n        println(\"[createDbIfNotExist] End on minecraft thread \" + Thread.currentThread().id)\n    }\n\n    suspend fun getDataFromPlayer(player : Player) : PlayerData {\n        println(\"[getDataFromPlayer] Start on minecraft thread \" + Thread.currentThread().id)\n        val playerData = withContext(Dispatchers.IO) {\n            println(\"[getDataFromPlayer] Retrieving player data on database io thread \" + Thread.currentThread().id)\n            // ... get from database by player uuid or create new playerData instance.\n            PlayerData(player.uuid, player.username, Date(), Date())\n        }\n\n        println(\"[getDataFromPlayer] End on minecraft thread \" + Thread.currentThread().id)\n        return playerData;\n    }\n\n    suspend fun saveData(player : Player, playerData : PlayerData) {\n        println(\"[saveData] Start on minecraft thread \" + Thread.currentThread().id)\n\n        withContext(Dispatchers.IO){\n            println(\"[saveData] Saving player data on database io thread \" + Thread.currentThread().id)\n            // insert or update playerData\n        }\n\n        println(\"[saveData] End on minecraft thread \" + Thread.currentThread().id)\n    }\n}\n
    import kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport org.spongepowered.api.entity.living.player.Player\nimport java.util.*\n\nclass Database() {\n    suspend fun createDbIfNotExist() {\n        println(\"[createDbIfNotExist] Start on minecraft thread \" + Thread.currentThread().id)\n        withContext(Dispatchers.IO){\n            println(\"[createDbIfNotExist] Creating database on database io thread \" + Thread.currentThread().id)\n            // ... create tables\n        }\n        println(\"[createDbIfNotExist] End on minecraft thread \" + Thread.currentThread().id)\n    }\n\n    suspend fun getDataFromPlayer(player : Player) : PlayerData {\n        println(\"[getDataFromPlayer] Start on minecraft thread \" + Thread.currentThread().id)\n        val playerData = withContext(Dispatchers.IO) {\n            println(\"[getDataFromPlayer] Retrieving player data on database io thread \" + Thread.currentThread().id)\n            // ... get from database by player uuid or create new playerData instance.\n            PlayerData(player.uniqueId, player.name, Date(), Date())\n        }\n\n        println(\"[getDataFromPlayer] End on minecraft thread \" + Thread.currentThread().id)\n        return playerData;\n    }\n\n    suspend fun saveData(player : Player, playerData : PlayerData) {\n        println(\"[saveData] Start on minecraft thread \" + Thread.currentThread().id)\n\n        withContext(Dispatchers.IO){\n            println(\"[saveData] Saving player data on database io thread \" + Thread.currentThread().id)\n            // insert or update playerData\n        }\n\n        println(\"[saveData] End on minecraft thread \" + Thread.currentThread().id)\n    }\n}\n

    Important

    Velocity does not have a main thread or minecraft thread. Instead it operates on different types of thread pools. This means, the thread id is not always the same if we suspend an operation. Therefore, it is recommend to print the name of the thread instead of the id to see which threadpool you are currently on.

    import com.velocitypowered.api.proxy.Player\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport java.util.*\n\nclass Database() {\n    suspend fun createDbIfNotExist() {\n        println(\"[createDbIfNotExist] Start on any thread \" + Thread.currentThread().name)\n        withContext(Dispatchers.IO) {\n            println(\"[createDbIfNotExist] Creating database on database io thread \" + Thread.currentThread().name)\n            // ... create tables\n        }\n        println(\"[createDbIfNotExist] End on velocity plugin threadpool \" + Thread.currentThread().name)\n    }\n\n    suspend fun getDataFromPlayer(player: Player): PlayerData {\n        println(\"[getDataFromPlayer] Start on any thread \" + Thread.currentThread().name)\n        val playerData = withContext(Dispatchers.IO) {\n            println(\"[getDataFromPlayer] Retrieving player data on database io thread \" + Thread.currentThread().name)\n            // ... get from database by player uuid or create new playerData instance.\n            PlayerData(player.uniqueId, player.username, Date(), Date())\n        }\n\n        println(\"[getDataFromPlayer] End on velocity plugin threadpool \" + Thread.currentThread().name)\n        return playerData;\n    }\n\n    suspend fun saveData(player: Player, playerData: PlayerData) {\n        println(\"[saveData] Start on any thread \" + Thread.currentThread().name)\n\n        withContext(Dispatchers.IO) {\n            println(\"[saveData] Saving player data on database io thread \" + Thread.currentThread().name)\n            // insert or update playerData\n        }\n\n        println(\"[saveData] End on velocity plugin threadpool \" + Thread.currentThread().name)\n    }\n}\n

    Create a new instance of the database and call it in your main class.

    BukkitBungeeCordFabricFoliaMinestomSpongeVelocity
    import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n    }\n\n    override suspend fun onDisableAsync() {\n    }\n}\n
    import com.github.shynixn.mccoroutine.bungeecord.SuspendingPlugin\n\nclass MCCoroutineSamplePlugin : SuspendingPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // BungeeCord Startup Thread\n        database.createDbIfNotExist()\n    }\n\n    override suspend fun onDisableAsync() {\n        // BungeeCord Shutdown Thread (Not the same as the startup thread)\n    }\n}\n
    class MCCoroutineSampleServerMod : DedicatedServerModInitializer {\n    override fun onInitializeServer() {\n        ServerLifecycleEvents.SERVER_STARTING.register(ServerLifecycleEvents.ServerStarting { server ->\n            // Connect Native Minecraft Scheduler and MCCoroutine.\n            mcCoroutineConfiguration.minecraftExecutor = Executor { r ->\n                server.submitAndJoin(r)\n            }\n            launch {\n                onServerStarting(server)\n            }\n        })\n\n        ServerLifecycleEvents.SERVER_STOPPING.register { server ->\n            mcCoroutineConfiguration.disposePluginSession()\n        }\n    }\n    /**\n     * MCCoroutine is ready after the server has started.\n     */\n    private suspend fun onServerStarting(server : MinecraftServer) {\n        // Minecraft Main Thread\n        val database = Database()\n        database.createDbIfNotExist()\n    }\n}\n
    import com.github.shynixn.mccoroutine.folia.SuspendingJavaPlugin\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // Global Region Thread\n        database.createDbIfNotExist()\n    }\n\n    override suspend fun onDisableAsync() {\n    }\n}\n
    import com.github.shynixn.mccoroutine.minestom.launch\nimport net.minestom.server.MinecraftServer\n\nfun main(args: Array<String>) {\n    val minecraftServer = MinecraftServer.init() \n    minecraftServer.launch {\n        // Minecraft Main Thread\n        val database = Database()\n        database.createDbIfNotExist()\n    }\n    minecraftServer.start(\"0.0.0.0\", 25565)\n}\n
    import com.github.shynixn.mccoroutine.sponge.SuspendingPluginContainer\n@Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Sponge.\"\n)\nclass MCCoroutineSamplePlugin {\n    private val database = Database()\n    @Inject\n    private lateinit var suspendingPluginContainer: SuspendingPluginContainer\n\n    @Listener\n    suspend fun onEnable(event: GameStartedServerEvent) {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n    }\n\n    @Listener\n    suspend fun onDisable(event: GameStoppingServerEvent) {\n        // Minecraft Main Thread\n    }\n}\n

    MCCoroutine requires to initialize the plugin coroutine scope manually in your plugin main class. This also allows to call suspending functions in your plugin main class.

    import com.github.shynixn.mccoroutine.velocity.SuspendingPluginContainer\n@Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Velocity.\"\n)\nclass MCCoroutineSamplePlugin {\n    private val database = Database()\n\n     @Inject\n    constructor(suspendingPluginContainer: SuspendingPluginContainer) {\n        suspendingPluginContainer.initialize(this)\n    }\n\n    @Subscribe\n    suspend fun onProxyInitialization(event: ProxyInitializeEvent) {\n        // Velocity Thread Pool\n        database.createDbIfNotExist()\n    }\n}\n
    "},{"location":"plugin/#test-the-plugin","title":"Test the Plugin","text":"

    Start your server to observe the createDbIfNotExist messages getting printed to your server log. Extend it with real database operations to get familiar with how it works.

    "},{"location":"plugindisable/","title":"Coroutines in onDisable","text":"

    (This site is only relevant for Spigot, CraftBukkit and Paper)

    After moving most of your code to suspend functions, you may want to launch a coroutine in the onDisable or any other function, which gets called, after the plugin has already been disabled.

    "},{"location":"plugindisable/#default-behaviour-shutdownstrategyscheduler","title":"Default Behaviour (ShutdownStrategy=Scheduler)","text":"

    The default behaviour of MCCoroutine is to stop all coroutines immediately, once the BukkitScheduler has been shutdown. This happens automatically and before your onDisable function of your JavaPlugin class gets called.

    If you try the following, you run into the following exception.

    override fun onDisable() {\n    println(\"[onDisable] Is starting on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n    val plugin = this\n\n    plugin.launch {\n        println(\"[onDisable] Simulating data save on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n        Thread.sleep(500)\n    }\n\n    println(\"[onDisable] Is ending on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n}\n
    java.lang.RuntimeException: Plugin MCCoroutine-Sample attempted to start a new coroutine session while being disabled.\n

    This behaviour makes sense, because the BukkitScheduler works in the same way. MCCoroutine is just a smart wrapper for it.

    "},{"location":"plugindisable/#calling-a-suspend-function","title":"Calling a suspend function","text":"

    However, you may have to call a suspend function anyway. This one of the few exceptions were using runBlocking makes sense:

    override fun onDisable() {\n    println(\"[onDisable] Is starting on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n    val plugin = this\n\n    runBlocking {\n        foo()\n    }\n\n    println(\"[onDisable] Is ending on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n}\n\nsuspend fun foo() {\n    println(\"[onDisable] Simulating data save on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n    Thread.sleep(500)\n}\n
    "},{"location":"plugindisable/#manual-behaviour-shutdownstrategymanual","title":"Manual Behaviour (ShutdownStrategy=Manual)","text":"

    The default strategy is the recommend one and you should design your plugin according that.

    However, there may be edge cases, where you need full control over handling remaining coroutine jobs and use minecraftDispatcher or asyncDispatcher after the plugin has been disabled.

    Change the shutdownStrategy in onEnable

    override fun onEnable() {\n    val plugin = this\n    plugin.mcCoroutineConfiguration.shutdownStrategy = ShutdownStrategy.MANUAL\n\n    // Your code ...\n}\n

    Call disposePluginSession after you are finished.

    override fun onDisable() {\n    // Your code ...\n\n    val plugin = this\n    plugin.mcCoroutineConfiguration.disposePluginSession()\n}\n
    "},{"location":"plugindisable/#pluginlaunch-is-back","title":"Plugin.launch is back","text":"

    This allows to use plugin.launch in your onDisable function.

    override fun onDisable() {\n    val plugin = this\n    println(\"[MCCoroutineSamplePlugin/onDisableAsync] Is starting on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n\n    plugin.launch {\n        println(\"[MCCoroutineSamplePlugin/onDisableAsync] Number 1:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n        delay(500)\n        println(\"[MCCoroutineSamplePlugin/onDisableAsync] Number 2:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n    }\n\n    plugin.mcCoroutineConfiguration.disposePluginSession()\n    println(\"[MCCoroutineSamplePlugin/onDisableAsync] Is ending on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n}\n
    [Server thread/INFO]: [MCCoroutine-Sample] Disabling MCCoroutine-Sample\n[Server thread/INFO]: [MCCoroutineSamplePlugin/onDisableAsync] Is starting on Thread:Server thread/55/primaryThread=true\n[Server thread/INFO]: [MCCoroutineSamplePlugin/onDisableAsync] Number 1:Server thread/55/primaryThread=true\n[Server thread/INFO]: [MCCoroutineSamplePlugin/onDisableAsync] Is ending on Thread:Server thread/55/primaryThread=true\n

    However, the message [MCCoroutineSamplePlugin/onDisableAsync] Number 2 will not printed, because plugin.mcCoroutineConfiguration.disposePluginSession() is called first (context switch of delay).

    This means, we need to use runBlocking anyway:

    override fun onDisable() {\n    val plugin = this\n    println(\"[MCCoroutineSamplePlugin/onDisableAsync] Is starting on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n\n    runBlocking {\n        plugin.launch {\n            println(\"[MCCoroutineSamplePlugin/onDisableAsync] Number 1:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n            delay(500)\n            println(\"[MCCoroutineSamplePlugin/onDisableAsync] Number 2:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n        }.join()\n    }\n\n    plugin.mcCoroutineConfiguration.disposePluginSession()\n    println(\"[MCCoroutineSamplePlugin/onDisableAsync] Is ending on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n}\n
    [Server thread/INFO]: [MCCoroutine-Sample] Disabling MCCoroutine-Sample\n[Server thread/INFO]: [MCCoroutineSamplePlugin/onDisableAsync] Is starting on Thread:Server thread/55/primaryThread=true\n[Server thread/INFO]: [MCCoroutineSamplePlugin/onDisableAsync] Number 1:Server thread/55/primaryThread=true\n[kotlinx.coroutines.DefaultExecutor/INFO]: [MCCoroutineSamplePlugin/onDisableAsync] Number 2:kotlinx.coroutines.DefaultExecutor/133/primaryThread=false\n[Server thread/INFO]: [MCCoroutineSamplePlugin/onDisableAsync] Is ending on Thread:Server thread/55/primaryThread=true\n

    This helps, however it is important to notice that the thread executing MCCoroutineSamplePlugin/onDisableAsync] Number 2 is no longer the primary thread even though we are using the plugin.launch scope, which should guarantee this. After the BukkitScheduler has been shutdown, MCCoroutine is no longer able to guarantee any context switches. Depending on your use case, you may or may not care about that.

    Therefore, think twice if you really want to have so much control. You are on your own, if you set the shutdownStrategy to manual.

    "},{"location":"plugindisable/#waiting-for-jobs-to-complete","title":"Waiting for jobs to complete","text":"

    One useful case, where you want to set the shutdownStrategy to manual is to be able to wait for long running jobs to complete before you disable the plugin.

    private var longRunningJob: Job? = null\n\noverride fun onEnable() {\n    val plugin = this\n    plugin.mcCoroutineConfiguration.shutdownStrategy = ShutdownStrategy.MANUAL\n\n    longRunningJob = plugin.launch {\n        delay(10000)\n        println(\"Over\")\n    }\n}\n\noverride fun onDisable() {\n    runBlocking {\n        longRunningJob!!.join()\n    }\n\n    val plugin = this\n    plugin.mcCoroutineConfiguration.disposePluginSession()\n}\n
    [Server thread/INFO]: [MCCoroutine-Sample] Disabling MCCoroutine-Sample\n[kotlinx.coroutines.DefaultExecutor/INFO]: Over\n
    "},{"location":"plugindisable/#waiting-for-all-jobs-to-complete","title":"Waiting for all jobs to complete","text":"

    You can also wait for all of your spawned open jobs to complete.

    override fun onEnable() {\n    val plugin = this\n    plugin.mcCoroutineConfiguration.shutdownStrategy = ShutdownStrategy.MANUAL\n\n    plugin.launch {\n        delay(10000)\n        println(\"Over\")\n    }\n}\n\noverride fun onDisable() {\n    val plugin = this\n\n    runBlocking {\n        plugin.scope.coroutineContext[Job]!!.children.forEach { childJob ->\n            childJob.join()\n        }\n    }\n\n    plugin.mcCoroutineConfiguration.disposePluginSession()\n}\n
    [Server thread/INFO]: [MCCoroutine-Sample] Disabling MCCoroutine-Sample\n[kotlinx.coroutines.DefaultExecutor/INFO]: Over\n
    "},{"location":"tasks/","title":"Suspending Delayed, Repeating Tasks","text":"

    This page explains how you can delay and repeat tasks using Kotlin Coroutines.

    "},{"location":"tasks/#delaying-tasks","title":"Delaying tasks","text":"

    If you are already in a suspend function, you can simply use delay to delay an execution.

    Using delay we can delay the current context (e.g. Main Thread) by some milliseconds, to easily delay actions without blocking the server. delay essentially suspends the current context and continuous after the given time.

    Difference between delay() and Thread.sleep()

    There is a big difference with delay() and Thread.sleep(). Consult the official Kotlin Coroutines documentation for details, however essentially Thread.sleep() blocks the thread for a given time and delay() suspends the thread for a given time. When a thread is suspended, it can do other work (e.g. server handles other operations like players joining or commands) compared to when a thread is blocked, it cannot do other work (e.g. server appears frozen).

    suspend fun sayHello() {\n    println(\"Please say hello in 2 seconds\")\n    delay(2000) // Delay for 2000 milliseconds\n    println(\"hello\")\n}\n

    If you are not in a suspend function, use plugin.launch together with delay.

    fun sayHello() {\n    plugin.launch {\n        println(\"Please say hello in 2 seconds\")\n        delay(2000) // Delay for 2000 milliseconds\n        println(\"hello\")\n    }\n}\n
    "},{"location":"tasks/#delay-ticks","title":"Delay Ticks","text":"

    MCCoroutine offers an extension method to use delay together with Bukkit and Sponge ticks.

    delay(1.ticks)\n

    Prefer using delay(1.ticks) when delaying on the minecraft main thread instead of delay(50). The tick extension function is more accurate than using milliseconds directly. The technical details are explained in this github issue.

    "},{"location":"tasks/#repeating-tasks","title":"Repeating tasks","text":"

    If you are already in a suspend function, you can simply use traditional loops with delay to repeat tasks.

    suspend fun sayHello() {\n    println(\"Please say hello 10 times every 2 seconds\")\n\n    for (i in 0 until 10) {\n        delay(2000) // Delay for 2000 milliseconds\n        println(\"hello\")\n    }\n}\n

    If you are not in a suspend function, use plugin.launch together with delay.

    fun sayHello() {\n    plugin.launch {\n        println(\"Please say hello 10 times every 2 seconds\")\n\n        for (i in 0 until 10) {\n            delay(2000) // Delay for 2000 milliseconds\n            println(\"hello\")\n        }\n    }\n}\n
    "},{"location":"tasks/#creating-a-minigame-using-delay-bukkit","title":"Creating a Minigame using delay (Bukkit)","text":"

    One example where delay is really useful is when creating minigames. It makes the contract of minigame classes very easy to understand. Let's start by implementing a basic minigame class.

    The first example shows a countdown in the start function of the minigame.

    import kotlinx.coroutines.delay\nimport org.bukkit.entity.Player\n\nclass MiniGame {\n    private var isStarted = false;\n    private var players = HashSet<Player>()\n\n    fun join(player: Player) {\n        if (isStarted) {\n            return\n        }\n\n        players.add(player)\n    }\n\n    suspend fun start() {\n        if (isStarted) {\n            return\n        }\n\n        isStarted = true\n\n        // This loop represents a traditional repeating task which ticks every 1 second and is called 20 times.\n        for (i in 0 until 20) {\n            sendMessageToPlayers(\"Game is starting in ${20 - i} seconds.\")\n            delay(1000)\n        }\n\n        // ... Teleport players to game.\n    }\n\n    private fun sendMessageToPlayers(message: String) {\n        players.forEach { p -> p.sendMessage(message) }\n    }\n}\n
    "},{"location":"tasks/#add-a-run-function-to-the-minigame-class","title":"Add a run function to the MiniGame class","text":"

    We can extend the start method to call run which contains a loop to tick the miniGame every 1 second.

    import kotlinx.coroutines.delay\nimport org.bukkit.entity.Player\n\nclass MiniGame {\n    private var isStarted = false;\n    private var players = HashSet<Player>()\n    private var remainingTime = 0\n\n    //...\n\n    suspend fun start() {\n        if (isStarted) {\n            return\n        }\n\n        isStarted = true\n\n        // This loop represents a traditional repeating task which ticks every 1 second and is called 20 times.\n        for (i in 0 until 20) {\n            sendMessageToPlayers(\"Game is starting in ${20 - i} seconds.\")\n            delay(1000)\n        }\n\n        // ... Teleport players to game.\n        run()\n    }\n\n    private suspend fun run() {\n        remainingTime = 300 // 300 seconds\n\n        while (isStarted && remainingTime > 0) {\n            sendMessageToPlayers(\"Game is over in ${remainingTime} seconds.\")\n            delay(1000)\n            remainingTime--\n        }\n    }\n\n    //...\n}\n
    "},{"location":"tasks/#add-a-function-to-stop-the-game","title":"Add a function to stop the game.","text":"

    An admin should be able to cancel the minigame, which we can implement by a stop function.

    import kotlinx.coroutines.delay\nimport org.bukkit.entity.Player\n\nclass MiniGame {\n    private var isStarted = false;\n    private var players = HashSet<Player>()\n    private var remainingTime = 0\n\n    //...\n\n    private suspend fun run() {\n        remainingTime = 300 // 300 seconds\n\n        while (isStarted && remainingTime > 0) {\n            sendMessageToPlayers(\"Game is over in ${remainingTime} seconds.\")\n            delay(1000)\n            remainingTime--\n        }\n\n        if (!isStarted) {\n            sendMessageToPlayers(\"Game was cancelled by external stop.\")\n        }\n\n        isStarted = false\n        // ... Teleport players back to lobby.\n    }\n\n    fun stop() {\n        if (!isStarted) {\n            return\n        }\n\n        isStarted = false\n    }\n\n    //...\n}\n
    "},{"location":"tasks/#the-full-minigame-class","title":"The full MiniGame class:","text":"
    import kotlinx.coroutines.delay\nimport org.bukkit.entity.Player\n\nclass MiniGame {\n    private var isStarted = false;\n    private var players = HashSet<Player>()\n    private var remainingTime = 0\n\n    fun join(player: Player) {\n        if (isStarted) {\n            return\n        }\n\n        players.add(player)\n    }\n\n    suspend fun start() {\n        if (isStarted) {\n            return\n        }\n\n        isStarted = true\n\n        // This loop represents a traditional repeating task which ticks every 1 second and is called 20 times.\n        for (i in 0 until 20) {\n            sendMessageToPlayers(\"Game is starting in ${20 - i} seconds.\")\n            delay(1000)\n        }\n\n        // ... Teleport players to game.\n        run()\n    }\n\n    private suspend fun run() {\n        remainingTime = 300 // 300 seconds\n\n        while (isStarted && remainingTime > 0) {\n            sendMessageToPlayers(\"Game is over in ${remainingTime} seconds.\")\n            delay(1000)\n            remainingTime--\n        }\n\n        if (!isStarted) {\n            sendMessageToPlayers(\"Game was cancelled by external stop.\")\n        }\n\n        isStarted = false\n        // ... Teleport players back to lobby.\n    }\n\n    fun stop() {\n        if (!isStarted) {\n            return\n        }\n\n        isStarted = false\n    }\n\n    private fun sendMessageToPlayers(message: String) {\n        players.forEach { p -> p.sendMessage(message) }\n    }\n}\n
    "},{"location":"tasks/#connect-javaplugin-listener-and-minigame","title":"Connect JavaPlugin, Listener and MiniGame","text":"
    import org.bukkit.event.EventHandler\nimport org.bukkit.event.Listener\nimport org.bukkit.event.player.PlayerJoinEvent\n\nclass MiniGameListener(private val miniGame: MiniGame) : Listener {\n    @EventHandler\n    suspend fun onPlayerJoinEvent(playerJoinEvent: PlayerJoinEvent) {\n        miniGame.join(playerJoinEvent.player)\n\n        // Just for testing purposes\n        miniGame.start()\n    }\n}\n
    import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin\nimport com.github.shynixn.mccoroutine.bukkit.registerSuspendingEvents\nimport com.github.shynixn.mccoroutine.bukkit.setSuspendingExecutor\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    private val database = Database()\n    private val miniGame = MiniGame()\n\n    override suspend fun onEnableAsync() {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n        server.pluginManager.registerSuspendingEvents(PlayerDataListener(database), this)\n        getCommand(\"playerdata\")!!.setSuspendingExecutor(PlayerDataCommandExecutor(database))\n        server.pluginManager.registerSuspendingEvents(MiniGameListener(miniGame), this)\n    }\n\n    override suspend fun onDisableAsync() {\n        // Minecraft Main Thread\n    }\n}\n
    "},{"location":"tasks/#test-the-minigame","title":"Test the MiniGame","text":"

    Join your server to observe Minigame messages getting printed to your server log.

    "},{"location":"timings/","title":"Timing Measurements","text":"

    (This site is only relevant for Spigot, Paper and CraftBukkit)

    It is often the case, that we want to measure performance using timings https://timings.spigotmc.org/. However, Coroutines do not yield a meaningful task name per default e.g. Task: CancellableContinuationImpl(Single), which makes it hard to debug for performance problems.

    As a solution, it is possible to pass an instance of CoroutineTimings, which is used to give the coroutine and its main thread tasks one meaningful name.

    For example, if you are starting a new coroutine like this:

    plugin.launch {\n    println(\"Please say hello in 2 seconds\")\n    delay(2000) // Delay for 2000 milliseconds\n    println(\"hello\")\n}\n

    Change it to the following:

    plugin.launch(plugin.minecraftDispatcher + object : CoroutineTimings() {}) {\n    println(\"Please say hello in 2 seconds\")\n    delay(2000) // Delay for 2000 milliseconds\n    println(\"hello\")\n}\n
    "},{"location":"timings/#command-executors","title":"Command Executors","text":"

    You can also assign a name to a SuspendingCommandExecutor. For this, add an object called coroutineTimings to your class implementing SuspendingCommandExecutor.

    class MyCommandExecutor : SuspendingCommandExecutor {\n    // Reference used for naming.\n    companion object coroutineTimings : CoroutineTimings()\n\n    override suspend fun onCommand(\n        sender: CommandSender,\n        command: Command,\n        label: String,\n        args: Array<out String>\n    ): Boolean {\n        TODO(\"Not yet implemented\")\n    }\n}\n

    Register the SuspendingCommandExecutor in your plugin class as follows:

    val myCommandExecutor = MyCommandExecutor()\nthis.getCommand(\"mycommand\")!!.setSuspendingExecutor(minecraftDispatcher + MyCommandExecutor.coroutineTimings, myCommandExecutor)\n
    "},{"location":"timings/#events","title":"Events","text":"

    Event measurements are currently not supported by MCCoroutine.

    You can temporarily remove suspend from your event method, use plugin.launch(plugin.minecraftDispatcher + object : CoroutineTimings() {}) {}, measure the time and then readd suspend again.

    "},{"location":"unittests/","title":"Unit-Tests with MCCoroutine","text":"

    (This site is only relevant for Spigot, Paper and CraftBukkit. If you need Unit-Tests support for BungeeCord, Sponge or Velocity, please submit an issue on GitHub)

    If you try to write Unit- or IntegrationTests for your Minecraft plugin, you may need to test suspend functions. These functions may use plugin.launch{...} or other extension methods from MCCoroutine.

    However, extensive mocking is required to get MCCoroutine to work without a running server. As a solution to this problem, a new test dependency is available, which closely simulates MCCoroutine under real conditions. This means you can focus on writing your tests and get a very close feedback to the real environment.

    "},{"location":"unittests/#1-add-the-dependency","title":"1. Add the dependency","text":"

    Do not shade this library into your final plugin.jar file. This should only be available during UnitTests.

    dependencies {\n    testImplementation(\"com.github.shynixn.mccoroutine:mccoroutine-bukkit-test:2.16.0\")\n}\n
    "},{"location":"unittests/#2-create-a-test-method","title":"2. Create a test method","text":"
    import org.junit.jupiter.api.Test\n\nclass MyExampleTest {\n    @Test\n    fun testCase01(){\n    }\n}\n
    "},{"location":"unittests/#3-change-the-mccoroutine-production-driver-to-the-test-driver","title":"3. Change the MCCoroutine Production-Driver to the Test-Driver","text":"
    import org.junit.jupiter.api.Test\n\nclass MyExampleTest {\n\n    init {\n        /**\n         * This switches MCCoroutine to the test implementation of MCCoroutine.\n         * It affects all the tests in the current session.\n         */\n        MCCoroutine.Driver = TestMCCoroutine.Driver\n    }\n\n    @Test\n    fun testCase01(){\n    }\n}\n
    "},{"location":"unittests/#4-use-mccoroutine-in-the-same-way-as-on-your-server","title":"4. Use MCCoroutine in the same way as on your server","text":"
    import org.junit.jupiter.api.Test\n\nclass MyExampleTest {\n\n    init {\n        /**\n         * This switches MCCoroutine to the test implementation of MCCoroutine.\n         * It affects all the tests in the current session.\n         */\n        MCCoroutine.Driver = TestMCCoroutine.Driver\n    }\n\n    @Test\n    fun testCase01(){\n        // Uses the mocking library called Mockito to mock a plugin instance.\n        // It does not matter how you create a plugin instance. Other mocking libraries work as well.\n        val plugin = Mockito.mock(Plugin::class.java)\n\n        // We need to use runBlocking here, otherwise the test exits early\n        runBlocking(plugin.minecraftDispatcher) {\n            println(\"Step 1: \" + Thread.currentThread().name + \"/\" + Thread.currentThread().id)\n\n            withContext(Dispatchers.IO) {\n                println(\"Step 2: \" + Thread.currentThread().name + \"/\" + Thread.currentThread().id)\n                delay(1000)\n            }\n\n            println(\"Step 3: \" + Thread.currentThread().name + \"/\" + Thread.currentThread().id)\n\n            // As always, prefer using Dispatchers.IO instead of plugin.asyncDispatcher.\n            withContext(plugin.asyncDispatcher) {\n                println(\"Step 4: \" + Thread.currentThread().name + \"/\" + Thread.currentThread().id)\n                delay(1000)\n            }\n\n            println(\"Step 5: \" + Thread.currentThread().name + \"/\" + Thread.currentThread().id)\n\n            // Just as an example, we can also use plugin.launch\n            plugin.launch {\n                println(\"Step 6: \" + Thread.currentThread().name + \"/\" + Thread.currentThread().id)\n                delay(1000)\n                println(\"Step 7: \" + Thread.currentThread().name + \"/\" + Thread.currentThread().id)\n            }.join() // Wait until finished.\n        }\n    }\n}\n
    "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Introduction","text":"

    MCCoroutine is a library, which adds extensive support for Kotlin Coroutines for Minecraft Server environments.

    Plugins for game servers and proxy servers often need to perform asynchronous operations (e.g. accessing databases) to be scalable for a large amount of players. MCCoroutine brings the full power of Kotlin Coroutines to them by extending the existing APIs with suspendable commands, events and schedules.

    Kotlin Coroutines

    Asynchronous or non-blocking programming is the new reality. Whether we're creating server-side, desktop or mobile applications, it's important that we provide an experience that is not only fluid from the user's perspective, but scalable when needed. There are many approaches to this problem, and in Kotlin we take a very flexible one by providing Coroutine support at the language level and delegating most of the functionality to libraries, much in line with Kotlin's philosophy. Source: (https://github.com/JetBrains/kotlin-web-site/blob/master/pages/docs/reference/coroutines-overview.md, Date: [09/11/2018], Licence copied to LICENCE).

    Supported Game Servers:

    • CraftBukkit
    • Fabric
    • Folia
    • Minestom
    • Paper
    • Spigot
    • SpongeVanilla
    • SpongeForge

    Supported Proxies:

    • BungeeCord
    • Velocity
    • Waterfall
    "},{"location":"#features","title":"Features","text":"
    • Full implementation of Kotlin Coroutines for Minecraft Servers and Minecraft Proxies
    • Extension functions for already established functions
    • Connection to events, commands, schedulers
    • Coroutine LifeCycle scope for plugins (supports plugin reloading)
    • No NMS
    • Support for Minecraft 1.7 - Latest
    • Support for Java 8 - Latest
    "},{"location":"caching/","title":"Suspending Caches, Background Repeating Tasks","text":"

    This page explains how you can create a lazy-loading cache using Kotlin Coroutines.

    In minecraft plugins, players can perform many actions in a short time period. If plugins want to keep track of them and store every action in the database, creating a new database call for every single action may cause performance problems. Therefore, caches are often implemented, which is a lot easier when using coroutines.

    Important

    The following code examples are for Bukkit, but work in a similar way in other mccoroutine implementations.

    "},{"location":"caching/#implementing-a-cache","title":"Implementing a Cache","text":"

    When taking a look at the Database implementation below, we can observe quite a lot of redundant database accesses when a player rejoins a server in a very short timeframe.

    For this, we put a lazy-loading cache in front of the Database implementation.

    class Database() {\n    fun createDbIfNotExist() {\n        // ... SQL calls\n    }\n\n    fun getDataFromPlayer(player : Player) : PlayerData {\n        // ... SQL calls\n    }\n\n    fun saveData(player : Player, playerData : PlayerData) {\n        // ... SQL calls\n    }\n}\n
    import kotlinx.coroutines.Deferred\nimport org.bukkit.entity.Player\n\nclass DatabaseCache(private val database: Database) {\n    private val cache = HashMap<Player, Deferred<PlayerData>>()\n\n    suspend fun getDataFromPlayer(player: Player): PlayerData {\n    }\n}\n
    "},{"location":"caching/#deferred-playerdata","title":"Deferred PlayerData","text":"

    Instead of using the type PlayerData directly, we use the type Deferred, which is the representation of a non-blocking job which has got PlayerData as result. This means we essentially store the job of retrieving data from the database into the cache.

    import kotlinx.coroutines.*\nimport org.bukkit.entity.Player\nimport org.bukkit.plugin.Plugin\n\nclass DatabaseCache(private val database: Database, private val plugin: Plugin) {\n    private val cache = HashMap<Player, Deferred<PlayerData>>()\n\n    suspend fun getDataFromPlayer(player: Player): PlayerData {\n        return coroutineScope {\n            if (!cache.containsKey(player)) {\n                // Cache miss, create a new job\n                cache[player] = async(Dispatchers.IO) {\n                    database.getDataFromPlayer(player)\n                }\n            }\n\n            // Await suspends the current context until the value of the Deferred job is ready.\n            cache[player]!!.await()\n        }\n    }\n}\n
    "},{"location":"caching/#implementing-cache-clearing","title":"Implementing cache clearing","text":"

    Clearing the cache is as simple as adding a clear method.

    import kotlinx.coroutines.*\nimport org.bukkit.entity.Player\nimport org.bukkit.plugin.Plugin\n\nclass DatabaseCache(private val database: Database, private val plugin: Plugin) {\n    private val cache = HashMap<Player, Deferred<PlayerData>>()\n\n    fun clear() {\n        cache.clear()\n    }\n\n    suspend fun getDataFromPlayer(player: Player): PlayerData {\n        return coroutineScope {\n            if (!cache.containsKey(player)) {\n                // Cache miss, create a new job\n                cache[player] = async(Dispatchers.IO) {\n                    database.getDataFromPlayer(player)\n                }\n            }\n\n            // Await suspends the current context until the value of the ``Deferred`` job is ready.\n            cache[player]!!.await()\n        }\n    }\n}\n
    "},{"location":"caching/#background-repeating-tasks","title":"Background Repeating Tasks","text":"

    After introducing a cache, we can implement a new suspendable background task to save the cached data every 10 minutes.

    import com.github.shynixn.mccoroutine.bukkit.launch\nimport kotlinx.coroutines.*\nimport org.bukkit.entity.Player\nimport org.bukkit.plugin.Plugin\n\nclass DatabaseCache(private val database: Database, private val plugin: Plugin) {\n    private val cache = HashMap<Player, Deferred<PlayerData>>()\n\n    init {\n        // This plugin.launch launches a new scope in the minecraft server context which can be understood\n        // to be a background task and behaves in a similar way to Bukkit.getScheduler().runTask(plugin, Runnable {  })\n        plugin.launch {\n            // This background task is a repeatable task which in this case is an endless loop. The endless loop\n            // is automatically stopped by MCCoroutine once you reload your plugin.\n            while (true) {\n                // Save all cached player data every 10 minutes.\n                for (player in cache.keys.toTypedArray()) {\n                    database.saveData(player, cache[player]!!.await())\n\n                    // Remove player when no longer online\n                    if (!player.isOnline) {\n                        cache.remove(player)\n                    }\n                }\n\n                // Suspending the current context is important in this case otherwise the minecraft thread will only execute this\n                // endless loop as it does not have time to execute other things. Delay gives the thread time to execute other things.\n                delay(10 * 60 * 1000) // 10 minutes\n            }\n        }\n    }\n\n    fun clear() {\n        cache.clear()\n    }\n\n    suspend fun getDataFromPlayer(player: Player): PlayerData {\n        return coroutineScope {\n            if (!cache.containsKey(player)) {\n                // Cache miss, create a new job\n                cache[player] = async(Dispatchers.IO) {\n                    database.getDataFromPlayer(player)\n                }\n            }\n\n            // Await suspends the current context until the value of the ``Deferred`` job is ready.\n            cache[player]!!.await()\n        }\n    }\n}\n
    "},{"location":"commandexecutor/","title":"Suspending Commandexecutors","text":"

    This page explains how you can use Kotlin Coroutines using the suspend key word for command executors in minecraft plugins.

    "},{"location":"commandexecutor/#create-the-commandexecutor","title":"Create the CommandExecutor","text":"BukkitBungeeCordFabricFoliaMinestomSpongeVelocity

    Create a traditional command executor but implement SuspendingCommandExecutor instead of CommandExecutor. Please consider, that the return value true is automatically assumed, if the function is suspended in one branch.

    import com.github.shynixn.mccoroutine.bukkit.SuspendingCommandExecutor\nimport org.bukkit.command.Command\nimport org.bukkit.command.CommandSender\nimport org.bukkit.entity.Player\n\nclass PlayerDataCommandExecutor(private val database: Database) : SuspendingCommandExecutor {\n    override suspend fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {\n        if (sender !is Player) {\n            return false\n        }\n\n        if (args.size == 2 && args[0].equals(\"rename\", true)) {\n            val name = args[1]\n            val playerData = database.getDataFromPlayer(sender)\n            playerData.name = name\n            database.saveData(sender, playerData)\n            return true\n        }\n\n        return false\n    }\n}\n

    Create a traditional command executor but extend from SuspendingCommand instead of Command.

    import com.github.shynixn.mccoroutine.bungeecord.SuspendingCommand\nimport net.md_5.bungee.api.CommandSender\nimport net.md_5.bungee.api.connection.ProxiedPlayer\n\nclass PlayerDataCommandExecutor(private val database: Database) : SuspendingCommand(\"playerdata\") {\n    override suspend fun execute(sender: CommandSender, args: Array<out String>) {\n        if (sender !is ProxiedPlayer) {\n            return\n        }\n\n        if (args.size == 2 && args[0].equals(\"rename\", true)) {\n            val name = args[1]\n            val playerData = database.getDataFromPlayer(sender)\n            playerData.name = name\n            database.saveData(sender, playerData)\n            return\n        }\n    }\n}\n

    Create a traditional command executor but extend from SuspendingCommand instead of SuspendingCommand.

    import com.github.shynixn.mccoroutine.fabric.SuspendingCommand\nimport com.mojang.brigadier.context.CommandContext\nimport net.minecraft.entity.player.PlayerEntity\nimport net.minecraft.server.command.ServerCommandSource\n\nclass PlayerDataCommandExecutor : SuspendingCommand<ServerCommandSource> {\n    override suspend fun run(context: CommandContext<ServerCommandSource>): Int {\n        if (context.source.entity is PlayerEntity) {\n            val sender = context.source.entity as PlayerEntity\n            println(\"[PlayerDataCommandExecutor] Is starting on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}\")\n        }\n\n        return 1\n    }\n}\n

    Folia schedules threads on the region of the entity who executed this command. For the console (globalregion) and command blocks (region) this rule applies as well. Other than that, usage is almost identical to Bukkit.

    import com.github.shynixn.mccoroutine.folia.SuspendingCommandExecutor\nimport org.bukkit.command.Command\nimport org.bukkit.command.CommandSender\nimport org.bukkit.entity.Player\n\nclass PlayerDataCommandExecutor(private val database: Database) : SuspendingCommandExecutor {\n    override suspend fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {\n        // In Folia, this will be the global region thread, or entity execution thread.\n        // In Bukkit, this will be the main thread.\n\n        if (sender !is Player) {\n            return false\n        }\n\n        if (args.size == 2 && args[0].equals(\"rename\", true)) {\n            val name = args[1]\n            withContext(plugin.mainDispatcher) {\n                // Make sure you switch to your plugin main thread before you do anything in your plugin.\n                val playerData = database.getDataFromPlayer(sender)\n                playerData.name = name\n                database.saveData(sender, playerData)\n            }\n\n            return true\n        }\n\n        return false\n    }\n}\n

    Create a traditional command and user server.launch or extension.launch in the addSyntax handler.

    import com.github.shynixn.mccoroutine.minestom.launch\nimport net.minestom.server.MinecraftServer\nimport net.minestom.server.command.builder.Command\nimport net.minestom.server.command.builder.arguments.ArgumentType\nimport net.minestom.server.entity.Player\n\nclass PlayerDataCommandExecutor(private val server: MinecraftServer, private val database: Database) : Command(\"mycommand\") {\n    init {\n        val nameArgument = ArgumentType.String(\"name\")\n        addSyntax({ sender, context ->\n            server.launch {\n                if (sender is Player) {\n                    val name : String = context.get(nameArgument)\n                    val playerData = database.getDataFromPlayer(sender)\n                    playerData.name = name\n                    database.saveData(sender, playerData)\n                }\n            }\n        })\n    }\n}\n

    Create a traditional command executor but extend from SuspendingCommandExecutor instead of CommandExecutor. Please consider, that the return value CommandResult.success() is automatically assumed, if the function is suspended in one branch.

    import com.github.shynixn.mccoroutine.sponge.SuspendingCommandExecutor\nimport org.spongepowered.api.command.CommandResult\nimport org.spongepowered.api.command.CommandSource\nimport org.spongepowered.api.command.args.CommandContext\nimport org.spongepowered.api.entity.living.player.Player\n\nclass PlayerDataCommandExecutor(private val database: Database) : SuspendingCommandExecutor {\n    override suspend fun execute(src: CommandSource, args: CommandContext): CommandResult {\n        if (src !is Player) {\n            return CommandResult.empty()\n        }\n\n        if (args.hasAny(\"name\")) {\n            val name = args.getOne<String>(\"name\").get()\n            val playerData = database.getDataFromPlayer(src)\n            playerData.name = name\n            database.saveData(src, playerData)\n            return CommandResult.success()\n        }\n\n        return CommandResult.empty()\n    }\n}\n

    There are multiple ways to create command executors in Velocity. MCCoroutine provides extensions for both the SimpleCommand and the BrigadierCommand to allow flexibility.

    A SimpleCommand can be created by implementing SuspendingSimpleCommand instead of SimpleCommand

    import com.github.shynixn.mccoroutine.velocity.SuspendingSimpleCommand\nimport com.velocitypowered.api.command.SimpleCommand\nimport com.velocitypowered.api.proxy.Player\n\nclass PlayerDataCommandExecutor(private val database: Database) : SuspendingSimpleCommand {\n    override suspend fun execute(invocation: SimpleCommand.Invocation) {\n        val source = invocation.source()\n\n        if (source !is Player) {\n            return\n        }\n\n        val args = invocation.arguments()\n\n        if (args.size == 2 && args[0].equals(\"rename\", true)) {\n            val name = args[1]\n            val playerData = database.getDataFromPlayer(source)\n            playerData.name = name\n            database.saveData(source, playerData)\n            return\n        }\n    }\n}\n

    A BrigadierCommand can be executed asynchronously using the executesSuspend extension function. More details below.

    "},{"location":"commandexecutor/#register-the-commandexecutor","title":"Register the CommandExecutor","text":"BukkitBungeeCordFabricFoliaMinestomSpongeVelocity

    Instead of using setExecutor, use the provided extension method setSuspendingExecutor to register a command executor.

    Important

    Do not forget to declare the playerdata command in your plugin.yml.

    import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin\nimport com.github.shynixn.mccoroutine.bukkit.registerSuspendingEvents\nimport com.github.shynixn.mccoroutine.bukkit.setSuspendingExecutor\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n        server.pluginManager.registerSuspendingEvents(PlayerDataListener(database), this)\n        getCommand(\"playerdata\")!!.setSuspendingExecutor(PlayerDataCommandExecutor(database))\n    }\n\n    override suspend fun onDisableAsync() {\n        // Minecraft Main Thread\n    }\n}\n

    Instead of using registerCommand, use the provided extension method registerSuspendingCommand to register a command executor.

    Important

    Do not forget to declare the playerdata command in your plugin.yml.

    import com.github.shynixn.mccoroutine.bungeecord.SuspendingPlugin\nimport com.github.shynixn.mccoroutine.bungeecord.registerSuspendingCommand\nimport com.github.shynixn.mccoroutine.bungeecord.registerSuspendingListener\n\nclass MCCoroutineSamplePlugin : SuspendingPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // BungeeCord Startup Thread\n        database.createDbIfNotExist()\n        proxy.pluginManager.registerSuspendingListener(this, PlayerDataListener(database))\n        proxy.pluginManager.registerSuspendingCommand(this, PlayerDataCommandExecutor(database))\n    }\n\n    override suspend fun onDisableAsync() {\n        // BungeeCord Shutdown Thread (Not the same as the startup thread)\n    }\n}\n
    class MCCoroutineSampleServerMod : DedicatedServerModInitializer {\n    override fun onInitializeServer() {\n        ServerLifecycleEvents.SERVER_STARTING.register(ServerLifecycleEvents.ServerStarting { server ->\n            // Connect Native Minecraft Scheduler and MCCoroutine.\n            mcCoroutineConfiguration.minecraftExecutor = Executor { r ->\n                server.submitAndJoin(r)\n            }\n            launch {\n                onServerStarting(server)\n            }\n        })\n\n        ServerLifecycleEvents.SERVER_STOPPING.register { server ->\n            mcCoroutineConfiguration.disposePluginSession()\n        }\n    }\n\n    /**\n     * MCCoroutine is ready after the server has started.\n     */\n    private suspend fun onServerStarting(server : MinecraftServer) {\n        // Register command\n        val command = PlayerDataCommandExecutor()\n        server.commandManager.dispatcher.register(CommandManager.literal(\"mccor\").executesSuspend(this, command))\n    }\n}\n

    Instead of using setExecutor, use the provided extension method setSuspendingExecutor to register a command executor.

    Important

    Do not forget to declare the playerdata command in your plugin.yml.

    import com.github.shynixn.mccoroutine.folia.SuspendingJavaPlugin\nimport com.github.shynixn.mccoroutine.folia.registerSuspendingEvents\nimport com.github.shynixn.mccoroutine.folia.setSuspendingExecutor\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // Global Region Thread.\n        database.createDbIfNotExist()\n        server.pluginManager.registerSuspendingEvents(PlayerDataListener(database), this)\n        getCommand(\"playerdata\")!!.setSuspendingExecutor(PlayerDataCommandExecutor(database))\n    }\n\n    override suspend fun onDisableAsync() {\n         // Global Region Thread.\n    }\n}\n

    Register the command in the same way as a traditional command.

    Instead of using executor, use the provided extension method suspendingExecutor to register a command executor.

    import com.github.shynixn.mccoroutine.sponge.SuspendingPluginContainer\nimport com.github.shynixn.mccoroutine.sponge.registerSuspendingListeners\nimport com.github.shynixn.mccoroutine.sponge.suspendingExecutor\nimport com.google.inject.Inject\nimport org.spongepowered.api.Sponge\nimport org.spongepowered.api.command.args.GenericArguments\nimport org.spongepowered.api.command.spec.CommandSpec\nimport org.spongepowered.api.event.Listener\nimport org.spongepowered.api.event.game.state.GameStartedServerEvent\nimport org.spongepowered.api.event.game.state.GameStoppingServerEvent\nimport org.spongepowered.api.plugin.Plugin\nimport org.spongepowered.api.plugin.PluginContainer\nimport org.spongepowered.api.text.Text\n\n@Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Sponge.\"\n)\nclass MCCoroutineSamplePlugin {\n    private val database = Database()\n\n    @Inject\n    private lateinit var suspendingPluginContainer: SuspendingPluginContainer\n\n    @Inject\n    private lateinit var pluginContainer: PluginContainer\n\n    @Listener\n    suspend fun onEnable(event: GameStartedServerEvent) {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n        Sponge.getEventManager().registerSuspendingListeners(pluginContainer, PlayerDataListener(database))\n        val commandSpec = CommandSpec.builder()\n            .description(Text.of(\"Command for operations.\"))\n            .permission(\"mccoroutine.sample\")\n            .arguments(\n                GenericArguments.onlyOne(GenericArguments.string(Text.of(\"name\")))\n            )\n            .suspendingExecutor(pluginContainer, PlayerDataCommandExecutor(database))\n        Sponge.getCommandManager().register(pluginContainer, commandSpec.build(), listOf(\"playerdata\"))\n    }\n\n    @Listener\n    suspend fun onDisable(event: GameStoppingServerEvent) {\n        // Minecraft Main Thread\n    }\n}\n

    Instead of using register, use the provided extension method registerSuspend to register a simple command executor.

    @Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Velocity.\"\n)\nclass MCCoroutineSamplePlugin {\n    private val database = Database()\n\n    @Inject\n    lateinit var proxyServer: ProxyServer\n\n    @Inject\n    constructor(suspendingPluginContainer: SuspendingPluginContainer) {\n        suspendingPluginContainer.initialize(this)\n    }\n\n    @Subscribe\n    suspend fun onProxyInitialization(event: ProxyInitializeEvent) {\n        // Velocity Thread Pool\n        database.createDbIfNotExist()\n        proxyServer.eventManager.registerSuspend(this, PlayerDataListener(database))\n        val meta = proxyServer.commandManager.metaBuilder(\"playerdata\").build()\n\n        // Register SimpleCommand\n        proxyServer.commandManager.registerSuspend(meta, PlayerDataCommandExecutor(database), this)\n\n        // Register BrigadierCommand\n        val helloCommand =\n            LiteralArgumentBuilder.literal<CommandSource>(\"test\")\n                .executesSuspend(this, { context: CommandContext<CommandSource> ->\n                    val message = Component.text(\"Hello World\", NamedTextColor.AQUA)\n                    context.getSource().sendMessage(message)\n                    1 // indicates success\n                })\n                .build()\n        proxyServer.commandManager.register(BrigadierCommand(helloCommand))\n    }\n}\n
    "},{"location":"commandexecutor/#test-the-commandexecutor","title":"Test the CommandExecutor","text":"

    Join your server and use the playerData command to observe getDataFromPlayer and saveData messages getting printed to your server log.

    "},{"location":"coroutine/","title":"Kotlin Coroutines and Minecraft Plugins","text":"

    When starting with Coroutines in Kotlin, you may wonder how you can use them for minecraft plugins and mods. This guide introduces concepts and a production ready API you can use, to start adding coroutines to your project.

    Important

    Make sure you have already installed MCCoroutine. See Installation for details.

    "},{"location":"coroutine/#starting-a-coroutine","title":"Starting a coroutine","text":"

    In order to start a coroutine, you can use the provided plugin.launch {} extension method. This is safe to be called anywhere in your plugin except in onDisable where you need to use runBlocking. However, keep in mind to avoid using runblocking anywhere else in any of your plugins.

    • To enter a coroutine anywhere in your code at any time:
    BukkitBungeeCordFabricFoliaSpongeVelocityMinestom
    import com.github.shynixn.mccoroutine.bukkit.launch\nimport org.bukkit.plugin.Plugin\n\nfun foo() {\n    plugin.launch {\n        // This will always be on the minecraft main thread.\n        // If you have been on the minecraft main thread before calling plugin.launch, this scope is entered immediately without any delay.\n    }\n}\n
    import com.github.shynixn.mccoroutine.bungeecord.launch\nimport net.md_5.bungee.api.plugin.Plugin\n\nfun foo() {\n    plugin.launch {\n        // This a random thread on the bungeeCord threadPool.\n        // If you have been on the bungeeCord threadPool before calling plugin.launch, this scope is executed in the next scheduler tick.\n        // If you pass CoroutineStart.UNDISPATCHED, you can enter this scope in the current tick. This is shown in a code example below.\n    }\n}\n

    Fabric has got 3 lifecycle scopes, the ModInitializer (both client and server) ClientModInitializer (client) and DedicatedServerModInitializer scope. This guide gives only DedicatedServerModInitializer examples but it works in the same way for the other scopes.

    import com.github.shynixn.mccoroutine.fabric.launch\nimport net.fabricmc.api.DedicatedServerModInitializer\n\nfun foo(){\n    mod.launch {\n        // This will always be on the minecraft main thread.\n    }\n}\n

    As Folia brings multithreading to Paper based servers, threading becomes a lot more complicated for plugin developers.

    Important

    You can run mccoroutine-folia in standard Bukkit servers as well. MCCoroutine automatically falls back to the standard Bukkit scheduler if the Folia schedulers are not found and the rules for mccoroutine-bukkit start to apply.

    Important

    If you have been using mccoroutine for Bukkit before, you have to perform some restructuring in your plugin. Simply changing the imports is not enough. plugin.launch {} works differently in Folia compared to Bukkit.

    First, it is important to understand that Folia does not have a server main thread. In order to access minecraft resources you need to use the correct thread for a given resource. For an entity, you need to use the currently assigned thread for that entity. MCCoroutine provides dispatchers for each of these usecases in Folia and automatically falls back to the Bukkit dispatchers when you launch your Folia plugin on a standard Bukkit server.

    However, this does not solve the problem of accessing our own data in our plugins. We do not have a main thread, so we default on accessing our data on the incoming thread. However, sometimes you have to make sure only 1 thread is accessing a resource at a time. This is important for ordering events and avoiding concurrency exceptions. Concurrent collections can help with that but you may still need synchronize access in other places.

    As a solution, MCCoroutine proposes that each plugin gets their own \"main thread\" and corresponding \"mainDispatcher\". It is intended to execute all the stuff the plugin is going to do. Examples for this are retrieving and matching data like having a List<Game> or List<Arena> in minigame plugins. For minecraft actions, like teleporting a player, you start a sub context, computate the result and return it back to your personal main thread. This concepts result into the following code.

    import com.github.shynixn.mccoroutine.folia.launch\nimport org.bukkit.plugin.Plugin\n\nfun foo(entity : Entity) {\n    plugin.launch { // or plugin.launch(plugin.mainDispatcher) {}\n        // Your plugin main thread. If you have already been on your plugin main thread, this scope is entered immidiatly.\n        // Regardless if your are on bukkit or on folia, this is your personal thread and you must not call bukkit methods on it.\n        // Now perform some data access on your plugin data like accessing a repository.\n        val storedEntityDataInDatabase = database.get()\n\n        // Apply the data on the entity thread using the entityDispatcher.\n        // The plugin.entityDispatcher(entity) parameter ensures, that we end up on the scheduler for the entity in the specific region.\n        withContext(plugin.entityDispatcher(entity)) {\n              // In Folia, this will be the correct thread for the given entity.\n              // In Bukkit, this will be the main thread.\n        }\n    }\n}\n
    import com.github.shynixn.mccoroutine.sponge.launch\nimport org.spongepowered.api.plugin.PluginContainer\n\nfun foo() {\n    plugin.launch {\n        // This will always be on the minecraft main thread.\n    }\n}\n
    import com.github.shynixn.mccoroutine.velocity.launch\nimport com.velocitypowered.api.plugin.PluginContainer\n\nfun foo() {\n    plugin.launch {\n        // This will be a random thread on the Velocity threadpool\n    }\n}\n

    Minestom has got 2 lifecycle scopes, the server scope and the extension scope. When this guide talks about a plugin, the corresponding class in Minestom is Extension or MinecraftServer depending on your usecase.

    Server level (if you are developing a new server):

    import com.github.shynixn.mccoroutine.minestom.launch\nimport net.minestom.server.MinecraftServer\n\nfun foo() {\n    server.launch {\n        // This will always be on the minecraft main thread.\n    }\n}\n

    Extension level (if you are developing a new extension):

    import com.github.shynixn.mccoroutine.minestom.launch\nimport net.minestom.server.extensions.Extension\n\nfun foo() {\n    extension.launch {\n        // This will always be on the minecraft main thread.\n    }\n}\n
    "},{"location":"coroutine/#switching-coroutine-context","title":"Switching coroutine context","text":"

    Later in the Coroutines in Kotlin guide, the terms coroutine-context and dispatchers are explained. A dispatcher determines what thread or threads the corresponding coroutine uses for its execution.

    BukkitBungeeCordFabricFoliaSpongeVelocityMinestom

    In Bukkit, MCCoroutine offers 2 custom dispatchers.

    • minecraftDispatcher (Allows to execute coroutines on the main minecraft thread)
    • asyncDispatcher (Allows to execute coroutines on the async minecraft threadpool)

    Important

    You may also use Dispatchers.IO instead of asyncDispatcher, to reduce the dependency on mccoroutine in your code.

    An example how this works is shown below:

    fun foo() {\n    plugin.launch {\n        // This will always be on the minecraft main thread.\n        // If you have been on the minecraft main thread before calling plugin.launch, this scope is entered immediately without any delay.\n\n        val result1 = withContext(plugin.minecraftDispatcher) {\n            // Perform operations on the minecraft main thread.\n            \"Player is \" // Optionally, return a result.\n        }\n\n        // Here we are automatically back on the main thread again.\n\n        // Prefer using Dispatchers.IO instead of asyncDispatcher \n        val result2 = withContext(Dispatchers.IO) {\n            // Perform operations asynchronously.\n            \" Max\"\n        }\n\n        // Here we are automatically back on the main thread again.\n\n        println(result1 + result2) // Prints 'Player is Max'\n    }\n}\n

    Normally, you do not need to call plugin.minecraftDispatcher in your code. Instead, you are guaranteed to be always on the minecraft main thread in the plugin.launch{} scope and use sub coroutines (e.g. withContext) to perform asynchronous operations. Such a case can be found below:

    // This is a Bukkit example, but it works in the same way in every other framework.\n@EventHandler\nfun onPlayerJoinEvent(event: PlayerJoinEvent) {\n    plugin.launch {\n        // This will always be on the minecraft main thread.\n        // A PlayerJoinEvent arrives on the main thread, therefore this scope is entered immediately without any delay.\n\n        val name = event.player.name\n        val listOfFriends = withContext(Dispatchers.IO) {\n            // IO Thread\n            val friendNames = Files.readAllLines(Paths.get(\"$name.txt\"))\n            friendNames\n        }\n\n        // Main Thread\n        val friendText = listOfFriends.joinToString(\", \")\n        event.player.sendMessage(\"My friends are: $friendText\")\n    }\n}\n

    In BungeeCord, MCCoroutine offers 1 custom dispatcher.

    • bungeeCordDispatcher (Allows to execute coroutines on the bungeeCord threadpool)

    An example how this works is shown below:

    fun foo() {\n    plugin.launch {\n        // This a random thread on the bungeeCord threadPool.\n        // If you have been on the bungeeCord threadPool before calling plugin.launch, this scope is executed in the next scheduler tick.\n        // If you pass CoroutineStart.UNDISPATCHED, you can enter this scope in the current tick. This is shown in a code example below.\n\n        val result = withContext(Dispatchers.IO) {\n              // Perform operations asynchronously.\n            \"Playxer is Max\"\n        }\n\n        // Here we are automatically back on a new random thread on the bungeeCord threadPool.\n        println(result) // Prints 'Player is Max'\n    }\n}\n
    fun foo() {\n    plugin.launch(start = CoroutineStart.UNDISPATCHED) {\n        // This is the same thread before calling plugin.launch\n\n        val result = withContext(Dispatchers.IO) {\n            // Perform operations asynchronously.\n            \"Playxer is Max\"\n        }\n\n        // Here we are automatically back on a new random thread on the bungeeCord threadPool.\n        println(result) // Prints 'Player is Max'\n    }\n}\n

    TBD

    In Folia, MCCoroutine offers 5 custom dispatchers.

    • mainDispatcher (Your personal plugin main thread, allows to execute coroutines on it)
    • globalRegionDispatcher (Allows to execute coroutines on the global region. e.g. Global Game Rules)
    • regionDispatcher (Allows to execute coroutines on a specific location in a world)
    • entityDispatcher (Allows to execute coroutines on a specific entity)
    • asyncDispatcher (Allows to execute coroutines on the async thread pool)

    An example how this works is shown below:

    fun foo(location: Location)) {\n    plugin.launch {\n        // Ensures that you are now on your plugin thread.\n\n        val resultBlockType = withContext(plugin.regionDispatcher(location)) {\n            // In Folia, this will be the correct thread for the given location\n            // In Bukkit, this will be the minecraft thread.\n            getTypeOfBlock()\n        }\n\n        myBlockTypeList.add(resultBlockType)\n\n        withContext(plugin.asyncDispatcher) {\n            // save myBlockTypeList to file.\n        }\n    }\n}\n

    TBD

    TBD

    TBD

    "},{"location":"coroutine/#plugin-launch-execution-order","title":"Plugin launch Execution order","text":"

    If you use plugin.launch, it is important to understand the execution order.

    // This is a Bukkit example, but it works in the same way in every other framework.\nclass Foo(private val plugin: Plugin) {\n\n    fun bar() {\n        // Main Thread\n        // If you have been on the minecraft main thread before calling plugin.launch, this scope is entered immediately without any delay.\n        println(\"I am first\")\n\n        val job = plugin.launch {\n            println(\"I am second\") // The context is not suspended when switching to the same suspendable context.\n            delay(1000)\n            println(\"I am fourth\") // The context is given back after 1000 milliseconds and continuous here.\n            bob()\n        }\n\n        // When calling delay the suspendable context is suspended and the original context immediately continuous here.\n        println(\"I am third\")\n    }\n\n    private suspend fun bob() {\n        println(\"I am fifth\")\n    }\n}\n
    \"I am first\"\n\"I am second\"\n\"I am third\"\n\"I am fourth\"\n\"I am fifth\"\n
    "},{"location":"coroutine/#coroutines-everywhere","title":"Coroutines everywhere","text":"

    Using plugin.launch{}is valuable if you migrate existing plugins to use coroutines. However, if you write a new plugin from scratch, you may consider using convenience integrations provided by MCCoroutine such as:

    • Suspending Plugin
    • Suspending Listeners
    • Suspending CommandExecutors
    "},{"location":"exception/","title":"Exception Handling","text":"

    MCCoroutine implements exception handling as explained by the official Coroutine docs.

    If an exception is not caught (e.g. an exception is thrown in a suspendable commandexecutor or listener), the exception is propagated upwards to MCCoroutine.

    "},{"location":"exception/#default-exception-behaviour","title":"Default Exception Behaviour","text":"

    By default, MCCoroutine logs every exception except CoroutineCancellation, which is thrown when a job is cancelled.

    logger.log(\n    Level.SEVERE,\n    \"This is not an error of MCCoroutine! See sub exception for details.\",\n    exception\n)\n
    "},{"location":"exception/#custom-exception-behaviour","title":"Custom Exception Behaviour","text":"

    You can handle exceptions by yourself by listening to the MCCoroutineExceptionEvent. This event is sent to the event bus of the minecraft frame work (e.g. Bukkit, Sponge, BungeeCord) and can be used for logging. The following points should be considered:

    • The event arrives at the main thread in Bukkit, Sponge, Minestom. In Folia, it arrives on the globalRegionThread.
    • The event is also called for CoroutineCancellation
    • Exceptions arrive for every plugin using MCCoroutine. Check if event.plugin equals your plugin.
    • You can cancel the event to disable logging the event with the default exception behaviour
    • You can make this event a suspend function, however put a try-catch over the entire function. Otherwise, any exception which occur while logging the original exception could stack indefinitely which eventually causes a OutOfMemoryException
    "},{"location":"faq/","title":"FAQ","text":"

    This page explains the most common questions regarding MCCoroutine.

    "},{"location":"faq/#how-is-mccoroutine-implemented","title":"How is MCCoroutine implemented?","text":"

    MCCoroutine simply wraps the existing schedulers of the minecraft frameworks. For example, when you suspend a function using withContext, MCCoroutine sends new tasks to the Bukkit scheduler if necessary. Every consideration about Bukkit schedulers applies to MCCoroutine as well.

    "},{"location":"faq/#does-mccoroutine-need-more-ram","title":"Does MCCoroutine need more RAM?","text":"

    MCCoroutine does not create any resources like threads or threadPools. This means MCCoroutine does not have any overhead. However, Kotlin Coroutines contains additional thread pools which may increase memory usage slightly. Take a look the the official Kotlin Coroutine docs for details.

    "},{"location":"faq/#are-suspendable-listenerscommand-executors-slower","title":"Are Suspendable Listeners/Command Executors slower?","text":"

    No, they are as fast as ordinary listeners and command executors. The registration of them is slightly slower because reflection calls are used to create them. Once players join the server and events arrive, they are the same speed.

    "},{"location":"faq/#how-to-cancel-all-running-jobs","title":"How to cancel all running jobs?","text":"

    You can control the behaviour of the coroutine using plugin.scope.

    plugin.scope.coroutineContext.cancelChildren()\n
    "},{"location":"faq/#how-to-cancel-suspendable-events","title":"How to cancel suspendable events?","text":"

    The following example is not possible. You cannot cancel events after you have suspended the context for the very first time. The event has already happened, and the outcome has already been decided.

    @EventHandler\nsuspend fun onPlayerInteractEvent(event: PlayerInteractEvent) {\n    withContext(Dispatchers.IO){\n        // e.g. read file/database\n        delay(50)\n    }\n    // Cancellation is not possible at this point.\n    event.isCancelled = true;\n}\n

    Cancelling events before the first suspension is still possible.

    @EventHandler\nsuspend fun onPlayerInteractEvent(event: PlayerInteractEvent) {\n    // Cancellation is possible at this point.\n    event.isCancelled = true;\n\n    withContext(Dispatchers.IO){\n        // e.g. read file/database\n        delay(50)\n    }\n}\n
    "},{"location":"installation/","title":"Getting Started","text":"

    In order to use the MCCoroutine Kotlin API, you need to include the following libraries into your project.

    "},{"location":"installation/#add-mccoroutine-libraries","title":"Add MCCoroutine Libraries","text":"BukkitBungeeCordFabricFoliaMinestomSpongeVelocity
    dependencies {\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-bukkit-api:2.16.0\")\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-bukkit-core:2.16.0\")\n}\n
    dependencies {\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-bungeecord-api:2.16.0\")\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-bungeecord-core:2.16.0\")\n}\n
    dependencies {\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-fabric-api:2.16.0\")\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-fabric-core:2.16.0\")\n}\n
    dependencies {\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-folia-api:2.16.0\")\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-folia-core:2.16.0\")\n}\n
    dependencies {\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-minestom-api:2.16.0\")\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-minestom-core:2.16.0\")\n}\n
    dependencies {\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-sponge-api:2.16.0\")\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-sponge-core:2.16.0\")\n}\n
    dependencies {\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-velocity-api:2.16.0\")\n    implementation(\"com.github.shynixn.mccoroutine:mccoroutine-velocity-core:2.16.0\")\n}\n
    "},{"location":"installation/#add-kotlin-coroutines-libraries","title":"Add Kotlin Coroutines Libraries","text":"

    MCCoroutine builds against Kotlin 1.3.x, however it does not distribute the Kotlin Runtime or Kotlin Coroutines Runtime. This means, you can use any Kotlin version in your plugins. It is even encouraged to always use the latest version.

    Replace 1.x.x with the actual versions.

    dependencies {\n    implementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.x.x\")\n}\n
    "},{"location":"installation/#shade-dependencies","title":"Shade Dependencies","text":"Bukkit Server 1.17 - LatestFoliaOther Server

    plugin.yml

    libraries:\n  - com.github.shynixn.mccoroutine:mccoroutine-bukkit-api:2.16.0\n  - com.github.shynixn.mccoroutine:mccoroutine-bukkit-core:2.16.0\n

    plugin.yml

    libraries:\n  - com.github.shynixn.mccoroutine:mccoroutine-folia-api:2.16.0\n  - com.github.shynixn.mccoroutine:mccoroutine-folia-core:2.16.0\n

    Shade the libraries into your plugin.jar file using gradle or maven.

    "},{"location":"installation/#test-the-plugin","title":"Test the Plugin","text":"

    Try to call launch{} in your onEnable() function in your Plugin class.

    Further help

    Please take a look at the sample plugins (e.g. mccoroutine-bukkit-sample or mccoroutine-sponge-sample) which can be found on Github. A real production plugin using MCCoroutine can be found here.

    "},{"location":"listener/","title":"Suspending Listeners","text":"

    This page explains how you can use Kotlin Coroutines using the suspend key word for listeners in minecraft plugins.

    "},{"location":"listener/#create-the-listener","title":"Create the Listener","text":"

    Create a listener and add suspend to all functions where you perform suspendable operations (e.g. calling other suspendable functions). You can mix suspendable and non suspendable functions in listeners.

    BukkitBungeeCordFabricFoliaMinestomSpongeVelocity
    import org.bukkit.event.EventHandler\nimport org.bukkit.event.Listener\nimport org.bukkit.event.player.PlayerJoinEvent\nimport org.bukkit.event.player.PlayerQuitEvent\nimport java.util.*\n\nclass PlayerDataListener(private val database: Database) : Listener {\n    @EventHandler\n    suspend fun onPlayerJoinEvent(event: PlayerJoinEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.name\n        playerData.lastJoinDate = Date()\n        database.saveData(player, playerData)\n    }\n\n    @EventHandler\n    suspend fun onPlayerQuitEvent(event: PlayerQuitEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.name\n        playerData.lastQuitDate = Date()\n        database.saveData(player, playerData)\n    }\n}\n

    In BungeeCord some events can be handled asynchronously. This allows full control over consuming, processing and resuming events when performing long running operations. When you create a suspend function using MCCoroutine, they automatically handle registerIntent and completeIntent. You do not have to do anything yourself, all suspend functions are automatically processed asynchronously.

    import net.md_5.bungee.api.event.PostLoginEvent\nimport net.md_5.bungee.api.event.ServerDisconnectEvent\nimport net.md_5.bungee.api.plugin.Listener\nimport net.md_5.bungee.event.EventHandler\nimport java.util.*\n\nclass PlayerDataListener(private val database: Database) : Listener {\n    @EventHandler\n    suspend fun onPlayerJoinEvent(event: PostLoginEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.name\n        playerData.lastJoinDate = Date()\n        database.saveData(player, playerData)\n    }\n\n    @EventHandler\n    suspend fun onPlayerQuitEvent(event: ServerDisconnectEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.name\n        playerData.lastQuitDate = Date()\n        database.saveData(player, playerData)\n    }\n}\n
    import net.minecraft.entity.Entity\nimport net.minecraft.entity.player.PlayerEntity\nimport net.minecraft.util.Hand\nimport net.minecraft.util.hit.EntityHitResult\nimport net.minecraft.world.World\nimport java.util.*\n\nclass PlayerDataListener(private val database: Database) {\n      suspend fun onPlayerAttackEvent(\n        player: PlayerEntity,\n        world: World,\n        hand: Hand,\n        entity: Entity,\n        hitResult: EntityHitResult?\n    ) {\n       val playerData = database.getDataFromPlayer(player)\n       playerData.name = player.name.toString()\n       playerData.lastJoinDate = Date()\n       database.saveData(player, playerData)\n    }\n}\n
    import org.bukkit.event.EventHandler\nimport org.bukkit.event.Listener\nimport org.bukkit.event.player.PlayerJoinEvent\nimport org.bukkit.event.player.PlayerQuitEvent\nimport java.util.*\n\nclass PlayerDataListener(private val database: Database) : Listener {\n    @EventHandler\n    suspend fun onPlayerJoinEvent(event: PlayerJoinEvent) {\n        // In Folia, this will be entity thread of the player.\n        // In Bukkit, this will be the main thread.\n        withContext(plugin.mainDispatcher) {\n            // Make sure you switch to your plugin main thread before you do anything in your plugin.\n            val player = event.player\n            val playerData = database.getDataFromPlayer(player)\n            playerData.name = player.name\n            playerData.lastJoinDate = Date()\n            database.saveData(player, playerData)\n        }\n    }\n\n    @EventHandler\n    fun onPlayerQuitEvent(event: PlayerQuitEvent) {\n        plugin.launch {\n            // Make sure you switch to your plugin main thread before you do anything in your plugin.\n            val player = event.player\n            val playerData = database.getDataFromPlayer(player)\n            playerData.name = player.name\n            playerData.lastQuitDate = Date()\n            database.saveData(player, playerData)\n        }\n    }\n}\n
    import net.minestom.server.event.player.PlayerDisconnectEvent\nimport net.minestom.server.event.player.PlayerLoginEvent\nimport java.util.*\n\nclass PlayerDataListener(private val database: Database) {\n    suspend fun onPlayerJoinEvent(event: PlayerLoginEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.username\n        playerData.lastJoinDate = Date()\n        database.saveData(player, playerData)\n    }\n\n    suspend fun onPlayerQuitEvent(event: PlayerDisconnectEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.username\n        playerData.lastQuitDate = Date()\n        database.saveData(player, playerData)\n    }\n}\n
    import org.spongepowered.api.event.Listener\nimport org.spongepowered.api.event.network.ClientConnectionEvent\nimport java.util.*\n\nclass PlayerDataListener(private val database: Database) {\n    @Listener\n    suspend fun onPlayerJoinEvent(event: ClientConnectionEvent.Join) {\n        val player = event.targetEntity\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.name\n        playerData.lastJoinDate = Date()\n        database.saveData(player, playerData)\n    }\n\n    @Listener\n    suspend fun onPlayerQuitEvent(event: ClientConnectionEvent.Disconnect) {\n        val player = event.targetEntity\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.name\n        playerData.lastQuitDate = Date()\n        database.saveData(player, playerData)\n    }\n}\n

    In Velocity events can be handled asynchronously. This allows full control over consuming, processing and resuming events when performing long running operations. When you create a suspend function using MCCoroutine, they automatically handle Continuation and EventTask. You do not have to do anything yourself, all suspend functions are automatically processed asynchronously.

    import com.velocitypowered.api.event.Subscribe\nimport com.velocitypowered.api.event.connection.DisconnectEvent\nimport com.velocitypowered.api.event.connection.PostLoginEvent\nimport java.util.*\n\nclass PlayerDataListener(private val database: Database) {\n    @Subscribe\n    suspend fun onPlayerJoinEvent(event: PostLoginEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.username\n        playerData.lastJoinDate = Date()\n        database.saveData(player, playerData)\n    }\n\n    @Subscribe\n    suspend fun onPlayerQuitEvent(event: DisconnectEvent) {\n        val player = event.player\n        val playerData = database.getDataFromPlayer(player)\n        playerData.name = player.username\n        playerData.lastQuitDate = Date()\n        database.saveData(player, playerData)\n    }\n}\n
    "},{"location":"listener/#register-the-listener","title":"Register the Listener","text":"BukkitBungeeCordFabricFoliaMinestomSpongeVelocity

    Instead of using registerEvents, use the provided extension method registerSuspendingEvents to allow suspendable functions in your listener. Please notice, that timing measurements are no longer accurate for suspendable functions.

    import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin\nimport com.github.shynixn.mccoroutine.bukkit.registerSuspendingEvents\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n        server.pluginManager.registerSuspendingEvents(PlayerDataListener(database), this)\n    }\n\n    override suspend fun onDisableAsync() {\n        // Minecraft Main Thread\n    }\n}\n

    Instead of using registerListener, use the provided extension method registerSuspendingListener to allow suspendable functions in your listener. Please notice, that timing measurements are no longer accurate for suspendable functions.

    import com.github.shynixn.mccoroutine.bungeecord.SuspendingPlugin\nimport com.github.shynixn.mccoroutine.bungeecord.registerSuspendingListener\n\nclass MCCoroutineSamplePlugin : SuspendingPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // BungeeCord Startup Thread\n        database.createDbIfNotExist()\n        proxy.pluginManager.registerSuspendingListener(this, PlayerDataListener(database))\n    }\n\n    override suspend fun onDisableAsync() {\n        // BungeeCord Shutdown Thread (Not the same as the startup thread)\n    }\n}\n
    class MCCoroutineSampleServerMod : DedicatedServerModInitializer {\n    override fun onInitializeServer() {\n        ServerLifecycleEvents.SERVER_STARTING.register(ServerLifecycleEvents.ServerStarting { server ->\n            // Connect Native Minecraft Scheduler and MCCoroutine.\n            mcCoroutineConfiguration.minecraftExecutor = Executor { r ->\n                server.submitAndJoin(r)\n            }\n            launch {\n                onServerStarting(server)\n            }\n        })\n\n        ServerLifecycleEvents.SERVER_STOPPING.register { server ->\n            mcCoroutineConfiguration.disposePluginSession()\n        }\n    }\n\n    /**\n     * MCCoroutine is ready after the server has started.\n     */\n    private suspend fun onServerStarting(server : MinecraftServer) {\n        // Minecraft Main Thread\n        val database = Database()\n        database.createDbIfNotExist()\n\n        val listener = PlayerDataListener(database)\n        val mod = this\n        AttackEntityCallback.EVENT.register(AttackEntityCallback { player, world, hand, entity, hitResult ->\n            mod.launch {\n                listener.onPlayerAttackEvent(player, world, hand, entity, hitResult)\n            }\n            ActionResult.PASS\n        })\n    }\n}\n

    Instead of using registerEvents, use the provided extension method registerSuspendingEvents to allow suspendable functions in your listener. Please notice, that timing measurements are no longer accurate for suspendable functions.

    import com.github.shynixn.mccoroutine.folia.SuspendingJavaPlugin\nimport com.github.shynixn.mccoroutine.folia.registerSuspendingEvents\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n        val plugin = this\n        // MCCoroutine for Folia cannot assume the correct dispatcher per event. You need to define how each event\n        // should find its correct dispatcher in MCCoroutine.\n        val eventDispatcher = mapOf<Class<out Event>, (event: Event) -> CoroutineContext>(\n            Pair(PlayerJoinEvent::class.java) {\n                require(it is PlayerJoinEvent)\n                plugin.entityDispatcher(it.player) // For a player event, the dispatcher is always player related.\n            },\n            Pair(PlayerQuitEvent::class.java) {\n                require(it is PlayerQuitEvent)\n                plugin.entityDispatcher(it.player)\n            }\n        )\n        server.pluginManager.registerSuspendingEvents(PlayerDataListener(database), this, eventDispatcher)\n    }\n\n    override suspend fun onDisableAsync() {\n        // Minecraft Main Thread\n    }\n}\n

    Instead of using addListener, use the provided extension method addSuspendingListener to allow suspendable functions in your listener. Please notice, that timing measurements are no longer accurate for suspendable functions.

    import com.github.shynixn.mccoroutine.minestom.addSuspendingListener\nimport com.github.shynixn.mccoroutine.minestom.launch\nimport com.github.shynixn.mccoroutine.minestom.sample.extension.impl.Database\nimport com.github.shynixn.mccoroutine.minestom.sample.extension.impl.PlayerDataListener\nimport net.minestom.server.MinecraftServer\nimport net.minestom.server.event.player.PlayerLoginEvent\n\nfun main(args: Array<String>) {\n    val minecraftServer = MinecraftServer.init() \n    minecraftServer.launch {\n        val database = Database()\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n\n        val listener = PlayerDataListener(database)\n        MinecraftServer.getGlobalEventHandler()\n            .addSuspendingListener(minecraftServer, PlayerLoginEvent::class.java) { e ->\n                listener.onPlayerJoinEvent(e)\n            }\n    }\n\n    minecraftServer.start(\"0.0.0.0\", 25565)\n}\n

    Instead of using registerListeners, use the provided extension method registerSuspendingListeners to allow suspendable functions in your listener. Please notice, that timing measurements are no longer accurate for suspendable functions.

    import com.github.shynixn.mccoroutine.sponge.SuspendingPluginContainer\nimport com.github.shynixn.mccoroutine.sponge.registerSuspendingListeners\nimport com.google.inject.Inject\nimport org.spongepowered.api.Sponge\nimport org.spongepowered.api.event.Listener\nimport org.spongepowered.api.event.game.state.GameStartedServerEvent\nimport org.spongepowered.api.event.game.state.GameStoppingServerEvent\nimport org.spongepowered.api.plugin.Plugin\nimport org.spongepowered.api.plugin.PluginContainer\n\n@Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Sponge.\"\n)\nclass MCCoroutineSamplePlugin {\n    private val database = Database()\n    @Inject\n    private lateinit var suspendingPluginContainer: SuspendingPluginContainer\n    @Inject\n    private lateinit var pluginContainer : PluginContainer\n\n    @Listener\n    suspend fun onEnable(event: GameStartedServerEvent) {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n        Sponge.getEventManager().registerSuspendingListeners(pluginContainer, PlayerDataListener(database))\n    }\n\n    @Listener\n    suspend fun onDisable(event: GameStoppingServerEvent) {\n        // Minecraft Main Thread\n    }\n}\n

    Instead of using register, use the provided extension method registerSuspend to allow suspendable functions in your listener. There are also method overloads for functional style listeners.

    import com.github.shynixn.mccoroutine.velocity.SuspendingPluginContainer\nimport com.github.shynixn.mccoroutine.velocity.registerSuspend\nimport com.google.inject.Inject\nimport com.velocitypowered.api.event.Subscribe\nimport com.velocitypowered.api.event.proxy.ProxyInitializeEvent\nimport com.velocitypowered.api.plugin.Plugin\nimport com.velocitypowered.api.proxy.ProxyServer\n\n@Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Velocity.\"\n)\nclass MCCoroutineSamplePlugin {\n    private val database = Database()\n\n    @Inject\n    lateinit var proxyServer: ProxyServer\n\n    @Inject\n    constructor(suspendingPluginContainer: SuspendingPluginContainer) {\n        suspendingPluginContainer.initialize(this)\n    }\n\n    @Subscribe\n    suspend fun onProxyInitialization(event: ProxyInitializeEvent) {\n        // Velocity Thread Pool\n        database.createDbIfNotExist()\n        proxyServer.eventManager.registerSuspend(this, PlayerDataListener(database))\n    }\n}\n
    "},{"location":"listener/#test-the-listener","title":"Test the Listener","text":"

    Join and leave your server to observe getDataFromPlayer and saveData messages getting printed to your server log.

    "},{"location":"plugin/","title":"Suspending Plugin","text":"

    This guide explains how Kotlin Coroutines can be used in minecraft plugins in various ways using MCCoroutine. For this, a new plugin is developed from scratch to handle asynchronous and synchronous code.

    Important

    Make sure you have already installed MCCoroutine. See Installation for details.

    "},{"location":"plugin/#plugin-main-class","title":"Plugin Main class","text":"

    MCCoroutine does not need to be called explicitly in your plugin main class. It is started implicitly when you use it for the first time and disposed automatically when you reload your plugin.

    BukkitBungeeCordFabricFoliaMinestomSpongeVelocity

    The first decision for Bukkit API based plugins is to decide between JavaPlugin or SuspendingJavaPlugin, which is a new base class extending JavaPlugin.

    If you want to perform async operations or call other suspending functions from your plugin class, go with the newly available type SuspendingJavaPlugin otherwise use JavaPlugin.

    import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    override suspend fun onEnableAsync() {\n        // Minecraft Main Thread\n    }\n\n    override suspend fun onDisableAsync() {\n        // Minecraft Main Thread\n    }\n}\n

    How onEnableAsync works

    The implementation which calls the onEnableAsync function manipulates the Bukkit Server implementation in the following way: If a context switch is made, it blocks the entire minecraft main thread until the context is given back. This means, in this method, you can switch contexts as you like but the plugin is not considered enabled until the context is given back. It allows for a clean startup as the plugin is not considered \"enabled\" until the context is given back. Other plugins which are already enabled, may or may not already perform work in the background. Plugins, which may get enabled in the future, wait until this plugin is enabled.

    The first decision for BungeeCord API based plugins is to decide between Plugin or SuspendingPlugin, which is a new base class extending Plugin.

    If you want to perform async operations or call other suspending functions from your plugin class, go with the newly available type SuspendingPlugin otherwise use Plugin.

    import com.github.shynixn.mccoroutine.bungeecord.SuspendingPlugin\n\nclass MCCoroutineSamplePlugin : SuspendingPlugin() {\n    override suspend fun onEnableAsync() {\n        // BungeeCord Startup Thread\n    }\n\n    override suspend fun onDisableAsync() {\n        // BungeeCord Shutdown Thread (Not the same as the startup thread)\n    }\n}\n

    How onEnableAsync works

    The implementation which calls the onEnableAsync function manipulates the BungeeCord Server implementation in the following way: If a context switch is made, it blocks the entire bungeecord startup thread until the context is given back. This means, in this method, you can switch contexts as you like but the plugin is not considered enabled until the context is given back. It allows for a clean startup as the plugin is not considered \"enabled\" until the context is given back. Other plugins which are already enabled, may or may not already perform work in the background. Plugins, which may get enabled in the future, wait until this plugin is enabled.

    MCCoroutine for Fabric does not have an dependency on Minecraft itself, therefore it is version independent from Minecraft. It only depends on the Fabric Api. This however means, we need to manually setup and dispose MCCoroutine. Register the SERVER_STARTING event and connect the native Minecraft Scheduler with MCCoroutine using an Executor. Dispose MCCoroutine in SERVER_STOPPING.

    class MCCoroutineSampleServerMod : DedicatedServerModInitializer {\n    override fun onInitializeServer() {\n        ServerLifecycleEvents.SERVER_STARTING.register(ServerLifecycleEvents.ServerStarting { server ->\n            // Connect Native Minecraft Scheduler and MCCoroutine.\n            mcCoroutineConfiguration.minecraftExecutor = Executor { r ->\n                server.submitAndJoin(r)\n            }\n            launch {\n                onServerStarting(server)\n            }\n        })\n\n        ServerLifecycleEvents.SERVER_STOPPING.register { server ->\n            mcCoroutineConfiguration.disposePluginSession()\n        }\n    }\n    /**\n     * MCCoroutine is ready after the server has started.\n     */\n    private suspend fun onServerStarting(server : MinecraftServer) {\n        // Minecraft Main Thread\n        // Your startup code with suspend support\n\n        this.launch {\n            // Launch new corroutines\n        }\n    }\n}\n

    The first decision for Bukkit API based plugins is to decide between JavaPlugin or SuspendingJavaPlugin, which is a new base class extending JavaPlugin.

    If you want to perform async operations or call other suspending functions from your plugin class, go with the newly available type SuspendingJavaPlugin otherwise use JavaPlugin.

    import com.github.shynixn.mccoroutine.folia.SuspendingJavaPlugin\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    override suspend fun onEnableAsync() {\n        // Global Region Thread\n    }\n\n    override suspend fun onDisableAsync() {\n        // Global Region Thread\n    }\n}\n

    How onEnableAsync works

    The implementation which calls the onEnableAsync function manipulates the Bukkit Server implementation in the following way: If a context switch is made, it blocks the entire global region thread until the context is given back. This means, in this method, you can switch contexts as you like but the plugin is not considered enabled until the context is given back. It allows for a clean startup as the plugin is not considered \"enabled\" until the context is given back. Other plugins which are already enabled, may or may not already perform work in the background. Plugins, which may get enabled in the future, wait until this plugin is enabled.

    MCCoroutine can be used on server or on extension level. The example below shows using MCCoroutine on server level. If you are developing an extension, you can use the instance of your Extension instead of the MinecraftServer

    import com.github.shynixn.mccoroutine.minestom.launch\nimport net.minestom.server.MinecraftServer\n\nfun main(args: Array<String>) {\n    val minecraftServer = MinecraftServer.init() \n    minecraftServer.launch {\n        // Suspendable operations   \n    }\n    minecraftServer.start(\"0.0.0.0\", 25565)\n}\n

    The first decision for Sponge API based plugins is to decide, if you want to call other suspending functions from your plugin class. If so, add a field which injects the type SuspendingPluginContainer. This turns your main class into a suspendable listener.

    import com.github.shynixn.mccoroutine.sponge.SuspendingPluginContainer\n@Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Sponge.\"\n)\nclass MCCoroutineSamplePlugin {\n    @Inject\n    private lateinit var suspendingPluginContainer: SuspendingPluginContainer\n\n    @Listener\n    suspend fun onEnable(event: GameStartedServerEvent) {\n        // Minecraft Main Thread\n    }\n\n    @Listener\n    suspend fun onDisable(event: GameStoppingServerEvent) {\n        // Minecraft Main Thread\n    }\n}\n

    MCCoroutine requires to initialize the plugin coroutine scope manually in your plugin main class. This also allows to call suspending functions in your plugin main class.

    import com.github.shynixn.mccoroutine.velocity.SuspendingPluginContainer\n@Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Velocity.\"\n)\nclass MCCoroutineSamplePlugin {\n     @Inject\n    constructor(suspendingPluginContainer: SuspendingPluginContainer) {\n        suspendingPluginContainer.initialize(this)\n    }\n\n    @Subscribe\n    suspend fun onProxyInitialization(event: ProxyInitializeEvent) {\n        // Velocity Thread Pool\n    }\n}\n
    "},{"location":"plugin/#calling-a-database-from-plugin-main-class","title":"Calling a Database from Plugin Main class","text":"

    Create a class containing properties of data, which we want to store into a database.

    class PlayerData(var uuid: UUID, var name: String, var lastJoinDate: Date, var lastQuitDate: Date) {\n}\n

    Create a class Database, which is responsible to store/retrieve this data into/from a database. Here, it is important that we perform all IO calls on async threads and returns on the minecraft main thread.

    BukkitBungeeCordFabricFoliaMinestomSpongeVelocity
    import kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport org.bukkit.entity.Player\nimport java.util.*\n\nclass Database() {\n    suspend fun createDbIfNotExist() {\n        println(\"[createDbIfNotExist] Start on minecraft thread \" + Thread.currentThread().id)\n        withContext(Dispatchers.IO){\n            println(\"[createDbIfNotExist] Creating database on database io thread \" + Thread.currentThread().id)\n            // ... create tables\n        }\n        println(\"[createDbIfNotExist] End on minecraft thread \" + Thread.currentThread().id)\n    }\n\n    suspend fun getDataFromPlayer(player : Player) : PlayerData {\n        println(\"[getDataFromPlayer] Start on minecraft thread \" + Thread.currentThread().id)\n        val playerData = withContext(Dispatchers.IO) {\n            println(\"[getDataFromPlayer] Retrieving player data on database io thread \" + Thread.currentThread().id)\n            // ... get from database by player uuid or create new playerData instance.\n            PlayerData(player.uniqueId, player.name, Date(), Date())\n        }\n\n        println(\"[getDataFromPlayer] End on minecraft thread \" + Thread.currentThread().id)\n        return playerData;\n    }\n\n    suspend fun saveData(player : Player, playerData : PlayerData) {\n        println(\"[saveData] Start on minecraft thread \" + Thread.currentThread().id)\n\n        withContext(Dispatchers.IO){\n            println(\"[saveData] Saving player data on database io thread \" + Thread.currentThread().id)\n            // insert or update playerData\n        }\n\n        println(\"[saveData] End on minecraft thread \" + Thread.currentThread().id)\n    }\n}\n

    Important

    BungeeCord does not have a main thread or minecraft thread. Instead it operates on different types of thread pools. This means, the thread id is not always the same if we suspend an operation. Therefore, it is recommend to print the name of the thread instead of the id to see which threadpool you are currently on.

    import kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport net.md_5.bungee.api.connection.ProxiedPlayer\nimport java.util.*\n\nclass Database() {\n    suspend fun createDbIfNotExist() {\n        println(\"[createDbIfNotExist] Start on any thread \" + Thread.currentThread().name)\n        withContext(Dispatchers.IO){\n            println(\"[createDbIfNotExist] Creating database on database io thread \" + Thread.currentThread().name)\n            // ... create tables\n        }\n        println(\"[createDbIfNotExist] End on bungeecord plugin threadpool \" + Thread.currentThread().name)\n    }   \n\n    suspend fun getDataFromPlayer(player : ProxiedPlayer) : PlayerData {\n        println(\"[getDataFromPlayer] Start on any thread \" + Thread.currentThread().name)\n        val playerData = withContext(Dispatchers.IO) {\n            println(\"[getDataFromPlayer] Retrieving player data on database io thread \" + Thread.currentThread().name)\n            // ... get from database by player uuid or create new playerData instance.\n            PlayerData(player.uniqueId, player.name, Date(), Date())\n        }\n\n        println(\"[getDataFromPlayer] End on bungeecord plugin threadpool \" + Thread.currentThread().name)\n        return playerData;\n    }\n\n    suspend fun saveData(player : ProxiedPlayer, playerData : PlayerData) {\n        println(\"[saveData] Start on any thread \" + Thread.currentThread().name)\n\n        withContext(Dispatchers.IO){\n            println(\"[saveData] Saving player data on database io thread \" + Thread.currentThread().name)\n            // insert or update playerData\n        }\n\n        println(\"[saveData] End on bungeecord plugin threadpool \" + Thread.currentThread().name)\n    }\n}\n
    import kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport net.minecraft.entity.player.PlayerEntity\nimport java.util.*\n\nclass Database() {\n    suspend fun createDbIfNotExist() {\n        println(\"[createDbIfNotExist] Start on minecraft thread \" + Thread.currentThread().id)\n        withContext(Dispatchers.IO){\n            println(\"[createDbIfNotExist] Creating database on database io thread \" + Thread.currentThread().id)\n            // ... create tables\n        }\n        println(\"[createDbIfNotExist] End on minecraft thread \" + Thread.currentThread().id)\n    }\n\n    suspend fun getDataFromPlayer(player: PlayerEntity) : PlayerData {\n        println(\"[getDataFromPlayer] Start on minecraft thread \" + Thread.currentThread().id)\n        val playerData = withContext(Dispatchers.IO) {\n            println(\"[getDataFromPlayer] Retrieving player data on database io thread \" + Thread.currentThread().id)\n            // ... get from database by player uuid or create new playerData instance.\n            PlayerData(player.uuid, player.name.toString(), Date(), Date())\n        }\n\n        println(\"[getDataFromPlayer] End on minecraft thread \" + Thread.currentThread().id)\n        return playerData;\n    }\n\n    suspend fun saveData(player: PlayerEntity, playerData : PlayerData) {\n        println(\"[saveData] Start on minecraft thread \" + Thread.currentThread().id)\n\n        withContext(Dispatchers.IO){\n            println(\"[saveData] Saving player data on database io thread \" + Thread.currentThread().id)\n            // insert or update playerData\n        }\n\n        println(\"[saveData] End on minecraft thread \" + Thread.currentThread().id)\n    }\n}\n
    import kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport org.bukkit.entity.Player\nimport java.util.*\n\nclass Database() {\n    suspend fun createDbIfNotExist() {\n        println(\"[createDbIfNotExist] Start on the caller thread \" + Thread.currentThread().id)\n        withContext(Dispatchers.IO){\n            println(\"[createDbIfNotExist] Creating database on database io thread \" + Thread.currentThread().id)\n            // ... create tables\n        }\n        println(\"[createDbIfNotExist] End on the caller thread \" + Thread.currentThread().id)\n    }\n\n    suspend fun getDataFromPlayer(player : Player) : PlayerData {\n        println(\"[getDataFromPlayer] Start on the caller thread \" + Thread.currentThread().id)\n        val playerData = withContext(Dispatchers.IO) {\n            println(\"[getDataFromPlayer] Retrieving player data on database io thread \" + Thread.currentThread().id)\n            // ... get from database by player uuid or create new playerData instance.\n            PlayerData(player.uniqueId, player.name, Date(), Date())\n        }\n\n        println(\"[getDataFromPlayer] End on the caller thread  \" + Thread.currentThread().id)\n        return playerData;\n    }\n\n    suspend fun saveData(player : Player, playerData : PlayerData) {\n        println(\"[saveData] Start on the caller thread  \" + Thread.currentThread().id)\n\n        withContext(Dispatchers.IO){\n            println(\"[saveData] Saving player data on database io thread \" + Thread.currentThread().id)\n            // insert or update playerData\n        }\n\n        println(\"[saveData] End on the caller thread  \" + Thread.currentThread().id)\n    }\n}\n
    import kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport net.minestom.server.entity.Player\nimport java.util.*\n\nclass Database() {\n    suspend fun createDbIfNotExist() {\n        println(\"[createDbIfNotExist] Start on minecraft thread \" + Thread.currentThread().id)\n        withContext(Dispatchers.IO){\n            println(\"[createDbIfNotExist] Creating database on database io thread \" + Thread.currentThread().id)\n            // ... create tables\n        }\n        println(\"[createDbIfNotExist] End on minecraft thread \" + Thread.currentThread().id)\n    }\n\n    suspend fun getDataFromPlayer(player : Player) : PlayerData {\n        println(\"[getDataFromPlayer] Start on minecraft thread \" + Thread.currentThread().id)\n        val playerData = withContext(Dispatchers.IO) {\n            println(\"[getDataFromPlayer] Retrieving player data on database io thread \" + Thread.currentThread().id)\n            // ... get from database by player uuid or create new playerData instance.\n            PlayerData(player.uuid, player.username, Date(), Date())\n        }\n\n        println(\"[getDataFromPlayer] End on minecraft thread \" + Thread.currentThread().id)\n        return playerData;\n    }\n\n    suspend fun saveData(player : Player, playerData : PlayerData) {\n        println(\"[saveData] Start on minecraft thread \" + Thread.currentThread().id)\n\n        withContext(Dispatchers.IO){\n            println(\"[saveData] Saving player data on database io thread \" + Thread.currentThread().id)\n            // insert or update playerData\n        }\n\n        println(\"[saveData] End on minecraft thread \" + Thread.currentThread().id)\n    }\n}\n
    import kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport org.spongepowered.api.entity.living.player.Player\nimport java.util.*\n\nclass Database() {\n    suspend fun createDbIfNotExist() {\n        println(\"[createDbIfNotExist] Start on minecraft thread \" + Thread.currentThread().id)\n        withContext(Dispatchers.IO){\n            println(\"[createDbIfNotExist] Creating database on database io thread \" + Thread.currentThread().id)\n            // ... create tables\n        }\n        println(\"[createDbIfNotExist] End on minecraft thread \" + Thread.currentThread().id)\n    }\n\n    suspend fun getDataFromPlayer(player : Player) : PlayerData {\n        println(\"[getDataFromPlayer] Start on minecraft thread \" + Thread.currentThread().id)\n        val playerData = withContext(Dispatchers.IO) {\n            println(\"[getDataFromPlayer] Retrieving player data on database io thread \" + Thread.currentThread().id)\n            // ... get from database by player uuid or create new playerData instance.\n            PlayerData(player.uniqueId, player.name, Date(), Date())\n        }\n\n        println(\"[getDataFromPlayer] End on minecraft thread \" + Thread.currentThread().id)\n        return playerData;\n    }\n\n    suspend fun saveData(player : Player, playerData : PlayerData) {\n        println(\"[saveData] Start on minecraft thread \" + Thread.currentThread().id)\n\n        withContext(Dispatchers.IO){\n            println(\"[saveData] Saving player data on database io thread \" + Thread.currentThread().id)\n            // insert or update playerData\n        }\n\n        println(\"[saveData] End on minecraft thread \" + Thread.currentThread().id)\n    }\n}\n

    Important

    Velocity does not have a main thread or minecraft thread. Instead it operates on different types of thread pools. This means, the thread id is not always the same if we suspend an operation. Therefore, it is recommend to print the name of the thread instead of the id to see which threadpool you are currently on.

    import com.velocitypowered.api.proxy.Player\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport java.util.*\n\nclass Database() {\n    suspend fun createDbIfNotExist() {\n        println(\"[createDbIfNotExist] Start on any thread \" + Thread.currentThread().name)\n        withContext(Dispatchers.IO) {\n            println(\"[createDbIfNotExist] Creating database on database io thread \" + Thread.currentThread().name)\n            // ... create tables\n        }\n        println(\"[createDbIfNotExist] End on velocity plugin threadpool \" + Thread.currentThread().name)\n    }\n\n    suspend fun getDataFromPlayer(player: Player): PlayerData {\n        println(\"[getDataFromPlayer] Start on any thread \" + Thread.currentThread().name)\n        val playerData = withContext(Dispatchers.IO) {\n            println(\"[getDataFromPlayer] Retrieving player data on database io thread \" + Thread.currentThread().name)\n            // ... get from database by player uuid or create new playerData instance.\n            PlayerData(player.uniqueId, player.username, Date(), Date())\n        }\n\n        println(\"[getDataFromPlayer] End on velocity plugin threadpool \" + Thread.currentThread().name)\n        return playerData;\n    }\n\n    suspend fun saveData(player: Player, playerData: PlayerData) {\n        println(\"[saveData] Start on any thread \" + Thread.currentThread().name)\n\n        withContext(Dispatchers.IO) {\n            println(\"[saveData] Saving player data on database io thread \" + Thread.currentThread().name)\n            // insert or update playerData\n        }\n\n        println(\"[saveData] End on velocity plugin threadpool \" + Thread.currentThread().name)\n    }\n}\n

    Create a new instance of the database and call it in your main class.

    BukkitBungeeCordFabricFoliaMinestomSpongeVelocity
    import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n    }\n\n    override suspend fun onDisableAsync() {\n    }\n}\n
    import com.github.shynixn.mccoroutine.bungeecord.SuspendingPlugin\n\nclass MCCoroutineSamplePlugin : SuspendingPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // BungeeCord Startup Thread\n        database.createDbIfNotExist()\n    }\n\n    override suspend fun onDisableAsync() {\n        // BungeeCord Shutdown Thread (Not the same as the startup thread)\n    }\n}\n
    class MCCoroutineSampleServerMod : DedicatedServerModInitializer {\n    override fun onInitializeServer() {\n        ServerLifecycleEvents.SERVER_STARTING.register(ServerLifecycleEvents.ServerStarting { server ->\n            // Connect Native Minecraft Scheduler and MCCoroutine.\n            mcCoroutineConfiguration.minecraftExecutor = Executor { r ->\n                server.submitAndJoin(r)\n            }\n            launch {\n                onServerStarting(server)\n            }\n        })\n\n        ServerLifecycleEvents.SERVER_STOPPING.register { server ->\n            mcCoroutineConfiguration.disposePluginSession()\n        }\n    }\n    /**\n     * MCCoroutine is ready after the server has started.\n     */\n    private suspend fun onServerStarting(server : MinecraftServer) {\n        // Minecraft Main Thread\n        val database = Database()\n        database.createDbIfNotExist()\n    }\n}\n
    import com.github.shynixn.mccoroutine.folia.SuspendingJavaPlugin\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    private val database = Database()\n\n    override suspend fun onEnableAsync() {\n        // Global Region Thread\n        database.createDbIfNotExist()\n    }\n\n    override suspend fun onDisableAsync() {\n    }\n}\n
    import com.github.shynixn.mccoroutine.minestom.launch\nimport net.minestom.server.MinecraftServer\n\nfun main(args: Array<String>) {\n    val minecraftServer = MinecraftServer.init() \n    minecraftServer.launch {\n        // Minecraft Main Thread\n        val database = Database()\n        database.createDbIfNotExist()\n    }\n    minecraftServer.start(\"0.0.0.0\", 25565)\n}\n
    import com.github.shynixn.mccoroutine.sponge.SuspendingPluginContainer\n@Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Sponge.\"\n)\nclass MCCoroutineSamplePlugin {\n    private val database = Database()\n    @Inject\n    private lateinit var suspendingPluginContainer: SuspendingPluginContainer\n\n    @Listener\n    suspend fun onEnable(event: GameStartedServerEvent) {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n    }\n\n    @Listener\n    suspend fun onDisable(event: GameStoppingServerEvent) {\n        // Minecraft Main Thread\n    }\n}\n

    MCCoroutine requires to initialize the plugin coroutine scope manually in your plugin main class. This also allows to call suspending functions in your plugin main class.

    import com.github.shynixn.mccoroutine.velocity.SuspendingPluginContainer\n@Plugin(\n    id = \"mccoroutinesample\",\n    name = \"MCCoroutineSample\",\n    description = \"MCCoroutineSample is sample plugin to use MCCoroutine in Velocity.\"\n)\nclass MCCoroutineSamplePlugin {\n    private val database = Database()\n\n     @Inject\n    constructor(suspendingPluginContainer: SuspendingPluginContainer) {\n        suspendingPluginContainer.initialize(this)\n    }\n\n    @Subscribe\n    suspend fun onProxyInitialization(event: ProxyInitializeEvent) {\n        // Velocity Thread Pool\n        database.createDbIfNotExist()\n    }\n}\n
    "},{"location":"plugin/#test-the-plugin","title":"Test the Plugin","text":"

    Start your server to observe the createDbIfNotExist messages getting printed to your server log. Extend it with real database operations to get familiar with how it works.

    "},{"location":"plugindisable/","title":"Coroutines in onDisable","text":"

    (This site is only relevant for Spigot, CraftBukkit and Paper)

    After moving most of your code to suspend functions, you may want to launch a coroutine in the onDisable or any other function, which gets called, after the plugin has already been disabled.

    "},{"location":"plugindisable/#default-behaviour-shutdownstrategyscheduler","title":"Default Behaviour (ShutdownStrategy=Scheduler)","text":"

    The default behaviour of MCCoroutine is to stop all coroutines immediately, once the BukkitScheduler has been shutdown. This happens automatically and before your onDisable function of your JavaPlugin class gets called.

    If you try the following, you run into the following exception.

    override fun onDisable() {\n    println(\"[onDisable] Is starting on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n    val plugin = this\n\n    plugin.launch {\n        println(\"[onDisable] Simulating data save on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n        Thread.sleep(500)\n    }\n\n    println(\"[onDisable] Is ending on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n}\n
    java.lang.RuntimeException: Plugin MCCoroutine-Sample attempted to start a new coroutine session while being disabled.\n

    This behaviour makes sense, because the BukkitScheduler works in the same way. MCCoroutine is just a smart wrapper for it.

    "},{"location":"plugindisable/#calling-a-suspend-function","title":"Calling a suspend function","text":"

    However, you may have to call a suspend function anyway. This one of the few exceptions were using runBlocking makes sense:

    override fun onDisable() {\n    println(\"[onDisable] Is starting on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n    val plugin = this\n\n    runBlocking {\n        foo()\n    }\n\n    println(\"[onDisable] Is ending on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n}\n\nsuspend fun foo() {\n    println(\"[onDisable] Simulating data save on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n    Thread.sleep(500)\n}\n
    "},{"location":"plugindisable/#manual-behaviour-shutdownstrategymanual","title":"Manual Behaviour (ShutdownStrategy=Manual)","text":"

    The default strategy is the recommend one and you should design your plugin according that.

    However, there may be edge cases, where you need full control over handling remaining coroutine jobs and use minecraftDispatcher or asyncDispatcher after the plugin has been disabled.

    Change the shutdownStrategy in onEnable

    override fun onEnable() {\n    val plugin = this\n    plugin.mcCoroutineConfiguration.shutdownStrategy = ShutdownStrategy.MANUAL\n\n    // Your code ...\n}\n

    Call disposePluginSession after you are finished.

    override fun onDisable() {\n    // Your code ...\n\n    val plugin = this\n    plugin.mcCoroutineConfiguration.disposePluginSession()\n}\n
    "},{"location":"plugindisable/#pluginlaunch-is-back","title":"Plugin.launch is back","text":"

    This allows to use plugin.launch in your onDisable function.

    override fun onDisable() {\n    val plugin = this\n    println(\"[MCCoroutineSamplePlugin/onDisableAsync] Is starting on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n\n    plugin.launch {\n        println(\"[MCCoroutineSamplePlugin/onDisableAsync] Number 1:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n        delay(500)\n        println(\"[MCCoroutineSamplePlugin/onDisableAsync] Number 2:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n    }\n\n    plugin.mcCoroutineConfiguration.disposePluginSession()\n    println(\"[MCCoroutineSamplePlugin/onDisableAsync] Is ending on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n}\n
    [Server thread/INFO]: [MCCoroutine-Sample] Disabling MCCoroutine-Sample\n[Server thread/INFO]: [MCCoroutineSamplePlugin/onDisableAsync] Is starting on Thread:Server thread/55/primaryThread=true\n[Server thread/INFO]: [MCCoroutineSamplePlugin/onDisableAsync] Number 1:Server thread/55/primaryThread=true\n[Server thread/INFO]: [MCCoroutineSamplePlugin/onDisableAsync] Is ending on Thread:Server thread/55/primaryThread=true\n

    However, the message [MCCoroutineSamplePlugin/onDisableAsync] Number 2 will not printed, because plugin.mcCoroutineConfiguration.disposePluginSession() is called first (context switch of delay).

    This means, we need to use runBlocking anyway:

    override fun onDisable() {\n    val plugin = this\n    println(\"[MCCoroutineSamplePlugin/onDisableAsync] Is starting on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n\n    runBlocking {\n        plugin.launch {\n            println(\"[MCCoroutineSamplePlugin/onDisableAsync] Number 1:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n            delay(500)\n            println(\"[MCCoroutineSamplePlugin/onDisableAsync] Number 2:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n        }.join()\n    }\n\n    plugin.mcCoroutineConfiguration.disposePluginSession()\n    println(\"[MCCoroutineSamplePlugin/onDisableAsync] Is ending on Thread:${Thread.currentThread().name}/${Thread.currentThread().id}/primaryThread=${Bukkit.isPrimaryThread()}\")\n}\n
    [Server thread/INFO]: [MCCoroutine-Sample] Disabling MCCoroutine-Sample\n[Server thread/INFO]: [MCCoroutineSamplePlugin/onDisableAsync] Is starting on Thread:Server thread/55/primaryThread=true\n[Server thread/INFO]: [MCCoroutineSamplePlugin/onDisableAsync] Number 1:Server thread/55/primaryThread=true\n[kotlinx.coroutines.DefaultExecutor/INFO]: [MCCoroutineSamplePlugin/onDisableAsync] Number 2:kotlinx.coroutines.DefaultExecutor/133/primaryThread=false\n[Server thread/INFO]: [MCCoroutineSamplePlugin/onDisableAsync] Is ending on Thread:Server thread/55/primaryThread=true\n

    This helps, however it is important to notice that the thread executing MCCoroutineSamplePlugin/onDisableAsync] Number 2 is no longer the primary thread even though we are using the plugin.launch scope, which should guarantee this. After the BukkitScheduler has been shutdown, MCCoroutine is no longer able to guarantee any context switches. Depending on your use case, you may or may not care about that.

    Therefore, think twice if you really want to have so much control. You are on your own, if you set the shutdownStrategy to manual.

    "},{"location":"plugindisable/#waiting-for-jobs-to-complete","title":"Waiting for jobs to complete","text":"

    One useful case, where you want to set the shutdownStrategy to manual is to be able to wait for long running jobs to complete before you disable the plugin.

    private var longRunningJob: Job? = null\n\noverride fun onEnable() {\n    val plugin = this\n    plugin.mcCoroutineConfiguration.shutdownStrategy = ShutdownStrategy.MANUAL\n\n    longRunningJob = plugin.launch {\n        delay(10000)\n        println(\"Over\")\n    }\n}\n\noverride fun onDisable() {\n    runBlocking {\n        longRunningJob!!.join()\n    }\n\n    val plugin = this\n    plugin.mcCoroutineConfiguration.disposePluginSession()\n}\n
    [Server thread/INFO]: [MCCoroutine-Sample] Disabling MCCoroutine-Sample\n[kotlinx.coroutines.DefaultExecutor/INFO]: Over\n
    "},{"location":"plugindisable/#waiting-for-all-jobs-to-complete","title":"Waiting for all jobs to complete","text":"

    You can also wait for all of your spawned open jobs to complete.

    override fun onEnable() {\n    val plugin = this\n    plugin.mcCoroutineConfiguration.shutdownStrategy = ShutdownStrategy.MANUAL\n\n    plugin.launch {\n        delay(10000)\n        println(\"Over\")\n    }\n}\n\noverride fun onDisable() {\n    val plugin = this\n\n    runBlocking {\n        plugin.scope.coroutineContext[Job]!!.children.forEach { childJob ->\n            childJob.join()\n        }\n    }\n\n    plugin.mcCoroutineConfiguration.disposePluginSession()\n}\n
    [Server thread/INFO]: [MCCoroutine-Sample] Disabling MCCoroutine-Sample\n[kotlinx.coroutines.DefaultExecutor/INFO]: Over\n
    "},{"location":"tasks/","title":"Suspending Delayed, Repeating Tasks","text":"

    This page explains how you can delay and repeat tasks using Kotlin Coroutines.

    "},{"location":"tasks/#delaying-tasks","title":"Delaying tasks","text":"

    If you are already in a suspend function, you can simply use delay to delay an execution.

    Using delay we can delay the current context (e.g. Main Thread) by some milliseconds, to easily delay actions without blocking the server. delay essentially suspends the current context and continuous after the given time.

    Difference between delay() and Thread.sleep()

    There is a big difference with delay() and Thread.sleep(). Consult the official Kotlin Coroutines documentation for details, however essentially Thread.sleep() blocks the thread for a given time and delay() suspends the thread for a given time. When a thread is suspended, it can do other work (e.g. server handles other operations like players joining or commands) compared to when a thread is blocked, it cannot do other work (e.g. server appears frozen).

    suspend fun sayHello() {\n    println(\"Please say hello in 2 seconds\")\n    delay(2000) // Delay for 2000 milliseconds\n    println(\"hello\")\n}\n

    If you are not in a suspend function, use plugin.launch together with delay.

    fun sayHello() {\n    plugin.launch {\n        println(\"Please say hello in 2 seconds\")\n        delay(2000) // Delay for 2000 milliseconds\n        println(\"hello\")\n    }\n}\n
    "},{"location":"tasks/#delay-ticks","title":"Delay Ticks","text":"

    MCCoroutine offers an extension method to use delay together with Bukkit and Sponge ticks.

    delay(1.ticks)\n

    Prefer using delay(1.ticks) when delaying on the minecraft main thread instead of delay(50). The tick extension function is more accurate than using milliseconds directly. The technical details are explained in this github issue.

    "},{"location":"tasks/#repeating-tasks","title":"Repeating tasks","text":"

    If you are already in a suspend function, you can simply use traditional loops with delay to repeat tasks.

    suspend fun sayHello() {\n    println(\"Please say hello 10 times every 2 seconds\")\n\n    for (i in 0 until 10) {\n        delay(2000) // Delay for 2000 milliseconds\n        println(\"hello\")\n    }\n}\n

    If you are not in a suspend function, use plugin.launch together with delay.

    fun sayHello() {\n    plugin.launch {\n        println(\"Please say hello 10 times every 2 seconds\")\n\n        for (i in 0 until 10) {\n            delay(2000) // Delay for 2000 milliseconds\n            println(\"hello\")\n        }\n    }\n}\n
    "},{"location":"tasks/#creating-a-minigame-using-delay-bukkit","title":"Creating a Minigame using delay (Bukkit)","text":"

    One example where delay is really useful is when creating minigames. It makes the contract of minigame classes very easy to understand. Let's start by implementing a basic minigame class.

    The first example shows a countdown in the start function of the minigame.

    import kotlinx.coroutines.delay\nimport org.bukkit.entity.Player\n\nclass MiniGame {\n    private var isStarted = false;\n    private var players = HashSet<Player>()\n\n    fun join(player: Player) {\n        if (isStarted) {\n            return\n        }\n\n        players.add(player)\n    }\n\n    suspend fun start() {\n        if (isStarted) {\n            return\n        }\n\n        isStarted = true\n\n        // This loop represents a traditional repeating task which ticks every 1 second and is called 20 times.\n        for (i in 0 until 20) {\n            sendMessageToPlayers(\"Game is starting in ${20 - i} seconds.\")\n            delay(1000)\n        }\n\n        // ... Teleport players to game.\n    }\n\n    private fun sendMessageToPlayers(message: String) {\n        players.forEach { p -> p.sendMessage(message) }\n    }\n}\n
    "},{"location":"tasks/#add-a-run-function-to-the-minigame-class","title":"Add a run function to the MiniGame class","text":"

    We can extend the start method to call run which contains a loop to tick the miniGame every 1 second.

    import kotlinx.coroutines.delay\nimport org.bukkit.entity.Player\n\nclass MiniGame {\n    private var isStarted = false;\n    private var players = HashSet<Player>()\n    private var remainingTime = 0\n\n    //...\n\n    suspend fun start() {\n        if (isStarted) {\n            return\n        }\n\n        isStarted = true\n\n        // This loop represents a traditional repeating task which ticks every 1 second and is called 20 times.\n        for (i in 0 until 20) {\n            sendMessageToPlayers(\"Game is starting in ${20 - i} seconds.\")\n            delay(1000)\n        }\n\n        // ... Teleport players to game.\n        run()\n    }\n\n    private suspend fun run() {\n        remainingTime = 300 // 300 seconds\n\n        while (isStarted && remainingTime > 0) {\n            sendMessageToPlayers(\"Game is over in ${remainingTime} seconds.\")\n            delay(1000)\n            remainingTime--\n        }\n    }\n\n    //...\n}\n
    "},{"location":"tasks/#add-a-function-to-stop-the-game","title":"Add a function to stop the game.","text":"

    An admin should be able to cancel the minigame, which we can implement by a stop function.

    import kotlinx.coroutines.delay\nimport org.bukkit.entity.Player\n\nclass MiniGame {\n    private var isStarted = false;\n    private var players = HashSet<Player>()\n    private var remainingTime = 0\n\n    //...\n\n    private suspend fun run() {\n        remainingTime = 300 // 300 seconds\n\n        while (isStarted && remainingTime > 0) {\n            sendMessageToPlayers(\"Game is over in ${remainingTime} seconds.\")\n            delay(1000)\n            remainingTime--\n        }\n\n        if (!isStarted) {\n            sendMessageToPlayers(\"Game was cancelled by external stop.\")\n        }\n\n        isStarted = false\n        // ... Teleport players back to lobby.\n    }\n\n    fun stop() {\n        if (!isStarted) {\n            return\n        }\n\n        isStarted = false\n    }\n\n    //...\n}\n
    "},{"location":"tasks/#the-full-minigame-class","title":"The full MiniGame class:","text":"
    import kotlinx.coroutines.delay\nimport org.bukkit.entity.Player\n\nclass MiniGame {\n    private var isStarted = false;\n    private var players = HashSet<Player>()\n    private var remainingTime = 0\n\n    fun join(player: Player) {\n        if (isStarted) {\n            return\n        }\n\n        players.add(player)\n    }\n\n    suspend fun start() {\n        if (isStarted) {\n            return\n        }\n\n        isStarted = true\n\n        // This loop represents a traditional repeating task which ticks every 1 second and is called 20 times.\n        for (i in 0 until 20) {\n            sendMessageToPlayers(\"Game is starting in ${20 - i} seconds.\")\n            delay(1000)\n        }\n\n        // ... Teleport players to game.\n        run()\n    }\n\n    private suspend fun run() {\n        remainingTime = 300 // 300 seconds\n\n        while (isStarted && remainingTime > 0) {\n            sendMessageToPlayers(\"Game is over in ${remainingTime} seconds.\")\n            delay(1000)\n            remainingTime--\n        }\n\n        if (!isStarted) {\n            sendMessageToPlayers(\"Game was cancelled by external stop.\")\n        }\n\n        isStarted = false\n        // ... Teleport players back to lobby.\n    }\n\n    fun stop() {\n        if (!isStarted) {\n            return\n        }\n\n        isStarted = false\n    }\n\n    private fun sendMessageToPlayers(message: String) {\n        players.forEach { p -> p.sendMessage(message) }\n    }\n}\n
    "},{"location":"tasks/#connect-javaplugin-listener-and-minigame","title":"Connect JavaPlugin, Listener and MiniGame","text":"
    import org.bukkit.event.EventHandler\nimport org.bukkit.event.Listener\nimport org.bukkit.event.player.PlayerJoinEvent\n\nclass MiniGameListener(private val miniGame: MiniGame) : Listener {\n    @EventHandler\n    suspend fun onPlayerJoinEvent(playerJoinEvent: PlayerJoinEvent) {\n        miniGame.join(playerJoinEvent.player)\n\n        // Just for testing purposes\n        miniGame.start()\n    }\n}\n
    import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin\nimport com.github.shynixn.mccoroutine.bukkit.registerSuspendingEvents\nimport com.github.shynixn.mccoroutine.bukkit.setSuspendingExecutor\n\nclass MCCoroutineSamplePlugin : SuspendingJavaPlugin() {\n    private val database = Database()\n    private val miniGame = MiniGame()\n\n    override suspend fun onEnableAsync() {\n        // Minecraft Main Thread\n        database.createDbIfNotExist()\n        server.pluginManager.registerSuspendingEvents(PlayerDataListener(database), this)\n        getCommand(\"playerdata\")!!.setSuspendingExecutor(PlayerDataCommandExecutor(database))\n        server.pluginManager.registerSuspendingEvents(MiniGameListener(miniGame), this)\n    }\n\n    override suspend fun onDisableAsync() {\n        // Minecraft Main Thread\n    }\n}\n
    "},{"location":"tasks/#test-the-minigame","title":"Test the MiniGame","text":"

    Join your server to observe Minigame messages getting printed to your server log.

    "},{"location":"timings/","title":"Timing Measurements","text":"

    (This site is only relevant for Spigot, Paper and CraftBukkit)

    It is often the case, that we want to measure performance using timings https://timings.spigotmc.org/. However, Coroutines do not yield a meaningful task name per default e.g. Task: CancellableContinuationImpl(Single), which makes it hard to debug for performance problems.

    As a solution, it is possible to pass an instance of CoroutineTimings, which is used to give the coroutine and its main thread tasks one meaningful name.

    For example, if you are starting a new coroutine like this:

    plugin.launch {\n    println(\"Please say hello in 2 seconds\")\n    delay(2000) // Delay for 2000 milliseconds\n    println(\"hello\")\n}\n

    Change it to the following:

    plugin.launch(plugin.minecraftDispatcher + object : CoroutineTimings() {}) {\n    println(\"Please say hello in 2 seconds\")\n    delay(2000) // Delay for 2000 milliseconds\n    println(\"hello\")\n}\n
    "},{"location":"timings/#command-executors","title":"Command Executors","text":"

    You can also assign a name to a SuspendingCommandExecutor. For this, add an object called coroutineTimings to your class implementing SuspendingCommandExecutor.

    class MyCommandExecutor : SuspendingCommandExecutor {\n    // Reference used for naming.\n    companion object coroutineTimings : CoroutineTimings()\n\n    override suspend fun onCommand(\n        sender: CommandSender,\n        command: Command,\n        label: String,\n        args: Array<out String>\n    ): Boolean {\n        TODO(\"Not yet implemented\")\n    }\n}\n

    Register the SuspendingCommandExecutor in your plugin class as follows:

    val myCommandExecutor = MyCommandExecutor()\nthis.getCommand(\"mycommand\")!!.setSuspendingExecutor(minecraftDispatcher + MyCommandExecutor.coroutineTimings, myCommandExecutor)\n
    "},{"location":"timings/#events","title":"Events","text":"

    Event measurements are currently not supported by MCCoroutine.

    You can temporarily remove suspend from your event method, use plugin.launch(plugin.minecraftDispatcher + object : CoroutineTimings() {}) {}, measure the time and then readd suspend again.

    "},{"location":"unittests/","title":"Unit-Tests with MCCoroutine","text":"

    (This site is only relevant for Spigot, Paper and CraftBukkit. If you need Unit-Tests support for BungeeCord, Sponge or Velocity, please submit an issue on GitHub)

    If you try to write Unit- or IntegrationTests for your Minecraft plugin, you may need to test suspend functions. These functions may use plugin.launch{...} or other extension methods from MCCoroutine.

    However, extensive mocking is required to get MCCoroutine to work without a running server. As a solution to this problem, a new test dependency is available, which closely simulates MCCoroutine under real conditions. This means you can focus on writing your tests and get a very close feedback to the real environment.

    "},{"location":"unittests/#1-add-the-dependency","title":"1. Add the dependency","text":"

    Do not shade this library into your final plugin.jar file. This should only be available during UnitTests.

    dependencies {\n    testImplementation(\"com.github.shynixn.mccoroutine:mccoroutine-bukkit-test:2.16.0\")\n}\n
    "},{"location":"unittests/#2-create-a-test-method","title":"2. Create a test method","text":"
    import org.junit.jupiter.api.Test\n\nclass MyExampleTest {\n    @Test\n    fun testCase01(){\n    }\n}\n
    "},{"location":"unittests/#3-change-the-mccoroutine-production-driver-to-the-test-driver","title":"3. Change the MCCoroutine Production-Driver to the Test-Driver","text":"
    import org.junit.jupiter.api.Test\n\nclass MyExampleTest {\n\n    init {\n        /**\n         * This switches MCCoroutine to the test implementation of MCCoroutine.\n         * It affects all the tests in the current session.\n         */\n        MCCoroutine.Driver = TestMCCoroutine.Driver\n    }\n\n    @Test\n    fun testCase01(){\n    }\n}\n
    "},{"location":"unittests/#4-use-mccoroutine-in-the-same-way-as-on-your-server","title":"4. Use MCCoroutine in the same way as on your server","text":"
    import org.junit.jupiter.api.Test\n\nclass MyExampleTest {\n\n    init {\n        /**\n         * This switches MCCoroutine to the test implementation of MCCoroutine.\n         * It affects all the tests in the current session.\n         */\n        MCCoroutine.Driver = TestMCCoroutine.Driver\n    }\n\n    @Test\n    fun testCase01(){\n        // Uses the mocking library called Mockito to mock a plugin instance.\n        // It does not matter how you create a plugin instance. Other mocking libraries work as well.\n        val plugin = Mockito.mock(Plugin::class.java)\n\n        // We need to use runBlocking here, otherwise the test exits early\n        runBlocking(plugin.minecraftDispatcher) {\n            println(\"Step 1: \" + Thread.currentThread().name + \"/\" + Thread.currentThread().id)\n\n            withContext(Dispatchers.IO) {\n                println(\"Step 2: \" + Thread.currentThread().name + \"/\" + Thread.currentThread().id)\n                delay(1000)\n            }\n\n            println(\"Step 3: \" + Thread.currentThread().name + \"/\" + Thread.currentThread().id)\n\n            // As always, prefer using Dispatchers.IO instead of plugin.asyncDispatcher.\n            withContext(plugin.asyncDispatcher) {\n                println(\"Step 4: \" + Thread.currentThread().name + \"/\" + Thread.currentThread().id)\n                delay(1000)\n            }\n\n            println(\"Step 5: \" + Thread.currentThread().name + \"/\" + Thread.currentThread().id)\n\n            // Just as an example, we can also use plugin.launch\n            plugin.launch {\n                println(\"Step 6: \" + Thread.currentThread().name + \"/\" + Thread.currentThread().id)\n                delay(1000)\n                println(\"Step 7: \" + Thread.currentThread().name + \"/\" + Thread.currentThread().id)\n            }.join() // Wait until finished.\n        }\n    }\n}\n
    "}]} \ No newline at end of file