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, ...). <file c 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; } </file> ==== Détails ==== <code c> 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); } </code> 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.// <code c> thread = al_create_thread(progressBar, &data); al_start_thread(thread); al_rest(10.0); /* Loading some awesome datas !! */ </code> 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 : [[allegro:addon_primitives|Primitive]]. On remarquera seulement ceci : <code c> al_set_target_bitmap(al_get_backbuffer(data->display)); /* new context */ </code> 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 : <code c> 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; } </code> 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 : <code C> al_set_thread_should_stop(thread); al_lock_mutex(data.mutex); al_broadcast_cond(data.cond); al_unlock_mutex(data.mutex); </code> 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. [[allegro:input|Précédent]] << [[allegro:start#articles|Sommaire]] >> [[allegro:addons|Suivant]]

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