Au sanglier codeur

Gestionnaires de ressource

Introduction

La gestion des ressources est primordiale dans toute application qui en manipule beaucoup, donc en particulier dans les jeux vidéos.

Imaginons par exemple qu'il y ait 5 ennemis identiques à l'écran. Il est hors de question de charger 5 fois la même image, ça prendrait trop de mémoire. Nous allons donc trouver un moyen de réutiliser la même.
Nous devons trouver une astuce ingénieuse, car le code ne doit pas perdre en généricité et ne doit pas non plus devenir affreux.

C'est là qu'entre en jeu le gestionnaire de texture (Texture manager). A chaque fois qu'une texture devra être chargée, c'est le gestionnaire qui s'en occupera. De cette manière, il vérifie qu'elle n'est pas déjà en mémoire, et si c'est le cas, il renvoie celle existante au lieu d'en charger une nouvelle.
Cela implique donc que le gestionnaire tient un "registre" de toutes les textures chargées... Nous verrons comment coder ça.

On peut (doit même), utiliser un gestionnaire pour tous les types de ressources utilisés, telles les textures, les sons, les polices de caractères, etc... Le principe est le même, peu de choses changent d'un gestionnaire à l'autre.

Un template de manager

Tout d'abord nous allons voir l'interface générale d'un manager.

Le code présenté utilise un template, ce qui signifie que nous pourrons l'utiliser pour chaque type de ressource que nous voudrons.
En effet, un template permet de créer une classe (ou une fonction) qui contient certaines variables de type indéterminé, qu'on précisera plus tard.
De plus, la classe présentée ici est abstraite, c'est à dire qu'on ne peut pas l'instancier pour le moment. La raison ? Elle contient des fonctions qu'on a définies (on en a donné le prototype), mais qu'on a pas encore codées (leur implémentation est inexistante).
Du coup, pour faire nos managers, nous allons dériver cette classe avec de nouvelles, qui elles implémenteront les méthodes non définies.

Revenons au manager : son but est de gérer toutes les ressources d'un type donné. (Jusque là rien de nouveau. ^^ )
Pour ce faire, on utilise une std::map en attribut (L.17), qui va conserver un pointeur vers chaque ressource de ce type, et qui lui associera une std::string. Celle-ci contiendra le chemin de fichier de la ressource, ce qui nous permettra de l'identifier.

De cette manière, lorsqu'on va vouloir charger une ressource, on fera appel à une méthode getItem (L.12) en passant le chemin du fichier à charger en paramètre.
getItem n'a qu'a parcourir la map pour vérifier si on à déjà ce chemin quelque part. Si c'est le cas, on renvoie la ressource associée ; sinon on charge le fichier, on ajoute ce nouvel élément à la map et on renvoie la ressource.
De cette manière, on est sûr de ne jamais charger 2 fois la même ressource. :)

Comme indiqué ligne 8, le rôle du destructeur va être de libérer toutes les ressources et de détruire la map pour libérer toute la mémoire.
Il n'a pas de définition pour le moment car la manière de libérer les ressources dépend justement du type de ressource.

/* 01 */    template <typename T>
/* 02 */    class IManager
/* 03 */    {
/* 04 */    public :
/* 05 */        // Le constructeur par défaut.
/* 06 */        IManager()
/* 07 */        {}
/* 08 */        // Le destructeur supprime automatiquement tous les items.
/* 09 */        virtual ~IManager()
/* 10 */        {}
/* 11 */        // Permet de récupérer un item à partir d'un fichier. Methode abstraite, a redefinir dans les classes enfants.
/* 12 */        virtual T* getItem(const std::string &filename)=0;
/* 13 */
/* 14 */    protected :
/* 15 */        // La liste des items en stock.
/* 16 */        std::map<std::string, T*> m_liste;
/* 17 */    };

Gestionnaire de sons : interface

Bien, voyons maintenant un exemple de spécialisation : un gestionnaire de sons.

Nous allons utiliser la bibliothèque SDL_mixer, les sons seront donc contenus dans des variables de type Mix_Chunk comme vous pouvez le voir lignes 1, 9 et 13.

La première nouveauté se situe ligne 13, il s'agit d'un itérateur qui va nous permettre de parcourir la map contenant les ressource. On aurait très bien put le construire uniquement au moment où il est utile , mais il est plus pratique d'en avoir un sous la main en permanence.

Juste au dessus nous parlions de dériver la classe template, et bien c'est chose faîte !
Si vous jetez un oeil ligne 1, vous verrez que notre manager est un enfant de la classe template.

Pour faire un manager de surfaces utilisant la SDL par exemple, il aurait fallut remplacer Mix_Chunk par SDL_Surface. (C'est exactement ce qui est fait dans le KGG)

A part ça rien de bien nouveau. Le plus dur se situe dans la définition des méthodes. ;)

