Skip to content

Commit 17cede6

Browse files
committed
Improved Godot Pool dispatcher
1 parent 872ed92 commit 17cede6

File tree

1 file changed

+51
-1
lines changed

1 file changed

+51
-1
lines changed

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

+51-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import godot.api.Object
55
import godot.api.SceneTree
66
import godot.api.WorkerThreadPool
77
import godot.core.asCallable
8+
import godot.internal.memory.MemoryManager
89
import kotlinx.coroutines.CoroutineDispatcher
910
import kotlinx.coroutines.Runnable
11+
import java.util.concurrent.locks.ReentrantLock
12+
import kotlin.concurrent.withLock
1013
import kotlin.coroutines.CoroutineContext
1114

1215
object GodotDispatchers {
@@ -23,8 +26,55 @@ object GodotDispatchers {
2326
}
2427

2528
private object GodotThreadPoolCoroutineDispatcher : CoroutineDispatcher() {
29+
private const val MIN_SIZE = 64
30+
31+
val lock = ReentrantLock()
32+
val currentTasks = HashMap<Runnable, Long>(MIN_SIZE)
33+
val terminatedTasks = ArrayList<Long>(MIN_SIZE)
34+
35+
init {
36+
// Schedule to clear remaining tasks when Godot terminates..
37+
MemoryManager.registerCallback(true, ::clearCompletedTasks)
38+
}
39+
2640
override fun dispatch(context: CoroutineContext, block: Runnable) {
27-
WorkerThreadPool.addTask({ block.run() }.asCallable())
41+
val callable = {
42+
try {
43+
block.run()
44+
} finally {
45+
lock.withLock {
46+
val id = currentTasks.remove(block)!!
47+
terminatedTasks.add(id)
48+
}
49+
}
50+
}.asCallable()
51+
52+
val taskID = WorkerThreadPool.addTask(callable)
53+
54+
val tasksToClear = lock.withLock {
55+
currentTasks[block] = taskID
56+
terminatedTasks.toTypedArray().also {
57+
terminatedTasks.clear()
58+
}
59+
}
60+
61+
tasksToClear.forEach {
62+
/**
63+
* It's mandatory in Godot to call this method at some point after adding a task to clean up memory.
64+
* We cannot wait immediately after adding the task because it would block the thread doing the dispatching, but we can do it after the task has been confirmed completed.
65+
* It would also be wasteful to allocate a separate thread to poll and check task completions so instead we do process the list of complete tasks right after a regular dispatch.
66+
*/
67+
WorkerThreadPool.waitForTaskCompletion(it)
68+
}
69+
}
70+
71+
72+
private fun clearCompletedTasks() {
73+
// Warning, this method is only supposed to be called when Godot terminates!
74+
terminatedTasks.forEach {
75+
WorkerThreadPool.waitForTaskCompletion(it)
76+
}
77+
terminatedTasks.clear()
2878
}
2979
}
3080

0 commit comments

Comments
 (0)