I created a gist which my problem is described in GraphGymnastic. The problem is heavily abstracted in there using Thread.sleep()
but it serves the purpose.
Problem description
I want to walk the scenegraph when a event is filtered (with EventFilters). When an event arrives, I want to calculate some stuff on all nodes within the graph. For 2 nodes this will work just fine with Platform.runLater()
but later, there will be n-Nodes and the calculation can take some time. I don't want the FX Thread blocked during this calculation.
So we thought about delegate the calculation to a second thread. Said, done. Now the problem we face is, we calculate on a second thread, but this thread holds references to the graph which can change in the meantime (we agree, this will not happen in most cases but it is to consider). So said, we calculate on a 'dynamic view'. What to do about this? Copy the graph? Then we are at the beginning, blocking the UI thread to copy the graph.
This is a pure design descision and I would be very thankful to hear of someone who has already done such things, if there are other ideas or approaches and if yes, how to solve this elegant not some construction site.
Thanks anyone who can help.
PS: In the gist, you must uncomment and comment the two lines with my comments to see the problem (after pressing button, try to move the window)
Edit: Insteed of gist, here is the code.
public class GraphGymnastic extends Application {
final ExecutorService serv = Executors.newFixedThreadPool(2);
public static void main(String argv[]) {
launch(argv);
}
@Override public void start(Stage primaryStage) throws Exception {
//Setup UI
primaryStage.setTitle("Demo");
final List<Node> nodesInGraph = new ArrayList<>();
final FlowPane p = new FlowPane() {{
setId("flowpane");
getChildren().addAll(new Label("Label") {{
setId("label");
}}, new Button("Button") {{
setId("button");
// setOnMouseClicked(event -> handle(event, nodesInGraph)); //Uncomment and comment below to see effects!
setOnMouseClicked(event -> handleAsync(event, nodesInGraph));
}});
setHgap(5);
}};
//Assume that this goes recursive and deep into a scene graph but still
// returns a list
//Here it takes the two childs for simplicity
nodesInGraph.addAll(p.getChildrenUnmodifiable());
//Show stage
primaryStage.setScene(new Scene(p));
primaryStage.show();
}
public void handle(MouseEvent ev, List<Node> nodesInGraph) {
if (null != nodesInGraph)
Platform.runLater(() -> nodesInGraph.forEach(node -> {
//This will block the UI thread, so there is the need for a second
//thread
System.out.println(
"Calculating heavy on node " + node.toString() + " with event from "
+ ev.getSource().toString());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
}
public void handleAsync(MouseEvent ev, List<Node> nodesInGraph) {
if (null != nodesInGraph)
serv.submit(() -> nodesInGraph.forEach(node -> {
//Now there is a second thread but it works on a LIVE view object
// list, which is ugly
//Option 1: Keep it like this, drawbacks? :S
//Option 2: Copy the graph, costs performance... How deep should it
// copy? :S
System.out.println(
"Calculating heavy on node " + node.toString() + " with event from "
+ ev.getSource().toString());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
}
}