4

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.

Yosry
  • 147
  • 2
  • 9
  • 2
    *"calculating the function execution time using std::chrono and subtracting it from the 1000ms parameter to sleep"* This is one of the simplest ways, I'd suggest it. – HolyBlackCat May 02 '18 at 14:04
  • "calculating the function execution time using std::chrono and subtracting it from the 1000ms": sounds like you have your answer already, what do you want from us exactly? **Edit** Oh come on! –  May 02 '18 at 14:04
  • 1
    I did this once with hardware timer interrupts. The hardware will branch to a code address at 1000 ms (plus or minus about 1 clock cycle), execute the code (which should be short and take a lot less than 1000 ms, then return. Hardware interrupts depend on hardware (no, duh), so I don't know if it will work for your specific scenario. It's just a good and accurate way to implement this. – the_storyteller May 02 '18 at 14:08
  • Do you have some [event loop](https://en.wikipedia.org/wiki/Event_loop) ? What graphic libraries do you use in your game? [SFML](https://www.sfml-dev.org/) or [Qt](http://qt.io/) or what? – Basile Starynkevitch May 02 '18 at 15:03
  • @the_storyteller I know what you are talking about, but I have done similar stuff in embedded systems, not sure how can I do this in a desktop application. – Yosry May 02 '18 at 15:18
  • @BasileStarynkevitch nope, not in the context of this question. I believe it is called CMU graphics. It is not my choice, provided by TAs (college project). – Yosry May 02 '18 at 15:20

4 Answers4

8

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
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Thanks a lot ! but this kind of answer is what I meant by being too complicated for my simple scenario. – Yosry May 02 '18 at 15:23
  • Just `sleep_until` instead of `sleep_for`, and you'll be fine. – Howard Hinnant May 02 '18 at 15:30
  • Thanks, will look it up – Yosry May 02 '18 at 15:33
  • @Yosry the relevant part is: using namespace std; using namespace std::chrono; auto next = system_clock::now() + 1s; while (!stop_) { update(); this_thread::sleep_until(next); next += 1s; } – Red.Wave May 02 '18 at 22:01
  • The key is to get the starting timepoint via "now", setting every next timepoint via incrementing with 1s and "wait_until" the next timepoint. – Red.Wave May 02 '18 at 22:05
  • @Yosry you're wellcome. Hope you can use it. And it was seccpur's answer. – Red.Wave May 02 '18 at 22:08
  • Is this approach resistant to clock changes? Because apparently condition variables are not. https://stackoverflow.com/questions/51005267/how-do-i-deal-with-the-system-clock-changing-while-waiting-on-a-stdcondition-v – NoSenseEtAl Jun 25 '18 at 03:30
  • One can wait on `steady_clock::time_points` and get resistance to clock changes. A `steady_clock` is not adjustable. That being said, the C++ API is ultimately a portable layer over OS API's, and no API is immune from bugs. – Howard Hinnant Jun 25 '18 at 03:40
2

You can avoid using chrono for this simpler option instead. However, if you want μs-ns accuracy, I highly suggest using chrono.

#include <ctime>

while(//some condition) {

     std::clock_t start = std::clock();
     update();
     std::clock_t end   = std::clock();
     Sleep(1000 - (end - start)/CLOCKS_PER_SEC * 1000);
}
Kostas
  • 4,061
  • 1
  • 14
  • 32
  • `Sleep` is specific to WinAPI. On POSIX, it could be something else, perhaps [nanosleep(2)](http://man7.org/linux/man-pages/man2/nanosleep.2.html) or maybe [poll(2)](http://man7.org/linux/man-pages/man2/poll.2.html) – Basile Starynkevitch May 02 '18 at 15:04
  • @BasileStarynkevitch I used it from his example. I assumed sleep is a custom function he made. – Kostas May 02 '18 at 15:53
2

The best way to ensure that something happens at the correct time is to use the this_thread::sleep_until() in the while loop, as a few others have recommended.

I like to make sure that my loop starts at the bottom of the second so calculated next_full_second time point using chrono time_since_epoch

interval variable can be set to seconds or milliseconds. Used open source date library by HowardHinnant to print out nice time values.

#include <chrono>
#include <iostream>
#include <thread>

#include "date/date.h"

using namespace date;
using namespace std;
using namespace std::chrono;

int main()
{

    system_clock::time_point now = system_clock::now();
    auto s = duration_cast<seconds>(now.time_since_epoch());
    system_clock::time_point next_full_second = system_clock::time_point(++s);

    auto interval = seconds(1); // or milliseconds(500) or whatever
    auto wait_until = next_full_second;

    while (1)
    {
        this_thread::sleep_until(wait_until);
        cout << system_clock::now() << endl;

        // do something

        wait_until += interval;
    }

    return 0;
}
jfdoolster
  • 21
  • 3
0

See this pseudo code. If every process ( which may take more than 1sec ) is done in separate threads asynchronously, you can sleep exactly 1 sec before next loop as desired.

void MainGameLoop(bool& running)
{
    while (running)
    {
        AnimateSpritesAsync();
        AnimateBulletsAsync();
        CheckCollisionAsync();
        CheckWinConditionsAsync();

        WaitPreciselyOneSecond();
    }
}

For example your update function will look like this:

void update(); //your update 

void UpdateAsync()  // the async update
{
   std::thread([]() { update(); }).detach();
}

Now you can:

void MainGameLoop(bool& running)
    {
        while (running)
        {
            UpdateAsync();    
            Sleep(1000); // i won't recommend Sleep tho
        }
    }
seccpur
  • 4,996
  • 2
  • 13
  • 21