Search code examples
c++sfmlgame-loop

Using sleep() in game loop makes delta time unstable


I'm writing a game loop with sfml. When I don't make it sleep, time elapsed for each loop iteration is ~1ms. But when I add sleep(sleepTime) suddenly dt is high. I restart dt at the beginning of the loop but it seems that it adds last sleep time to it. What causes it?

sf::Clock clock;
float dt;
sf::Time sleepTime = sf::milliseconds(0);

while(m_Window.isOpen())
{
    sf::Time elapsed = clock.restart();
    dt = elapsed.asMilliseconds();

    cout << "Elapsed: " << dt;

    sf::Event event;

    while(m_Window.pollEvent(event))
    {
        switch(event.type)
        {
            case sf::Event::Closed:
                m_Window.close();
                break;
        }
    }

    sleepTime = sf::milliseconds(16 - dt);

    float time = sleepTime.asMilliseconds();
    cout << "\tSleep time: " << time << endl;

    if(sleepTime >= sf::Time::Zero)
    {
        sf::sleep(sleepTime);
    }
    else
    {
        cout << "Shit." << endl;
    }

Without sleep: https://aww.moe/sn1z0a.png

With sleep: https://aww.moe/7seof1.png


Solution

  • What you're trying to do – limiting the game to a specific framerate – is already built into SFML. Just call sf::Window::setFrameRateLimit() with your intended framerate as parameter and you're set. It's also possible to use vertical synchronization (by using sf::Window::setVerticalSyncEnabled()) to limit the number of frames/updates, although it's often considered a bad idea, since the game would also slow down if the target machine can't render at the desired framerate (or speed up for high end screens running at 120 or 140Hz).

    However, you'll typically want to disconnect your game updates from your frame rate so the game doesn't slow down, even if the current machine can't update the screen fast enough.

    The basic approach using SFML will typically look like this (this is from memory, so might include bugs or typos):

    sf::Clock updateTimer; // Clock to monitor the time passed
    sf::Time passedTime; // Accumulated game time
    const sf::Time frameTime(sf::milliseconds(10)); // intended time per frame; here: 10ms
    
    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            // Event handling
        }
    
        // First add the time passed
        passedTime += updateClock.restart();
    
        unsigned int numUpdates = 0; // Count the updates done
    
        // As long as enough time passed, do an update
        // Up to a specific maximum to avoid problems, e.g.
        // the main thread was blocked or can't catch up
        while (passedTime >= frameTime) {
    
            if (numUpdates++ < 10) {
                // Do your game update here
            }
    
            // Subtract the time we've "handled"
            passedTime -= frameTime;
        }
    
        window.clear();
        // Draw your game here
        window.display();
    }
    

    The usage of numUpdates might not be clear at first, but just imagine a situation where the machine is barely able to run the desired 100 updates per second. If you're 20 updates behind (some hick-up or whatever) the machine will never be able to catch up again properly, causing heavy stuttering or the game becoming unresponsive.