Outils pour utilisateurs

Outils du site


allegro:threads

Allegro — Threads

Dans cet article nous allons voir comment utiliser l'interface de threading d'Allegro.

Exemple basique

Comme Allegro est basé sur un système d'events, les threads sont assez peu utiles : ils servent à répartir la charge si on a plusieurs coeurs, ou à gérer le réseau si votre jeu est multijoueur.

Dans cet exemple, nous allons créer un thread qui va faire avancer une barre de progression pendant que le thread principal est occupé (à charger un niveau, des images, …).

main.c
#include <stdio.h>
#include <allegro5/allegro.h>
 
typedef struct s_data 
{
   ALLEGRO_MUTEX   *mutex;
   ALLEGRO_DISPLAY *display;
   ALLEGRO_BITMAP  *caret;
} DATA;
 
void init_DATA(DATA *data, ALLEGRO_DISPLAY *display, ALLEGRO_BITMAP  *caret)
{
   data->mutex   = al_create_mutex();
   data->display = display;
   data->caret   = caret;
}
 
void dest_DATA(DATA *data)
{
   al_destroy_mutex(data->mutex);
}
 
const float FPS        = 30;
const int SCREEN_W     = 640;
const int SCREEN_H     = 480;
const int CARET_HEIGHT = 32;
const int CARET_WIDTH  = 16;
const int PADDING      = 64;
 
static void *progressBar(ALLEGRO_THREAD *thr, void *arg);
 
int main(int argc, char **argv)
{
   ALLEGRO_DISPLAY     *display     = NULL;
   ALLEGRO_EVENT_QUEUE *event_queue = NULL;
   ALLEGRO_BITMAP      *caret       = NULL;
   ALLEGRO_THREAD      *thread      = NULL;
   DATA data;
 
   if(!al_init()) {
      fprintf(stderr, "failed to initialize allegro!\n");
      return -1;
   }
 
   al_set_new_display_flags(ALLEGRO_NOFRAME | ALLEGRO_WINDOWED);
   display = al_create_display(SCREEN_W, SCREEN_H);
   if(!display) {
      fprintf(stderr, "failed to create display!\n");
      return -1;
   }
 
   caret = al_create_bitmap(CARET_WIDTH, CARET_HEIGHT);
   if(!caret) {
      fprintf(stderr, "failed to create bouncer bitmap!\n");
      al_destroy_display(display);
      return -1;
   }
 
   al_set_target_bitmap(caret);
   al_clear_to_color(al_map_rgb(255, 0, 255));
   al_set_target_bitmap(al_get_backbuffer(display));
   event_queue = al_create_event_queue();
 
   if(!event_queue) {
       fprintf(stderr, "failed to create event_queue!\n");
       al_destroy_bitmap(caret);
       al_destroy_display(display);
       return -1;
   }
 
   al_register_event_source(event_queue, al_get_display_event_source(display));
   al_clear_to_color(al_map_rgb(0,0,0));
   al_flip_display();
 
   init_DATA(&data, display, caret);
 
   thread = al_create_thread(progressBar, &data);
   al_start_thread(thread);
 
   al_rest(10.0); /* Loading some awesome datas !! */
 
   //al_set_thread_should_stop(thread); /* appelée implicitement par l'instruction ci-dessous */
   al_join_thread(thread, NULL);
 
   al_destroy_thread(thread);
   dest_DATA(&data);
 
   al_destroy_bitmap(caret);
   al_destroy_display(display);
   al_destroy_event_queue(event_queue);
 
   return 0;
}
 
static void *progressBar(ALLEGRO_THREAD *thr, void *arg)
{
   ALLEGRO_TIMER       *timer       = NULL;
   ALLEGRO_EVENT_QUEUE *event_queue = NULL;
   ALLEGRO_EVENT       ev;
 
   DATA *data  = (DATA*) arg;
   float num   = 2.0;
   float xpos  = PADDING;
 
   if (!data->display || !data->caret) {
      fprintf(stderr, "args are NULL!\n");
      return NULL;
   }
 
   timer = al_create_timer(1.0 / FPS);
   if(!timer) {
      fprintf(stderr, "failed to create timer!\n");
      return NULL;
   }
 
   event_queue = al_create_event_queue();
   if(!event_queue) {
      fprintf(stderr, "failed to create event queue!\n");
      return NULL;
   }
   al_register_event_source(event_queue, al_get_timer_event_source(timer));
   al_start_timer(timer);
   al_set_target_bitmap(al_get_backbuffer(data->display)); /* new context */
 
   while(!al_get_thread_should_stop(thr))
   {
      al_wait_for_event(event_queue, &ev);
      // if(ev.type == ALLEGRO_EVENT_TIMER) /* ne peut être autre chose */
 
      al_draw_bitmap(data->caret, xpos, SCREEN_H-PADDING-CARET_HEIGHT, 0);
 
      xpos += num;
      if (xpos >= SCREEN_W-PADDING)
         break;
 
      al_flip_display();
   }
 
   al_destroy_timer(timer);
   al_destroy_event_queue(event_queue);
   return NULL;
}

