How efficient is the java game loop?

464    Asked by RogerLum in Java , Asked on Oct 13, 2022

 I'm quite new to java.

I was watching 2 video tutorials on how to make a java game and read a couple articles about game loops. However, I'm still confused about how the game loop actually works.

The first tutorial used code that looked like this (I modified some codes from the tutorial and commented on top):

/*
 * Game Loop of the game. It uses a fixed timestep. 
 * Optimal time represents time needed to update one frame.
 * Update time represents time actually taken to update one frame.
 * 
 * By calculating the difference between optimal and actual time,
 * we can let Thread to sleep for the exact time we are aiming for, which is 60 FPS.
 */


public void run() {

    long now;

    long updateTime;

    long wait;


    final int TARGET_FPS = 60;
    final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
    while (isRunning) {
        now = System.nanoTime();
        update();
        render();
        updateTime = System.nanoTime() - now;
        wait = (OPTIMAL_TIME - updateTime) / 1000000;
        try {
            Thread.sleep(wait);
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

}

This is quite easy to understand and I have no problem with using this game loop. However, the second tutorial used more complex game loop that I had a hard time understanding it:


public void run() {
    long startTime = System.nanoTime();
    double amountOfTicks = 60.0;
    double ns = 1000000000 / amountOfTicks;
    double delta = 0;
    long timer = System.currentTimeMillis();
    int frames = 0 ;
    while (running) {
        long now = System.nanoTime();
        delta += (now - lastTime) / ns;
        lastTime = now;
        while (delta >= 1) {
            tick();
            delta--;
        }
        if(running)
            render();
        frames++;
        if(System.currentTimeMillis() - timer > 1000) {
            timer += 1000;
            System.out.println("FPS: " + frames);
            frames = 0;
        }
    }
    stop();
}

I do not understand the use of timer and frames variable and how delta is being used. Is timer and frames simply used for printing out the FPS on the console screen? Also, help me clarify the use of the delta variable in this code.


The use of delta variables is quite different from the other game loop that I know of. From the articles that I read, I've seen a different use of delta. Here's what I tried to come up with on my own after reading the articles:


/*
 * Delta is the ratio of how fast a computer is running. 
 * It is achieved by calculating: 
 * (actual time) / (optimal time) 
 * 
 * This is passed to update method to calculate time taken
 */
public void run() {
    long lastLoopTime = System.nanoTime();
    final int TARGET_FPS = 60;
    final long OPTIMAL_TIME = 1000000000 / TARGET_FPS;
    while (isRunning) {
        long now = System.nanoTime();
        long updateTime = now - lastLoopTime;
        lastLoopTime = now;
        double delta = updateTime / (double) OPTIMAL_TIME;
        update(delta);
        render();
    }
}

Will the above code be fine as it is?

Out of these three sets of codes, which game loop would be the most optimal to use? I feel like the first set of code is too simple that it might not be as efficient as the other two (not that it actually is, but just a feeling that I get).

Answered by Rohan Rao

Comparison of the three versions

First version

The first version of the code, there is no measure of the elapsed time since the last update. That would usually make the game frame rate dependent. It is compensated by inserting waiting via Thread.Sleep, which will give you a fixed timestep on a fast machine. Which is good in that it prevents the render thread from eating up CPU time.

By the way, we usually update first to have something to render on the first iteration, also we do not want to wait between update and render.

Note: The time the system does not guarantee to preempt the thread for exactly the time you are in Thread.Sleep.

Second version

The second java game loop does not pass the elapsed time either, instead it will let render happen as fast as it can and call update (tick) at a steady frequency, making the game stable. It also includes a simple FPS counter. Advanced FPS counters are beyond the scope of this answer. This version of the game loop is better if you want to debug the render performance (see how fast you can get it to run), however, it is not optimal.

The check of running before calling render, is there because update (tick) can change running (notice running is not local), and since the call to render happens after, this prevents an extra call. Should not be a problem, assuming teardown happens inside of the stop method. If it were a problem, I would break the loop instead.

About the use of the variable timer, as you may know, you can simulate timers in the update method by this technique. However, I would recommend to use the approach used for delta, because it avoids possible overflows on a long run. In fact, you may want to avoid System.currentTimeMillis because it is susceptible to changing the system clock.

Third version

The third code (the one you came up with) will also let rendering happen as fast as it can, and passes the elapsed time to the update method. Yet, it does not control the update rate.

About the naming of delta

In the second version, delta is an accumulator that is used to decide when to call update (tick). On the last version, it is the elapsed time. Usually delta means the latter. I have also seen game loops that name the former delta because it is the time elapsed since the last call to update.

Picking a game loop

Now, should you use a fixed timestep (like the first version), or should you use a variable timestep (like the others)?

Fixed timestep

The first version

First, the fixed timestep will serve you better if you know the target hardware. As I said, it will prevent the render thread from eating too much CPU, which is good on slow machines, which probably will have a low frame rate also.

Another advantage is that the physic computations are simpler, because you are not taking the elapsed time from the last frame. That also means that any variance in update time will be more noticeable.

However, it may not age well, for instance if you make your game for 30 or 60 FPS when people are expecting 120 FPS.

Thus, if you do not know the hardware, I would advise to make the timestep a configurable option... yet, you would have to take that into account for the update (remember that in the first version, the update does not take the elapsed time).

Semi-fixed timestep

Not represented on the question

You take the structure of the fixed timestep approach, and still measure the elapsed time from the last update, and still use it to decide if you call update or not... but you may also pass it to the update method.

You would lose the simple physics (which is probably good if your game requires complex physics anyway), you will still be saving CPU time, and you will be accounting for any variance in the update times.

The target timestep is still something you control.

Variable timestep

The second version controls the update rate, the third does not

With this version, the game will run as smoothly as the system allows it, meaning it will have a render rate as high as possible. It has the drawback of requiring actual physics computations that use the elapsed time.

There is no way to configure the render rate; you may still control the update rate.

If you let the update rate be variable, it can give you problems with fast moving objects, in particular on slow hardware. The elapsed time could mean that objects moved a long distance and you would have to resolve a large amount of collisions per frame, dragging the performance.

While keeping the update rate variable, a common approach is to have a maximum of collisions you solve. However, that can lead to objects tunnelling through other objects or getting out of bounds. An alternative is to clamp the elapsed time. That would make the game go slower (things move at a lower speed) on slow hardware.

Beyond the game loop

First, do not expect to create a game loop that fits every game. In fact, some games can work perfectly by listening to the events provided by the widget library (that is still a loop, but you did not write it). Each game loop has its tradeoffs, and it is a good idea to test and profile on the target hardware.

With that said, here are some other considerations:

For an Android game, you may want to implement a pause field, that if set, you do not call update. You would then update it according to the activity lifecycle.

You can take advantage of threading, in particular to call subsystems that require more time than one update. The mechanism for this is beyond the scope of the answer.

However, I want to note that threads will compete for CPU time with the game loop, in that case having a call to Thread.Sleep in the game loop will help, even if only Thread.Sleep(0). In fact, insert a Thread.Sleep(0) at the end of the loop, even in the fixed timestep approach, in case the system is too slow that it never reaches the Thread.Sleep you already had.

Another thing you can do is have the render method be aware of the elapsed time. In particular, if you have a fixed timestep or if you control the update rate, you can compute the time for the next update... with that, you compute the fraction of the time to the next update that has elapsed, and pass it to the render method. The render method would then pass it to the shader as a uniform, allowing animations to happen in GPU. This can give you smooth animation regardless of update rate, making them great for the fixed timestep approach... you upload the fraction, the old state and the new state to the GPU, and let it interpolate (fantastic for morph target animation).

Finally, consider breaking the interaction with every external system. Such as getting input, playing sounds, etc. Those are not exactly rendering, and are not exactly updating.



Your Answer

Interviews

Parent Categories