JavaFX Thread Failure

I have a JavaFX application that animates robots (black dots) on the screen and draws a small white line against a lighgray background, wherever they are (think Throne). For this, I save all the coordinates of the robots and all white pixels. The behavior of the robots is controlled by another thread that implements Runnable and can be changed during the simulation. The coordinates of the robot are stored in HashMap with the coordinates being a class that extends Point and uses doubles for higher internal calculation accuracy for x and y values. For white points, I use HashMap, because integer accuracy is sufficient for them, since they do not move and remain indefinitely on these x and y coordinates on the screen.

Now the program works very well, but when the HashMap saves dots for white dots, it becomes more likely to destroy the flow of the JavaFX application (think about it, more precisely, this is just the canvas where the robots are drawn.) The sliders of the controls remain responsive, and the text fields for iterations and HashMap sizes continue to be updated. But nothing comes to life, and after a few seconds the canvas turns white. Increasing ms for Thread.sleep (ms) makes the program more stable, but it is painfully slow as it already is. It also happens more often and faster on my slow school netbook (running Win XP) than on my home desktop PC (running Win7 64Bit). An exception is also literal. For desktop, this is the following:

java.lang.InternalError: Unrecognized PGCanvas token: 68
at com.sun.javafx.sg.prism.NGCanvas.renderStream(NGCanvas.java:651)
at com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:320)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:187)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:39)
at com.sun.javafx.sg.BaseNode.render(BaseNode.java:1145)
at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:204)
at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:420)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:187)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:39)
at com.sun.javafx.sg.BaseNode.render(BaseNode.java:1145)
at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:117)
at com.sun.javafx.tk.quantum.AbstractPainter.paintImpl(AbstractPainter.java:175)
at com.sun.javafx.tk.quantum.PresentingPainter.run(PresentingPainter.java:73)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:351)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:178)
at com.sun.prism.render.RenderJob.run(RenderJob.java:37)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:98)
at java.lang.Thread.run(Thread.java:724)

For a netbook, this is:

java.lang.IllegalArgumentException: alpha value out of range
at java.awt.AlphaComposite.<init>(AlphaComposite.java:624)
at java.awt.AlphaComposite.getInstance(AlphaComposite.java:689)
at java.awt.AlphaComposite.derive(AlphaComposite.java:761)
at com.sun.prism.j2d.J2DPrismGraphics.setExtraAlpha(J2DPrismGraphics.java:569)
at com.sun.javafx.sg.prism.NGCanvas.renderStream(NGCanvas.java:739)
at com.sun.javafx.sg.prism.NGCanvas.renderContent(NGCanvas.java:389)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:201)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:40)
at com.sun.javafx.sg.BaseNode.render(BaseNode.java:1145)
at com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:204)
at com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:420)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:201)
at com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:40)
at com.sun.javafx.sg.BaseNode.render(BaseNode.java:1145)
at com.sun.javafx.tk.quantum.ViewPainter.doPaint(ViewPainter.java:117)
at com.sun.javafx.tk.quantum.AbstractPainter.paintImpl(AbstractPainter.java:182)
at com.sun.javafx.tk.quantum.PresentingPainter.run(PresentingPainter.java:73)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:351)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:178)
at com.sun.prism.render.RenderJob.run(RenderJob.java:37)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
at com.sun.javafx.tk.quantum.QuantumRenderer$PipelineRunnable.run(QuantumRenderer.java:108)
at java.lang.Thread.run(Thread.java:722)

Any help to correct or narrow this problem would be greatly appreciated.

: , , . Visualizer,

import java.awt.Point;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import javafx.application.Application;
import javafx.geometry.Rectangle2D;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Screen;
import javafx.stage.Stage;

public class Visualizer extends Application {

    private static GraphicsContext gc;
    private static Canvas canvas;
    private static BorderPane pane;
    private static Scene scene;
    private static Thread thread;
    private static Simulator sim = new Simulator();
    private static int optionsWidth = 200;
    private static HashSet<Point> manganCollected = new HashSet<Point>();
    private static HashMap<Integer, Coordinates> coordinates = new HashMap<Integer, Coordinates>();
    private static final int zoom = 4;
    private static Point cmpP;
    /**
     * Height in pixels that available to draw the simulation on dependent on used monitor resolution 
     */
    public static double simHeight;
    /**
     * Width in pixels that available to draw the simulation on dependent on used monitor resolution 
     */
    public static double simWidth;


    /**
     * Launches the JavaFX application
     * @param args command line arguments if there are any
     */
    public static void main(String[] args) {
        launch(args);   
    }

    /**
     * Sets up the GUI with all options and the canvas to draw the robots on 
     */
    @Override
    public void start(Stage stage) throws Exception {

        // Determine screen width of the monitor
        Screen screen = Screen.getPrimary();
        Rectangle2D bounds = screen.getVisualBounds();
        double screenHeight = bounds.getHeight();
        double screenWidth = bounds.getWidth();

        // set the stage
        stage.setFullScreen(true);
        stage.setHeight(screenHeight);
        stage.setWidth(screenWidth);
        stage.setTitle("Manganernte");

        // Canvas to draw the simulation on 
        canvas = new Canvas();
        simHeight = screenHeight;
        simWidth = screenWidth - optionsWidth;
        canvas.setHeight(simHeight);
        canvas.setWidth(simWidth);
        gc = canvas.getGraphicsContext2D();
        gc = canvas.getGraphicsContext2D();
        gc.setFill(Color.LIGHTGRAY);
        gc.fillRect(0, 0, simWidth, simHeight);
        gc.setStroke(Color.BLACK);
        gc.setLineWidth(3);
        gc.strokeRect(0, 0, simWidth, simHeight);

        // BorderPane containing the buttons box and the Simulator canvas
        pane = new BorderPane();
        pane.setCenter(canvas);

        // Scene containing the pane
        scene = new Scene(pane);

        // Show the whole stage
        stage.setScene(scene);
        stage.show();
        thread = new Thread(sim);
        thread.start();
    }

