Search code examples
c++multithreadingasynchronousdelaysleep

Calling a function every 1 second (precisely)


I am working on a simple game simulation program in C++, there's a function called update() that updates the current state of the game, it has to be called every 1 second precisely. If I use a loop like this:

while(//some condition) {
     update();
     Sleep(1000);
}

Then the function will not be called every 1 second, instead, every (1 + execution time of update () ). I read about various solutions like async functions, multithreading, or calculating the function's execution time using std::chrono and subtracting it from the 1000ms parameter to sleep. Some of those were too complicated for my simple case, and others seemed unsafe to use if I don't understand them really well.

Can anyone tell me what would be a suitable solution to my requirement? Thanks in advance.


Solution

  • Instead of sleeping for a duration, you need to sleep until a time point. For example, if your first update is at precisely 2:00:00.000, your future updates should come as closely as possible to 2:00:01.000, 2:00:02.000, etc.

    To achieve this you can dedicate a thread to updating, and after the update, goes to sleep until the next time to do a scheduled update. chrono::system_clock::time_point and this_thread::sleep_until are your tools to do this.

    For example:

    #include <atomic>
    #include <chrono>
    #include <iostream>
    #include <thread>
    
    class UpdateManager
    {
    public:
        explicit UpdateManager() = default;
    
    private:
        static std::atomic<int> now_;
        static std::atomic<bool> stop_;
    
        struct update_thread
            : private std::thread
        {
            ~update_thread();
            update_thread(update_thread&&) = default;
    
            using std::thread::thread;
        };
    
    public:
        static update_thread start();
    };
    
    void update();
    
    // source
    
    std::atomic<int>  UpdateManager::now_{0};
    std::atomic<bool> UpdateManager::stop_{false};
    
    UpdateManager::update_thread::~update_thread()
    {
        if (joinable())
        {
            stop_ = true;
            join();
        }
    }
    
    UpdateManager::update_thread
    UpdateManager::start()
    {
        return update_thread{[]
                             {
                                 using namespace std;
                                 using namespace std::chrono;
                                 auto next = system_clock::now() + 1s;
                                 while (!stop_)
                                 {
                                     update();
                                     this_thread::sleep_until(next);
                                     next += 1s;
                                 }
                             }};
    }
    
    #include "date/date.h"
    
    void
    update()
    {
        using namespace date;
        using namespace std;
        using namespace std::chrono;
        cerr << system_clock::now() << '\n';
    }
    
    // demo
    
    int
    main()
    {
        auto t = UpdateManager::start();
        using namespace std;
        this_thread::sleep_for(10s);
    }
    

    Just for demo purposes (not necessary for the logic), I'm using Howard Hinnant's, free, open-source date/time library to print the current time (UTC) to microsecond precision in order to illustrate the stability of this technique. A sample output of this program is:

    2018-05-02 15:14:25.634809
    2018-05-02 15:14:26.637934
    2018-05-02 15:14:27.636629
    2018-05-02 15:14:28.637947
    2018-05-02 15:14:29.638413
    2018-05-02 15:14:30.639437
    2018-05-02 15:14:31.637217
    2018-05-02 15:14:32.637895
    2018-05-02 15:14:33.637749
    2018-05-02 15:14:34.639084