1

I'm writing a standard game loop using std::chrono. I need to pass a float to my render method that represents how far into the next frame I am. To calculate the number I'm using the count() method of chrono::duration, hence I need to convert two durations to the same ratio.

void engine::run() {
  using namespace std::chrono;
  using updates = duration<steady_clock::rep, std::ratio<1, 40>>;
  using common = std::common_type<updates, steady_clock::duration>::type;

  constexpr updates time_per_update{1};
  auto previous = steady_clock::now();
  auto lag = steady_clock::duration::zero();

  while (!quit) {
    auto now = steady_clock::now();
    auto delta = now - previous;

    previous = now;
    lag += delta;

    while (lag >= time_per_update) {
      lag -= time_per_update;
      update(time_per_update);
    }

    render(common{lag}.count() / static_cast<double>(common{time_per_update}.count()));
  }
}

If I change the ratio in 'updates' to, say 41, I get a compile error at the subtraction, because a 1/41 of a second cannot be precisely converted to steady_clock::duration. However, when I rewrite the code to this, it compiles just fine:

void engine::run() {
  using namespace std::chrono;
  using updates = duration<steady_clock::rep, std::ratio<1, 41>>;
  using common = std::common_type<updates, steady_clock::duration>::type;

  constexpr common time_per_update{updates{1}};
  auto previous = steady_clock::now();
  common lag = steady_clock::duration::zero();

  while (!quit) {
    auto now = steady_clock::now();
    auto delta = now - previous;

    previous = now;
    lag += delta;

    while (lag >= time_per_update) {
      lag -= time_per_update;
      update(time_per_update);
    }

    render(lag.count() / static_cast<double>(time_per_update.count()));
  }
}

I was under the impression the conversion to common_type happens implicitly during the subtraction. What am I missing? Is there a better way to do this?

2
  • It is easier for people to help you if you provide a minimal reproducible example. Commented Jun 2, 2019 at 10:44
  • I would recommend you to use the chrono functions only to obtain the unix timestamp in seconds/milliseconds/microseconds/nanoseconds as a size_t and then compute lags in a POD type (size_t, int64_t,...) as differences. Use for example size_t microseconds=std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count(); and adjust microseconds to nanoseconds if you need such a precision. Commented Jun 2, 2019 at 12:39

1 Answer 1

1

To clarify, this is the line of code that gets an error:

 lag -= time_per_update;

test.cpp:27:11: error: no viable overloaded '-='
      lag -= time_per_update;
      ~~~ ^  ~~~~~~~~~~~~~~~

I was under the impression the conversion to common_type happens implicitly during the subtraction.

You are correct, but not exactly correct.

The binary subtraction operator returns the common_type of the two arguments:

template <class Rep1, class Period1, class Rep2, class Period2>
  constexpr
  typename common_type<duration<Rep1, Period1>, duration<Rep2, Period2>>::type
  operator-(const duration<Rep1, Period1>& lhs, const duration<Rep2, Period2>& rhs);

However the -= operator has to be equivalent to:

lag = lag - delta;

And if the result of lag - delta is not implicitly convertible to the type of lag, then you have a problem (as is the case for your example).

Changing the type of lag to common is the correct fix:

common lag = steady_clock::duration::zero();

Here's another way to do that. The choice between these two is stylistic:

auto lag = steady_clock::duration::zero() + updates{0};

Finally, just as personal nit, I like to minimize (if not eliminate) the uses of .count() as this is the equivalent of a reinterpret_cast from duration to integral (or scalar). This is not hard to do in your example, and involves no loss in efficiency.

First add an equivalent to common that uses double as the representation:

using dcommon = duration<double, common::period>;

Then you just convert lag to dcommon and divide that by time_per_update to get your ratio:

render(dcommon{lag} / time_per_update);

Finally, there is one more stylistic variation for you to consider: Just represent lag as double-based up front:

auto lag = steady_clock::duration::zero() + updates{0} + duration<double>{0};

Now your call to render is even simpler:

render(lag / time_per_update);

And common and dcommon are no longer even needed (you can remove them).

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.