Skip to content

Coroutines and Skedule

Gabriel Souza edited this page Aug 8, 2020 · 4 revisions

Skedule

KotlinBukkitAPI has Skedule library built in, it's brings Bukkit "thread system" to the Kotlin Coroutines.

KotlinBukkitAPI provide some extensions to work with Skedule in the project style.

WithPlugin<*>.BukkitDispatchers & Plugin.BukkitDispatchers will provide to you the SYNC (Bukkit Main Thread) and ASYNC executions for Coroutines.

pluginCoroutineScope.launch(BukkitDispatchers.SYNC) {
   val allBlocks = blocksAroundThePlayer(100)
   
   val result = withContext(Dispatchers.IO) {
       someHaveCalculations(allBlocks)
   }
   
   result.forEach { it.applyToBlock() }
}

Normally you will be able to easy access BukkitDispatchers in your code base if you are using WithPlugin or any interface that implements it like LifeclycleListener.

Coroutines

KotlinBukkitAPI has a lot to offer when using Coroutines, we recommend that if you not use yet, starts to use it because help us in many tasks. The project has coroutines built in the Command DSL, provides an Event Flow and much more.

Scopes

If you notice, in the previous snippet, we use pluginCoroutineScope, this scopes is provided by the KotlinBukkitAPI.

What he does is that when your plugin get disabled, all of yours scopes will be canceled prevents leaks in your Plugin user server.

This is a part of the Architecture system of KotlinBukkitAPI and is only provided if you are using KotlinPlugin. It can be used inside of your KotlinPlugin and in any LifecycleListener (that will be most likely any of your Managers instances)

LifecycleListener<*>.pluginCoroutineScope & KotlinPlugin.pluginCoroutineScope

We also provide a playerCoroutineScope, the key difference is that is bound to a Player as well, and when the player get disconnect your Job will be canceled too.

LifecycleListener<*>.playerCoroutineScope & KotlinPlugin.playerCoroutineScope

Usage recommendations

When using Coroutines in general the recommendations is to build your APIs safe as possible, using Structured concurrency is really important to let your project Job leak free.

Main-safety: this is a technique that allows to you safe call any suspend functions without care where it will run in the current Dispatcher or in a specific one like IO. This improves your API readability and removes a lot of withContext just for "safety" in your project.

// before Main-safety
suspend updateBlocks(world: World, blocks: List<Pair<BlockPos, Material>>) {
  for((pos, type) in blocks) {
    pos.asBlock(world).type = type
  }
}

pluginCoroutineScope.launch(Dispatchers.IO) {
  val blocks = retrieveBlocksFromDatabase()
  
  withContext(BukkitDispatchers.SYNC) {
    updateBlocks(world, blocks)
  }
}

// after, with Main-Safety

suspend updateBlocks(world: World, blocks: List<Pair<BlockPos, Material>>) = withContext(BukkitDispachers.SYNC) {
  for((pos, type) in blocks) {
    pos.asBlock(world).type = type
  }
}

pluginCoroutineScope.launch(Dispatchers.IO) {
  val blocks = retrieveBlocksFromDatabase()
  
  updateBlocks(world, blocks)
}

This will improve safety because you can use this updateBlocks in other parts of your code base but sometimes you can forget the withContext.

Utilities

takeMaxPerTick

Plugin.takeMaxPerTick(time: Millisecond) & WithPlugin<*>.takeMaxPerTick(time: Millisecond)


Is possible to suspend your Coroutines until the next server tick if it uses the max milliseconds that you want to use in a tick.

val maxBlock = locMax.block
val minBlock = locMin.block

for(block in minBlock..maxBlock) {
  block.type = Material.DIRT
  takeMaxPerTick(5.millisecond)
}

Result

takeMaxPerTick result

Clone this wiki locally