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

后端 未结 4 1407
不思量自难忘°
不思量自难忘° 2021-01-30 22:38

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::ch         


        
相关标签:
4条回答
  • 2021-01-30 23:23

    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    
    
    0 讨论(0)
  • 2021-01-30 23:26

    The alternating delta times are arising from a logic problem: you're adding a delay to one frame based on the duration of the frame before (in terms of how the frame durations are reckoned). This means that after a long frame (~200ms) you don't apply a delay and get a short frame (few ms), which then triggers a delay on the next frame giving a long frame, and so on.

    0 讨论(0)
  • 2021-01-30 23:28

    I usualy do something like this:

    #include <chrono>
    #include <iostream>
    
    int main()
    {
        using clock = std::chrono::steady_clock;
    
        auto next_frame = clock::now();
    
        while(true)
        {
            next_frame += std::chrono::milliseconds(1000 / 5); // 5Hz
    
            // do stuff
            std::cout << std::time(0) << '\n'; // 5 for each second
    
            // wait for end of frame
            std::this_thread::sleep_until(next_frame);
        }
    }
    

    Output: (five for each second value)

    1470173964
    1470173964
    1470173964
    1470173964
    1470173964
    1470173965
    1470173965
    1470173965
    1470173965
    1470173965
    1470173966
    1470173966
    1470173966
    1470173966
    1470173966
    
    0 讨论(0)
  • 2021-01-30 23:35

    This is much like Galik's answer, but it keeps the syntax of the OP's question and doesn't drop down to the C API. Additionally it creates a custom unit for the frame duration which I believe is important for readability:

    #include <chrono>
    #include <cstdint>
    #include <iostream>
    #include <thread>
    
    int
    main()
    {
        using namespace std;
        using namespace std::chrono;
    
        using frames = duration<int64_t, ratio<1, 5>>;  // 5Hz
        auto nextFrame = system_clock::now();
        auto lastFrame = nextFrame - frames{1};;
    
        while (true)
        {
            // Perform intersection test
    
            this_thread::sleep_until(nextFrame);
            cout << "Time: "  // just for monitoring purposes
                 << duration_cast<milliseconds>(system_clock::now() - lastFrame).count()
                 << "ms\n";
            lastFrame = nextFrame;
            nextFrame += frames{1};
        }
    }
    

    This outputs for me:

    Time: 200ms
    Time: 205ms
    Time: 205ms
    Time: 203ms
    Time: 205ms
    Time: 205ms
    Time: 200ms
    Time: 200ms
    Time: 200ms
    ...
    

    Key things to note:

    • A concise way to document 5Hz: using frames = duration<int64_t, ratio<1, 5>>;
    • Use of sleep_until instead of sleep_for, which takes care of the unknown of how long it takes to get the real work done.
    • No use of .count() except for I/O, and here's a library to get rid of that.
    • No manual conversion of units (e.g. / 1000).
    • No floating point units, not that there's anything wrong with that.
    • Minimal need to specify or depend upon explicit units.

    With the addition of the duration I/O library, here is how the above code would be changed:

    #include "chrono_io.h"
    #include <chrono>
    #include <cstdint>
    #include <iostream>
    #include <thread>
    
    int
    main()
    {
        using namespace date;
        using namespace std;
        using namespace std::chrono;
    
        using frames = duration<int64_t, ratio<1, 5>>;  // 5Hz
        auto nextFrame = system_clock::now();
        auto lastFrame = nextFrame - frames{1};;
    
        while (true)
        {
            // Perform intersection test
    
            this_thread::sleep_until(nextFrame);
            // just for monitoring purposes
            cout << "Time: " << system_clock::now() - lastFrame << '\n';
            lastFrame = nextFrame;
            nextFrame += frames{1};
        }
    }
    

    The output would differ depending upon platform (depending upon the "native duration" of system_clock). On my platform it looks like this:

    Time: 200042µs
    Time: 205105µs
    Time: 205107µs
    Time: 200044µs
    Time: 205105µs
    Time: 200120µs
    Time: 204307µs
    Time: 205136µs
    Time: 201978µs
    ...
    
    0 讨论(0)
提交回复
热议问题