Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 1 | /* |
Roman Elizarov | 1f74a2d | 2018-06-29 19:19:45 +0300 | [diff] [blame^] | 2 | * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 3 | */ |
| 4 | |
| 5 | package kotlinx.coroutines.experimental |
| 6 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 7 | import kotlinx.coroutines.experimental.timeunit.TimeUnit |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 8 | import kotlin.coroutines.experimental.* |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 9 | import org.w3c.dom.* |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 10 | |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 11 | internal class NodeDispatcher : CoroutineDispatcher(), Delay { |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 12 | override fun dispatch(context: CoroutineContext, block: Runnable) { |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 13 | setTimeout({ block.run() }, 0) |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 14 | } |
| 15 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 16 | override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) { |
Vsevolod Tolstopyatov | cd00643 | 2018-04-26 16:03:40 +0300 | [diff] [blame] | 17 | val handle = setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.toIntMillis(unit)) |
| 18 | // Actually on cancellation, but clearTimeout is idempotent |
Roman Elizarov | 3e9f244 | 2018-04-28 17:38:22 +0300 | [diff] [blame] | 19 | 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 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 invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle { |
| 29 | val handle = setTimeout({ block.run() }, time.toIntMillis(unit)) |
Roman Elizarov | 3e9f244 | 2018-04-28 17:38:22 +0300 | [diff] [blame] | 30 | return ClearTimeout(handle) |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 31 | } |
| 32 | } |
| 33 | |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 34 | internal class WindowDispatcher(private val window: Window) : CoroutineDispatcher(), Delay { |
| 35 | private val messageName = "dispatchCoroutine" |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 36 | |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 37 | private val queue = object : MessageQueue() { |
| 38 | override fun schedule() { |
| 39 | window.postMessage(messageName, "*") |
| 40 | } |
| 41 | } |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 42 | |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 43 | init { |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 44 | window.addEventListener("message", { event: dynamic -> |
| 45 | if (event.source == window && event.data == messageName) { |
| 46 | event.stopPropagation() |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 47 | queue.process() |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 48 | } |
| 49 | }, true) |
| 50 | } |
| 51 | |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 52 | override fun dispatch(context: CoroutineContext, block: Runnable) { |
| 53 | queue.enqueue(block) |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 54 | } |
| 55 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 56 | override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) { |
| 57 | window.setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, time.toIntMillis(unit)) |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 58 | } |
| 59 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 60 | override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle { |
| 61 | val handle = window.setTimeout({ block.run() }, time.toIntMillis(unit)) |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 62 | return object : DisposableHandle { |
| 63 | override fun dispose() { |
| 64 | window.clearTimeout(handle) |
| 65 | } |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 66 | } |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 67 | } |
| 68 | } |
| 69 | |
| 70 | internal 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 Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 79 | if (!scheduled) { |
| 80 | scheduled = true |
| 81 | schedule() |
| 82 | } |
| 83 | } |
| 84 | |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 85 | 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 Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 102 | private fun Long.toIntMillis(unit: TimeUnit): Int = |
| 103 | unit.toMillis(this).coerceIn(0L, Int.MAX_VALUE.toLong()).toInt() |
| 104 | |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 105 | internal 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 Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 133 | var i = head |
| 134 | var j = 0 |
Roman Elizarov | 1867f6d | 2018-01-13 11:20:32 +0300 | [diff] [blame] | 135 | val a = arrayOfNulls<Any?>(queue.size * 2) |
Roman Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 136 | 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 Elizarov | 6d54802 | 2018-01-12 20:21:19 +0300 | [diff] [blame] | 149 | } |
| 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) |
| 153 | private external fun setTimeout(handler: dynamic, timeout: Int = definedExternally): Int |
| 154 | private external fun clearTimeout(handle: Int = definedExternally) |