blob: f12a5dce3eb6f72919d8cd1da6887ccd1681f15e [file] [log] [blame]
Roman Elizarov3754f952017-01-18 20:47:54 +03001package kotlinx.coroutines.experimental.javafx
2
3import javafx.animation.AnimationTimer
4import javafx.animation.KeyFrame
5import javafx.animation.Timeline
6import javafx.application.Platform
7import javafx.event.ActionEvent
8import javafx.event.EventHandler
9import javafx.util.Duration
10import kotlinx.coroutines.experimental.*
11import kotlinx.coroutines.experimental.javafx.JavaFx.delay
12import java.util.concurrent.CopyOnWriteArrayList
13import java.util.concurrent.TimeUnit
Roman Elizarov67891d82017-01-23 16:47:20 +030014import kotlin.coroutines.CoroutineContext
Roman Elizarov3754f952017-01-18 20:47:54 +030015
16
17/**
18 * Dispatches execution onto JavaFx application thread and provides native [delay] support.
19 */
Roman Elizarovd528e3e2017-01-23 15:40:05 +030020object JavaFx : CoroutineDispatcher(), Yield, Delay {
Roman Elizarov3754f952017-01-18 20:47:54 +030021 private val pulseTimer by lazy {
22 PulseTimer().apply { start() }
23 }
24
Roman Elizarov67891d82017-01-23 16:47:20 +030025 override fun isDispatchNeeded(context: CoroutineContext): Boolean = !Platform.isFxApplicationThread()
26 override fun dispatch(context: CoroutineContext, block: Runnable) = Platform.runLater(block)
Roman Elizarov3754f952017-01-18 20:47:54 +030027
28 /**
29 * Suspends coroutine until next JavaFx pulse and returns time of the pulse on resumption.
30 * If the [Job] of the current coroutine is completed while this suspending function is waiting, this function
31 * immediately resumes with [CancellationException] .
32 */
33 suspend fun awaitPulse(): Long = suspendCancellableCoroutine { cont ->
34 pulseTimer.onNext(cont)
35 }
36
Roman Elizarovd528e3e2017-01-23 15:40:05 +030037 override fun scheduleResume(continuation: CancellableContinuation<Unit>) {
38 Platform.runLater { continuation.resume(Unit) }
39 }
40
41 override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) {
Roman Elizarov3754f952017-01-18 20:47:54 +030042 val timeline = Timeline(KeyFrame(Duration.millis(unit.toMillis(time).toDouble()),
43 EventHandler<ActionEvent> { continuation.resume(Unit) }))
44 timeline.play()
45 continuation.onCompletion { timeline.stop() }
46 }
47}
48
49private class PulseTimer : AnimationTimer() {
50 val next = CopyOnWriteArrayList<CancellableContinuation<Long>>()
51
52 override fun handle(now: Long) {
53 val cur = next.toTypedArray()
54 next.clear()
55 for (cont in cur)
56 cont.resume(now)
57 }
58
59 fun onNext(cont: CancellableContinuation<Long>) {
60 next += cont
61 }
62}