Handling an update loop using C++ Chrono?
Asked Answered
A

1

8

I'm definitely a bit lost with the new C++ chrono library.

Here I have an update loop. It runs two operations:

engine.Update()
engine.Render()

These are long operations, and it's hard to tell how long they are.

Thus, we measure how long they took, then do some calculations and figure the best way to gradually call update before we call render.

To do this, i'm using C++11's Chrono functionality. I chose it because it sounded like a good deal: More accurate, More platform dependent. I'm finding i'm hitting more problems than now now though.

Following is my code, as well as my primary problem. Any help on either the problem, or a proper way to do my operations, is greatly needed!

I marked my questions in comments directly next to the lines in question, which i'll re-iterate below.

The header file:

class MyClass
{
private:
    typedef std::chrono::high_resolution_clock Clock;
    Clock::time_point mLastEndTime;
    milliseconds mDeltaTime;
}

The simplified update loop

// time it took last loop
milliseconds frameTime;
// The highest we'll let that time go. 60 fps = 1/60, and in milliseconds, * 1000
const milliseconds kMaxDeltatime((int)((1.0f / 60.0f) * 1000.0f)); // It's hard to tell, but this seems to come out to some tiny number, not what I expected!
while (true)
{
    // How long did the last update take?
    frameTime = duration_cast<milliseconds>(Clock::now() - mLastEndTime); // Is this the best way to get the delta time, with a duration cast?
    // Mark the last update time
    mLastEndTime = Clock::now();

    // Don't update everything with the frameTime, keep it below our maximum fps.
    while (frameTime.count() > 0) // Is this the best way to measure greater than 0 milliseconds?
    {
        // Determine the minimum time. Our frametime, or the max delta time?
        mDeltaTime = min(frameTime, kMaxDeltatime);

        // Update our engine.
        engine->Update((long)mDeltaTime.count()); // From here, it's so much easier to deal with code in longs. Is this the best way to shove a long through my code?

        // Subtract the delta time out of the total update time 
        frameTime -= mDeltaTime;
    }
    engine->Render();
}

The main question is: My mDeltaTime always comes out tiny. It's basically stuck in an almost-infinite loop. This is because the kMaxDeltatime is super small, but if i'm targeting 60 Frames per Second, didn't I calculate the correct milliseconds?

Here are all the questions listed from above:

const milliseconds kMaxDeltatime((int)((1.0f / 60.0f) * 1000.0f)); // It's hard to tell, but this seems to come out to some tiny number, not what I expected!

frameTime = duration_cast<milliseconds>(Clock::now() - mLastEndTime); // Is this the best way to get the delta time, with a duration cast?

while (frameTime.count() > 0) // Is this the best way to measure greater than 0 milliseconds?

engine->Update((long)mDeltaTime.count()); // From here, it's so much easier to deal with code in longs. Is this the best way to shove a long through my code?

I'm sorry for the confusion guys. I feel like an idiot with this chrono library. Most of the help sites, or reference material, or even the direct code itself is very confusing to read and understand to what i'm applying it to. Pointers into how I should be searching for solutions or code are very much welcomed!

EDIT: Joachim pointed out that std::min/max works just fine for milliseconds! Updated code to reflect change.

