Virritus
Virritus

Reputation: 39

JavaFX poor Timeline animation performance

I am currently creating a quite simple JavaFX program, simulating planes and ships transporting passengers between cities. So far I've been able to make the planes perform short flights through a few cities but the problem is that when I add more than 3 or 4 planes, the animation is terribly slow.

What I'm doing is using the Timeline class as my main game loop and clearing and then redrawing the plane images on the Canvas every frame at 60 FPS. Here is the timeline part:

Timeline gameLoop = new Timeline();
gameLoop.setCycleCount( Timeline.INDEFINITE );

    KeyFrame keyFrame = new KeyFrame(
            Duration.seconds(0.017),
            new EventHandler<ActionEvent>()
            {
                public void handle(ActionEvent ae)
                {
                    dynamicGC.clearRect(0, 0, 1280, 800);

                    for (Plane plane : Spawner.planeList)
                    {
                        if(plane.isInFlight()) plane.Draw(dynamicGC);
                    }
                }
            });

gameLoop.getKeyFrames().add(keyFrame);
gameLoop.play();

Here is how I create new threads for the planes:

//plane initalization
Thread t = new Thread(plane);
t.setDaemon(true);
t.start();

Here is the run() method defined in the Plane class:

public void run() {
    int routeCounter = 0;
    int direction = 1;
    boolean active = true;
    double recordedTime = 0.0;
    double aParameter = 0.0;
    while(active) {
        if (inFlight) {
            double tmpTime = System.currentTimeMillis();
            double timeDifference = tmpTime - recordedTime;
            recordedTime = tmpTime;

            double change = planeVelocity * (timeDifference) * direction;
            xCoordinate += change;
            yCoordinate += change * aParameter;
            if ( Math.abs(xCoordinate - Spawner.airportList.get(nextAirport).getXCoordinate()) < Spawner.airportList.get(nextAirport).getDetectionPrecision()
                    && Math.abs(yCoordinate - Spawner.airportList.get(nextAirport).getYCoordinate()) < Spawner.airportList.get(nextAirport).getDetectionPrecision()) {
                inFlight = false;
                routeCounter++;
            }
        } else {
            if(routeCounter < plannedRoute.size() ) {
                nextAirport = plannedRoute.get(routeCounter);
                aParameter = (Spawner.airportList.get(nextAirport).getYCoordinate() - this.yCoordinate)/
                        (Spawner.airportList.get(nextAirport).getXCoordinate() - this.xCoordinate);
                if (Spawner.airportList.get(nextAirport).getXCoordinate() < this.xCoordinate) {
                    direction = -1;
                } else direction = 1;
                recordedTime = System.currentTimeMillis();
                inFlight = true;
            }
            else active = false;
        }
    }

}

I'm aware that the code is pretty messy, but right now I want to know why do animations slow down so dramatically. I'm unsure if I'm using threads correctly. Also I've tried some solutions like caching the Canvas and Group nodes like in an answer to this question and it did nothing.

EDIT: It seems that it's the threads that slow down the execution of the program.

I create a new plane (and a new thread for it) every time a certain button is pressed with this handler in the JavaFX Application Thread:

@FXML
private void handleNewPassengerPlaneCreated(ActionEvent event) {
    ArrayList<Integer> route = new ArrayList<>();
    int startingAirport = Spawner.randomNumberGenerator.nextInt(Spawner.airportList.size());
    for(int i = 0; i < 3; i++) {
        int addedAirport = Spawner.randomNumberGenerator.nextInt(Spawner.airportList.size());
        if ( addedAirport != startingAirport)
            route.add(addedAirport);
    }

    System.out.println(Spawner.airportList.get(startingAirport).xCoordinate + " " + Spawner.airportList.get(startingAirport).yCoordinate );
    PassengerPlane plane = new PassengerPlane(Spawner.airportList.get(startingAirport).xCoordinate,
            Spawner.airportList.get(startingAirport).yCoordinate, "ID", 10, 1000, route, 300);
    Spawner.planeList.add(plane);
    new Thread(plane).start();
}

Upvotes: 0

Views: 1643

Answers (4)

Virritus
Virritus

Reputation: 39

The problem was I wasn't calling Thread.sleep(time) in my run() method, when I added it in with time = 10 the animation started working smoothly.

Upvotes: -1

mipa
mipa

Reputation: 10640

I am wondering why you are using a canvas for this purpose at all. Probably just because you are used to it from AWT or similar framework. The scene graph is much better suited for this kind of problem. Just place some objects in the scene graph at their initial positions and then update their positions and orientation and what else in ONE AnimationTimer and you are done.

Upvotes: 0

hotzst
hotzst

Reputation: 7526

I have a similar problem, however there are a few things that you might want to consider, that can greatly improve the performance of your code:

  1. For each plane that needs animation, create a Path along which the plane is animated. Take a look at How to write text along a bezier curve. However you path is probably not made out of a single Bèzier curve, but more likely a Bèzier path. so take a look at these articles: Bezier curves a tutorial and Bèzier path algorithms with examples in C#. Then you can simply define a PathTransition for each plane.
  2. If the above is not possible for one reason or another, consider splitting up the responsibilities:
    • Create a model storage for the planes that are travelling (can be something easy like a list or map)
    • Create a worker thread (most likely an AnimationTimer). This one will be called on each pulse of the UI application thread, which should equal your frame rate. In that thread you can then update the positions of all the planes in your storage and update the positions of your planes in the UI. Take a look at this question and answer

From what I can see from your code, you are creating a separate thread for each plane. Threads are expensive objects, especially for tasks that only take a few cpu cycles each time the thread is run. Then you have to consider the costs of the thread switches over the actual computation time. Therefore when using a thread make sure you use it for long running tasks.

Upvotes: 0

Roland
Roland

Reputation: 18425

You should use a different approach. Take a look at the answer in "In JavaFX how do I move a sprite across the screen?".

Instead of a timeline you should use an AnimationTimer and update the canvas in there.

Regarding your question: One problem may be that you are mixing Thread with the JavaFX Thread which is probably causing issues, can't tell without seeing the full code.

Upvotes: 1

Related Questions