Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package kotlinx.coroutines.experimental |
| 18 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 19 | import kotlinx.coroutines.experimental.timeunit.TimeUnit |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 20 | import kotlin.coroutines.experimental.* |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 21 | import org.w3c.dom.* |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 22 | |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 23 | internal class NodeDispatcher : CoroutineDispatcher(), Delay { |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 24 | override fun dispatch(context: CoroutineContext, block: Runnable) { |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 25 | setTimeout({ block.run() }, 0) |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 26 | } |
| 27 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 28 | override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) { |
Vsevolod Tolstopyatov | cd00643 | 2018-04-26 16:03:40 +0300 | [diff] [blame] | 29 | val handle = setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.toIntMillis(unit)) |
| 30 | // Actually on cancellation, but clearTimeout is idempotent |
Roman Elizarov | 3e9f244 | 2018-04-28 17:38:22 +0300 | [diff] [blame] | 31 | 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 Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 38 | } |
| 39 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 40 | override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle { |
| 41 | val handle = setTimeout({ block.run() }, time.toIntMillis(unit)) |
Roman Elizarov | 3e9f244 | 2018-04-28 17:38:22 +0300 | [diff] [blame] | 42 | return ClearTimeout(handle) |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 43 | } |
| 44 | } |
| 45 | |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 46 | internal class WindowDispatcher(private val window: Window) : CoroutineDispatcher(), Delay { |
| 47 | private val messageName = "dispatchCoroutine" |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 48 | |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 49 | private val queue = object : MessageQueue() { |
| 50 | override fun schedule() { |
| 51 | window.postMessage(messageName, "*") |
| 52 | } |
| 53 | } |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 54 | |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 55 | init { |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 56 | window.addEventListener("message", { event: dynamic -> |
| 57 | if (event.source == window && event.data == messageName) { |
| 58 | event.stopPropagation() |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 59 | queue.process() |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 60 | } |
| 61 | }, true) |
| 62 | } |
| 63 | |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 64 | override fun dispatch(context: CoroutineContext, block: Runnable) { |
| 65 | queue.enqueue(block) |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 66 | } |
| 67 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 68 | override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) { |
| 69 | window.setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.toIntMillis(unit)) |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 70 | } |
| 71 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 72 | override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle { |
| 73 | val handle = window.setTimeout({ block.run() }, time.toIntMillis(unit)) |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 74 | return object : DisposableHandle { |
| 75 | override fun dispose() { |
| 76 | window.clearTimeout(handle) |
| 77 | } |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 78 | } |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 79 | } |
| 80 | } |
| 81 | |
| 82 | internal 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 Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 91 | if (!scheduled) { |
| 92 | scheduled = true |
| 93 | schedule() |
| 94 | } |
| 95 | } |
| 96 | |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 97 | 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 Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 114 | private fun Long.toIntMillis(unit: TimeUnit): Int = |
| 115 | unit.toMillis(this).coerceIn(0L, Int.MAX_VALUE.toLong()).toInt() |
| 116 | |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 117 | internal 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 Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 145 | var i = head |
| 146 | var j = 0 |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 147 | val a = arrayOfNulls<Any?>(queue.size * 2) |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 148 | 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 Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 161 | } |
| 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) |
| 165 | private external fun setTimeout(handler: dynamic, timeout: Int = definedExternally): Int |
| 166 | private external fun clearTimeout(handle: Int = definedExternally) |