Search code examples
c++c++-chrono

How to limit FPS in a loop with C++?


I'm trying to limit the frames per second in a loop that is performing intersection checking, using C++ with chrono and thread.

Here is my code:

std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
std::chrono::system_clock::time_point lastFrame = std::chrono::system_clock::now();

while (true)
{
    // Maintain designated frequency of 5 Hz (200 ms per frame)
    now = std::chrono::system_clock::now();
    std::chrono::duration<double, std::milli> delta = now - lastFrame;
    lastFrame = now;

    if (delta.count() < 200.0)
    {
        std::chrono::duration<double, std::milli> delta_ms(200.0 - delta.count());
        auto delta_ms_duration = std::chrono::duration_cast<std::chrono::milliseconds>(delta_ms);
        std::this_thread::sleep_for(std::chrono::milliseconds(delta_ms_duration.count()));
    }

    printf("Time: %f \n", delta.count());

    // Perform intersection test

}

The problem I'm having is that every other output of delta is showing miniscule amounts, rather than the ~200 ms / frame I'm aiming for:

Time: 199.253200
Time: 2.067700
Time: 199.420400
Time: 2.408100
Time: 199.494200
Time: 2.306200
Time: 199.586800
Time: 2.253400
Time: 199.864000
Time: 2.156500
Time: 199.293800
Time: 2.075500
Time: 201.787500
Time: 4.426600
Time: 197.304100
Time: 4.530500
Time: 198.457200
Time: 3.482000
Time: 198.365300
Time: 3.415400
Time: 198.467400
Time: 3.595000
Time: 199.730100
Time: 3.373400

Any thoughts as to why this is happening?


Solution

  • If you think about how your code works, you'll find out that it works exactly how you wrote it. Delta oscillates because of a logical mistake in the code.

    This is what happens:

    • We start with delta == 0.
    • Because the delta is smaller than 200, you code sleeps 200 - delta(0) == 200 ms.
    • Now, the delta itself becomes close to 200 (because you've measured that sleep time as well as an actual work) and you sleep 200 - delta(200) == 0 ms.
    • After that the cycle repeats.

    To fix the problem you need to not measure the sleep time.

    This is how it can be done:

    #include <iostream>
    #include <cstdio>
    #include <chrono>
    #include <thread>
    
    std::chrono::system_clock::time_point a = std::chrono::system_clock::now();
    std::chrono::system_clock::time_point b = std::chrono::system_clock::now();
    
    int main()
    {
        while (true)
        {
            // Maintain designated frequency of 5 Hz (200 ms per frame)
            a = std::chrono::system_clock::now();
            std::chrono::duration<double, std::milli> work_time = a - b;
    
            if (work_time.count() < 200.0)
            {
                std::chrono::duration<double, std::milli> delta_ms(200.0 - work_time.count());
                auto delta_ms_duration = std::chrono::duration_cast<std::chrono::milliseconds>(delta_ms);
                std::this_thread::sleep_for(std::chrono::milliseconds(delta_ms_duration.count()));
            }
    
            b = std::chrono::system_clock::now();
            std::chrono::duration<double, std::milli> sleep_time = b - a;
    
            // Your code here
    
            printf("Time: %f \n", (work_time + sleep_time).count());
        }
    }
    

    This code gives me a steady sequence of deltas:

    Time: 199.057206 
    Time: 199.053581 
    Time: 199.064718 
    Time: 199.053515 
    Time: 199.053307 
    Time: 199.053415 
    Time: 199.053164 
    Time: 199.053511 
    Time: 199.053280 
    Time: 199.053283