Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » fps and timer stuff

Credits go to Derezo, Erkle, Evert, flares, FMC, gering, ImLeftFooted, jamal, Marco Radaelli, Matt Smith, Nunca Ese, Oscar Giner, Rampage, tobing, Trezker, and umperio for helping out!
This thread is locked; no one can reply to it. rss feed Print
 1   2   3 
fps and timer stuff
Nunca Ese
Member #5,262
November 2004
avatar

Quote:

It completely changes the dynamics of the game if what was a fast-paced jump'n run game suddenly runs at half speed. Imagine what would happen if you tried to run an RTS over a network where not all computers have the same speed.
It's more important that the logic runs at a constant rate on all computers (in sofar as that is possible) than that you don't skip animation frames because otherwise the animation will be less smooth.

Agree. I was talking about my turn based game and also solo games.
When computer is fast enough, both mine and yours works perfectly.
When computer is too slow for the fps rate:
- mine: skips logic and frames -> slow game = entire timeline rendered
- yours: skip frames -> normal game = timeline complete, but only pieces of the timeline shown
Think mine option is better for turn based and solo games.

evert said:

Did you remember to declare speed_counter as volatile?

could be, dunno.

evert said:

The problem is that timeslice size changes from so version, so its like rest(variable) IMO.
I'm not sure what you mean?

1st so = os (sorry, spanish SO -> english OS (Operating System))
It should read as: "The problem is that timeslice size changes from O.S. version, so its like rest(variable) IMO." you will rest un uncertain time, you dont know timeslice size and how far you are from the end of the timeslice.

evert said:

Look at the code. They're not calling it from the active (meaning logic) loop.
The way I do this myself is to run the logic until it is up to date, then draw a frame if that is nescessary, otherwise just yield/rest.

Dont see why 'active' means 'inside logic', explain please ;)

Evert
Member #794
November 2000
avatar

Quote:

I was talking about my turn based game and also solo games.
[...]Think mine option is better for turn based and solo games.

It could a viable alternative in those cases. I wouldn't say it's better.
I'd hate for spiffy special effects to take up too much of my time while playing a game, especially if I know they will not eat so much time when playing on a faster computer.

Quote:

you will rest un uncertain time, you dont know timeslice size and how far you are from the end of the timeslice.

If your program is designed well enough, then you shouldn't care about this. As is said, rest(1) rests on average 1 ms.

Quote:

Dont see why 'active' means 'inside logic', explain please

Nothing to explain, really. That's just what the docs mean by active.

Nunca Ese
Member #5,262
November 2004
avatar

evert said:

It could a viable alternative in those cases. I wouldn't say it's better.

Yea, depends on who consumes the time, render or logic, and what you want to show.

evert said:

you will rest un uncertain time, you dont know timeslice size and how far you are from the end of the timeslice.
If your program is designed well enough, then you shouldn't care about this. As is said, rest(1) rests on average 1 ms.

Agree. Im using yield_timeslace.

Quote:

Dont see why 'active' means 'inside logic', explain please
Nothing to explain, really. That's just what the docs mean by active.

Ok. My mistake then.

Nice thread by the way! 8-)

Trezker
Member #1,739
December 2001
avatar

The official guide to the use of Allegro timers.
Timers

It's far from complete, please improve it if you have things to add or correct.

Oscar Giner
Member #2,207
April 2002
avatar

Quote:

One reason why not is beat frequencies. Your example would give an artifact at 15 Hz, which is acceptable IMO, but an extreme example would be logic at 60Hz with display at 61Hz, which in a slow scroll would produce a visible jerkiness at 1Hz intevals

Actually that's the big problem with hardcoding the ticks per second in the game. Playing a game coded at 60fps on a 85Hz display mode looks very jerky (this is most noticeable in 3D games or 2D games with fast scrolling). One solution is to force display frequency, but not all video drivers allow it, or the user may have disallowed it, so it's not a perfect solution.

That's why most comercial games use a variable timing interval (they check how much time has passed since the last logic, using WinAPI's GetPerformanceTimer()). I haven't tried this method yet, but I'll certainly do. The general algorithm to move an object this way would be something like this:

1myGame()
2{
3 while(!end)
4 {
5 do_logic();
6 draw_stuff();
7 }
8}
9 
10int last_update = 0;
11do_logic()
12{
13 int cur_time = CurrentTime();
14 int ellapsed_time = cur_time - last_update;
15 
16 ball_position_x += speed * ellapsed_time;
17 
18 last_update = cur_time();
19}

Marco Radaelli
Member #3,028
December 2002
avatar

I suggested rest(1), but probably I should have said rest(0). Right now I think the latter is the correct. IIRC there was a bug somewhere about yield_timeslice() and people suggested me to use rest(), probably with 0, not with 1.

Using 0, IIRC will release the timeslice, without any wait.

lucaz
Member #4,194
January 2004

Well I'm more confused now.

For example, to show a circle that change it color per loop.
If I want 60 different colors per second, how is the code?.

