1

I'm trying to show the process of a maze being generated on screen, but the screen never updates until the process is complete. I'm using Consumers to notify the JavaFX side. I've tried using Platform.runLater() to no avail.

Here are the files in use:

"hello-view.fxml"

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.StackPane?>
<StackPane onMouseClicked="#randomize" fx:id="sp" xmlns:fx="http://javafx.com/fxml" fx:controller="com.example.demo2.HelloController">
</StackPane>

Here's a truncated version of the maze generator, "BasicMaze.java":

package com.example.demo2;
import java.util.*;
import java.util.function.Consumer;

public class BasicMaze {
    public int width;
    public int height;

    private transient List<Consumer<BasicMaze>> consumers = new ArrayList<>();

    public void reset(int w, int h) {
        width = w;
        height = h;
        alertConsumers();
    }

    public void addConsumer(Consumer<BasicMaze> l) {
        if (consumers == null) consumers = new ArrayList<>();
        consumers.add(l);
    }

    public void alertConsumers() {
        consumers.forEach(c -> c.accept(this));
    }

    public void generate() {
        int slots = width * height;

        do {
            if (Math.random() * 10 < 1) {
                alertConsumers(); //Platform.runLater tried here
                slots--;
            }
        } while (slots > 1);
        System.out.println("DONE!");
    }
}

Here's the Controller, "HelloController.java":

package com.example.demo2;

import javafx.fxml.FXML;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;

public class HelloController {
    @FXML
    StackPane sp;

    final Canvas canvas = new Canvas(250,250);
    BasicMaze maze = new BasicMaze();

    @FXML
    public void initialize() {
        sp.getChildren().add(canvas);
        maze.addConsumer(basicMaze -> this.draw()); //platform.runLater tried here.
    }

    public void randomize() {
        maze.reset(5, 5);
        maze.generate();
    }

    public void draw() {
        GraphicsContext gc = canvas.getGraphicsContext2D() ;
        gc.setLineWidth(1.0);
        gc.setFill(Color.BLACK);
        for (int j = 0; j < maze.height; j++) {
            for (int i = 0; i < maze.width; i++) {
                gc.moveTo(Math.random()*250, Math.random()*250);
                gc.lineTo(Math.random()*250, Math.random()*250);
                gc.stroke();
            }
        }

    }

}

And here's the main function stub:

package com.example.demo2;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("hello-view.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 320, 240);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}

If you run this and click on the screen, "DONE!" will appear on the console, there'll be a lag, then the screen will present a bunch of black lines. I'm trying to get the screen to update as each line is drawn.

user3810626
  • 6,798
  • 3
  • 14
  • 19
  • 1
    How is your maze displayed over JavaFX? – fireandfuel Sep 11 '21 at 22:03
  • 1
    It is your `draw()` method which needs to use Platform.runLater. – VGR Sep 11 '21 at 22:14
  • I tried runLater and not sure what I'm seeing. (Updated above.) – user3810626 Sep 12 '21 at 01:10
  • 1
    [mcve] please.. – kleopatra Sep 12 '21 at 02:15
  • 1
    This [example](https://stackoverflow.com/a/53853731/3992939) might help. For more help post mre as already requested. – c0der Sep 12 '21 at 04:18
  • Edited to add MRE. – user3810626 Sep 12 '21 at 05:30
  • I believe you want your JavaFX application to be animated. There are many online resources explaining how to achieve this, for example this [tutorial](https://docs.oracle.com/javase/8/javafx/visual-effects-tutorial/title.htm). Just search for **JavaFX animation** – Abra Sep 12 '21 at 06:36
  • Looking at your code I think you have numerous issues. Some are errors, some are suboptimal implementations. I’d suggest using the scene graph rather than the canvas or at least not drawing the entire maze on each generation step. If you want to multithread, you need to create a thread and use a Task with feedback to the scene on each step, pausing between steps (read the Task Java doc and JavaFX concurrency docs). Even better than multithreading would probably be to use a Timeline with a cycled key frame to generate the next maze portion. – jewelsea Sep 12 '21 at 08:55
  • @jewelsea I'm not drawing on the canvas for the actual program. That just seemed the easiest way to demonstrate the issue. The actual example adds Rectangles to the graph, but behaves exactly the same. – user3810626 Sep 12 '21 at 15:59

1 Answers1

1

OK, this actually turns out to be super easy (barely an inconvenience!), which I sussed out from some of Sergey Grinev's posts. All that has to be changed (almost) is to run the background task on its own thread.

So, we wrap the maze.generate() in a thread:

public void randomize() {
    maze.reset(500, 500);
    new Thread(maze::generate).start();
}

Now the runLater in the initialize will work!

@FXML
public void initialize() {
    sp.getChildren().add(canvas);
    maze.addConsumer(basicMaze -> Platform.runLater(this::draw));
}

The underlying object doesn't need to change at all.

0009laH
  • 1,960
  • 13
  • 27
user3810626
  • 6,798
  • 3
  • 14
  • 19