Roman Elizarov | f16fd27 | 2017-02-07 11:26:00 +0300 | [diff] [blame] | 1 | /* |
Roman Elizarov | 1f74a2d | 2018-06-29 19:19:45 +0300 | [diff] [blame^] | 2 | * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. |
Roman Elizarov | f16fd27 | 2017-02-07 11:26:00 +0300 | [diff] [blame] | 3 | */ |
| 4 | |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 5 | package kotlinx.coroutines.experimental.javafx |
| 6 | |
Roman Elizarov | db89a7c | 2017-09-06 12:16:58 +0300 | [diff] [blame] | 7 | import com.sun.javafx.application.PlatformImpl |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 8 | import javafx.animation.AnimationTimer |
| 9 | import javafx.animation.KeyFrame |
| 10 | import javafx.animation.Timeline |
| 11 | import javafx.application.Platform |
| 12 | import javafx.event.ActionEvent |
| 13 | import javafx.event.EventHandler |
| 14 | import javafx.util.Duration |
| 15 | import kotlinx.coroutines.experimental.* |
| 16 | import kotlinx.coroutines.experimental.javafx.JavaFx.delay |
| 17 | import java.util.concurrent.CopyOnWriteArrayList |
| 18 | import java.util.concurrent.TimeUnit |
Roman Elizarov | ea4a51b | 2017-01-31 12:01:25 +0300 | [diff] [blame] | 19 | import kotlin.coroutines.experimental.CoroutineContext |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 20 | |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 21 | /** |
| 22 | * Dispatches execution onto JavaFx application thread and provides native [delay] support. |
| 23 | */ |
Roman Elizarov | 7cf452e | 2017-01-29 21:58:33 +0300 | [diff] [blame] | 24 | object JavaFx : CoroutineDispatcher(), Delay { |
Roman Elizarov | db89a7c | 2017-09-06 12:16:58 +0300 | [diff] [blame] | 25 | init { |
| 26 | // :kludge: to make sure Toolkit is initialized if we use JavaFx dispatcher outside of JavaFx app |
Roman Elizarov | 9c446c0 | 2017-09-06 19:13:26 +0300 | [diff] [blame] | 27 | initPlatform() |
Roman Elizarov | db89a7c | 2017-09-06 12:16:58 +0300 | [diff] [blame] | 28 | } |
| 29 | |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 30 | private val pulseTimer by lazy { |
| 31 | PulseTimer().apply { start() } |
| 32 | } |
| 33 | |
Roman Elizarov | 67891d8 | 2017-01-23 16:47:20 +0300 | [diff] [blame] | 34 | override fun dispatch(context: CoroutineContext, block: Runnable) = Platform.runLater(block) |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 35 | |
| 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 Elizarov | d528e3e | 2017-01-23 15:40:05 +0300 | [diff] [blame] | 45 | override fun scheduleResumeAfterDelay(time: Long, unit: TimeUnit, continuation: CancellableContinuation<Unit>) { |
Roman Elizarov | daa1d9d | 2017-03-02 19:00:50 +0300 | [diff] [blame] | 46 | val timeline = schedule(time, unit, EventHandler<ActionEvent> { |
Roman Elizarov | a198bad | 2017-02-10 13:30:38 +0300 | [diff] [blame] | 47 | with(continuation) { resumeUndispatched(Unit) } |
Roman Elizarov | daa1d9d | 2017-03-02 19:00:50 +0300 | [diff] [blame] | 48 | }) |
Vsevolod Tolstopyatov | 80a2947 | 2018-04-17 16:02:02 +0300 | [diff] [blame] | 49 | continuation.invokeOnCancellation { timeline.stop() } |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 50 | } |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 51 | |
Roman Elizarov | daa1d9d | 2017-03-02 19:00:50 +0300 | [diff] [blame] | 52 | 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 Elizarov | a198bad | 2017-02-10 13:30:38 +0300 | [diff] [blame] | 66 | private class PulseTimer : AnimationTimer() { |
| 67 | val next = CopyOnWriteArrayList<CancellableContinuation<Long>>() |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 68 | |
Roman Elizarov | a198bad | 2017-02-10 13:30:38 +0300 | [diff] [blame] | 69 | 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 Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 79 | } |
| 80 | |
Roman Elizarov | a198bad | 2017-02-10 13:30:38 +0300 | [diff] [blame] | 81 | override fun toString() = "JavaFx" |
Roman Elizarov | 3754f95 | 2017-01-18 20:47:54 +0300 | [diff] [blame] | 82 | } |
Roman Elizarov | 9c446c0 | 2017-09-06 19:13:26 +0300 | [diff] [blame] | 83 | |
| 84 | internal fun initPlatform() { |
| 85 | PlatformImpl.startup {} |
| 86 | } |