Issue
In the docs it says that coroutines are lighter than threads and so I wanted to use a kotlin coroutine instead of the BukkitRunnable.
//Defined as class field
private val scope = coroutineScope(Dispatchers.Default)
//In class method
scope.launch {/* wait some seconds and then change blockdata */}
Calling setBlockData from Dispatchers.Default thread throws an error because the spigot API is not thread safe and you can't call API stuff from a thread other than the main.
java.lang.IllegalStateException: Asynchronous block remove!
I was thinking that changing block data is the equivalent of android UI changes in Minecraft which means that the coroutine needs to be run/injected into the main thread. So it would make sense to run my coroutine in Dispatchers.Main. However, I can't find a way use Dispatchers.Main and set it to the main thread without getting an illegalStateException
I hope my logic is correct here
Solution
If you want a simple method that is able to bridge the suspending code with the main thread (with the possibility of fetching some information from the main thread and use that on your coroutine), you can use this method:
suspend fun <T> suspendSync(plugin: Plugin, task: () -> T): T = withTimeout(10000L) {
// Context: The current coroutine context
suspendCancellableCoroutine { cont ->
// Context: The current coroutine context
Bukkit.getScheduler().runTask(plugin) {
// Context: Bukkit MAIN thread
// runCatching is used to forward any exception that may occur here back to
// our coroutine, keeping the exception transparency of Kotlin coroutines
runCatching(task).fold({ cont.resume(it) }, cont::resumeWithException)
}
}
}
I've commented on what context each part of the code is executed so you can visualize the context switch. suspendCancellableCoroutine is a way of getting hold of the continuation object all coroutines use under the hood, so we can manually resume it once the main thread execute our task.
The outer block withTimeout is used so that if the main thread does not complete our task within 10 seconds, our coroutine gives up instead of hanging forever.
And the use is very simple too:
val plugin = // comes from somewhere
// example coroutine scope
CoroutineScope(Dispatchers.Default).launch {
// doing stuff async
// oh no, I need some data from the main thread!
val block = suspendSync(plugin) {
// this code runs on the MAIN thread
Bukkit.getWorld("blah").getBlockAt(0, 0, 0)
}
// back to async here, do stuff with block (just don't MODIFY it async, use more suspendSync if needed)
}
If you have any questions or thing I can improve this answer, don't be afraid of letting me know.
Answered By - SecretX
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.