blob: 8317caeb03068389fbb9dbda4dfee0b9ad010aa3 [file] [log] [blame]
Roman Elizarov6d548022018-01-12 20:21:19 +03001/*
Roman Elizarov1f74a2d2018-06-29 19:19:45 +03002 * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
Roman Elizarov6d548022018-01-12 20:21:19 +03003 */
4
5package kotlinx.coroutines.experimental
6
Roman Elizarovaa461cf2018-04-11 13:20:29 +03007import kotlinx.coroutines.experimental.timeunit.TimeUnit
Roman Elizarov6d548022018-01-12 20:21:19 +03008import kotlin.coroutines.experimental.*
Roman Elizarov1867f6d2018-01-13 11:20:32 +03009import org.w3c.dom.*
Roman Elizarov6d548022018-01-12 20:21:19 +030010
Roman Elizarov1867f6d2018-01-13 11:20:32 +030011internal class NodeDispatcher : CoroutineDispatcher(), Delay {
Roman Elizarov6d548022018-01-12 20:21:19 +030012 override fun dispatch(context: CoroutineContext, block: Runnable) {
Roman Elizarov1867f6d2018-01-13 11:20:32 +030013 setTimeout({ block.run() }, 0)
Roman Elizarov6d548022018-01-12 20:21:19 +030014 }
15
Roman Elizarovaa461cf2018-04-11 13:20:29 +030016 override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) {
Vsevolod Tolstopyatovcd006432018-04-26 16:03:40 +030017 val handle = setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.toIntMillis(unit))
18 // Actually on cancellation, but clearTimeout is idempotent
Roman Elizarov3e9f2442018-04-28 17:38:22 +030019 continuation.invokeOnCancellation(handler = ClearTimeout(handle).asHandler)
20 }
21
22 private class ClearTimeout(private val handle: Int) : CancelHandler(), DisposableHandle {
23 override fun dispose() { clearTimeout(handle) }
24 override fun invoke(cause: Throwable?) { dispose() }
25 override fun toString(): String = "ClearTimeout[$handle]"
Roman Elizarov6d548022018-01-12 20:21:19 +030026 }
27
Roman Elizarovaa461cf2018-04-11 13:20:29 +030028 override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle {
29 val handle = setTimeout({ block.run() }, time.toIntMillis(unit))
Roman Elizarov3e9f2442018-04-28 17:38:22 +030030 return ClearTimeout(handle)
Roman Elizarov6d548022018-01-12 20:21:19 +030031 }
32}
33
Roman Elizarov1867f6d2018-01-13 11:20:32 +030034internal class WindowDispatcher(private val window: Window) : CoroutineDispatcher(), Delay {
35 private val messageName = "dispatchCoroutine"
Roman Elizarov6d548022018-01-12 20:21:19 +030036
Roman Elizarov1867f6d2018-01-13 11:20:32 +030037 private val queue = object : MessageQueue() {
38 override fun schedule() {
39 window.postMessage(messageName, "*")
40 }
41 }
Roman Elizarov6d548022018-01-12 20:21:19 +030042
Roman Elizarov1867f6d2018-01-13 11:20:32 +030043 init {
Roman Elizarov6d548022018-01-12 20:21:19 +030044 window.addEventListener("message", { event: dynamic ->
45 if (event.source == window && event.data == messageName) {
46 event.stopPropagation()
Roman Elizarov1867f6d2018-01-13 11:20:32 +030047 queue.process()
Roman Elizarov6d548022018-01-12 20:21:19 +030048 }
49 }, true)
50 }
51
Roman Elizarov1867f6d2018-01-13 11:20:32 +030052 override fun dispatch(context: CoroutineContext, block: Runnable) {
53 queue.enqueue(block)
Roman Elizarov6d548022018-01-12 20:21:19 +030054 }
55
Roman Elizarovaa461cf2018-04-11 13:20:29 +030056 override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) {
57 window.setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.toIntMillis(unit))
Roman Elizarov6d548022018-01-12 20:21:19 +030058 }
59
Roman Elizarovaa461cf2018-04-11 13:20:29 +030060 override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle {
61 val handle = window.setTimeout({ block.run() }, time.toIntMillis(unit))
Roman Elizarov1867f6d2018-01-13 11:20:32 +030062 return object : DisposableHandle {
63 override fun dispose() {
64 window.clearTimeout(handle)
65 }
Roman Elizarov6d548022018-01-12 20:21:19 +030066 }
Roman Elizarov1867f6d2018-01-13 11:20:32 +030067 }
68}
69
70internal abstract class MessageQueue : Queue<Runnable>() {
71 val yieldEvery = 16 // yield to JS event loop after this many processed messages
72
73 private var scheduled = false
74
75 abstract fun schedule()
76
77 fun enqueue(element: Runnable) {
78 add(element)
Roman Elizarov6d548022018-01-12 20:21:19 +030079 if (!scheduled) {
80 scheduled = true
81 schedule()
82 }
83 }
84
Roman Elizarov1867f6d2018-01-13 11:20:32 +030085 fun process() {
86 try {
87 // limit number of processed messages
88 repeat(yieldEvery) {
89 val element = poll() ?: return@process
90 element.run()
91 }
92 } finally {
93 if (isEmpty) {
94 scheduled = false
95 } else {
96 schedule()
97 }
98 }
99 }
100}
101
Roman Elizarovaa461cf2018-04-11 13:20:29 +0300102private fun Long.toIntMillis(unit: TimeUnit): Int =
103 unit.toMillis(this).coerceIn(0L, Int.MAX_VALUE.toLong()).toInt()
104
Roman Elizarov1867f6d2018-01-13 11:20:32 +0300105internal open class Queue<T : Any> {
106 private var queue = arrayOfNulls<Any?>(8)
107 private var head = 0
108 private var tail = 0
109
110 val isEmpty get() = head == tail
111
112 fun poll(): T? {
113 if (isEmpty) return null
114 val result = queue[head]!!
115 queue[head] = null
116 head = head.next()
117 @Suppress("UNCHECKED_CAST")
118 return result as T
119 }
120
121 tailrec fun add(element: T) {
122 val newTail = tail.next()
123 if (newTail == head) {
124 resize()
125 add(element) // retry with larger size
126 return
127 }
128 queue[tail] = element
129 tail = newTail
130 }
131
132 private fun resize() {
Roman Elizarov6d548022018-01-12 20:21:19 +0300133 var i = head
134 var j = 0
Roman Elizarov1867f6d2018-01-13 11:20:32 +0300135 val a = arrayOfNulls<Any?>(queue.size * 2)
Roman Elizarov6d548022018-01-12 20:21:19 +0300136 while (i != tail) {
137 a[j++] = queue[i]
138 i = i.next()
139 }
140 queue = a
141 head = 0
142 tail = j
143 }
144
145 private fun Int.next(): Int {
146 val j = this + 1
147 return if (j == queue.size) 0 else j
148 }
Roman Elizarov6d548022018-01-12 20:21:19 +0300149}
150
151// We need to reference global setTimeout and clearTimeout so that it works on Node.JS as opposed to
152// using them via "window" (which only works in browser)
153private external fun setTimeout(handler: dynamic, timeout: Int = definedExternally): Int
154private external fun clearTimeout(handle: Int = definedExternally)