Arbiter answered 9/2, 2013 at 6:17 Comment(15)
As for min/max functions for times, it's easy to create simple overloaded functions for them.Cardwell
@JoachimPileborg Could you elaborate?Arbiter
Why the heck do you have a "busy loop"? "Polling is Evil". If you want a delay, why not block on a timer?Harbinger
Actually, have you tried using std::min or std::max? They are templated so should work if there is a suitable overload of the < operator. Of you could use the variants that allows you to pass in your own comparator function.Cardwell
@Harbinger Assuming you mean the while(true) piece, it's not real. It runs off a boolean value that can be set to false. Once the loop is done, it runs some shutdown functions.Arbiter
Do you have a legit reason for wanting to run at 60fps? Normally you want to run as fast as you can (which, in your case sounds like it would be 500fps or something)Gasaway
@Dave To not run the processor at 100% speed, constantly. Currently, after a few minutes of drawing a simple circle, my fans are kicking up full speed.Arbiter
@JoachimPileborg Just tried it, works perfectly. Thanks for the tip!Arbiter
@Arbiter You're trying to spin lock, which won't really fix that. You should do all your work in a frame and then std::this_thread::sleep_until the end of your 1/60th of a second timeslice is upGasaway
@Dave I apologize, I was mistaken in my answer. My edit time didn't allow me to answer. I need to cut down the DeltaTime because if I shove a high deltatime through the Update(), the game, in particular Physics, will act very screwy. Things will fly out of control if it's too high. Thus, I cull it at some number, and run the update as many times until I hit zero. The appropriate number seemed to be 60 fps for me. Does that make more sense?Arbiter
@Arbiter Oh, that's a fine reason and a common approach to a common problem. But that has nothing to do with not running the processor at 100%...Gasaway
Your update loop looks correct. Can you verify that your engine->Update call takes less than mDeltaTime to execute in the worst case? Because if you're updating your state for X milliseconds and taking > X milliseconds to do so, your simulation will spiral out of control.Culture
@KapilKapre If I understand you correctly, I believe it's okay. It doesnt use delta time for much yet anyways. Its just stuck in the loop. I'm pretty convinced it's because the value i gave kMaxDeltaTime is super tiny. I feel like the ctor for milliseconds is converting my time to something smaller but... all documentation on it is very confusing, hard to understand.Arbiter
No, kMaxDeltatime has the correct value (16). I'm not sure why your program gets stuck in a loop. Here is some code I threw together based on your original sample and it works as expected : ideone.com/rYe3sLCulture
@KapilKapre Aha! mLastEndTime was never set to an initial value on my code! Must have been garbage data, some huge number, that never let the loop really finish. Thanks so much for taking the time to code something up, it helped a lot!Arbiter
L
20

When using std::chrono you should avoid, as much as possible, casting durations or converting durations to raw integral values. Instead you should stick with the natural durations and take advantage of the type safety that duration types provide.

Below is a series of specific recommendations. For each recommendation I'll quote lines of your original code and then show how I would re-write those lines.


const milliseconds kMaxDeltatime((int)((1.0f / 60.0f) * 1000.0f)); // It's hard to tell, but this seems to come out to some tiny number, not what I expected!

There's no reason to do this sort of computation with manual conversion constants. Instead you can do:

typedef duration<long,std::ratio<1,60>> sixtieths_of_a_sec;
constexpr auto kMaxDeltatime = sixtieths_of_a_sec{1};

frameTime = duration_cast<milliseconds>(Clock::now() - mLastEndTime); // Is this the best way to get the delta time, with a duration cast?

You can just keep the value in its native type:

auto newEndTime = Clock::now();
auto frameTime = newEndTime - mLastEndTime;
mLastEndTime = newEndTime;

while (frameTime.count() > 0) // Is this the best way to measure greater than 0 milliseconds?

Instead use:

while (frameTime > milliseconds(0))

engine->Update((long)mDeltaTime.count()); // From here, it's so much easier to deal with code in longs. Is this the best way to shove a long through my code?

It's best to write code that uses chrono::duration types throughout, rather than to use generic integral types at all, but if you really need to get a generic integral type (for example if you must pass a long to a third-party API) then you can do something like:

auto mDeltaTime = ... // some duration type

long milliseconds = std::chrono::duration_cast<std::duration<long,std::milli>>(mDeltaTime).count();
third_party_api(milliseconds);

Or:

auto milliseconds = mDeltaTime/milliseconds(1);

And to get the delta you should do something like:

typedef std::common_type<decltype(frameTime),decltype(kMaxDeltatime)>::type common_duration;
auto mDeltaTime = std::min<common_duration>(frameTime, kMaxDeltatime); 
Libava answered 5/4, 2013 at 17:24 Comment(2)
I know this has long passed, but the std::common_type call does not compile for me with clang++ on linux. It says "no member called "type" exists" which causes the min call to also not compile.Ardithardme
It had to do with the initializer list I think screwing up the compiler.Ardithardme

© 2022 - 2024 — McMap. All rights reserved.