5
\$\begingroup\$

Some time ago I made my simple game loop, so here's the code:

public class Game {

    private final int TARGET_FPS = 60;
    /** optimal waiting time in milliseconds */
    private final long OPTIMAL_TIME = 1000 / TARGET_FPS;

    /** last Frame time */
    private long lastFrame;
    private int fps;
    /** last FPS time in ms */
    private long lastFPS;
    private boolean running;

    public Game() {
        running = true;
    }

    public static void main(String[] args) {
        Game game = new Game();
        game.gameLoop();
    }

    private void gameLoop() {
        initialize();

        while (running) {
            int delta = getDelta();

            update(delta);
            render();

            synchronize(lastFrame - getTime() + OPTIMAL_TIME);
        }
    }

    private void initialize() {
        getDelta();
        lastFPS = getTime();
    }

    private void update(int delta) {
        // ToDo
        updateFPS();
    }

    private void render() {
        // ToDo
    }

    private void synchronize(long ms) {
        try {
            if (ms > 0) {
                Thread.sleep(ms);
            }
        } catch (InterruptedException ex) {
            System.err.println(ex.getMessage());
            System.exit(-1);
        }
    }

    /**
     * Returns System time in milliseconnds
     * @return System time in ms
     */
    private long getTime() {
        return System.nanoTime() / 1000000;
    }

    /**
     * Returns time difference since the last Frame
     * @return time difference in ms
     */
    private int getDelta() {
        long time = getTime();
        int delta = (int) (time - lastFrame);
        lastFrame = time;
        return delta;
    }

    private void updateFPS() {
        if (getTime() - lastFPS > 1000) {
            System.out.println("FPS: " + fps);
            fps = 0;
            lastFPS += 1000;
        }
        fps++;
    }
}

Now I want to ask, what can I improve here?

One thing that I would really like to change is the updateFPS() method. Of course that System.out would get deleted, but recently I thought about lastFPS... if the game is running long enough then it could overflow right? Because of that I tried to solve it like this:

private void updateFPS(int delta) {
    if (lastFPS > 1000) {
        System.out.println("FPS: " + fps);
        fps = 0;
        lastFPS = 0;
    }
    lastFPS += delta;
    fps++;
}

Running fine so far, but there's something that bothers me:

When I count the fps in the original version it usually gets fps between 59-60, but when I try that new version it'll show 61-62 fps. I don't know... can the new version be faster after that change or is my calculation wrong?

\$\endgroup\$
4
\$\begingroup\$

I would expect that your loop will run at 62.5 FPS based on the code you have.... but with some variation on timers and rounding-down, I would expect that to drop slightly to 61 FPS occasionally.

This would reflect your results where you see between 61 and 62 FPS.

Why?

Because your code is full of integer-division, and you are losing precision everywhere.

Consider the first statements in your code:

private final int TARGET_FPS = 60;
/** optimal waiting time in milliseconds */
private final long OPTIMAL_TIME = 1000 / TARGET_FPS;

Target frames-per-second is 60.

Now, 1000ms / 60 is 16.666 milliseconds, except, it's not. It is 16ms because you are doing integer division.

So, your frame period is set at 16ms... with 16ms frames, you get and actual FPS of 62.5 FPS.

But, later on, calculate the current time in milliseconds as:

private long getTime() {
    return System.nanoTime() / 1000000;
}

Which does another integer division... and the use of nanoTime is completely useless because all the nanoseconds are simply lost. so you don't do any 'rounding' on the code.

The code is full of these types of integer-based division problems.

What you need to do is settle on just using values denominated in nanoseconds. The rounding errors will be 1 millionth of what you have now, and the time constraints will 'just work'.

Note that the Thread.sleep(long, TimeUnit) method allows you to specify Nanosecond delays, but the reality is that it will not help because many systems are not that granular with their interrupts.

Having said all of that, what you really want to do is use a repeating timer with a gated access to a result.

Set up a ScheduledThreadPool which can either run your frame's work, or alternatively it can gate the main thread.

This would be the best way to get consistent (and predictable) frame rates

\$\endgroup\$
  • 1
    \$\begingroup\$ Well I thought using integer was okay because Thread.sleep() won't take some floats. Didn't consider the rounding erros would be relevant. But I'll definitely look into that ScheduledThreadPool :) \$\endgroup\$ – GamingGuy Feb 28 '14 at 20:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.