5
5
package kotlinx.coroutines
6
6
7
7
import kotlinx.coroutines.internal.*
8
- import kotlin.coroutines.*
9
8
import org.w3c.dom.*
9
+ import kotlin.coroutines.*
10
+ import kotlin.js.*
10
11
11
12
private const val MAX_DELAY = Int .MAX_VALUE .toLong()
12
13
13
14
private fun delayToInt (timeMillis : Long ): Int =
14
15
timeMillis.coerceIn(0 , MAX_DELAY ).toInt()
15
16
16
- internal class NodeDispatcher : CoroutineDispatcher (), Delay {
17
- override fun dispatch (context : CoroutineContext , block : Runnable ) {
18
- setTimeout({ block.run () }, 0 )
19
- }
17
+ internal object NodeDispatcher : CoroutineDispatcher(), Delay {
18
+ override fun dispatch (context : CoroutineContext , block : Runnable ) = NodeJsMessageQueue .enqueue(block)
20
19
21
20
override fun scheduleResumeAfterDelay (timeMillis : Long , continuation : CancellableContinuation <Unit >) {
22
21
val handle = setTimeout({ with (continuation) { resumeUndispatched(Unit ) } }, delayToInt(timeMillis))
@@ -37,48 +36,77 @@ internal class NodeDispatcher : CoroutineDispatcher(), Delay {
37
36
}
38
37
39
38
internal class WindowDispatcher (private val window : Window ) : CoroutineDispatcher(), Delay {
40
- private val messageName = " dispatchCoroutine"
39
+ private val queue = WindowMessageQueue (window)
40
+
41
+ override fun dispatch (context : CoroutineContext , block : Runnable ) = queue.enqueue(block)
42
+
43
+ override fun scheduleResumeAfterDelay (timeMillis : Long , continuation : CancellableContinuation <Unit >) {
44
+ window.setTimeout({ with (continuation) { resumeUndispatched(Unit ) } }, delayToInt(timeMillis))
45
+ }
41
46
42
- private val queue = object : MessageQueue () {
43
- override fun schedule () {
44
- window.postMessage(messageName, " *" )
47
+ override fun invokeOnTimeout (timeMillis : Long , block : Runnable ): DisposableHandle {
48
+ val handle = window.setTimeout({ block.run () }, delayToInt(timeMillis))
49
+ return object : DisposableHandle {
50
+ override fun dispose () {
51
+ window.clearTimeout(handle)
52
+ }
45
53
}
46
54
}
55
+ }
56
+
57
+ private class WindowMessageQueue (private val window : Window ) : MessageQueue() {
58
+ private val messageName = " dispatchCoroutine"
47
59
48
60
init {
49
61
window.addEventListener(" message" , { event: dynamic ->
50
62
if (event.source == window && event.data == messageName) {
51
63
event.stopPropagation()
52
- queue. process()
64
+ process()
53
65
}
54
66
}, true )
55
67
}
56
68
57
- override fun dispatch ( context : CoroutineContext , block : Runnable ) {
58
- queue.enqueue(block )
69
+ override fun schedule ( ) {
70
+ Promise .resolve( Unit ).then({ process() } )
59
71
}
60
72
61
- override fun scheduleResumeAfterDelay ( timeMillis : Long , continuation : CancellableContinuation < Unit > ) {
62
- window.setTimeout({ with (continuation) { resumeUndispatched( Unit ) } }, delayToInt(timeMillis) )
73
+ override fun reschedule ( ) {
74
+ window.postMessage(messageName, " * " )
63
75
}
76
+ }
64
77
65
- override fun invokeOnTimeout (timeMillis : Long , block : Runnable ): DisposableHandle {
66
- val handle = window.setTimeout({ block.run () }, delayToInt(timeMillis))
67
- return object : DisposableHandle {
68
- override fun dispose () {
69
- window.clearTimeout(handle)
70
- }
71
- }
78
+ private object NodeJsMessageQueue : MessageQueue() {
79
+ override fun schedule () {
80
+ // next tick is even faster than resolve
81
+ process.nextTick({ process() })
82
+ }
83
+
84
+ override fun reschedule () {
85
+ setTimeout({ process() }, 0 )
72
86
}
73
87
}
74
88
89
+ /* *
90
+ * An abstraction over JS scheduling mechanism that leverages micro-batching of [dispatch] blocks without
91
+ * paying the cost of JS callbacks scheduling on every dispatch.
92
+ *
93
+ * Queue uses two scheduling mechanisms:
94
+ * 1) [schedule] is used to schedule the initial processing of the message queue.
95
+ * JS engine-specific microtask mechanism is used in order to boost performance on short runs and a dispatch batch
96
+ * 2) [reschedule] is used to schedule processing of the queue after yield to the JS event loop.
97
+ * JS engine-specific macrotask mechanism is used not to starve animations and non-coroutines macrotasks.
98
+ *
99
+ * Yet there could be a long tail of "slow" reschedules, but it should be amortized by the queue size.
100
+ */
75
101
internal abstract class MessageQueue : ArrayQueue <Runnable >() {
76
- val yieldEvery = 16 // yield to JS event loop after this many processed messages
102
+ val yieldEvery = 16 // yield to JS macrotask event loop after this many processed messages
77
103
78
104
private var scheduled = false
79
105
80
106
abstract fun schedule ()
81
107
108
+ abstract fun reschedule ()
109
+
82
110
fun enqueue (element : Runnable ) {
83
111
addLast(element)
84
112
if (! scheduled) {
@@ -98,7 +126,7 @@ internal abstract class MessageQueue : ArrayQueue<Runnable>() {
98
126
if (isEmpty) {
99
127
scheduled = false
100
128
} else {
101
- schedule ()
129
+ reschedule ()
102
130
}
103
131
}
104
132
}
@@ -108,3 +136,4 @@ internal abstract class MessageQueue : ArrayQueue<Runnable>() {
108
136
// using them via "window" (which only works in browser)
109
137
private external fun setTimeout (handler : dynamic , timeout : Int = definedExternally): Int
110
138
private external fun clearTimeout (handle : Int = definedExternally)
139
+ private external val process: dynamic
0 commit comments