All Downloads are FREE. Search and download functionalities are using the official Maven repository.

.kotlinx.kotlinx-coroutines-javafx.1.9.0-RC.2.source-code.JavaFxDispatcher.kt Maven / Gradle / Ivy

There is a newer version: 1.10.1
Show newest version
package kotlinx.coroutines.javafx

import javafx.animation.*
import javafx.application.*
import javafx.event.*
import javafx.util.*
import kotlinx.coroutines.*
import kotlinx.coroutines.internal.*
import java.lang.UnsupportedOperationException
import java.lang.reflect.*
import java.util.concurrent.*
import kotlin.coroutines.*

/**
 * Dispatches execution onto JavaFx application thread and provides native [delay] support.
 */
@Suppress("unused")
public val Dispatchers.JavaFx: JavaFxDispatcher
    get() = kotlinx.coroutines.javafx.JavaFx

/**
 * Dispatcher for JavaFx application thread with support for [awaitPulse].
 *
 * This class provides type-safety and a point for future extensions.
 */
public sealed class JavaFxDispatcher : MainCoroutineDispatcher(), Delay {

    /** @suppress */
    override fun dispatch(context: CoroutineContext, block: Runnable): Unit = Platform.runLater(block)

    /** @suppress */
    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) {
        val timeline = schedule(timeMillis) {
            with(continuation) { resumeUndispatched(Unit) }
        }
        continuation.invokeOnCancellation { timeline.stop() }
    }

    /** @suppress */
    override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
        val timeline = schedule(timeMillis) {
            block.run()
        }
        return DisposableHandle { timeline.stop() }
    }

    private fun schedule(timeMillis: Long, handler: EventHandler): Timeline =
        Timeline(KeyFrame(Duration.millis(timeMillis.toDouble()), handler)).apply { play() }
}

internal class JavaFxDispatcherFactory : MainDispatcherFactory {
    override fun createDispatcher(allFactories: List): MainCoroutineDispatcher = JavaFx

    override val loadPriority: Int
        get() = 1 // Swing has 0
}

private object ImmediateJavaFxDispatcher : JavaFxDispatcher() {
    override val immediate: MainCoroutineDispatcher
        get() = this

    override fun isDispatchNeeded(context: CoroutineContext): Boolean = !Platform.isFxApplicationThread()

    override fun toString() = toStringInternalImpl() ?: "JavaFx.immediate"
}

/**
 * Dispatches execution onto JavaFx application thread and provides native [delay] support.
 */
internal object JavaFx : JavaFxDispatcher() {
    init {
        // :kludge: to make sure Toolkit is initialized if we use JavaFx dispatcher outside of JavaFx app
        initPlatform()
    }

    override val immediate: MainCoroutineDispatcher
        get() = ImmediateJavaFxDispatcher

    override fun toString() = toStringInternalImpl() ?: "JavaFx"
}

private val pulseTimer by lazy {
    PulseTimer().apply { start() }
}

/**
 * Suspends coroutine until next JavaFx pulse and returns time of the pulse on resumption.
 * If the [Job] of the current coroutine is completed while this suspending function is waiting, this function
 * immediately resumes with [CancellationException][kotlinx.coroutines.CancellationException].
 */
public suspend fun awaitPulse(): Long = suspendCancellableCoroutine { cont ->
    pulseTimer.onNext(cont)
}

private class PulseTimer : AnimationTimer() {
    private val next = CopyOnWriteArrayList>()

    override fun handle(now: Long) {
        val cur = next.toTypedArray()
        next.clear()
        for (cont in cur)
            with (cont) { JavaFx.resumeUndispatched(now) }
    }

    fun onNext(cont: CancellableContinuation) {
        next += cont
    }
}

/** @return true if initialized successfully, and false if no display is detected */
internal fun initPlatform(): Boolean = PlatformInitializer.success

// Lazily try to initialize JavaFx platform just once
private object PlatformInitializer {
    @JvmField
    val success = run {
        /*
         * Try to instantiate JavaFx platform in a way which works
         * both on Java 8 and Java 11 and does not produce "illegal reflective access".
         */
        try {
            val runnable = Runnable {}
            // Invoke the public API if it is present.
            runCatching {
                Class.forName("javafx.application.Platform")
                        .getMethod("startup", java.lang.Runnable::class.java)
            }.map { method ->
                method.invoke(null, runnable)
                return@run true
            }
            // If we are here, it means the public API is not present. Try the private API.
            Class.forName("com.sun.javafx.application.PlatformImpl")
                    .getMethod("startup", java.lang.Runnable::class.java)
                    .invoke(null, runnable)
            true
        } catch (exception: InvocationTargetException) {
            // Can only happen as a result of [Method.invoke].
            val cause = exception.cause!!
            when {
                // Maybe the problem is that JavaFX is already initialized? Everything is good then.
                cause is IllegalStateException && "Toolkit already initialized" == cause.message -> true
                // If the problem is the headless environment, it is okay.
                cause is UnsupportedOperationException && "Unable to open DISPLAY" == cause.message -> false
                // Otherwise, the exception demonstrates an anomaly.
                else -> throw cause
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy