0

I'm working on a java 2d game, using this simple game loop to cap the FPS and UpdatesPS to 60:

public void run() {

        final int MAX_FPS = 60;
        final int MAX_UPS = 60;

        final double fOPTIMAL_TIME = 1000000000 / MAX_FPS;
        final double uOPTIMAL_TIME = 1000000000 / MAX_UPS;

        double uDeltaTime = 0, fDeltaTime = 0;
        int frames = 0, updates = 0;
        long startTime = System.nanoTime();
        long timer = System.currentTimeMillis();

        // GameLOOP starts here
        while (running) {

            long currentTime = System.nanoTime();
            uDeltaTime += (currentTime - startTime);
            fDeltaTime += (currentTime - startTime);
            startTime = currentTime;

            if (uDeltaTime >= uOPTIMAL_TIME) {
                gameUpdate();
                updates++;
                uDeltaTime -= uOPTIMAL_TIME;
            }

            if (fDeltaTime >= fOPTIMAL_TIME) {
                gameRender();
                gameDraw();
                frames++;
                fDeltaTime -= fOPTIMAL_TIME;
            }

            if (System.currentTimeMillis() - timer >= 1000) {

                fps = frames;   //saves the current FPS
                ups = updates;  //saves the current UPS

                updates = 0;
                frames = 0;
                timer += 1000;
            }

        }
    }

The loop works, but I get only 30 FPS for the first ~10 Seconds after starting the game.

After I wait, the FPS raises up to the wanted 60. I don't have a problem to wait a few seconds to let the program stabilize and reach the wanted framerate. But I can't find the reason like a methode who drops the FPS because it's fetching a big file after startup.

Do you have any idea why my engine needs so long to stabilize the framerate?

Thanks for your help!

  • 1
    This busy loop wastefully consumes 100% of one CPU, while these CPU resources could be otherwise spent on a useful job like JIT compilation. – apangin Feb 06 '21 at 17:38
  • 1
    To reduce busy wait, instead of skipping the two `if` many times, you should compute the *next* time which will be relevant for an update or a redraw and sleep until this time. – prog-fh Feb 06 '21 at 18:07
  • Your optimal time constants shouldn't be doubles if you're going to create them with integer division. I'd make them integers anyway. – NomadMaker Feb 06 '21 at 20:54

1 Answers1

0

I think this should do the trick:

    public static void run()
    {
        final int desiredFPS = 60;
        final int desiredUPS = 60;
        
        final long updateThreshold = 1000000000 / desiredUPS;
        final long drawThreshold = 1000000000 / desiredFPS;
        
        long lastFPS = 0, lastUPS = 0, lastFPSUPSOutput = 0;
        
        int fps = 0, ups = 0;
        
        loop:
        while(true)
        {
            if((System.nanoTime() - lastFPSUPSOutput) > 1000000000)
            {
                System.out.println("FPS: " + (double)fps);
                System.out.println("UPS: " + (double)ups);
                
                fps = 0;
                ups = 0;
                
                lastFPSUPSOutput = System.nanoTime();
            }
            
            if((System.nanoTime() - lastUPS) > updateThreshold)
            {
                lastUPS = System.nanoTime();
                updateGame();
                ups++;
            }
            
            if((System.nanoTime() - lastFPS) > drawThreshold)
            {
                lastFPS = System.nanoTime();
                drawGame();
                fps++;
            }
            
            // Calculate next frame, or skip if we are running behind
            if(!((System.nanoTime() - lastUPS) > updateThreshold || (System.nanoTime() - lastFPS) > drawThreshold))
            {
                long nextScheduledUP = lastUPS + updateThreshold;
                long nextScheduledDraw = lastFPS + drawThreshold;
                
                long minScheduled = Math.min(nextScheduledUP, nextScheduledDraw);
                
                long nanosToWait = minScheduled - System.nanoTime();
                
                // Just in case
                if(nanosToWait <= 0)
                    continue loop;
                    
                try
                {
                    Thread.sleep(nanosToWait / 1000000);
                } 
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        }
    }

Edit: I fixed the issue now! The issue was that I was saving the lastFPS/lastUPS after the scene was updated/drawn, and when I set both lastUPS/lastFPS before the scene is drawn/updated, we get the desired fps!

Another neat thing about this code is that it doesn't consume a whole cpu core(I measured the difference, your code was consuming 100%, whilest my code only consumed about 10%. If you want to measure it yourself, please note that for some reason, the core on which the code is executed regularly switches(at least this was the case when I measured the code))

By the way if you use LWJGL (or have direct access to a windowing library like GLFW) you can activate V-Sync, which would cut your fps down to 60 fps.

Budschie
  • 32
  • 6
  • Ok there is a bug at the moment; this currently only gets to 49 fps, but I checked the CPU usage and it went down from 100% on one(your code) core to about 5%(my code). – Budschie Feb 06 '21 at 20:55
  • Thanks for your suggestion. But still have the same problem. The game starts with exactly 30 FPS and 30 UPS and slowly raises up. Even if the game reaches the 60 FPS, the rate isn't stable. – Jannes van Rüschen Feb 07 '21 at 18:19
  • @JannesvanRüschen What exactly is your game? Is it a 2D-game, 3D-game, how do you draw it, with what are you drawing it (Java GUI, OpenGL) etc? – Budschie Feb 07 '21 at 21:09
  • It is a swing based 2d game. I use a double buffering Methode to render a graphics object in the first and draw it to the screen in the second step. It’s not very complex and I don’t think it will take the whole capacity of one cpu core. – Jannes van Rüschen Feb 07 '21 at 22:25
  • @JannesvanRüschen Did you monitor your CPU cores? Maybe you could find a hint there. I'd recommend you looking at the capacity of the RAM and the CPU. Additionally, you might want to use JVM arguments to change GC or "add" RAM (how exactly is explained pretty good in this thread https://stackoverflow.com/questions/14763079/what-are-the-xms-and-xmx-parameters-when-starting-jvm)). Sadly, I'm not an expert with Swing, so I can't give you any tips on how to optimize the usage of it. But I'd recommend you looking closely to what happens at startup of your program. – Budschie Feb 08 '21 at 10:52