Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Inconsistent time acceleration problem  (Read 6020 times)

0 Members and 1 Guest are viewing this topic.

C with a C

  • Newbie
  • *
  • Posts: 21
    • View Profile
Inconsistent time acceleration problem
« on: June 07, 2016, 09:57:25 am »
Is there an inconsistency I'm not aware of when dealing with very low amounts of time?

I'm trying to increment minutes in my game by 240/s (4h/s)...
elapsed += timer.restart();
(...)
switch(timeSpeed)
{
    case 1:     // 1 s/2s - real game time
        if (elapsed.asMicroseconds() >= 2000000)    countMinutes();
        break;
    case 2:     // 5 m/s
        if (elapsed.asMicroseconds() >= 200000)     countMinutes();
        break;
    (...)
    case 5:     // 4 h/s
        if (elapsed.asMicroseconds() >= 4166)       countMinutes();
        break;
}
 
...but it only passes 3h/s if I unlock the frame rate, and 1h/s if I lock it at 60. It seems there is a bottom limit to how many microseconds I can manage too, since lowering the value does nothing to increase the speed.

This is my countMinutes() function.
++db.minutes;

if(db.minutes >= 60)
{
    db.minutes = 0;
    countHours();
}

elapsed = reset;
 

Also, reset is another sf::Time, that is used for nothing else than to reset elapsed. I'm doing this because elapsed = 0 doesn't seem to be possible and using % doesn't seem to work well with timers.

Is this a valid approach?

If I let it go beyond two millions I won't be able to call countMinutes() anymore. I don't know another way of doing it, but this one also creates a bit of a delay (-1s per every ~10s or something like that).






« Last Edit: June 07, 2016, 04:47:41 pm by C with a C »

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Inconsistent time acceleration problem
« Reply #1 on: June 07, 2016, 02:07:40 pm »
The timer isn't 100% accurate; it's updated within a specific error-range dependant on the system's actual clock's ability.

If your frame/cycle/tick is extraordinarily short/fast, restarting the clock each cycle can cause the elapsed time to actually be zero. This may explain why disabling the framerate limit can cause problems.
A possibly better way to deal with this is to avoid restarting the clock and keeping track of a "previous elapsed time" and compare the two each cycle. That way, the clock's time is passing at real-time, not missing any time because the cycle's time is considered zero.

elapsed = timer.getElapsedTime();
would reduce the need to set elapsed to zero; you could reset the clock instead. Resetting every now and again can be better than resetting every frame. However, as mentioned above, you could still use:
elapsed += frameTime;
which would be the difference between the current time and the previous time. This avoids having to reset the clock at all, although "elapsed" could be named something more accurate like "accumulation" or "timeSoFar".

This would probably lead to having to reset that variable though, so, assuming you stuck with elapsed in this case, you can reset it thusly:
elapsed = sf::Time::Zero;
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

C with a C

  • Newbie
  • *
  • Posts: 21
    • View Profile
Re: Inconsistent time acceleration problem
« Reply #2 on: June 07, 2016, 04:33:33 pm »
Indeed the name elapsed is a placeholder. Got it from tutorials.

But maybe I'm missing something in what you told me, it doesn't seem to work. I got rid of += in passing the clock's value to elapsed. But now, how exactly would you get the difference into frameTime? I'm supposing you'd use even more sf::Times?

(by the way, can we use many sf::Times, and with the same clock?)

Some problems I noticed: if I reset the clock, it works, but delays a lot, being slower than I want it to be (never getting 4h/s), so it's part of the problem I was having before; if I don't reset the clock but reset elapsed, the latter will reach 2 million, be reset to zero, and from then on will always receive a value that is over 2 million from the clock, since the clock never stopped adding up.

That will make any if statement in the switch always be true on every frame (minutes will go wild and hours will go 1h/s - regardless of time speed, which confuses me), forever, or until I finally reset the clock.

If I had a way to divide the clock's value to reach something lower that could be relatively accurate...
My math is quite rusty though.

The thing is, I could be doing all this really easily by just calling countHours()directly at every quarter of a second, but then I wouldn't be counting the minutes - they would halt on the counter. It was my intention to have them rolling fast, for a nice visual effect and for a better sense of the speed at which the time is passing. EDIT: or maybe I could count minutes, a few at a time.
« Last Edit: June 07, 2016, 04:41:48 pm by C with a C »

Arcade

  • Full Member
  • ***
  • Posts: 230
    • View Profile
Re: Inconsistent time acceleration problem
« Reply #3 on: June 07, 2016, 04:49:55 pm »
I'm not sure if I fully understand what you are trying to do, but it sounds like maybe your game has a notion of time and you are trying to "fast-forward" the time.
Quote
case 5:     // 4 h/s
        if (elapsed.asMicroseconds() >= 4166)       countMinutes();
        break;
 