Détails

typedef struct s_data 
{
   ALLEGRO_MUTEX   *mutex;
   ALLEGRO_DISPLAY *display;
   ALLEGRO_BITMAP  *caret;
} DATA;
 
void init_DATA(DATA *data, ALLEGRO_DISPLAY *display, ALLEGRO_BITMAP  *caret)
{
   data->mutex   = al_create_mutex();
   data->display = display;
   data->caret   = caret;
}
 
void dest_DATA(DATA *data)
{
   al_destroy_mutex(data->mutex);
}

On crée une structure qui contient les ressources partagées, elle doit contenir minimum le mutex qui permet de demander l'accès exclusif aux données afin d'éviter de les corrompre (si par exemple 2 threads veulent modifier la même variable au même moment, la valeur finale de la variable est inconnue).

On peut ajouter une condition de type ALLEGRO_COND qui permet à un thread d'en interrompre un autre et le de relancer grâce aux fonctions al_wait_cond et al_broadcast_cond, utile pour les threads concurrents.

   thread = al_create_thread(progressBar, &data);
   al_start_thread(thread);
 
   al_rest(10.0); /* Loading some awesome datas !! */

On crée notre thread avec al_create_thread qui prend en paramètres une fonction (qui sera appelée par le thread nouvellement crée) et un argument (qui sera transmis à la fonction).

al_start_thread démarre notre thread, ensuite on peut faire tourner le code gros consommateur de temps (représenté par al_rest), on sait que l'affichage est géré par la fonction progressBar.

C'est une fonction très simple que reprend des notions des articles précédents. Sachez que dessiner la barre de progression avec un bitmap c'est pas très intelligent ! on dispose de fonctions de dessin via ce greffon : Primitive.

On remarquera seulement ceci :

   al_set_target_bitmap(al_get_backbuffer(data->display)); /* new context */

En effet, chaque thread possède son propre context, c'est à dire tout ce qui est global dans allegro est limité à un thread, donc pour pouvoir dessiner sur le canvas démarré dans le thread principal, il faut le transmettre via notre structure et prévenir notre nouveau thread.
Si votre jeu termine sur un ASSERT, c'est certainement du à une variable du context qui est à NULL, donc faites attention !

Amelioration

On décide d'améliorer notre barre de progression, on veut que le thread principal contrôle l'avancement de la barre : on ajoute donc la variable data.percent, un entier compris entre 0 et 100 pour définir la taille de notre barre en fonction de l'avancement dans notre thread principal.
On doit aussi ajouter une condition pour que le thread principal déclenche le dessin dès qu'il modifie la variable.

On modifie alors notre fonction qui dépend plus du timer :

static void *progressBar(ALLEGRO_THREAD *thr, void *arg)
{
   DATA *data  = (DATA*) arg;
   float pbar_1pc = (SCREEN_W-2*PADDING)/100.0;
   int goon = 1;
 
   al_set_target_bitmap(al_get_backbuffer(data->display));
 
   while(!al_get_thread_should_stop(thr) && goon)
   {
      al_lock_mutex(data->mutex);
      al_wait_cond(data->cond, data->mutex);
      al_draw_bitmap(data->caret, data->percent*pbar_1pc, SCREEN_H-PADDING-CARET_HEIGHT, 0);
      if (data->percent == 100)
         goon = 0;
      al_unlock_mutex(data->mutex);
 
      al_flip_display();
   }
 
   return NULL;
}

La fonction restera bloquée sur al_wait_cond tant que le thread parent n'a pas appelé al_broadcast_cond.

Pour terminer ce type de thread, un join ne suffit pas car il n'enlève pas les locks dûs aux conditions. Vous devez utiliser à la place :

    al_set_thread_should_stop(thread);
    al_lock_mutex(data.mutex);
    al_broadcast_cond(data.cond);
    al_unlock_mutex(data.mutex);

Maintenant que vous savez utiliser les threads, vous savez pratiquement tout ce qu'il y a à savoir sur le core d'allegro, dans la suite de ce tutoriel nous aborderons les différents greffons d'Allegro.

Précédent « Sommaire » Suivant

allegro/threads.txt · Dernière modification: 2012/06/29 17:02 par mrhide