Skip to content

Commit 7ed3062

Browse files
authored
Improve coroutine dispatcher (#933)
1 parent fcc83ea commit 7ed3062

5 files changed

Lines changed: 39 additions & 30 deletions

File tree

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
11
package godot.coroutines
22

3-
import kotlinx.coroutines.async
3+
import kotlinx.coroutines.withContext
44

55
suspend inline fun <R> awaitProcessFrame(
66
crossinline block: () -> R
7-
): R {
8-
val job = GodotCoroutine.async(GodotDispatchers.ProcessFrame) {
9-
block()
10-
}
11-
return job.await()
7+
): R = withContext(GodotDispatchers.ProcessFrame) {
8+
block()
129
}
1310

1411
suspend inline fun <R> awaitPhysicsFrame(
1512
crossinline block: () -> R
16-
): R {
17-
val job = GodotCoroutine.async(GodotDispatchers.PhysicsFrame) {
18-
block()
19-
}
20-
21-
return job.await()
13+
): R = withContext(GodotDispatchers.PhysicsFrame) {
14+
block()
2215
}

kt/godot-library/godot-coroutine-library/src/main/kotlin/godot/coroutines/GodotDispatchers.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import kotlinx.coroutines.Runnable
1111
import java.util.concurrent.locks.ReentrantLock
1212
import kotlin.concurrent.withLock
1313
import kotlin.coroutines.CoroutineContext
14+
import godot.api.Thread as GodotThread
1415

1516
object GodotDispatchers {
1617

@@ -20,6 +21,23 @@ object GodotDispatchers {
2021
val PhysicsFrame: CoroutineDispatcher = GodotPhysicsFrameCoroutineDispatcher
2122

2223
private object GodotMainThreadCoroutineDispatcher : CoroutineDispatcher() {
24+
@Volatile
25+
private var cachedMainThread: Thread? = null
26+
27+
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
28+
val current = Thread.currentThread()
29+
val cached = cachedMainThread
30+
// Already on the main thread → no dispatch needed.
31+
if (cached != null) return cached !== current
32+
33+
// Slow path: ask Godot. Caches on success so subsequent checks are JNI-free.
34+
if (GodotThread.isMainThread()) {
35+
cachedMainThread = current
36+
return false
37+
}
38+
return true
39+
}
40+
2341
override fun dispatch(context: CoroutineContext, block: Runnable) {
2442
{ block.run() }.asCallable().callDeferred()
2543
}
Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
package godot.coroutines
22

3-
import kotlinx.coroutines.async
3+
import kotlinx.coroutines.withContext
44

55

66
/**
7-
* Suspends the current coroutine until the given block is executed.
7+
* Suspends the current coroutine until the given block is executed on Godot's main thread.
88
*
9-
* The block will be executed at the end of the frame on the main thread.
9+
* If the caller is already on the main thread, the block runs synchronously on the current stack.
10+
* Otherwise the block is posted to the main thread via `callDeferred` and runs at the end of the
11+
* current frame.
1012
*
11-
* Use it to call not thread safe code from godot and wait for the execution of it.
13+
* Use it to call not-thread-safe Godot code and wait for its result.
1214
*
13-
* @param block the code block to execute at the end of the frame
15+
* @param block the code block to execute on the main thread
1416
*/
1517
suspend inline fun <R> awaitMainThread(
1618
crossinline block: () -> R
17-
): R {
18-
val job = GodotCoroutine.async(GodotDispatchers.MainThread) {
19-
block()
20-
}
21-
return job.await()
19+
): R = withContext(GodotDispatchers.MainThread) {
20+
block()
2221
}

kt/godot-library/godot-coroutine-library/src/main/kotlin/godot/coroutines/awaitResourceLoad.kt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package godot.coroutines
33
import godot.api.Resource
44
import godot.api.ResourceLoader
55
import godot.api.ResourceLoader.CacheMode
6-
import kotlinx.coroutines.async
6+
import kotlinx.coroutines.withContext
77

88

99
/**
@@ -14,7 +14,7 @@ import kotlinx.coroutines.async
1414
* @param cacheMode The cache mode to be used while loading the resource.
1515
* @return The loaded resource, or null if there was an error.
1616
*/
17-
public suspend inline fun ResourceLoader.awaitLoad(
17+
suspend inline fun ResourceLoader.awaitLoad(
1818
path: String,
1919
typeHint: String = "",
2020
cacheMode: CacheMode = CacheMode.REUSE,
@@ -24,12 +24,11 @@ public suspend inline fun ResourceLoader.awaitLoad(
2424
return load(path)
2525
}
2626

27-
// Start a new job so we have a suspension point in case the coroutine is currently in the main thread.
28-
val job = GodotCoroutine.async(GodotDispatchers.ThreadPool) {
27+
// Switch to the worker thread pool so the load doesn't block the caller's thread
28+
// (especially important when the caller is on the main thread).
29+
return withContext(GodotDispatchers.ThreadPool) {
2930
load(path, typeHint, cacheMode)
3031
}
31-
32-
return job.await()
3332
}
3433

3534
/**
@@ -40,7 +39,7 @@ public suspend inline fun ResourceLoader.awaitLoad(
4039
* @param cacheMode The cache mode to be used while loading the resource.
4140
* @return The loaded resource, or null if there was an error.
4241
*/
43-
public suspend inline fun <R> ResourceLoader.awaitLoadAs(
42+
suspend inline fun <R> ResourceLoader.awaitLoadAs(
4443
path: String,
4544
typeHint: String = "",
4645
cacheMode: CacheMode = CacheMode.REUSE,

kt/plugins/godot-intellij-plugin/src/main/resources/META-INF/plugin.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
<resource-bundle>messages.generalLabels</resource-bundle>
2424

25-
<depends>com.intellij.modules.java</depends>
25+
<depends>com.intellij.java</depends>
2626
<depends>org.jetbrains.kotlin</depends>
2727
<depends optional="true" config-file="godotjvmideaplugin-with-scala.xml">org.intellij.scala</depends>
2828
</idea-plugin>

0 commit comments

Comments
 (0)