Sign in to follow this  
blueshogun96

C++ How to fix this timestep once and for all?

Recommended Posts

blueshogun96    2264

One of the biggest reasons why I haven't released my game is because of this annoying timestep issue I have.  To be frank, this game was poorly planned, poorly coded, and was originally written as a small tech demo and a mini-game.  Now it has evolved into a fully featured (and very messy code base of a) game.  If you thought Lugaru was bad, Looptil is far worse!  So what happens is that the delta is not really consistent.  Sometimes enemies don't spawn fast enough because the delta isn't even consistent at 60fps, which is a big reason why the game is broken.

static uint64_t last_time = 0;
uint64_t current_time = time_get_time(); //( get_current_time() * 1000.0f );

int fps_limit = 60;
float frame_time = float( current_time - last_time );

if( last_time != 0 )
	This->m_delta_speed = frame_time / ( 1000.0f / 60.0f );

And this is my timing function:

uint64_t time_get_time()
{
#ifdef _WINRT
	return GetTickCount64();
#endif
	
#if __ANDROID__	/* TODO: Fix std::chrono for Android NDK */
	uint64_t ms = 0;
	timeval tv;

	gettimeofday( &tv, NULL );

	ms = tv.tv_sec * 1000;
	ms += tv.tv_usec / 1000;

	return ms;
#else
	std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
    std::chrono::system_clock::duration tp = now.time_since_epoch();
	std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds>(tp);
	return (uint64_t) ms.count();
#endif
}

Now I know some of you will cringe when you see GetTickCount64(), but that's the only function that gives me reliable results on Windows 10 (UWP) ports, so that's staying. 

One more thing to note here, my game has a badly written game loop.  It uses a switch statement, followed by draw_game_mode(), update_game_mode(), so I kinda screwed myself there.  I tried changing it, but it broke the game completely, so I left it in it's messy state.  Is it possible to simply just have a proper delta calculation function?  Because it's adjusting itself based on the current frame time.  This may not be the best of ideas, but it was something I whipped up because I needed to have this run okay when it goes down to 30fps without running half the speed.  This works in general, but it's innacurate and causes problems.

Any ideas?  Thanks.

Shogun

EDIT: Feel free to ask anything in case I missed a vital detail.  My lunch break is ending and it's time for me to go.  Thanks.

Share this post


Link to post
Share on other sites
blueshogun96    2264

Although I see that link shared alot, it actually made my timing issues worse for this particular game.  In the future, I'll be sure to follow that guide to avoid future headaches.

Also, I fixed the problem.  Instead of using frame times, I used my game's actual frame rate divided by 1000.  Now it works perfectly (so far).  L. Spiro is going to kill me if he reads this, but I just want this game to work!

Thanks.

Shogun

Share this post


Link to post
Share on other sites
ApochPiQ    23004

Frame rate = Frames Per Second by most usual definitions. Or, Frames Per 1000 Milliseconds, if you will.

If your framerate is N frames per second, then it is also true that your framerate is N/1000 frames per millisecond. Frame time is milliseconds per frame, or seconds per frame / 1000.

It sounds like you just have a units/order-of-magnitude mixup in the original code, and your 1000 is adjusting for it.

Share this post


Link to post
Share on other sites
L. Spiro    25622
On 7/30/2017 at 11:43 PM, blueshogun96 said:

Although I see that link shared alot, it actually made my timing issues worse for this particular game.  In the future, I'll be sure to follow that guide to avoid future headaches.

Also, I fixed the problem.  Instead of using frame times, I used my game's actual frame rate divided by 1000.  Now it works perfectly (so far).  L. Spiro is going to kill me if he reads this, but I just want this game to work!

Thanks.

Shogun

You.

Dirty.

RAT!!

You didn’t make the game work, you just hid the problem under a rug.  It will work differently on various devices so I am not sure how this helps you release anything.

You don’t show the whole game loop.  What is This->m_delta_speed?
Are you accumulating time from 0 = launch of game?  Why the conversion to float?


L. Spiro

Share this post


