Timers

From Allegro Wiki

Jump to: navigation, search

Contents

Introduction

Welcome to the tutorial on Allegro timer usage. You should read this if you have problems making your game run at the right speed on your computer, or if you made a game and put it on another computer just to find out that it suddenly runs at a different speed. This tutorial is supposed to teach you how to use Allegro timers to make your game run at the right speed on all computers. If this tutorial doesn't help, go to allegro.cc and search the forums, this subject has been discussed a lot.

To make use of this tutorial: You should be familiar with Allegro, if not visit http://www.allegro.cc/ and http://alleg.sourceforge.net/. You should also have tried to make a game run at the right speed and felt that there must be a better way.

Timers

What are Timers?

Timers are a part of the allegro library, so include allegro.h and don't forget to link to the lib when you compile.

#include <allegro.h>

You use the function install_int or install_int_ex to start a timer. These functions take a function parameter void *(proc)(), and a speed parameter. The function you pass to the timer will be called as often as the speed parameter dictates. Each call to the function is called a 'tick'. For the install_int function, the speed parameter is the time in milliseconds between ticks.

Interally, however, Allegro timers do not use milliseconds, they use hardware clock ticks. In fact, the speed parameter of install_int_ex takes hardware ticks instead of milliseconds. Using this function is a little trickier than install_int. Fortunately, Allegro has a handful of macros defined which convert from a more useful scale to hardware ticks.

Here is the description of all macros from the allegro manual.

     SECS_TO_TIMER(secs)  - give the number of seconds between
                            each tick. The timer ticks every 
                            secs  seconds.
     MSEC_TO_TIMER(msec)  - give the number of milliseconds
                            between ticks. The timer ticks every
                            msecs milliseconds.
     BPS_TO_TIMER(bps)    - give the number of ticks per second.
                            The timer ticks bps times a second.
     BPM_TO_TIMER(bpm)    - give the number of ticks per minute
                            The timer ticks bpm times a minute.

You can see that the first two take normal time values, seconds and milliseconds. Using the second would have exactly the same effect as using install_int. These would be useful for timing things, session length, reaction time and such. But if you want a light that flashes 10 times per minute or an object that moves 30 pixels per second you should use the other two.

How to use them?

First, you need a function that the timer will call every tick, the ticker. This ticker function must execute very fast, so all we're going to do is to increase a tick counter. Since timers are using interrupts to call your ticker there are some special rules for the safety of your program. You should read the documentation for specific information about this.

   //One of these rules state that the variables handled in your ticker need be volatile
   //So let's declare it globally.
   volatile int ticks=0;
 
   //Then comes the ticker, in my example it simply increments ticks.
   //After the function another rule bothers us.
   //You need the macro END_OF_FUNCTION after the ticker.
   void ticker()
   {
      ticks++;
   }
   END_OF_FUNCTION(ticker)
 
   //Time to install our timer.
   //Before doing that, we simply must follow the rules.
   //Lock variable and function using those nice macros.
   void main()
   {
      LOCK_VARIABLE(ticks);
      LOCK_FUNCTION(ticker);
 
      //Finally we can install the timer, using either method.
      install_int(ticker,10);
      install_int_ex(ticker, BPS_TO_TIMER(100));
   }
   END_OF_MAIN()

And now we have a timer that ticks 100 times per second.

Making use of the ticks

Before the timer your main game loop would in general look like this.

   while(!quit)
   {
      DoLogic();
      DrawEverything();
      delay();
   }

Without the delay, it might be a pause, for loop or even vsync, your program runs as fast as it can. Probably the best result when trying to run your game at the same speed on any computer would be when using vsync. But vsync isn't very reliable, it works different on different systems and causes all kinds of problems.

Now let us use the ticks instead. Let's look at the whole picture.

#include <allegro.h>
 
volatile int ticks = 0;
void ticker()
{
	ticks++;
}
END_OF_FUNCTION(ticker)
 
const int updates_per_second = 60;
 
