blob: 8284daef8d5d8497df962bd59249911ab99c6b47 [file] [log] [blame]
Roman Elizarov4747f3a2017-12-27 13:15:11 +03001/*
Vsevolod Tolstopyatov6d1a6e32020-02-18 15:28:00 +03002 * Copyright 2016-2020 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
Roman Elizarov0950dfa2018-07-13 10:33:25 +03005package kotlinx.coroutines
Roman Elizarov4747f3a2017-12-27 13:15:11 +03006
Roman Elizarov51738242018-12-21 16:41:39 +03007import kotlinx.coroutines.internal.*
Roman Elizarov4747f3a2017-12-27 13:15:11 +03008import org.w3c.dom.Window
9
10/**
Roman Elizarov1867f6d2018-01-13 11:20:32 +030011 * Converts an instance of [Window] to an implementation of [CoroutineDispatcher].
12 */
13public fun Window.asCoroutineDispatcher(): CoroutineDispatcher =
14 @Suppress("UnsafeCastFromDynamic")
15 asDynamic().coroutineDispatcher ?: WindowDispatcher(this).also {
16 asDynamic().coroutineDispatcher = it
17 }
18
19/**
Roman Elizarov4747f3a2017-12-27 13:15:11 +030020 * Suspends coroutine until next JS animation frame and returns frame time on resumption.
21 * The time is consistent with [window.performance.now()][org.w3c.performance.Performance.now].
22 * This function is cancellable. If the [Job] of the current coroutine is completed while this suspending
23 * function is waiting, this function immediately resumes with [CancellationException].
24 */
25public suspend fun Window.awaitAnimationFrame(): Double = suspendCancellableCoroutine { cont ->
Roman Elizarov1867f6d2018-01-13 11:20:32 +030026 asWindowAnimationQueue().enqueue(cont)
Roman Elizarov4747f3a2017-12-27 13:15:11 +030027}
Roman Elizarov1867f6d2018-01-13 11:20:32 +030028
29private fun Window.asWindowAnimationQueue(): WindowAnimationQueue =
30 @Suppress("UnsafeCastFromDynamic")
31 asDynamic().coroutineAnimationQueue ?: WindowAnimationQueue(this).also {
32 asDynamic().coroutineAnimationQueue = it
33 }
34
35private class WindowAnimationQueue(private val window: Window) {
36 private val dispatcher = window.asCoroutineDispatcher()
37 private var scheduled = false
Roman Elizarov51738242018-12-21 16:41:39 +030038 private var current = ArrayQueue<CancellableContinuation<Double>>()
39 private var next = ArrayQueue<CancellableContinuation<Double>>()
Roman Elizarov1867f6d2018-01-13 11:20:32 +030040 private var timestamp = 0.0
41
42 fun enqueue(cont: CancellableContinuation<Double>) {
Roman Elizarov51738242018-12-21 16:41:39 +030043 next.addLast(cont)
Roman Elizarov1867f6d2018-01-13 11:20:32 +030044 if (!scheduled) {
45 scheduled = true
46 window.requestAnimationFrame { ts ->
47 timestamp = ts
48 val prev = current
49 current = next
50 next = prev
51 scheduled = false
52 process()
53 }
54 }
55 }
56
57 fun process() {
58 while(true) {
Roman Elizarov51738242018-12-21 16:41:39 +030059 val element = current.removeFirstOrNull() ?: return
Roman Elizarov1867f6d2018-01-13 11:20:32 +030060 with(element) { dispatcher.resumeUndispatched(timestamp) }
61 }
62 }
Vsevolod Tolstopyatov6d1a6e32020-02-18 15:28:00 +030063}