@@ -3,20 +3,23 @@ package com.github.sgdan.webviewredux
33import javafx.animation.AnimationTimer
44import javafx.concurrent.Worker
55import javafx.scene.web.WebView
6- import kotlinx.coroutines.experimental.channels.Channel
7- import kotlinx.coroutines.experimental.javafx.JavaFx
6+ import kotlinx.coroutines.experimental.channels.actor
87import kotlinx.coroutines.experimental.launch
98import kotlinx.coroutines.experimental.runBlocking
9+ import mu.KotlinLogging
1010import netscape.javascript.JSObject
1111import org.w3c.dom.Document
1212import org.w3c.dom.Node
1313import org.w3c.dom.Text
1414import java.io.StringWriter
15- import java.util.concurrent.atomic.AtomicBoolean
15+ import java.util.concurrent.atomic.AtomicReference
1616import javax.xml.parsers.DocumentBuilderFactory
1717import javax.xml.transform.TransformerFactory
1818import javax.xml.transform.dom.DOMSource
1919import javax.xml.transform.stream.StreamResult
20+ import kotlinx.coroutines.experimental.javafx.JavaFx as UI
21+
22+ private val log = KotlinLogging .logger {}
2023
2124/* *
2225 * Provide a simple redux-like framework for a JavaFX WebView component.
@@ -52,23 +55,28 @@ class Redux<S>(
5255 // fn to update the state by performing an action
5356 private val update : (Action , S ) -> S
5457) {
55- private val actions = Channel <Action >()
58+ private val actionProcessor = actor<Action > {
59+ var currentState = state.get()
60+ for (action in channel) {
61+ // perform the action and update the state
62+ val nextState = update(action, currentState)
63+ state.set(nextState)
64+ currentState = nextState
65+ }
66+ }
67+
5668 private val engine = webview.engine
5769
58- private var state = initialState
70+ private val state = AtomicReference ( initialState)
5971 private var currentView = view(initialState)
6072
61- /* * Set when a new view should be generated */
62- private val refresh = AtomicBoolean (false )
63-
6473 init {
6574 renderInitialView()
66- launchActionProcessor()
6775 launchTimer()
6876 }
6977
7078 private fun renderInitialView () {
71- launch(JavaFx ) {
79+ launch(UI ) {
7280 engine.loadWorker.stateProperty().addListener { _, _, newValue ->
7381 if (newValue == Worker .State .SUCCEEDED ) {
7482 // add hook for actions
@@ -83,34 +91,21 @@ class Redux<S>(
8391 }
8492 }
8593
86- private fun launchActionProcessor () {
87- launch {
88- while (! actions.isClosedForReceive) {
89- // perform the action and update the state
90- val action = actions.receive()
91- val currentState = state
92- val nextState = update(action, currentState)
93- state = nextState
94-
95- // update the view based on the new state, only once per frame refresh
96- if (refresh.getAndSet(false )) {
97- val prevView = currentView
98- val nextView = view(nextState)
99- launch(JavaFx ) {
100- engine.document?.documentElement?.let { copy(prevView, nextView, it) }
101- }
102- currentView = nextView
103- }
104- }
105- }
106- }
107-
108- /* * Set refresh flag each frame cycle */
94+ /* * Refresh view if required on JavaFX pulse */
10995 private fun launchTimer () {
110- // set refresh flag each frame cycle
11196 object : AnimationTimer () {
97+ var previousState: S ? = null
98+
11299 override fun handle (now : Long ) {
113- refresh.set(true )
100+ val currentState = state.get()
101+ if (currentState != previousState) {
102+ // update the view based on the new state
103+ val prevView = currentView
104+ val nextView = view(currentState)
105+ engine.document?.documentElement?.let { copy(prevView, nextView, it) }
106+ currentView = nextView
107+ previousState = currentState
108+ }
114109 }
115110 }.apply { start() }
116111 }
@@ -119,7 +114,7 @@ class Redux<S>(
119114 * Actions go through the channel to be processed in order
120115 */
121116 fun perform (action : Action ) {
122- runBlocking { actions .send(action) }
117+ runBlocking { actionProcessor .send(action) }
123118 }
124119
125120 /* * Convenience method to perform an action */
@@ -130,7 +125,8 @@ class Redux<S>(
130125 */
131126 private val hook = object {
132127 fun perform (args : JSObject ) {
133- this @Redux.perform(Action .from(args))
128+ val action = Action .from(args) // UI thread
129+ launch { this @Redux.perform(action) } // don't block the UI thread
134130 }
135131 }
136132}
0 commit comments