1volatile int timer = 0;
2void timer_handle() { timer++; } END_FUNCION(timer_handle)
3 
4int main() {
5 allegro_init();
6 set_gfx_mode(GFX_AUTODETECT,680,400);
7 LOCK_VARIABLE(timer);
8 LOCK_FUNCTION(timer_handle);
9 install_int_ex(timer,BPS_TO_TIMER(60))
10
11 int color;
12 while(!key[KEY_ESC]) {
13 ???
14 color++;
15 circle(screen,screen->w/2,screen->h/2,50,color);
16 }
17}
18END_OF_MAIN()

ImLeftFooted
Member #3,935
October 2003
avatar

1volatile int timer = 0;
2void timer_handle() { timer++; } END_FUNCION(timer_handle)
3 
4int main() {
5 if(allegro_init() != 0) exit(1);
6 if(set_gfx_mode(GFX_AUTODETECT,680,480,0,0) != 0) exit(1);
7 LOCK_VARIABLE(timer);
8 LOCK_FUNCTION(timer_handle);
9 install_int_ex(timer_handle,BPS_TO_TIMER(60));
10
11 unsigned int color = 0;
12 while(!key[KEY_ESC])
13 if(timer) {
14 timer--;
15 color++;
16 circle(screen,screen->w/2,screen->h/2,50,color);
17 }
18}
19END_OF_MAIN()

Nunca Ese
Member #5,262
November 2004
avatar

@ImLeftFooted

...
if(timer) {
timer-- // vs ++ 
...

;)

1volatile int timer = 0;
2void timer_handle() { timer++; } END_OF_FUNCTION(timer_handle);
3 
4#define RATE 1
5 
6int main(int argc, char* args[]) {
7 allegro_init();
8 set_gfx_mode(GFX_AUTODETECT,640,480,0,0); // 640x480 , not 680x400 ;)
9 LOCK_VARIABLE(timer);
10 LOCK_FUNCTION(timer_handle);
11 install_int_ex(timer_handle,BPS_TO_TIMER(RATE));
12 install_keyboard(); // Would key[] work without this?
13 
14 int color = 0;
15 while(!key[KEY_ESC]) {
16 if(timer > 0) {
17 color++;
18 circlefill(screen,screen->w/2,screen->h/2,50,color);
19 rectfill(screen, 10, 10, 50, 20, 0);
20 textprintf(screen, font, 10, 10, makecol(255,255,255), "Frame %d", color);
21 timer--;
22 }
23 rest(1);
24 }
25 return 0;
26}
27END_OF_MAIN();

This code works (some mistakes on lucaz code). Take it and play with RATE param (set to 60 or whatever u want).

lucaz
Member #4,194
January 2004

Yeesssssssssssssss, I hope Ive understood the idea.
When 'timer' becomes <0 it inmediatly turns to 60 again?

Nunca Ese
Member #5,262
November 2004
avatar

lucaz said:

Yeesssssssssssssss, I hope Ive understood the idea.
When 'timer' becomes <0 it inmediatly turns to 60 again?

Not exactly. Timers are events thrown 'n' times in a sec. Those 'n' times are also known as ticks. What the program is doing is firing 60 ticks per second.
Every tick (=every 16,6ms) you call timer++.
Every 'while' iteration is supposed to run faster than 16,6, so you get timer == 1 every 16ms, process it (thats if(timer) which means if(timer!=0)) and set timer to 0 again (timer--) inside the condition.
You wont enter the if(timer) condition again till a new tick is fired (and timer++ is done) -> you will enter it 60 times in a sec.

change

textprintf(screen, font, 10, 10, makecol(255,255,255), "Frame %d", color);
with
textprintf(screen, font, 10, 10, makecol(255,255,255), "Timer %d", timer);

you will notice that timer is always 1.

lucaz
Member #4,194
January 2004

thanks a lot people!

Kitty Cat
Member #2,815
October 2002
avatar

Don't draw in the logic loop.

while(playing) {
     while(timer > 0) {
            color++;
            timer--;
      }      
     circlefill(screen,screen->w/2,screen->h/2,50,color);
     rectfill(screen, 10, 10, 50, 20, 0);
     textprintf(screen, font, 10, 10, makecol(255,255,255), "Frame %d", color);
     while(!timer)
            yield_timeslice();
}

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

lucaz
Member #4,194
January 2004

Why not? :S

Kitty Cat
Member #2,815
October 2002
avatar

Because the rendering is usually the most expensive (system-wise) in your program. You want it seperated out so you can skip it if you fall behind (ie. frame skipping). Even you absolutely have to draw for your logic to properly run (which is rare), you still don't need to draw to the screen (unless you're using direct dirty rectangles like Allegro's GUI). It also helps the CPU cache when you access larger buffers, since the CPU wouldn't have to try to cache the buffer everytime you draw something more after doing a bit more logic. As well, WIndows and X don't really like mixing drawing to VRAM and using other input functions at the same time.

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

