0

I have a JavaFX app where I draw on a canvas. The drawing follows the mouse. This movement is a bit laggy, because the rendering takes some time. That's ok so far. But when I stop the mouse, its coordinates are sometimes still on an old position.

The following code reproduces the problem:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.EllipseBuilder;
import javafx.stage.Stage;

public class TestApp extends Application
{
    public static void main(String[] args)
    {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception
    {
        Pane p = new Pane();

        final Ellipse ellipse = EllipseBuilder.create().radiusX(10).radiusY(10).fill(Color.RED).build();
        p.getChildren().add(ellipse);

        p.setOnMouseMoved(event ->
        {
            ellipse.setCenterX(event.getX());
            ellipse.setCenterY(event.getY());
            Platform.runLater(() -> doSomeWork());
        });

        Scene scene = SceneBuilder.create().root(p).width(1024d).height(768d).build();
        primaryStage.setScene(scene);

        primaryStage.show();
    }

    void doSomeWork()
    {
        try
        {
            Thread.sleep(100);
        }
        catch (Exception ignore) { }
    }
}

When you move the mouse fast and stop abrupt, the Circle is sometimes not under the mouse.

I've play with use or don't use Platform.runLater() or the call order. No success.

Edit: I can't reproduce this behaviour under Windows.

Thomas Klier
  • 449
  • 4
  • 16
  • you MUST NOT sleep (nor do any long-lasting work) on the fx-application thread - no exception. – kleopatra Oct 21 '15 at 16:16
  • see also: http://stackoverflow.com/questions/10682107/correct-way-to-move-a-node-by-dragging-in-javafx-2 (it's a very similar question, though asked against fx2, didn't vote to clos this as duplicate because I suspect this particular use-case slightly different) – kleopatra Oct 21 '15 at 16:18

1 Answers1

1

My assumption is that onMouseMoved is called on the UI-Thread as well as the Platform.runLater. This blocks the UI-Thread and therefore, the last onMouseMoved calls are discarded (which lets the circle be at the position of the last call from onMouseMoved). As defined in the Platform.runLater-Doc:

Additionally, long-running operations should be done on a background thread where possible, freeing up the JavaFX Application Thread for GUI operations.

So try to do your work on an extra-thread and publish it to the UI via runLater after the calculation is done:

public class TestApp extends Application
{

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

    @Override
    public void start(Stage primaryStage) throws Exception
    {
        Pane p = new Pane();

        final Ellipse ellipse = EllipseBuilder.create().radiusX(10).radiusY(10).fill(Color.RED).build();
        p.getChildren().add(ellipse);

        p.setOnMouseMoved(event ->
        {
            ellipse.setCenterX(event.getX());
            ellipse.setCenterY(event.getY());
            doSomeWork();
        });

        Scene scene = SceneBuilder.create().root(p).width(1024d).height(768d).build();
        primaryStage.setScene(scene);

        primaryStage.show();
    }

    void doSomeWork()
    {
        new Thread(){
            public void run(){

                try
                {
                    Thread.sleep(100);
                    Platform.runLater(() -> {
                        // ui updates
                    });
                }
                catch (Exception ignore) { }
            }
        }.start();
    }
}
MalaKa
  • 3,734
  • 2
  • 18
  • 31
  • But the rendering has to be done on the UI-Thread. I could render to an image and draw this image to the canvas. But this drawing would also take some time and wouldn't solve the problem completely. – Thomas Klier Oct 21 '15 at 14:10
  • If the `doSomeWork` is only rendering, then I agree. But this would also mean, that you render inefficiently or too much. If `doSomeWork` on the other hand calculates a lot before actually rendering, then I think these calculations should be decoupled from the rendering itself. – MalaKa Oct 21 '15 at 14:20
  • p.s.: If `doSomework` only renders a scene that does not change with the position of the mouse, you may think about introducing a [rendering loop](http://gameprogrammingpatterns.com/game-loop.html) where the mouse-input is part of processing the input and `doSomeWork` is part of the rendering. – MalaKa Oct 21 '15 at 14:26
  • @ThomasKlier it's a bug: https://bugs.openjdk.java.net/browse/JDK-8087922 - happens even without doSomework – kleopatra Oct 21 '15 at 16:13
  • @kleopatra Good hint. But the issue also occurs with oracle java-8 when `doSomeWork` takes too long. – MalaKa Oct 22 '15 at 07:56
  • I meant to say that this bug only applies to openjdk. It does not seem to occur with oracle-java. At least I couldn't recognize it on my machine (linux mint, oracle-java-8). – MalaKa Oct 22 '15 at 08:42