Link to post
Share on other sites
Hodgman    51234
On 30/07/2017 at 6:40 AM, blueshogun96 said:

 


static uint64_t last_time = 0;
uint64_t current_time = time_get_time(); //( get_current_time() * 1000.0f );

int fps_limit = 60;
float frame_time = float( current_time - last_time );

if( last_time != 0 )
	This->m_delta_speed = frame_time / ( 1000.0f / 60.0f );

 

Is that the real algorithm? You never update last_time in that algorithm.

On 30/07/2017 at 6:40 AM, blueshogun96 said:

Now I know some of you will cringe when you see GetTickCount64(), but that's the only function that gives me reliable results on Windows 10 (UWP) ports, so that's staying. 

 

Every Win10/UWP device supports the QueryPerformanceCounter/Frequency API - the normal way to do precision timing.

Milliseconds are shitty for game timing. If your loop frequency is 60Hz, then a millisecond timer is accurate to +/- 6%... What's worse is that GetTickCount64 says that it typically has 10-16ms accuracy (+/-60% to 96% error) :(

On 31/07/2017 at 4:43 PM, blueshogun96 said:

Also, I fixed the problem.  Instead of using frame times, I used my game's actual frame rate divided by 1000.  Now it works perfectly (so far).  L. Spiro is going to kill me if he reads this, but I just want this game to work!

 

Maybe you've got code that just happens to output a small value every frame, which is not actually a measurement of delta time, but happens to simply be some arbitrary number that's small enough to act as a plausible fixed timestep value.

e.g. if you simply hardcode an arbitrary delta, such as "m_delta_speed = 0.06f;" do you get similar results?

Share this post


Link to post
Share on other sites
blueshogun96    2264
On 8/4/2017 at 4:41 AM, L. Spiro said:

You.

Dirty.

RAT!!

You didn’t make the game work, you just hid the problem under a rug.  It will work differently on various devices so I am not sure how this helps you release anything.

You don’t show the whole game loop.  What is This->m_delta_speed?
Are you accumulating time from 0 = launch of game?  Why the conversion to float?


L. Spiro

Yes, now I am finding the flaws as they surface.  Sometimes after coming out of the background or a suspended state, the FPS calculation will spew a really high number and cause the game to move rapidly for one second, then go back to normal.  This will result in death many times for the user.  So yes, I dun f@#%ed up even more.