You didn't show where in your game loop you are resetting your clock, but I'm guessing this will be problematic because you are relying on your game being very fast for it to work. As you discovered, if you lock your framerate at 60Hz then elapsed.asMicroseconds() might return something like 16666 (again, depending on when you are resetting you clock in the game loop).

Why not something like this?:
case 5:     // 4 h/s
        int time = elapsed.asMicroseconds();
        while(time >= 4166)
        {
            countMinutes();
            time -= 4166;
         }      
        break;
 
You will also want to save whatever remains in 'time' and add it to your next loop.

Or, to remove the while loop, just do time / 4166 and pass that in your countMinutes() calculation, and save time % 4166 for your next game loop.
« Last Edit: June 07, 2016, 05:14:10 pm by Arcade »

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Inconsistent time acceleration problem
« Reply #4 on: June 07, 2016, 05:12:40 pm »
Yes, you can use multiple sf::Times. They are simple time storage classes. They can even have calculation performed on them directly.
For example:
static sf::Time previousTime = currentTime;
const sf::Time currentTime = clock.getElapsedTime();
const sf::Time frameTime = currentTime - previousTime;

If I had a way to divide the clock's value
const float clockDivision = 10.f;
const sf::Time elapsedTime = clock.getElapsedTime();
const sf::Time processedTime = elapsedTime / clockDivision;
processedTime would be ten times slower.

You may want to consider using my small timing library, Kairos, which includes a clock (called Continuum) that can be paused and have its speed adjusted; it can even go backwards!
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

C with a C

  • Newbie
  • *
  • Posts: 21
    • View Profile
Re: Inconsistent time acceleration problem
« Reply #5 on: June 07, 2016, 05:39:15 pm »
You didn't show where in your game loop you are resetting your clock, but I'm guessing this will be problematic because you are relying on your game being very fast for it to work.
I tried resetting the clock in several places. Was all the same, as far as I could tell.

