blob: d137974e001ca6a3c368ab88af43e9714326aa63 [file] [log] [blame]
Roman Elizarov4747f3a2017-12-27 13:15:11 +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 Elizarov4747f3a2017-12-27 13:15:11 +03003 */
4
5package kotlinx.coroutines.experimental
6
7import org.w3c.dom.Window
8
9/**
Roman Elizarov1867f6d2018-01-13 11:20:32 +030010 * Converts an instance of [Window] to an implementation of [CoroutineDispatcher].
11 */
12public fun Window.asCoroutineDispatcher(): CoroutineDispatcher =
13 @Suppress("UnsafeCastFromDynamic")
14 asDynamic().coroutineDispatcher ?: WindowDispatcher(this).also {
15 asDynamic().coroutineDispatcher = it
16 }
17
18/**
Roman Elizarov4747f3a2017-12-27 13:15:11 +030019 * Suspends coroutine until next JS animation frame and returns frame time on resumption.
20 * The time is consistent with [window.performance.now()][org.w3c.performance.Performance.now].
21 * This function is cancellable. If the [Job] of the current coroutine is completed while this suspending
22 * function is waiting, this function immediately resumes with [CancellationException].
23 */
24public suspend fun Window.awaitAnimationFrame(): Double = suspendCancellableCoroutine { cont ->
Roman Elizarov1867f6d2018-01-13 11:20:32 +030025 asWindowAnimationQueue().enqueue(cont)
Roman Elizarov4747f3a2017-12-27 13:15:11 +030026}
Roman Elizarov1867f6d2018-01-13 11:20:32 +030027
28private fun Window.asWindowAnimationQueue(): WindowAnimationQueue =
29 @Suppress("UnsafeCastFromDynamic")
30 asDynamic().coroutineAnimationQueue ?: WindowAnimationQueue(this).also {
31 asDynamic().coroutineAnimationQueue = it
32 }
33
34private class WindowAnimationQueue(private val window: Window) {
35 private val dispatcher = window.asCoroutineDispatcher()
36 private var scheduled = false
37 private var current = Queue<CancellableContinuation<Double>>()
38 private var next = Queue<CancellableContinuation<Double>>()
39 private var timestamp = 0.0
40
41 fun enqueue(cont: CancellableContinuation<Double>) {
42 next.add(cont)
43 if (!scheduled) {
44 scheduled = true
45 window.requestAnimationFrame { ts ->
46 timestamp = ts
47 val prev = current
48 current = next
49 next = prev
50 scheduled = false
51 process()
52 }
53 }
54 }
55
56 fun process() {
57 while(true) {
58 val element = current.poll() ?: return
59 with(element) { dispatcher.resumeUndispatched(timestamp) }
60 }
61 }
62}