    /**
     * Transforms double coordinates as used by the simulator (0/0 in the center) to monitor coordinates (0/0 top left corner)
     * @param coordinates Floating point coordinates that should be transformed
     * @return Coordinates Floating point coordinates that have been transformed
     */
    private static Coordinates transform(Coordinates coordinates) {
        return new Coordinates(Math.round((simWidth / 2) + (zoom * coordinates.getX())), Math.round((simHeight / 2) + (zoom * coordinates.getY()))); 
    }

    /**
     * Transforms integer coordinates as used by the simulator (0/0 in the center) to monitor coordinates (0/0 top left corner)
     * @param Point Integer coordinates that should be transformed
     * @return Point Integer coordinates that have been transformed
     */
    private static Point transform(Point point) {
        return new Point((int)Math.round((simWidth / 2) + (zoom * point.getX())), (int)Math.round(((simHeight / 2) + (zoom * point.getY())))); 
    }

    /**
     * Clear the canvas by drawing a rectangle filled with light gray background
     */
    private static void clear () {
        gc.setFill(Color.LIGHTGRAY);
        gc.fillRect(0, 0, simWidth, simHeight);
        gc.setStroke(Color.BLACK);
        gc.setLineWidth(3);
        gc.strokeRect(0, 0, simWidth, simHeight);
    }

    /**
     * Clears the canvas and then draws first the collected mangan as white rectangles followed by robots as black circles the given coordinates
     * @param redraw boolean that set to true if iteration hasn't changed but a redraw should be forced anyway (e.g. when simulation is paused and the zoom is used)
     */
    public static void DrawRobots() {
        coordinates = Simulator.coordinates;
        manganCollected = Simulator.manganCollected;
        // clear the canvas with light gray background
        clear();
        // draw harvested mangan as white dots
        gc.setFill(Color.WHITE);
        Iterator<Point> it = manganCollected.iterator(); 
        while(it.hasNext()) {
            cmpP = it.next().getLocation();
            double x = transform(cmpP).getX();
            double y = transform(cmpP).getY();
            gc.fillRect(x, y, zoom, zoom);
        }
        // draw robots
        gc.setFill(Color.BLACK);
        for(int i = 1; i <= coordinates.size(); i++) {
            double x = transform(coordinates.get(i)).getX();
            double y = transform(coordinates.get(i)).getY();
            gc.fillOval(x, y, zoom, zoom);
        }
    }
}

:

import java.awt.Point;
import java.util.HashMap;
import java.util.HashSet;

public class Simulator implements Runnable {

    // start variable declarations
    // HashMap of all robot objects the simulator controls
    private static HashMap<Integer, Robot> robots = new HashMap<Integer, Robot>();
    // HashMap of all coordinate objects the simulator controls
    public static HashMap<Integer, Coordinates> coordinates = new HashMap<Integer, Coordinates>();
    // HashMap of all point objects containing the coordinates of places where the mangan has already been collected
    public static HashSet<Point> manganCollected = new HashSet<Point>();
    /**
     * communication radius of the robots according to the requirements
     */
    public static int processSpeed = 100;
    // end variable declarations

    /**
     * Create a robot with x and y
     * @param x x-coordinate
     * @param y y-coordinate
     */
    public static void createRobot(int x, int y) {
        coordinates.put(coordinates.size() + 1, new Coordinates(x, y));
        robots.put(robots.size() + 1, new Robot());
    }

    /**
     * Checks the status, changes it if necessary and acts accordingly
     */
    @Override
    public void run() {
        for(int i = 0; i < 100; i++) createRobot(i - 50, 0);
        Visualizer.DrawRobots();
        while(true) {
            for(int i = 1; i <= robots.size(); i++) robots.get(i).think();
            for(int i = 1; i <= robots.size(); i++) {
                coordinates.get(i).add(robots.get(i).move());
                manganCollected.add(new Point((int)Math.round(coordinates.get(i).getX()), (int)Math.round(coordinates.get(i).getY())));
            }
            Visualizer.DrawRobots();
            try {
                Thread.sleep(processSpeed);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

, , , :

public class Robot {

    Coordinates future = new Coordinates(0, 0);

    public void think () {
        future.setY(1.0);
    }

    public Coordinates move() {
        return future;
    }
}

UPDATE JDK 8, :

Exception in thread "Thread-4" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-4

, Platform.runLater(), . , . , :

Class Visualizer, , main() , , GraphicsContext. Simulator , Runnable. run() Simulator while (true). , , thread.sleep(). "", , Visualizer . , , , JavaFX.

runLater(), , , ?

2 , ( ), , - UI/Canvas runLater, .

public static void drawStuff() {
    Platform.runLater(new Runnable() {
    @Override
        public void run() {
            // draw stuff
        }
    });
}
+4

All Articles