11package com.github.sgdan
22
33import javafx.application.Application
4+ import javafx.concurrent.Worker
45import javafx.scene.web.WebView
56import kotlinx.coroutines.experimental.launch
6- import kotlinx.coroutines.experimental.newFixedThreadPoolContext
77import netscape.javascript.JSObject
88import tornadofx.*
99import java.io.File
10+ import java.lang.Thread.sleep
1011import java.net.URI
1112import java.net.URL
1213import java.nio.file.FileSystems
1314import java.nio.file.Files
14- import java.nio.file.Path
1515import java.nio.file.Paths
1616import java.nio.file.StandardWatchEventKinds.*
17- import java.util.concurrent.LinkedBlockingQueue
1817import javax.script.ScriptEngineManager
1918import kotlinx.coroutines.experimental.javafx.JavaFx as UI
2019
2120class WebAppWrapper : App (WebAppView : :class)
2221
23- /* *
24- * For worker threads to log to System.out, simulates console.log()
25- */
26- class Console {
27- fun log (vararg data : Any? ) = println (data.joinToString())
28- }
29-
3022/* * Task to be executed by worker thread */
3123data class Task (val name : String , val args : Array <Any >)
3224
3325class WebAppView : View () {
3426 private val nashorn = ScriptEngineManager ().getEngineByName(" nashorn" )
35- private val console = Console () // redirect messages from workers to console
36-
37- /* * Tasks to be processed by the workers */
38- private val tasks = LinkedBlockingQueue <Task >()
3927
4028 private val web = WebView ()
4129 override val root = web
@@ -44,13 +32,14 @@ class WebAppView : View() {
4432
4533 fun inDevMode () = devFolder().exists()
4634
47- fun window () = web.engine.executeScript(" window" ) as JSObject
48-
4935 /* * For workers to send messages to the UI */
5036 private val ui = object {
5137 fun send (name : String , args : Array <Any >) {
5238 // call named method in JavaFX UI thread
53- launch(UI ) { window().call(name, args) }
39+ launch(UI ) {
40+ val window = web.engine.executeScript(" window" ) as JSObject
41+ window.call(name, args)
42+ }
5443 }
5544 }
5645
@@ -60,13 +49,6 @@ class WebAppView : View() {
6049 else emptyArray()
6150 }
6251
63- /* * For UI to create tasks */
64- private val tasksHook = object {
65- fun add (name : String , args : JSObject ) {
66- tasks.add(Task (name, toArray(args)))
67- }
68- }
69-
7052 /* *
7153 * Check both classpath and current folder for a "web" folder resource
7254 */
@@ -77,31 +59,44 @@ class WebAppView : View() {
7759 // from classpath (i.e. contained within executable jar
7860 else WebAppWrapper ::class .java.classLoader.getResource(" web/$name " )
7961
62+ val uiHook: URL
63+ val workerHook: URL
64+ var workers: Workers ? = null
65+
66+ /* * For UI to create tasks */
67+ val tasks = object {
68+ fun add (name : String , args : JSObject ) {
69+ workers?.add(name, args)
70+ }
71+ }
72+
73+ /* *
74+ * Reload UI and reset thread pool & workers
75+ */
76+ fun reset () {
77+ workers?.stop()
78+ workers = Workers (nashorn, workerHook, ui)
79+ launch(UI ) {
80+ web.engine.reload()
81+ }
82+ }
83+
8084 init {
81- val frontEndHook = checkNotNull(findResource(" ui.html" )) {
82- " Must provide front end hook: web/ui.html"
85+ uiHook = checkNotNull(findResource(" ui.html" )) {
86+ " Must provide UI hook: web/ui.html"
8387 }
84- val backEndHook = checkNotNull(findResource(" worker.js" )) {
85- " Must provide back end hook: web/worker.js"
88+ workerHook = checkNotNull(findResource(" worker.js" )) {
89+ " Must provide worker hook: web/worker.js"
8690 }
91+ workers = Workers (nashorn, workerHook, ui)
8792
88- // load worker threads, leave one core for the UI
89- val nWorkers = Math .max(Runtime .getRuntime().availableProcessors() - 1 , 1 )
90- val pool = newFixedThreadPoolContext(nWorkers, " worker-pool" )
91- (1 .. nWorkers).forEach {
92- launch(pool) {
93- // bindings aren't thread safe, so one for each thread
94- val bindings = nashorn.createBindings()
95- bindings.put(" console" , console) // support console.log for workers
96- bindings.put(" tasks" , tasks)
97- bindings.put(" ui" , ui)
98- nashorn.eval(backEndHook.readText(), bindings)
93+ web.engine.loadWorker.stateProperty().addListener { _, _, newValue ->
94+ if (newValue == Worker .State .SUCCEEDED ) {
95+ val window = web.engine.executeScript(" window" ) as JSObject
96+ window.setMember(" tasks" , tasks)
9997 }
10098 }
101-
102- // load front end
103- window().setMember(" tasks" , tasksHook)
104- web.engine.load(frontEndHook.toExternalForm())
99+ web.engine.load(uiHook.toExternalForm())
105100
106101 // watch folder for changes
107102 if (inDevMode()) {
@@ -111,10 +106,9 @@ class WebAppView : View() {
111106 launch {
112107 while (true ) {
113108 val key = watcher.take()
114- key.pollEvents().forEach {
115- println (" event: ${it.kind()} " )
116- }
117- launch(UI ) { web.engine.reload() }
109+ sleep(50 ) // wait a bit, in case there are multiple events
110+ key.pollEvents()
111+ reset()
118112 key.reset()
119113 }
120114 }
0 commit comments