blob: f9f1f628f16678ffee044603ff49155f5b54c3c9 [file] [log] [blame]
Roman Elizarov6d548022018-01-12 20:21:19 +03001/*
2 * Copyright 2016-2017 JetBrains s.r.o.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package kotlinx.coroutines.experimental
18
Roman Elizarovaa461cf2018-04-11 13:20:29 +030019import kotlinx.coroutines.experimental.timeunit.TimeUnit
Roman Elizarov6d548022018-01-12 20:21:19 +030020import kotlin.coroutines.experimental.*
Roman Elizarov1867f6d2018-01-13 11:20:32 +030021import org.w3c.dom.*
Roman Elizarov6d548022018-01-12 20:21:19 +030022
Roman Elizarov1867f6d2018-01-13 11:20:32 +030023internal class NodeDispatcher : CoroutineDispatcher(), Delay {
Roman Elizarov6d548022018-01-12 20:21:19 +030024 override fun dispatch(context: CoroutineContext, block: Runnable) {
Roman Elizarov1867f6d2018-01-13 11:20:32 +030025 setTimeout({ block.run() }, 0)
Roman Elizarov6d548022018-01-12 20:21:19 +030026 }
27
Roman Elizarovaa461cf2018-04-11 13:20:29 +030028 override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) {
Vsevolod Tolstopyatovcd006432018-04-26 16:03:40 +030029 val handle = setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.toIntMillis(unit))
30 // Actually on cancellation, but clearTimeout is idempotent
Roman Elizarov3e9f2442018-04-28 17:38:22 +030031 continuation.invokeOnCancellation(handler = ClearTimeout(handle).asHandler)
32 }
33
34 private class ClearTimeout(private val handle: Int) : CancelHandler(), DisposableHandle {
35 override fun dispose() { clearTimeout(handle) }
36 override fun invoke(cause: Throwable?) { dispose() }
37 override fun toString(): String = "ClearTimeout[$handle]"
Roman Elizarov6d548022018-01-12 20:21:19 +030038 }
39
Roman Elizarovaa461cf2018-04-11 13:20:29 +030040 override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle {
41 val handle = setTimeout({ block.run() }, time.toIntMillis(unit))
Roman Elizarov3e9f2442018-04-28 17:38:22 +030042 return ClearTimeout(handle)
Roman Elizarov6d548022018-01-12 20:21:19 +030043 }
44}
45
Roman Elizarov1867f6d2018-01-13 11:20:32 +030046internal class WindowDispatcher(private val window: Window) : CoroutineDispatcher(), Delay {
47 private val messageName = "dispatchCoroutine"
Roman Elizarov6d548022018-01-12 20:21:19 +030048
Roman Elizarov1867f6d2018-01-13 11:20:32 +030049 private val queue = object : MessageQueue() {
50 override fun schedule() {
51 window.postMessage(messageName, "*")
52 }
53 }
Roman Elizarov6d548022018-01-12 20:21:19 +030054
Roman Elizarov1867f6d2018-01-13 11:20:32 +030055 init {
Roman Elizarov6d548022018-01-12 20:21:19 +030056 window.addEventListener("message", { event: dynamic ->
57 if (event.source == window && event.data == messageName) {
58 event.stopPropagation()
Roman Elizarov1867f6d2018-01-13 11:20:32 +030059 queue.process()
Roman Elizarov6d548022018-01-12 20:21:19 +030060 }
61 }, true)
62 }
63
Roman Elizarov1867f6d2018-01-13 11:20:32 +030064 override fun dispatch(context: CoroutineContext, block: Runnable) {
65 queue.enqueue(block)
Roman Elizarov6d548022018-01-12 20:21:19 +030066 }
67
Roman Elizarovaa461cf2018-04-11 13:20:29 +030068 override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) {
69 window.setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.toIntMillis(unit))
Roman Elizarov6d548022018-01-12 20:21:19 +030070 }
71
Roman Elizarovaa461cf2018-04-11 13:20:29 +030072 override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle {
73 val handle = window.setTimeout({ block.run() }, time.toIntMillis(unit))
Roman Elizarov1867f6d2018-01-13 11:20:32 +030074 return object : DisposableHandle {
75 override fun dispose() {
76 window.clearTimeout(handle)
77 }
Roman Elizarov6d548022018-01-12 20:21:19 +030078 }
Roman Elizarov1867f6d2018-01-13 11:20:32 +030079 }
80}
81
82internal abstract class MessageQueue : Queue<Runnable>() {
83 val yieldEvery = 16 // yield to JS event loop after this many processed messages
84
85 private var scheduled = false
86
87 abstract fun schedule()
88
89 fun enqueue(element: Runnable) {
90 add(element)
Roman Elizarov6d548022018-01-12 20:21:19 +030091 if (!scheduled) {
92 scheduled = true
93 schedule()
94 }
95 }
96
Roman Elizarov1867f6d2018-01-13 11:20:32 +030097 fun process() {
98 try {
99 // limit number of processed messages
100 repeat(yieldEvery) {
101 val element = poll() ?: return@process
102 element.run()
103 }
104 } finally {
105 if (isEmpty) {
106 scheduled = false
107 } else {
108 schedule()
109 }
110 }
111 }
112}
113
Roman Elizarovaa461cf2018-04-11 13:20:29 +0300114private fun Long.toIntMillis(unit: TimeUnit): Int =
115 unit.toMillis(this).coerceIn(0L, Int.MAX_VALUE.toLong()).toInt()
116
Roman Elizarov1867f6d2018-01-13 11:20:32 +0300117internal open class Queue<T : Any> {
118 private var queue = arrayOfNulls<Any?>(8)
119 private var head = 0
120 private var tail = 0
121
122 val isEmpty get() = head == tail
123
124 fun poll(): T? {
125 if (isEmpty) return null
126 val result = queue[head]!!
127 queue[head] = null
128 head = head.next()
129 @Suppress("UNCHECKED_CAST")
130 return result as T
131 }
132
133 tailrec fun add(element: T) {
134 val newTail = tail.next()
135 if (newTail == head) {
136 resize()
137 add(element) // retry with larger size
138 return
139 }
140 queue[tail] = element
141 tail = newTail
142 }
143
144 private fun resize() {
Roman Elizarov6d548022018-01-12 20:21:19 +0300145 var i = head
146 var j = 0
Roman Elizarov1867f6d2018-01-13 11:20:32 +0300147 val a = arrayOfNulls<Any?>(queue.size * 2)
Roman Elizarov6d548022018-01-12 20:21:19 +0300148 while (i != tail) {
149 a[j++] = queue[i]
150 i = i.next()
151 }
152 queue = a
153 head = 0
154 tail = j
155 }
156
157 private fun Int.next(): Int {
158 val j = this + 1
159 return if (j == queue.size) 0 else j
160 }
Roman Elizarov6d548022018-01-12 20:21:19 +0300161}
162
163// We need to reference global setTimeout and clearTimeout so that it works on Node.JS as opposed to
164// using them via "window" (which only works in browser)
165private external fun setTimeout(handler: dynamic, timeout: Int = definedExternally): Int
166private external fun clearTimeout(handle: Int = definedExternally)