/* 01 */    class CSoundManager : public IManager<Mix_Chunk>
/* 02 */    {
/* 03 */    public :
/* 04 */        // Le constructeur par défaut.
/* 05 */        CSoundManager();
/* 06 */        // Le destructeur supprime automatiquement tous les sons.
/* 07 */        ~CSoundManager();
/* 08 */        // Charge un son.
/* 09 */        Mix_Chunk* getItem(const std::string &filename);
/* 10 */
/* 11 */    private:
/* 12 */        // L'iterateur qui va permettre de parcourir la map.
/* 13 */        std::map<std::string, Mix_Chunk*>::iterator m_listeIter;
/* 14 */    };

Gestionnaire de sons : implémentation

C'est maintenant qu'on commence à s'amuser. =D

Nous pensons que le constructeur se passe de commentaires. ^^

Le destructeur (L.5) est déjà un peu plus intéressant.
Je rappelle que son but est de libérer toutes les ressources allouées et de détruire la map.
Et c'est exactement ce qui se passe : la boucle de la ligne 8 se sert de l'itérateur pour parcourir toute la map, et les ressources (2eme partie de l'élément de la map) sont libérées ligne 10 (on se sert de l'itérateur pour accéder à la 2eme partie de l'élément).
Et enfin on supprime l'élément de la map à la ligne 12. C'est à dire qu'on efface carrément le pointeur qui pointait vers la ressource de la map.

Voyons maintenant la méthode getItem (L.17), qui permet de charger une ressource en vérifiant qu'elle n'est pas déjà en mémoire.
La première chose à faire est de comparer le chemin du fichier passé en paramètre avec ceux déjà en stock.
On utilise pour cela la méthode std::map::find qui renvoie l'itérateur correspondant à l'élément recherché s'il existe, ou un itérateur sur la fin de la map s'il n'existe pas. (L.20)
Si l'élément existe déjà, pas de problème on renvoie la ressource. (L.21)

Si l'élément n'existe pas, nous allons devoir le créer, et en l'occurrence en chargeant le son (L.25), puis l'ajouter à la map, et enfin la retourner.

/* 01 */    CSoundManager::CSoundManager()
/* 02 */    {}
/* 03 */
/* 04 */
/* 05 */    CSoundManager::~CSoundManager()
/* 06 */    {
/* 07 */        //On passe en revue tous les sons.
/* 08 */        for(m_listeIter = m_liste.begin(); m_listeIter  != m_liste.end(); m_listeIter++)
/* 09 */        {
/* 10 */            //On libere le son et on supprime l'element de la map.
/* 11 */            Mix_FreeChunk(m_listeIter->second);
/* 12 */            m_liste.erase(m_listeIter);
/* 13 */        }
/* 14 */    }
/* 15 */
/* 16 */
/* 17 */    Mix_Chunk* CSoundManager::getItem(const std::string &filename)
/* 18 */    {
/* 19 */        //On parcourt toute la liste pour savoir si le son est déjà stocké.
/* 20 */        if ((m_listeIter = m_liste.find(filename)) != m_liste.end())
/* 21 */            return m_listeIter->second;
/* 22 */        //Si ce n'est pas le cas on tente de charger le fichier.
/* 23 */        else
/* 24 */        {
/* 25 */            m_liste[filename] = Mix_LoadWAV(filename.c_str());
/* 26 */            //Si le chargement du fichier a échoué
/* 27 */            if (m_liste[filename] == NULL)
/* 28 */                CInternalLogger::getInstance()->logToFile(filename + " could not be loaded");
/* 29 */            //On renvoie la ressource
/* 30 */            return m_liste[filename];
/* 31 */        }
/* 32 */    }

Conclusion

Voilà, vous avez donc construit un manager générique, qui va vous permettre de créer toutes sortes de gestionnaires très facilement par la suite.
Pour bien comprendre, nous avons même implémenté un gestionnaire de sons à base de SDL_Mixer. Pour vous entraîner, vous pouvez faire un gestionnaire de textures (d'images, en fait), un de polices de caractères, et un de musiques.

Ainsi, à aucun moment vos ressources ne seront chargées en doubles, mais en plus toute la mémoire sera libérée automatiquement (si vous pensez à détruire vos gestionnaires). C'est pas la belle vie ça ? :)
Par contre, il faut absolument éviter d'instancier deux fois le même manager. Pour ça, nous vous recommandons de jetter un oeil aux singletons.


Valid XHTML 1.0 Strict - Le Sanglier Codeur, par GuilOooo & Kevin Leonhart - Remonter en haut - Valid CSS !