The entire gameloop is too large and is a complete mess (I'll never code a game this way ever again).  The delta_speed variable is a percentage that is multiplied against the entity's speed value so that it moves at an adjusted speed based on frame rates.  I am not accumulating time as I did not plan this thing ahead or even consider the need for time based movement when I originally wrote it.  Then when primitive counts started reaching the millions, frame rates dropped and then I realize "I dun screwed up".

 

On 8/4/2017 at 7:30 AM, Hodgman said:

Is that the real algorithm? You never update last_time in that algorithm.

Every Win10/UWP device supports the QueryPerformanceCounter/Frequency API - the normal way to do precision timing.

Milliseconds are shitty for game timing. If your loop frequency is 60Hz, then a millisecond timer is accurate to +/- 6%... What's worse is that GetTickCount64 says that it typically has 10-16ms accuracy (+/-60% to 96% error) :(

Maybe you've got code that just happens to output a small value every frame, which is not actually a measurement of delta time, but happens to simply be some arbitrary number that's small enough to act as a plausible fixed timestep value.

e.g. if you simply hardcode an arbitrary delta, such as "m_delta_speed = 0.06f;" do you get similar results?

The loop is updated further down.  I forgot to add that.

If milisecond timing is a bad design choice, then I will do a way with it pronto.  I wasn't aware of the poor accuracy, and if the margin of error is that great, then I'll most definitely stop using it.  I wrote that half arsed timing function out of laziness.  Speaking of high resolution timers, I'll need one that's portable to all three major OSes.  Which I did find here: http://roxlu.com/2014/047/high-resolution-timer-function-in-c-c--

/* ----------------------------------------------------------------------- */
/*
  Easy embeddable cross-platform high resolution timer function. For each 
  platform we select the high resolution timer. You can call the 'ns()' 
  function in your file after embedding this. 
*/
#include <stdint.h>
#if defined(__linux)
#  define HAVE_POSIX_TIMER
#  include <time.h>
#  ifdef CLOCK_MONOTONIC
#     define CLOCKID CLOCK_MONOTONIC
#  else
#     define CLOCKID CLOCK_REALTIME
#  endif
#elif defined(__APPLE__)
#  define HAVE_MACH_TIMER
#  include <mach/mach_time.h>
#elif defined(_WIN32)
#  define WIN32_LEAN_AND_MEAN
#  include <windows.h>
#endif
static uint64_t ns() {
  static uint64_t is_init = 0;
#if defined(__APPLE__)
    static mach_timebase_info_data_t info;
    if (0 == is_init) {
      mach_timebase_info(&info);
      is_init = 1;
    }
    uint64_t now;
    now = mach_absolute_time();
    now *= info.numer;
    now /= info.denom;
    return now;
#elif defined(__linux)
    static struct timespec linux_rate;
    if (0 == is_init) {
      clock_getres(CLOCKID, &linux_rate);
      is_init = 1;
    }
    uint64_t now;
    struct timespec spec;
    clock_gettime(CLOCKID, &spec);
    now = spec.tv_sec * 1.0e9 + spec.tv_nsec;
    return now;
#elif defined(_WIN32)
    static LARGE_INTEGER win_frequency;
    if (0 == is_init) {
      QueryPerformanceFrequency(&win_frequency);
      is_init = 1;
    }
    LARGE_INTEGER now;
    QueryPerformanceCounter(&now);
    return (uint64_t) ((1e9 * now.QuadPart)  / win_frequency.QuadPart);
#endif
}
/* ----------------------------------------------------------------------- */

Since this game is cross platform, it has to work on everything.  If nano seconds are the way to go, then I'll use that instead.

And yes, using the frame rate isn't really a reliable way to do this (it blew up in my face).  I found that using a fixed value will give me consistent results.  A fixed delta doesn't generate any issues for me. 

Shogun

Share this post


Link to post
Share on other sites
Krohm    5030

As a side note, in case it helps you (on some future project I guess): bullet (the physics API) ticked at constant rate, but it still allowed for frame-specific updates. OFC it only interpolated those between two known states. So it's has both predictable behavior and hi-frame-rate-butter-smooth goodness; apparently nobody noticed it's a tick late.

I tried something similar in an TD game I tried years ago I don't think you remember: the implication is that you have to correct for inconsistencies as an enemy spawned at 0.5 tick still has to be half-a-tick evolved and cannot be backwards interpolated at 0.3 ticks. Since the game wanted to be deterministic in nature I couldn't let players the chance to get different patterns due to hardware power. Ew! Hopefully you don't need this detail!

Share this post


Link to post
Share on other sites
Hodgman    51234
On 10/08/2017 at 10:56 AM, blueshogun96 said:

  I found that using a fixed value will give me consistent results.  A fixed delta doesn't generate any issues for me. 

;(

Try it in a PC with a 144Hz monitor now..

Share this post


Link to post
Share on other sites
Kylotan    9860

'Easy' fix: run your physics with a 720 Hz timestep. Iterate it 5 times per frame for 144Hz monitors/vsync, 12 times for a 60Hz monitor, and 24 times for a 30Hz monitor. No interpolation needed!

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this  

  • Similar Content

    • By suliman
      Hi!
      I've been doing games as a hobby in c++ for many years, but have little formal programming education. As a result i might have adopted a rather arcane way of structuring my games. I typically have:
      1. A master-class called "world" that is huuge. Keeps track of game state as well as lists for units, buildings, projectiles etc. Some things are the same in many games, but i have a unique world class in every game.
      2. Typically: cPlayer player[10]; where player[0] is the player itself in a single-player game and the rest is ai/opponents.
      3. A main loop where i do stuff like
      if(world.running){ if(world.editorOn) runEditor(); else runWorld(); } else runLobby(); 4. Typically entities like "units" have their own functions like unit::update, unit::move etc, but all of this is held together by the "world"-class.
      I guess my thinking is very "loop-based" ei sequencial. Is this problematic? I don't think it's a very modern way of coding.
      Thankful for any feedback!
      Erik
    • By L. Spiro
      I haven’t touched Windows development since Windows Vista, and I have only worked with the Win32 API (not MFC or etc.)
      I got a project from online that was made for Visual Studio 2015 and up to Windows 8.1, but I see no reason I should not be able to compile it with Visual Studio 2017 (I am running Windows 10 but I don’t think it was re-targeted towards Windows 10 during the project upgrade).
      But it gives me this error:
      using Microsoft::WRL::ComPtr; // Error C2653 'Microsoft': is not a class or namespace name Obviously searching for what I need to #include to get a namespace called “Microsoft” is useless—the returns are just MSDN articles explaining what namespaces are as it is not clever enough to realize I am telling it that the namespace is named Microsoft.
      So clearly I am missing some #include but this is a bit baffling as the project should just work.  My research shows this is part of a “Universal” framework.  I’ve spent the last 4 hours adding all the optional components to Visual Studio 2017 that could possibly be related and nothing fixes it.
      What the hell is this, what is the missing #include, and what the hell components do I need in Visual Studio 2017?


      L. Spiro
    • By Johannes1991
      I am trying to rotate a vector to a goal vector.
      My main problem now is that I can't wrap my head on is which way to rotate the vector, so finding the "shortest" way to rotate (clockwise or counter clockwise).
    • By ccuccherini
      I'm running to some issues with a random number generator that I am trying to create to test what type of attack an enemy will use (weapon or magic) and whether or not they land a hit with the attack.  What I want it to do is generate a random number number for the type of attack, then generate a separate random number for if the hit lands or not.  What actually happens is it is generating a different number each iteration, but the same number for each item.  So for instance, if the random number is a 6, both attack type and if it hit is a 6, if it's a 7 both are 7, etc.   I'm almost certain the issue is within how I'm using time in conjunction with this, but everything else I've found hasn't been helpful.  I've tried doing one function, but had the same issue and switched to two functions in the hopes that would help, but it didn't and now I'm not quite sure what to try next.
      Here is the code I have so far:
      // Example program #include <iostream> #include <string> #include <stdlib.h> #include <ctime> using namespace std; int getRandAttack(); int getRandHit(); int main() { int randomAttack; int randomHit; randomAttack = getRandAttack(); cout<<randomAttack<<endl; //used to ensure same number isn't given every time //decides what happens based on what number was given if (randomAttack <= 5) { cout<<"Enemy used a weapon!"<<endl; }else if (randomAttack > 5) { cout<<"Enemy used magic!"<<endl; } randomHit = getRandHit(); cout<<randomHit<<endl; //used to ensure same number isn't given every time //decides what happens based on what number was given if (randomHit <= 5) { cout<<"The attack missed!"; }else if (randomHit > 5) { cout<<"The attack hit!"; } } //gets random number to determine enemy attack type int getRandAttack () { int random; srand (time(NULL)); random = rand() % 10 + 1; return random; } //gets random number to determine if enemy attack lands a hit int getRandHit () { int random; srand (time(NULL)); random = rand() % 10 + 1; return random; } I know there is an easier way to do this that involves classes, but the online compiler I'm messing around with right now doesn't seem to support extra files, so I'm making functions work instead.  Any help and lead in the right direction would be greatly appreciated.
    • By khawk
      Urho3D 1.7 has been released. The release for the open source, cross-platform 2D/3D game engine includes a number of new features and bug fixes, including new IK support, AppleTV platform support, WebAssembly support, improved font rendering, better integration with Bullet and Box2D, renderer improvements, and more.
      Download the release and view the full changelog at https://urho3d.github.io/releases/2017/08/19/urho3d-1.7-release.html#changelog.
       

      View full story
  • Popular Now