int main()
{
	allegro_init();
	install_timer();
 
	//initialize the timer
	LOCK_VARIABLE(ticks);
	LOCK_FUNCTION(ticker);
	install_int_ex(ticker, BPS_TO_TIMER(updates_per_second));
 
	bool quit = false;
 
	while(!quit)
	{
		while(ticks == 0)
		{
			rest(1);//rest until a full tick has passed
		}
 
		while(ticks > 0)
		{
			int old_ticks = ticks;
 
			DoLogic();
 
			ticks--;
			if(old_ticks <= ticks)//logic is taking too long: abort and draw a frame
				break; 
		}
 
		DrawEverything();
	}
	return 0;
}
END_OF_MAIN()

As you can see I create a timer, and install it at 60 BPS via a constant variable.. Then we start the game loop. In this we update the logic of the game as many times as it's supposed to be updated the current loop. Each time the timer ticks, the logic is supposed to be updated, so we simply update as many times as there has been ticks since the last loop. We also keep track of how long the logic step is taking: if it starts taking too long, we break out of the logic loop and draw a frame. If the drawing takes more than one tick, it updates for every tick passed before drawing again. If the drawing takes less than a full tick, we rest until the full tick has passed, allowing for an efficient utilization of the CPU.

Yielding the CPU using a semaphore

Using the rest() function to wait until the next tick is not always the most optimal solution, as it can be slow on some systems, unnecessarily wasting CPU time.

Our goal is to put the process to sleep while we are busy waiting and to wake it back up when we are ready again. The trick is to use a semaphore. Below is some code which will run natively on Linux and Mac OS X. Windows has its own semaphore mechanism but the pthreads-win32 [1] library provides the means to run this code under Windows.

#include <allegro.h>
#include <semaphore.h>
 
sem_t sem_rest;
 
volatile int ticks = 0;
void ticker()
{
	sem_post(&sem_rest);//unlock the semaphore, and allow the game loop to continue
	ticks++;
}
END_OF_FUNCTION(ticker)
 
const int updates_per_second = 60;
 
int main()
{
	//initialize the semaphore
	//following function works for linux and win32 pthreads
	sem_init(&sem_rest, 0, 1);
 
	//for mac os x, named semaphores must be used instead, first by declaring a pointer sem_t* sem_rest_ptr
	//and then doing sem_rest_ptr = sem_open("sem_name", O_CREATE, (S_IRUSR | S_IWUSR), 1)
	//sem_name is the name of the semaphore, which should be unique
 
	allegro_init();
	install_timer();
 
	LOCK_VARIABLE(ticks);
	LOCK_FUNCTION(ticker);
	install_int_ex(ticker, BPS_TO_TIMER(updates_per_second));
 
	bool quit = false;
 
	while(!quit)
	{
		sem_wait(&sem_rest);//wait until a full tick has passed using the semaphore
 
		while(ticks > 0)
		{
			int old_ticks = ticks;
 
			DoLogic();
 
			ticks--;
			if(old_ticks <= ticks)
				break; 
		}
 
		DrawEverything();
	}
 
        remove_int(ticker);
 
	//destroy the semaphore (important!)
        sem_destroy(&sem_rest);
 
	//on mac osx, sem_destroy does not work, so do sem_close(sem_rest_ptr) and sem_unlink("sem_name") instead
 
	return 0;
}
END_OF_MAIN()

As you can see, we replaced the resting loop with a function call that causes the main thread to sleep until it is woken up by the timer routine. The complete documentation of the semaphore related functions is available at opengroup [2].

FPS

Simple

Sometimes it's good to know how many frames your game produces. The standard value to measure this is FPS, frames per second. Using the method described, logic updates with the same interval on all computers, but the drawing does not. And that's what's interesting, you can check the FPS on your game on different computers to decide required and recommended systems. Some people also like to see their FPS so they can brag about their computers.

#include <allegro.h>
 
volatile int ticks = 0;
void ticker()
{
	ticks++;
}
END_OF_FUNCTION(ticker)
 
