Skip to content

Commit e66abb0

Browse files
committed
Separate action processor and renderer
1 parent c6cbfa5 commit e66abb0

File tree

3 files changed

+35
-39
lines changed

3 files changed

+35
-39
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ dependencies {
2929
compile 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
3030
compile 'org.jetbrains.kotlin:kotlin-reflect'
3131
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-javafx:0.21'
32+
compile 'io.github.microutils:kotlin-logging:1.4.9'
3233

3334
testCompile 'junit:junit:4.12'
3435
testCompile 'org.slf4j:slf4j-simple:1.7.25'
35-
testCompile 'io.github.microutils:kotlin-logging:1.4.9'
3636
testCompile 'org.jetbrains.kotlinx:kotlinx-html-jvm:0.6.6'
3737
}
3838

src/main/kotlin/com/github/sgdan/webviewredux/Redux.kt

Lines changed: 33 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,23 @@ package com.github.sgdan.webviewredux
33
import javafx.animation.AnimationTimer
44
import javafx.concurrent.Worker
55
import 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
87
import kotlinx.coroutines.experimental.launch
98
import kotlinx.coroutines.experimental.runBlocking
9+
import mu.KotlinLogging
1010
import netscape.javascript.JSObject
1111
import org.w3c.dom.Document
1212
import org.w3c.dom.Node
1313
import org.w3c.dom.Text
1414
import java.io.StringWriter
15-
import java.util.concurrent.atomic.AtomicBoolean
15+
import java.util.concurrent.atomic.AtomicReference
1616
import javax.xml.parsers.DocumentBuilderFactory
1717
import javax.xml.transform.TransformerFactory
1818
import javax.xml.transform.dom.DOMSource
1919
import 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
}

src/test/kotlin/com/github/sgdan/webviewredux/ManyActionsExample.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class ManyActionsExample : Application() {
9393
// timer for next frame of simple animation
9494
launch {
9595
while (true) {
96-
redux?.perform(NEXT)
96+
redux.perform(NEXT)
9797
delay(50)
9898
}
9999
}

0 commit comments

Comments
 (0)