<lambda>null1 package kotlinx.coroutines.javafx
2
3 import javafx.animation.*
4 import javafx.application.*
5 import javafx.event.*
6 import javafx.util.*
7 import kotlinx.coroutines.*
8 import kotlinx.coroutines.internal.*
9 import java.lang.UnsupportedOperationException
10 import java.lang.reflect.*
11 import java.util.concurrent.*
12 import kotlin.coroutines.*
13
14 /**
15 * Dispatches execution onto JavaFx application thread and provides native [delay] support.
16 */
17 @Suppress("unused")
18 public val Dispatchers.JavaFx: JavaFxDispatcher
19 get() = kotlinx.coroutines.javafx.JavaFx
20
21 /**
22 * Dispatcher for JavaFx application thread with support for [awaitPulse].
23 *
24 * This class provides type-safety and a point for future extensions.
25 */
26 public sealed class JavaFxDispatcher : MainCoroutineDispatcher(), Delay {
27
28 /** @suppress */
29 override fun dispatch(context: CoroutineContext, block: Runnable): Unit = Platform.runLater(block)
30
31 /** @suppress */
32 override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
33 val timeline = schedule(timeMillis) {
34 with(continuation) { resumeUndispatched(Unit) }
35 }
36 continuation.invokeOnCancellation { timeline.stop() }
37 }
38
39 /** @suppress */
40 override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
41 val timeline = schedule(timeMillis) {
42 block.run()
43 }
44 return DisposableHandle { timeline.stop() }
45 }
46
47 private fun schedule(timeMillis: Long, handler: EventHandler<ActionEvent>): Timeline =
48 Timeline(KeyFrame(Duration.millis(timeMillis.toDouble()), handler)).apply { play() }
49 }
50
51 internal class JavaFxDispatcherFactory : MainDispatcherFactory {
createDispatchernull52 override fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher = JavaFx
53
54 override val loadPriority: Int
55 get() = 1 // Swing has 0
56 }
57
58 private object ImmediateJavaFxDispatcher : JavaFxDispatcher() {
59 override val immediate: MainCoroutineDispatcher
60 get() = this
61
62 override fun isDispatchNeeded(context: CoroutineContext): Boolean = !Platform.isFxApplicationThread()
63
64 override fun toString() = toStringInternalImpl() ?: "JavaFx.immediate"
65 }
66
67 /**
68 * Dispatches execution onto JavaFx application thread and provides native [delay] support.
69 */
70 internal object JavaFx : JavaFxDispatcher() {
71 init {
72 // :kludge: to make sure Toolkit is initialized if we use JavaFx dispatcher outside of JavaFx app
73 initPlatform()
74 }
75
76 override val immediate: MainCoroutineDispatcher
77 get() = ImmediateJavaFxDispatcher
78
toStringnull79 override fun toString() = toStringInternalImpl() ?: "JavaFx"
80 }
81
82 private val pulseTimer by lazy {
83 PulseTimer().apply { start() }
84 }
85
86 /**
87 * Suspends coroutine until next JavaFx pulse and returns time of the pulse on resumption.
88 * If the [Job] of the current coroutine is completed while this suspending function is waiting, this function
89 * immediately resumes with [CancellationException][kotlinx.coroutines.CancellationException].
90 */
awaitPulsenull91 public suspend fun awaitPulse(): Long = suspendCancellableCoroutine { cont ->
92 pulseTimer.onNext(cont)
93 }
94
95 private class PulseTimer : AnimationTimer() {
96 private val next = CopyOnWriteArrayList<CancellableContinuation<Long>>()
97
handlenull98 override fun handle(now: Long) {
99 val cur = next.toTypedArray()
100 next.clear()
101 for (cont in cur)
102 with (cont) { JavaFx.resumeUndispatched(now) }
103 }
104
onNextnull105 fun onNext(cont: CancellableContinuation<Long>) {
106 next += cont
107 }
108 }
109
110 /** @return true if initialized successfully, and false if no display is detected */
initPlatformnull111 internal fun initPlatform(): Boolean = PlatformInitializer.success
112
113 // Lazily try to initialize JavaFx platform just once
114 private object PlatformInitializer {
115 @JvmField
116 val success = run {
117 /*
118 * Try to instantiate JavaFx platform in a way which works
119 * both on Java 8 and Java 11 and does not produce "illegal reflective access".
120 */
121 try {
122 val runnable = Runnable {}
123 // Invoke the public API if it is present.
124 runCatching {
125 Class.forName("javafx.application.Platform")
126 .getMethod("startup", java.lang.Runnable::class.java)
127 }.map { method ->
128 method.invoke(null, runnable)
129 return@run true
130 }
131 // If we are here, it means the public API is not present. Try the private API.
132 Class.forName("com.sun.javafx.application.PlatformImpl")
133 .getMethod("startup", java.lang.Runnable::class.java)
134 .invoke(null, runnable)
135 true
136 } catch (exception: InvocationTargetException) {
137 // Can only happen as a result of [Method.invoke].
138 val cause = exception.cause!!
139 when {
140 // Maybe the problem is that JavaFX is already initialized? Everything is good then.
141 cause is IllegalStateException && "Toolkit already initialized" == cause.message -> true
142 // If the problem is the headless environment, it is okay.
143 cause is UnsupportedOperationException && "Unable to open DISPLAY" == cause.message -> false
144 // Otherwise, the exception demonstrates an anomaly.
145 else -> throw cause
146 }
147 }
148 }
149 }
150