blob: de74844b1dd903fcfc80f36ae9a81e23b428db5f [file] [log] [blame]
Roman Elizarov4747f3a2017-12-27 13:15:11 +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
19import org.w3c.dom.Window
20
21/**
Roman Elizarov1867f6d2018-01-13 11:20:32 +030022 * Converts an instance of [Window] to an implementation of [CoroutineDispatcher].
23 */
24public fun Window.asCoroutineDispatcher(): CoroutineDispatcher =
25 @Suppress("UnsafeCastFromDynamic")
26 asDynamic().coroutineDispatcher ?: WindowDispatcher(this).also {
27 asDynamic().coroutineDispatcher = it
28 }
29
30/**
Roman Elizarov4747f3a2017-12-27 13:15:11 +030031 * Suspends coroutine until next JS animation frame and returns frame time on resumption.
32 * The time is consistent with [window.performance.now()][org.w3c.performance.Performance.now].
33 * This function is cancellable. If the [Job] of the current coroutine is completed while this suspending
34 * function is waiting, this function immediately resumes with [CancellationException].
35 */
36public suspend fun Window.awaitAnimationFrame(): Double = suspendCancellableCoroutine { cont ->
Roman Elizarov1867f6d2018-01-13 11:20:32 +030037 asWindowAnimationQueue().enqueue(cont)
Roman Elizarov4747f3a2017-12-27 13:15:11 +030038}
Roman Elizarov1867f6d2018-01-13 11:20:32 +030039
40private fun Window.asWindowAnimationQueue(): WindowAnimationQueue =
41 @Suppress("UnsafeCastFromDynamic")
42 asDynamic().coroutineAnimationQueue ?: WindowAnimationQueue(this).also {
43 asDynamic().coroutineAnimationQueue = it
44 }
45
46private class WindowAnimationQueue(private val window: Window) {
47 private val dispatcher = window.asCoroutineDispatcher()
48 private var scheduled = false
49 private var current = Queue<CancellableContinuation<Double>>()
50 private var next = Queue<CancellableContinuation<Double>>()
51 private var timestamp = 0.0
52
53 fun enqueue(cont: CancellableContinuation<Double>) {
54 next.add(cont)
55 if (!scheduled) {
56 scheduled = true
57 window.requestAnimationFrame { ts ->
58 timestamp = ts
59 val prev = current
60 current = next
61 next = prev
62 scheduled = false
63 process()
64 }
65 }
66 }
67
68 fun process() {
69 while(true) {
70 val element = current.poll() ?: return
71 with(element) { dispatcher.resumeUndispatched(timestamp) }
72 }
73 }
74}