Ok...I've read the FAQ and some examples of how to do this...but I still can't find anything that shows what I need.
I want to be able to set a 'speed limit', if you will, for fast computers. I am going to design the logic / render loops so they are optimized for the target computer...but I don't want the faster computers to run faster than the target. In the unused time slice, I would like to yeild to the OS.
So, my question is...how do I implement this into my game?
Also...if the interrupt doesn't change the value of a variable, does it still need to be volatile? I wouldn't think so, since the interrupt doesn't change it, but I want to be correct on this.
I would like something like:
1 | ////////////////////////////////////////////////////////////////////////////////////////////////// |
2 | #include <time.h> |
3 | #include <allegro.h> |
4 | ////////////////////////////////////////////////////////////////////////////////////////////////// |
5 | volatile int iFPS = 0; // Actual frames per second. |
6 | int iTargetFPS = 20; // the FPS speed limit. |
7 | int iFPSTimerSpeed = 2; // update the FPS counter this fast. |
8 | int iFPSCounter = 0; |
9 | ////////////////////////////////////////////////////////////////////////////////////////////////// |
10 | void FPSTimer( void ) |
11 | { |
12 | iFPS = iFPSCounter * iFPSTimerSpeed; |
13 | iFPSCounter = 0; |
14 | } |
15 | END_OF_FUNCTION(FPSTimer) |
16 | ////////////////////////////////////////////////////////////////////////////////////////////////// |
17 | void InitSYS( void ) |
18 | { |
19 | allegro_init(); |
20 | set_color_depth(32); |
21 | set_gfx_mode(GFX_DIRECTX_WIN,400,400,0,0); |
22 | set_color_conversion(COLORCONV_TOTAL); |
23 | install_timer(); |
24 | install_keyboard(); |
25 | install_mouse(); |
26 | } // I know...I should check for return values....I don't care right now. |
27 | ////////////////////////////////////////////////////////////////////////////////////////////////// |
28 | int main( void ) |
29 | { |
30 | InitSYS(); |
31 | LOCK_VARIABLE(iFPS); |
32 | LOCK_FUNCTION(FPSCounter); |
33 | install_int_ex(FPSTimer,BPS_TO_TIMER(iFPSTimerSpeed)); |
34 | while ( true ) |
35 | { |
36 | if ( keypressed() ) |
37 | { |
38 | if ( key[KEY_ESC] ) |
39 | { |
40 | clear_keybuf(); |
41 | break; |
42 | } |
43 | } |
44 | iFPSCounter++; |
45 | if ( iFPS >= iTargetFPS ) |
46 | { |
47 | // Do logic / rendering stuff. |
48 | } |
49 | else |
50 | { |
51 | // yield to system. |
52 | } |
53 | textprintf(screen,font,0,0,makecol(255,255,255),"FPS: %i", iFPS); |
54 | } |
55 | return 0; |
56 | } |
57 | END_OF_MAIN() |
58 | ////////////////////////////////////////////////////////////////////////////////////////////////// |
Edit...corrected the code.
Normally:
1 | voaltile int iLogic; |
2 | void Timer( void ) |
3 | { |
4 | iLogic++; |
5 | } |
6 | END_OF_FUNCTION(FPSTimer) |
7 | |
8 | //main loop |
9 | bool bRanLogic=false; |
10 | iLogicCounter = 0; |
11 | while(iLogic > 0 && iLogicCounter < 5) |
12 | { |
13 | logic(); |
14 | iLogicCounter++; |
15 | iLogic--; |
16 | bRanLogic = true; |
17 | } |
18 | if(bRanLogic) |
19 | draw(); //only need to draw if the logic ran |
20 | else |
21 | //some sort of yeild, to free up OS, I normally skip this |
On very fast computers the logic wouldn't run more than once each time it enters the while loop, which would be the timers time 60 BPS if the norm I think, but on slow computers it could get into an infinit loop. That is why we have iLogicCounter. If we get into a situation where logic() runs for a long time and iLogic keeps getting ++ before it hits iLogic--, then the iLogicCounter will make sure we only do this 5 times before breaking out and showing to the user what we did.
Heres a "better" main loop to consider... It uses the timer (speed_counter is the variable for that), and avoids some wasted processing for faster computers. It could still use some minor tweeks, though (Check Rick's post, for example).
1 | Init(); |
2 | |
3 | // other initialization crap |
4 | |
5 | while( !quit ) |
6 | { |
7 | if (key[KEY_ESC]) { |
8 | quit=true; } |
9 | |
10 | bool bLogicUpdate = false; |
11 | |
12 | while(speed_counter <= 0) { |
13 | rest(1); } |
14 | |
15 | while(speed_counter > 0) |
16 | { // logic "step" |
17 | // accept input |
18 | // do some calculations |
19 | |
20 | bLogicUpdate = true; |
21 | --speed_counter; |
22 | } |
23 | |
24 | // redraw |
25 | if( bLogicUpdate == true ) { |
26 | ScreenSystem->draw_flip(); // <-- this blits to the screen...just my way of doing it. |
27 | fps++; } // <-- this is a variable i use to keep track of FPS. ignore it. |
28 | yield_timeslice(); |
29 | } |
30 | |
31 | return 0; |
Elverion, your bLogicUpdate variable is rather useless there. Since you wait with rest(1) until you have logic to do, bLogicUpdate will always be true before it gets checked. As well, yield_timeslice is unneeded since you rest when you're caught up (and if you yield when you're not caught up, you're just wasting time and causing lost frames).
I don't know about the rest of you huys, but I haven't managed to produce smooth animation using allegro timers yet, unless I lock in on the physical screen refresh rate (or a multiple of that).
QueryPerformanceCounter() or gettimeofday() are the way to go, they're cheap, accurate and easy to code. Only fall back on allegro timers if you have to.