But you just got me thinking about something I read on some other old thread, that maybe my actual problem is with me being drawing way too many Sprites, keeping the game busy drawing for too much time (at least that's the idea I got from there). I'm building graphics by tiles, to mimic consoles/terminals (much like roguelikes do), and using two vectors<Sprite> as buffers, one for graphics, the other one for text. Even if the text buffer is only initialized with null pointers to Sprites, I still have a minimum of 4000 Sprites (80x50) in the other buffer at all times (only two Textures (tilesets), though).

I was thinking of converting it all to vertex arrays. My game runs exactly 10x faster if I turn off the drawing functions. Goes from 150fps (unlocked) to 1500fps.

Maybe I should do that first, and then see if the problem persists?

(EDIT:forgot to mention, yes, I'm trying to fast forward. If you've played Jagged Alliance 2 or X-Com Apocalypse, I'm trying to do something very similar)

You may want to consider using my small timing library, Kairos, which includes a clock (called Continuum) that can be paused and have its speed adjusted; it can even go backwards!

Thanks. That might actually be useful.
I want to see if I can understand my mistakes first, though...

const float clockDivision = 10.f;
const sf::Time elapsedTime = clock.getElapsedTime();
const sf::Time processedTime = elapsedTime / clockDivision;
processedTime would be ten times slower.
Then, I guess that by multiplying it I could make it faster, too.
« Last Edit: June 07, 2016, 05:51:39 pm by C with a C »

Arcade

  • Full Member
  • ***
  • Posts: 230
    • View Profile
Re: Inconsistent time acceleration problem
« Reply #6 on: June 07, 2016, 07:56:59 pm »
Quote
I was thinking of converting it all to vertex arrays. My game runs exactly 10x faster if I turn off the drawing functions. Goes from 150fps (unlocked) to 1500fps.

Maybe I should do that first, and then see if the problem persists?

That sounds like a possible optimization, but I would suggest not relying on your game running at a certain frame rate in order for your fast forward feature to function properly. Someone might want to run your game on a slower computer and then you'll be back to having this problem again. As I mentioned in my last post, it seems like you should just have your countMinutes() function handle incrementing more than one minute at a time if necessary. So, for example, if 4166 microseconds have elapse you add one minute. If 8332 microseconds have elapsed you add 2 minutes. etc.

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Inconsistent time acceleration problem
« Reply #7 on: June 08, 2016, 01:02:21 am »
Then, I guess that by multiplying it I could make it faster, too.
Of course. Be aware of two things important things here though:
If you "speed up" time by multiplying it, frame time will seem longer for the logic/physics. It has to process longer time passing so may skip important steps (collisions can be completely skipped - consider maybe fixing your timestep)
Changing the time can't be done easily. It can be set easily to start with but for it to change, it needs to remember the amount passed at the current speed and then start again with the new speed. Consider if you multiply time by 2. If original time is 10, and multiplied time is 20, changing the speed to multiply by 0.5 would instantly change the current time to 10 * 0.5, which is 5, so time would jump from 20 to 5.
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

C with a C

  • Newbie
  • *
  • Posts: 21
    • View Profile
Re: Inconsistent time acceleration problem
« Reply #8 on: June 08, 2016, 09:28:58 am »
Gonna try to fix the timestep to see how it goes. I'm currently just using the simplest:

// main.cpp

elapsed = timer.getElapsedTime();    // could've been restarting the clock here anyway
if (elapsed.asMicroseconds() >= 16666)
{
    window.clear(sf::Color::Black);

    coreState.update();
    debuger.update();

    coreState.draw(terminal);
    terminal->render(&window);
    debuger.draw(&window);

    window.display();

    if (!running) window.close();
    timer.restart();
}

I didn't think it was relevant before, to show much of the code, as I didn't think the problem might be rooted somewhere else (and I wanted to receive answers solely focused on this, not other possible unrelated mistakes), but since now I think it is, this is what I was doing:

I was creating the clock in the World class, as a quick and dirty experimental thing, since it makes sense to me that the World class gets to manage the world's time (either that or a specific time manager class, is what I figured). I'm keeping some global variables in a separate database while I don't know enough to organize things better, and that includes the world's time and date variables (so that the UI can also access them and show the clock).

This is all stuff that may change in the future anyway. I may want to display a visual rotating sun and moon or something else entirely, and end up not needing global variables or even an actual clock display, but meanwhile I'm going with this, since the numbers help me see if it actually works as intended.

So I had two clocks. One in the main loop, that I copied from a youtube video some months ago, and the other in the World class. I suppose the clocks wouldn't interfere with each other, but now I think there's no point in having two. I can use the main one to pass the time to the database, or pass the time down the update() functions.

Although, I wonder if sf::Clocks aren't something like sf::Fonts, that we shouldn't go wild with. I had never tried to actually understand timers and clocks until now.
« Last Edit: June 08, 2016, 09:37:01 am by C with a C »

C with a C

  • Newbie
  • *
  • Posts: 21
    • View Profile
Re: Inconsistent time acceleration problem
« Reply #9 on: June 09, 2016, 09:56:06 am »
So I tried Gaffer On Games' way:

sf::Clock timer;
sf::Time elapsed;

(...)

float t = 0.0f;
float dt = 0.016f;
float currentTime = 0.0f;
float accumulator = 0.0f;

while(window.isOpen())
{
    // (...) // events

    elapsed = timer.getElapsedTime();

    const float newTime = elapsed.asSeconds();
    float deltaTime = newTime - currentTime;
    currentTime = newTime;

    if (deltaTime>0.016f) deltaTime = 0.016f;
    accumulator += deltaTime;

    while (accumulator >= dt)
    {
        accumulator -= dt;
       
        window.clear(sf::Color::Black);

        coreState.update();
        db.update();
        debuger.update();

        coreState.draw(terminal);
        terminal->render(&window);
        debuger.draw(&window);
       
        t += dt;
    }
    window.display();
    if (!running) window.close();
}
 

I didn't separate the drawing functions like he advises because I don't see a way to apply the same integration tricks he did, so if separate them I will get the visual stuttering he mentions.

I suppose I could pass the time into the update functions, but if that's is so, which one? t? dt? currentTime? I'm not resetting the clock now, though, so both the clock and t will go on forever. I have no clue whether that's a bad thing...

This does seem to solve my counter problems, even if I'm still doing the same thing I was doing in the World class with the timers, except I'm now counting more minutes at a time in the fastest time speeds.

I am, however, essentially doing two variations of the same thing in two different places, this one in the main() and the other in the World class. I wonder if that's a good idea, or if that's the way it should be.

There is an improvement in the frame rates. I now have ~1500 fps when unlocked, and I haven't even implemented Vertex Arrays instead of Sprites, yet.

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Inconsistent time acceleration problem
« Reply #10 on: June 10, 2016, 07:48:49 pm »
Adding the draw and display to the timestep update loop doesn't avoid the missing-interpolation effects. It is basically drawing and displaying the window but the display itself will only update at its own refresh rate so only one of those frame will actually be shown. Actually, it's more likely that two consecutive frames are shown together will a "tear" or "rip".

I've recently been discussing the interpolation part of the fixed timestep loop ("The Final Touch" in the Gaffer On Games article) on this very forum. Have a look through this short thread:
http://en.sfml-dev.org/forums/index.php?topic=20405
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*