Search code examples
c++c++-chronogame-loop

std::chrono different results - fixed time step loop


Could somebody help me to find out where is the difference? Because first code:

#include <iostream>
#include <chrono>
#include <ratio>

using namespace std::chrono;

const nanoseconds timePerFrame = duration_cast<nanoseconds>(duration<steady_clock::rep, std::ratio<1, 60>>(1));
nanoseconds accumulator(0);
nanoseconds counter(0);
steady_clock::time_point begin;
int i = 0;

int main()
{
   while(true)
   {
      begin = steady_clock::now();
      while(accumulator >= timePerFrame)
      {
          accumulator -= timePerFrame;
          ++i;
      }
      accumulator += steady_clock::now() - begin;
      counter += steady_clock::now() - begin;
      if(counter >= seconds(1))
      {
        std::cout << i << std::endl;
        break;
      }
  }
}

Outputs: 30, and a second code:

#include <iostream>
#include <chrono>
#include <ratio>

using namespace std::chrono;

const nanoseconds timePerFrame = duration_cast<nanoseconds>(duration<steady_clock::rep, std::ratio<1, 60>>(1));
nanoseconds accumulator(0);
nanoseconds counter(0);
steady_clock::time_point begin;
steady_clock::time_point end;
int i = 0;

int main()
{
  while(true)
  {
      begin = steady_clock::now();
      while(accumulator >= timePerFrame)
      {
          accumulator -= timePerFrame;
          ++i;
      }
      end = steady_clock::now();
      accumulator += end - begin;
      counter += end - begin;
      if(counter >= seconds(1))
      {
        std::cout << i << std::endl;
        break;
      }
  }
}

Outputs: 60;

The only one difference is using "end" variable in second example. In my opinion it shouldn't cause such difference. I mean, isn't steady_clock::now() exactly the same as end = steady_clock::now()?


Solution

  • The difference is that here

      accumulator += steady_clock::now() - begin;
      counter += steady_clock::now() - begin;
    

    the two instances of now() return 2 different values, thus counter wont be in sync with accumulator and the next if condition will trigger one iteration earlier as compared to

      end = steady_clock::now();
      accumulator += end - begin;
      counter += end - begin;
    

    because here both, accumulator and counter are incremented by the same amount.

    You can verify this by changing the order of the two statements to

      counter += steady_clock::now() - begin;
      accumulator += steady_clock::now() - begin;
    

    that will yield quite unpredicatable output in my case i got a 117.

    To make the code even more readable I would write it like this:

    auto delta = end - begin;
    accumulator += delta;
    counter     += delta;
    

    It is always good to avoid typing exactly the same thing more than once. In this case it really matters that they are incremented by the same amount, so why not make it explicit in the code?!

    TL;DR steady_clock::now() is "the same" as end = steady_clock::now(), but steady_clock::now() wont return the same value when you call it twice.