gering
Member #4,101
December 2003
avatar

read this - about timing

http://www.geisswerks.com/ryan/FAQS/timing.html

Average duration of Sleep(1)
Gemini: 10 ms (10 calls to Sleep(1) took exactly 100 ms)
Vaio: ~4 ms (10 calls to Sleep(1) took 35-45 ms)
HP: 10 ms (10 calls to Sleep(1) took exactly 100 ms)

---

timeBeginPeriod() solves the problem:
it lowers the granularity of Sleep() to whatever parameter you give it.
So if you're on windows 2000 and you call timeBeginPeriod(1) and then
Sleep(1), it will truly sleep for just 1 millisecond, rather than the
default 10!

__________________________________________________________
[ FreeGameDevLibs ]

Kitty Cat
Member #2,815
October 2002
avatar

Quote:

So if you're on windows 2000 and you call timeBeginPeriod(1) and then Sleep(1), it will truly sleep for just 1 millisecond, rather than the default 10!

I bet it doesn't do a thing for CPU usage that way either.

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

tobing
Member #5,213
November 2004
avatar

Thx for this really interesting article.

gering
Member #4,101
December 2003
avatar

Kitty Cat said:

I bet it doesn't do a thing for CPU usage that way either.

It does! :o

__________________________________________________________
[ FreeGameDevLibs ]

gnolam
Member #2,030
March 2002
avatar

If it really works it should be added to Allegro...

--
Move to the Democratic People's Republic of Vivendi Universal (formerly known as Sweden) - officially democracy- and privacy-free since 2008-06-18!

count
Member #5,401
January 2005

Ok, this is one of the best threads I ever read.
I love you ALL!!

My code currently looks like this and it works very good!

1 if (no_skip == 1) speed_counter = 1;
2 
3 while(exityn_game == 0)
4 {
5 
6 while (speed_counter > 0)
7 {
8 input();
9 speed_counter--;
10 fps_counter++;
11 }
12 draw_map();
13
14 /* added this line after I read this topic */
15 if (no_skip != 1) {while(speed_counter == 0){rest(1);}}
16 
17 }

THX!

Derezo
Member #1,666
April 2001
avatar

Quote:

timeBeginPeriod() solves the problem:
it lowers the granularity of Sleep() to whatever parameter you give it.
So if you're on windows 2000 and you call timeBeginPeriod(1) and then
Sleep(1), it will truly sleep for just 1 millisecond, rather than the
default 10!

That's some interesting information :)
Will definitely keep that in mind.

Quote:

I bet it doesn't do a thing for CPU usage that way either.

Why wouldn't it?
Normally when there is nothing to update, your CPU uses all of it's time to check if there is a need to update, and loop back. It does it over and over again, because that's what the loop tells it to do. Which means millions of "if (x)" checks and looping mechanisms (actual amount depends on the computer).
If you're averaging a wait of 16ms (~60fps) in 1ms increments, you're only making a maximum 16 "if (x)" checks each logic update. So naturally you're using far less CPU time.

"He who controls the stuffing controls the Universe"

lucaz
Member #4,194
January 2004

I wonder how much confused can I be! ;D

Kitty Cat
Member #2,815
October 2002
avatar

Quote:

If you're averaging a wait of 16ms (~60fps) in 1ms increments, you're only making a maximum 16 "if (x)" checks each logic update. So naturally you're using far less CPU time.

But something needs to constantly check if that 1ms time period has passed or not. The scheduler granularity in Windows is 10ms, so the process that does the Sleep checking will only run approximately once every 10ms (unless you give it a higher priority, which will cause it to take more CPU). To get more than that, you'd need to busy loop, which doesn't do anything for CPU usage.

More than that though, even if you can get more Sleep granularity while still sleeping the CPU, the efficiency of Sleep reducing CPU usage will deteriorate since it will have to return to the program and check if another frame is ready more often, before going back to sleep. It's like going to bed then waking up every hour to check if 8 hours have passed, or sleeping at least 8 hours and not waking up before you need to. A game with 100 logic frames per second (or less) will do just fine with a Sleep granularity of ~10ms.

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

tobing
Member #5,213
November 2004
avatar

I have done some experiments with timeBeginPeriod() and rest() and such. First, thanks to all for these really interesting infos.

So now I can understand my measurements much better, which relate internal CPU counter and the CPU usage indicated in the task manager. Now, using timeBeginPeriod(1) in the start of my program changes the confusing behaviour I saw before into some understandable behaviour now.

Well, I was forced to include winmm.dll and stuff into my main program, so I tried to move timeBeginPeriod() into the implementation of rest(). That was a good idea! Works perfectly well for my program, and also better than changing the main program only.

So how can I propose this change for consideration for the next allegro change?

Next I have again analysed the behaviour of dlg, which uses 100% CPU on doing nothing, but not all the time. I think I should write the results in an extra thread, but again I have two little changes to propose here, one for allegro and one for dlg. How do I do that?

 1   2   3 


Go to: