• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download

<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