//a new set of timing variables to keep the game time
volatile int game_time = 0;
void game_time_ticker()
{
	game_time++;
}
END_OF_FUNCTION(game_time_ticker)
 
const int updates_per_second = 60;
 
int main()
{
	allegro_init();
	install_timer();
 
	LOCK_VARIABLE(ticks);
	LOCK_FUNCTION(ticker);
	install_int_ex(ticker, BPS_TO_TIMER(updates_per_second));
 
	LOCK_VARIABLE(game_time);
	LOCK_FUNCTION(game_time_ticker);
	install_int_ex(game_time_ticker, BPS_TO_TIMER(10));//i.e. game time is in tenths of seconds
 
	bool quit = false;
 
	int fps = 0;
	int frames_done = 0;
	int old_time = 0;
 
	while(!quit)
	{
		while(ticks == 0)
		{
			rest(1);
		}
 
		while(ticks > 0)
		{
			int old_ticks = ticks;
 
			DoLogic();
 
			ticks--;
			if(old_ticks <= ticks)
				break; 
		}
 
		if(game_time - old_time >= 10)//i.e. a second has passed since we last measured the frame rate
		{
			fps = frames_done;
			//fps now holds the the number of frames done in the last second
			//you can now output it using textout_ex et al.
 
			//reset for the next second
			frames_done = 0;
			old_time = game_time;
		}
 
		DrawEverything();
		frames_done++;//we drew a frame!
	}
	return 0;
}
END_OF_MAIN()

One way to do accomplish this goal is to create a new timer that keeps track of the game time. The main loop is modified to count the number of frames passed, which are then stored in the fps variable as soon as the game time timer reports that a second has passed.

Advanced

A problem with the previous algorithm is that it can only report the FPS every second or so. While this may be fine for most purposes, sometimes one wishes to have the FPS variable be updated more often. We can modify the above code to yield this:

#include <allegro.h>
 
volatile int ticks = 0;
void ticker()
{
	ticks++;
}
END_OF_FUNCTION(ticker)
 
volatile int game_time = 0;
void game_time_ticker()
{
	game_time++;
}
END_OF_FUNCTION(game_time_ticker)
 
const int updates_per_second = 60;
 
int main()
{
	allegro_init();
	install_timer();
 
	LOCK_VARIABLE(ticks);
	LOCK_FUNCTION(ticker);
	install_int_ex(ticker, BPS_TO_TIMER(updates_per_second));
 
	LOCK_VARIABLE(game_time);
	LOCK_FUNCTION(game_time_ticker);
	install_int_ex(game_time_ticker, BPS_TO_TIMER(10));//i.e. game time is in tenths of seconds
 
	bool quit = false;
 
	int fps = 0;
	int frames_done = 0;
	int old_time = 0;
 
	int frames_array[10];//an array to store the number of frames we did during the last 10 tenths of a second
	int frame_index = 0;//used to store the index of the last updated value in the array
	for(int ii = 0; ii < 10; ii++)
		frames_array[ii] = 0;//initialize the array to 0
 
	while(!quit)
	{
		while(ticks == 0)
		{
			rest(1);
		}
 
		while(ticks > 0)
		{
			int old_ticks = ticks;
			DoLogic();
			ticks--;
			if(old_ticks <= ticks)
				break; 
		}
 
		if(game_time >= old_time + 1)//i.e. a 0.1 second has passed since we last counted the frames
		{
			fps -= frames_array[frame_index];//decrement the fps by the frames done a second ago
			frames_array[frame_index] = frames_done;//store the number of frames done this 0.1 second
			fps += frames_done;//increment the fps by the newly done frames
 
			frame_index = (frame_index + 1) % 10;//increment the frame index and snap it to 10
 
			frames_done = 0;
			old_time += 1;
		}
 
		DrawEverything();
		frames_done++;//we drew a frame!
	}
	return 0;
}
END_OF_MAIN()

This algorithm updates the fps variable 10 times per second. We use a ring buffer to store the number of frames done in each of the 0.1 second intervals.


Personal tools
Adsense