blob: 264764f937c1c6ad3ed42b6d9278d2de9bebb68f [file] [log] [blame]
Roman Elizarovf16fd272017-02-07 11:26:00 +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 Elizarovf16fd272017-02-07 11:26:00 +03003 */
4
Roman Elizarov3754f952017-01-18 20:47:54 +03005package kotlinx.coroutines.experimental.javafx
6
Roman Elizarovdb89a7c2017-09-06 12:16:58 +03007import com.sun.javafx.application.PlatformImpl
Roman Elizarov3754f952017-01-18 20:47:54 +03008import javafx.animation.AnimationTimer
9import javafx.animation.KeyFrame
10import javafx.animation.Timeline
11import javafx.application.Platform
12import javafx.event.ActionEvent
13import javafx.event.EventHandler
14import javafx.util.Duration
15import kotlinx.coroutines.experimental.*
16import kotlinx.coroutines.experimental.javafx.JavaFx.delay
17import java.util.concurrent.CopyOnWriteArrayList
18import java.util.concurrent.TimeUnit
Roman Elizarovea4a51b2017-01-31 12:01:25 +030019import kotlin.coroutines.experimental.CoroutineContext
Roman Elizarov3754f952017-01-18 20:47:54 +030020
Roman Elizarov3754f952017-01-18 20:47:54 +030021/**
22 * Dispatches execution onto JavaFx application thread and provides native [delay] support.
23 */
Roman Elizarov7cf452e2017-01-29 21:58:33 +030024object JavaFx : CoroutineDispatcher(), Delay {
Roman Elizarovdb89a7c2017-09-06 12:16:58 +030025 init {
26 // :kludge: to make sure Toolkit is initialized if we use JavaFx dispatcher outside of JavaFx app
Roman Elizarov9c446c02017-09-06 19:13:26 +030027 initPlatform()
Roman Elizarovdb89a7c2017-09-06 12:16:58 +030028 }
29
Roman Elizarov3754f952017-01-18 20:47:54 +030030 private val pulseTimer by lazy {
31 PulseTimer().apply { start() }
32 }
33
Roman Elizarov67891d82017-01-23 16:47:20 +030034 override fun dispatch(context: CoroutineContext, block: Runnable) = Platform.runLater(block)
Roman Elizarov3754f952017-01-18 20:47:54 +030035
36 /**
37 * Suspends coroutine until next JavaFx pulse and returns time of the pulse on resumption.
38 * If the [Job] of the current coroutine is completed while this suspending function is waiting, this function
39 * immediately resumes with [CancellationException] .
40 */
41 suspend fun awaitPulse(): Long = suspendCancellableCoroutine { cont ->
42 pulseTimer.onNext(cont)
43 }
44
Roman Elizarovd528e3e2017-01-23 15:40:05 +030045 override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) {
Roman Elizarovdaa1d9d2017-03-02 19:00:50 +030046 val timeline = schedule(time, unit, EventHandler<ActionEvent> {
Roman Elizarova198bad2017-02-10 13:30:38 +030047 with(continuation) { resumeUndispatched(Unit) }
Roman Elizarovdaa1d9d2017-03-02 19:00:50 +030048 })
Vsevolod Tolstopyatov80a29472018-04-17 16:02:02 +030049 continuation.invokeOnCancellation { timeline.stop() }
Roman Elizarov3754f952017-01-18 20:47:54 +030050 }
Roman Elizarov3754f952017-01-18 20:47:54 +030051
Roman Elizarovdaa1d9d2017-03-02 19:00:50 +030052 override fun invokeOnTimeout(time: Long, unit: TimeUnit, block: Runnable): DisposableHandle {
53 val timeline = schedule(time, unit, EventHandler<ActionEvent> {
54 block.run()
55 })
56 return object : DisposableHandle {
57 override fun dispose() {
58 timeline.stop()
59 }
60 }
61 }
62
63 private fun schedule(time: Long, unit: TimeUnit, handler: EventHandler<ActionEvent>): Timeline =
64 Timeline(KeyFrame(Duration.millis(unit.toMillis(time).toDouble()), handler)).apply { play() }
65
Roman Elizarova198bad2017-02-10 13:30:38 +030066 private class PulseTimer : AnimationTimer() {
67 val next = CopyOnWriteArrayList<CancellableContinuation<Long>>()
Roman Elizarov3754f952017-01-18 20:47:54 +030068
Roman Elizarova198bad2017-02-10 13:30:38 +030069 override fun handle(now: Long) {
70 val cur = next.toTypedArray()
71 next.clear()
72 for (cont in cur)
73 with (cont) { resumeUndispatched(now) }
74 }
75
76 fun onNext(cont: CancellableContinuation<Long>) {
77 next += cont
78 }
Roman Elizarov3754f952017-01-18 20:47:54 +030079 }
80
Roman Elizarova198bad2017-02-10 13:30:38 +030081 override fun toString() = "JavaFx"
Roman Elizarov3754f952017-01-18 20:47:54 +030082}
Roman Elizarov9c446c02017-09-06 19:13:26 +030083
84internal fun initPlatform() {
85 PlatformImpl.startup {}
86}