Ponto V!

Home Arquitetura Programação Gerenciando Recursos
Bruno Crivelari Sanches
Gerenciando RecursosImprimir
Escrito por Bruno Crivelari Sanches
Índice do Artigo
Gerenciando Recursos
Implementação classe RefCounter
Classe Resource
Classe TextureResource
Classe ResourceManager
O Programa Exemplo
Considerações Finais e Código Fonte
Todas Páginas

O gerenciamento de recursos é um dos pontos chave para o bom funcionamento de um jogo. Neste artigo veremos uma forma de construir um gerenciador para texturas (usando SDL) que garante que cada imagem seja carregada uma única vez na memória (mesmo se usada por vários objetos). Apesar do exemplo ser baseado em SDL e gerenciamento de texturas, a técnica pode ser aplicada para qualquer framework e para qualquer tipo de recurso sem grandes dificuldades.

Conhecimentos Necessários

Este artigo assume que o leitor já possui conhecimento prévio no uso de Smart Pointers, em especial nos Intrusive Pointers da Boost, de contêineres intrusivos e do uso básico da SDL.

A escolha dos smart pointers e dos contêineres intrusivos foi para poder ilustrar o uso destes na prática, a SDL foi escolhida devido a simplicidade de uso (tanto na instalação quanto na programação), permitindo assim focar o artigo no gerenciamento dos recursos e não em detalhes de API.

Decisões de Design

O código exemplo construído para este artigo foi feito para fins didáticos, sendo que para tal não resolve todos os problemas relacionados a gerenciamento de recursos e apresenta um solução básica para o problema, que dependendo do caso pode precisar de muito mais funcionalidades, como suporte a multi-thread, carga em background, suporte a cache, etc.

Recursos

Vamos primeiramente definir o que são recursos no contexto deste artigo: É considerado um recurso todo o tipo de dado que que o jogo utiliza e não modifica, os exemplos clássicos são:

  • Texturas
  • Sons
  • Vídeos
  • Musicas
  • Modelos 3d

Nem todo jogo faz uso destes e existem jogos que podem vir adicionar muito mais itens a esta lista. O principal fato sobre um recurso é que este durante sua vida não sofre modificações, caso venha a sofrer, todos os objetos que compartilham um mesmo recurso vão ser afetados.

Cada recurso possui uma identidade única e no nosso artigo vamos considerar como identidade do recurso o nome do seu arquivo e caminho do mesmo, alguns exemplos:

  • data\textures\porta_madeira\porta.bmp
  • data\textures\porta_metal\porta.bmp
  • data\textures\monstro\pele_diabo.bmp

No exemplo acima temos três recursos, note que os dois primeiros possuem o mesmo nome de arquivo, mas como estão em diretórios diferentes são considerados recursos diferentes, caso os arquivos sejam os mesmos, cabe ao desenvolvedor referenciar os mesmos arquivos e não permitir que cópias sejam criadas na estrutura do jogo.

Visão Geral

Abaixo temos o diagrama de classes do sistema que vai nos ajudar a visualizar a construção do mesmo:

uml

As classes do sistema são listadas a seguir:

  • RefCounter: esta classe é utilizada para fazer o controle de referências do intrusive_ptr, usamos uma classe base para que não seja preciso implementar a contagem em todas as classes.
  • Resource: classe base de todo recurso que o sistema vai gerenciar, no nosso exemplo vamos implementar apenas a TextureResource.
  • TextureResource: esta classe gerencia uma única textura (ou SDL_Surface), para cada textura que o jogo precisa uma instância desta é criada.
  • SoundResource: apenas para ilustrar a flexibilidade do sistema, esta classe pode ser implementada para gerenciar sons.
  • ResourceManager: este é o gerenciador do recursos, ele mantém internamente uma lista dos recursos carregados e toda a carga de recursos deve ser feita através deste. Note que é um singleton.
  • Sprite: esta classe foi construída apenas para demonstrar como o sistema pode ser utlizado, ela representa um sprite em um jogo 2d e sempre que é criada requisita ao gerenciador de recursos uma textura para representar o sprite.

Agora que já temos uma idéia melhor do que será construído, mãos as obra!


Classe RefCounter

Como foi explicado anteriormente, o sistema utiliza intrusive_ptr sempre que possível e para simplificar o uso do mesmo criamos uma classe base que cuida da contagem de referências, assim sempre que alguma classe precisar ser gerenciada por um intrusive_ptr ela pode herdar desta classe e problema resolvido.

Vamos ver então a declaração desta classe (arquivo RefCounter.h):

#include <boost/detail/atomic_count.hpp>
#include <boost/utility.hpp>

#include "RefCounterFwd.h"

namespace pontov
{
    /**
        Esta classe representa um contador de referências a ser usado com 
        smart pointers intrusivos.

    */
    class RefCounter_c: public boost::noncopyable
    {        
        protected:
            RefCounter_c();
            virtual ~RefCounter_c() {};

        private:                        
            void AddRef();
            void ReleaseRef();

            //Funces chamadas pelo intrusive_ptr para controlar as referencias
            friend inline void boost::intrusive_ptr_add_ref(RefCounter_c *);
            friend inline void boost::intrusive_ptr_release(RefCounter_c *);

        private:            
            //Usamos atomic_count na esperança de deixar o código thread safe
            //Nao testado, use com threads por sua conta e risco
            boost::detail::atomic_count tCount;
    };
}

namespace boost
{
    //Aqui temos as funcoes que o intrusive ptr invoca para controle de referencias
    inline void intrusive_ptr_add_ref(pontov::RefCounter_c *p)
    {
        p->AddRef();
    }

    inline void intrusive_ptr_release(pontov::RefCounter_c *p)
    {
        p->ReleaseRef();
    }
}

Comecemos pelos includes, o primeiro é o de utilidades da Boost, onde usamos o boost::noncopyable, que faz com que a classe não seja copiável, assim evitamos que um RefCounter seja copiado (ou um recurso duplicado).

Em seguida temos o “RefCounterFwd.h”, nesse arquivos temos as declarações do próprio RefCounter, este arquivo foi criado apenas para deixar o header do RefCounter mais simples.

Finalmente chegamos na classe RefCounter_c, note que ela herda de boost::noncopyable e não possui declaração alguma na parte publica (inclusive o construtor e destrutor são protegidos), isto é para deixar claro que a classe não é para ser instanciada diretamente, mas apenas herdada.

Na parte privada temos AddRef e ReleaseRef, que incrementam e decrementam o contador de referências respectivamente. Um detalhe é que ReleaseRef checa se a contagem chegou a zero, caso sim, destrói o objeto (cometendo um suicídio).

Em seguida temos duas funções amigas, que são as funções que o boost::intrusive_ptr utliza para incrementar e decrementar a contagem de referências. Declaramos estas como amigas pois elas precisam acessar os métodos AddRef e ReleaseRef, que desejamos que sejam privados para evitar que sejam chamados indevidamente em outro contexto.

Por fim temos o contador de referências, que utliza a classe atomic_count, que garante que o valor vai ser alterado de forma atômica e não teremos problemas com threads (não testado, use com threads por sua conta e risco).

No final do arquivo definimos dentro do namespace da Boost as funções intrusive_ptr_add_ref e intrusive_ptr_release, que são chamadas pelo intrusive_ptr para controlar as referências do objeto, note que ambas apenas chamam AddRef e ReleaseRef, respectivamente.

Agora vamos ver a implementação da classe no arquivo RefCounter.cpp:

#include "RefCounter.h"

namespace pontov
{
    RefCounter_c::RefCounter_c():
        tCount(0)
    {
    }
        
    void RefCounter_c::AddRef()
    {
        ++tCount;
    }

    void RefCounter_c::ReleaseRef()
    {
        //Reduzimos o contador
        //Note que jogamos o valor em um temporario, pois como tCount pode ser modificado por threads
        //confiamos apenas no retorno de --
        long count = --tCount;

        //ultima referencia? Entao suicidio!
        if(count == 0)
            delete this;
    }    

}

A implementação no fundo é mais simples que declaração, temos um construtor simples que inicializa o contador de referências com zero, depois temos a implementação de AddRef e ReleaseRef.

O método AddRef não possui nada demais, apenas incrementa o contador. Já a implementação de ReleaseRef possui um pequeno truque, como utilizamos um contador “atômico”, não podemos confiar no seu valor, pois ele pode estar sendo alterado em múltiplas threads, por isso quando chamamos “- -“ armazenamos o resultado em uma variável temporária e checamos apenas o valor desta para ver se temos zero referências. Se chegamos a zero isso indica que não temos mais nenhum intrusive_ptr acessando este objeto, então podemos destruí-lo, fazemos isso cometendo um suicídio (delete this).


Classe Resource

Agora vamos criar a classe Resource (recurso) que representa um recurso da aplicação. Ela armazena apenas o nome do recurso e possui os requisitos necessários para ser armazenada no Gerenciador de Recursos, vamos dar uma olhada no código (Resource.h):

#include "RefCounter.h"

#include <boost/intrusive/set.hpp>

namespace pontov
{
    typedef boost::intrusive::set_base_hook<boost::intrusive::link_mode<boost::intrusive::auto_unlink> > ResourceAutoUnlinkHook_t;

    /**
        Essa classe é a base para armazenar um recurso da aplicacao.

        Recursos sao controlados pelo nome do arquivo (incluindo caminho) do qual representam

    */
    class Resource_c: public RefCounter_c, public ResourceAutoUnlinkHook_t
    {
        public:            
            inline bool operator<(const Resource_c &rhs) const
            {
                return strName.compare(rhs.strName) < 0;    
            }

            inline std::string GetName() const
            {
                return strName;
            }

        protected:
            Resource_c(const std::string &name);

        private:
            std::string strName;
    };
}

#endif

Esta classe precisa de dois includes também, o primeiro é nossa classe que conta referências e em seguida temos a classe boost::intrusive::set (lembra dos contêineres intrusivos?). O que tem que ficar claro é que a classe Resource não possui contêineres intrusivos, mas ela vai ser armazenada em um, então por isso precisamos definir o hook que ela vai usar. Onde e porquê ela vai ser armazenada, veremos na implementação do gerenciador de recursos.

Em seguida já no namespace do pontov temos a declaração do hook que vamos utilizar, este é um hook para um set e possui a propriedade de auto_unlink, ou seja, quando uma instância de Resource for destruída, ela automaticamente é removida do contêiner que a contém.

Finalmente chegamos na declaração da classe Resource, que herda de RefCounter e do hook que definimos acima. Na sequência temos a definição do operador “<” que é utilizado para compararmos recursos (este operador é utilizado pelo set durante as buscas e inserções). Como havíamos discutido anteriormente, a única coisa que difere um recurso do outro é o nome, por esta razão o operador apenas compara o nome de ambos os recursos.

Temos então o método GetName (que acredito não precisa de explicações) e na sequência a parte protegida da classe, onde declaramos o construtor da mesma, este cuida apenas de inicializar o nome do recurso. Note que não existe forma de mudar o nome de um recurso após este ser criado, isso foi intencional pois não existe motivo para que isto ocorra e não queremos que isso seja feito por acidente.

Por fim temos a declaração do único atributo do Resource, seu nome (strName).

A implementação da classe Resource é bem simples e podemos conferir isso no código abaixo:

#include "Resource.h"

namespace pontov
{
    Resource_c::Resource_c(const std::string &name):
        strName(name)
    {
        //empty
    }    
}

Classe TextureResource

Agora vamos ver como um recurso deve ser implementado, a classe TextureResource possui uma única SDL_Surface que vai conter a nossa textura, a declaração da classe pode ser vista abaixo:

#include <SDL.h>

#include "Resource.h"

namespace pontov
{
    typedef boost::intrusive_ptr<class TextureResource_c> TextureResourcePtr_t;

    class TextureResource_c: public Resource_c
    {
        public:
            TextureResource_c(const std::string &name);            

            void Blit(SDL_Surface &dest, int x, int y) const;

        private:
            ~TextureResource_c();

        private:
            SDL_Surface *pSurface;
    };
}

Temos primeiramente um typedef onde definimos o TextureResourcePtr_t, que é apenas para simplificar a criação de intrusive_ptr específicos para esta classe.

Depois vem a declaração da classe, como esperado ela herda de Resource e implementa o único construtor da classe (note que finalmente temos um construtor publico) que recebe como parâmetro o nome da textura a ser criada).

Depois temos o método Blit, este método não faz parte da lógica de gerenciamento de recursos, foi criado apenas para a implementação do exemplo, sua tarefa consiste em fazer um blit da textura na surface passada como parâmetro.

Agora vamos dar uma olhada no destrutor, note que este é privado, assim evitamos que a classe seja destruída diretamente (lembre-se, ela vai ser destruída apenas pelo método ReleaseRef da classe RefCounter).

Esta classe possui um único atributo que é a SDL_Surface que armazena a imagem da textura.

Agora vamos ver a implementação desta classe:

#include "TextureResource.h"

#include <exception>
#include <sstream>

using namespace std;

namespace pontov
{
    TextureResource_c::TextureResource_c(const std::string &name):
        Resource_c(name)
    {
        //carregando a surface
        pSurface = SDL_LoadBMP(this->GetName().c_str());
        if(pSurface == NULL)
        {
            stringstream stream;
            stream << "Cant load " + name;
            throw exception(stream.str().c_str());
        }
    }

    TextureResource_c::~TextureResource_c()
    {
        SDL_FreeSurface(pSurface);
    }

    void TextureResource_c::Blit(SDL_Surface &dest, int x, int y) const
    {
        SDL_Rect rect;

        rect.x = x;
        rect.y = y;
        rect.w = pSurface->w;
        rect.h = pSurface->h;

        SDL_BlitSurface(pSurface, NULL, &dest, &rect);        
    }
}

Primeiramente temos o construtor, que cuida de passar para o construtor da classe Resource o nome do novo recurso, na sequência chamamos SDL_LoadBMP para carregar a textura (utilizando o nome do recurso que deve conter o caminho e o nome do arquivo da textura). Depois é verificado se a surface foi criada ou não, caso não tenha sido criada jogamos uma exceção avisando do erro (não vamos permitir recursos não inicializados).

A seguir vem o destrutor, que simplesmente libera a surface, logo abaixo vemos o método de Blit que como explicado anteriormente, apenas faz o blit de uma surface para a outra.


Classe ResourceManager

Finalmente vamos ver como gerenciar os recursos, para tal temos a classe ResourceManager, que mantém uma lista de cada tipo de recurso, na nossa implementação vamos criar apenas a lista de texturas.

Outras implementações podem vir a criar um gerenciador para cada tipo de recurso (muitas vezes utilizando templates), mas aqui para fins ilustrativos vamos utilizar um único gerenciador, que pode ser visto abaixo:

#include <string>

#include <boost/intrusive/set.hpp>

#include "TextureResource.h"

namespace pontov
{
    class ResourceManager_c
    {
        public:
            static ResourceManager_c &GetInstance();

            TextureResourcePtr_t LoadTexture(const std::string &name);

        private:
            static ResourceManager_c clManager_gl;

            typedef boost::intrusive::set<TextureResource_c, boost::intrusive::constant_time_size<false> > TexturesSet_t;
            TexturesSet_t setTextures;
    };
}

O gerenciador é um singleton, por isso possui o método GetInstance que é utilizado para acessar a única instância do gerenciador, mas o importante mesmo é o método LoadTexture. Este método é chamado toda vez que a aplicação precisa de uma determinada textura, a aplicação fornece o nome da textura e recebe de volta uma instância da classe TextureResource (encapsulada dentro de um intrusive_ptr).

Já na parte privada da classe temos a declaração do gerenciador de recursos (para implementar o singleton) e logo abaixo o contêiner set que vai armazenar as texturas que já foram carregadas.

Podemos então começar a visualizar o que o gerenciador faz: ele mantém uma lista de todas as texturas carregadas, aqui é possível ver uma vantagem de usarmos o contêiner intrusivo, pois toda vez que uma textura for destruída, ela automaticamente é removida da lista de texturas carregadas, assim o nosso gerenciador sempre vai possuir uma lista de texturas validas e em uso.

Abaixo podemos ver a implementação do ResourceManager e o gerenciamento da lista de texturas:

#include "ResourceManager.h"

namespace pontov
{
    ResourceManager_c ResourceManager_c::clManager_gl;

    ResourceManager_c &ResourceManager_c::GetInstance()
    {
        return clManager_gl;
    }

    /**
        Para que nao seja preciso instanciar um recurso apenas para fazer a busca no conteiner
        usamos um "comparador" especifico para strings

    */
    struct ResourceComp_s
    {
        bool operator()(const std::string &name, const Resource_c &res) const
        {
            return name.compare(res.GetName()) < 0;    
        }

        bool operator()(const Resource_c &res, const std::string &name) const
        {
            return res.GetName().compare(name) < 0;
        }
    };


    TextureResourcePtr_t ResourceManager_c::LoadTexture(const std::string &name)
    {        
        //pegando o recurso no set
        TexturesSet_t::iterator it(setTextures.find(name, ResourceComp_s()));

        //recurso nao existe?
        if(it == setTextures.end())
        {
            //criamos um novo e inserimos no set
            TextureResourcePtr_t newTexture = new TextureResource_c(name);
            setTextures.insert(*newTexture);

            return newTexture;
        }

        //recurso ja carregado, entao devolvemos o ja existente
        return TextureResourcePtr_t(&(*it));
    }
}

No código acima, primeiro temos a definição da instância do singleton e o método GetInstance, que fazem apenas o trabalho comum de um singleton.

Em seguida temos uma estrutura, chamada de ResourceComp, esta estrutura é utilizada para fazermos buscas na lista de texturas (ou de qualquer outro recurso que venha a ser criado no futuro) utilizando o nome do recurso, sem esta estrutura seria preciso criar uma instância do recurso para buscarmos por este mesmo recurso, que arruinaria nosso sistema, pois o objetivo é justamente evitar a criação de recursos que já existem. Na estrutura possuímos dois operadores de função, o primeiro compara uma string com um recurso, o segundo compara um recurso com uma string, ambos fazem a mesma coisa, mas comparam em ordem diferente.

Agora vamos dar uma olhada no método LoadTexture, este método inicialmente faz um busca no set de texturas utilizando o nome passado como parâmetro e a estrutura ResourceComp que foi definida logo acima. Na sequência é verificado se o iterador retornado pelo find é valido, caso não seja tudo indica que a textura não foi carregada ainda (ou caso tenha sido, mas foi destruída). O código então cuida de criar uma nova textura e inserir ela na lista de texturas carregadas, depois retorna a textura recém criada.

Caso a textura já exista na lista (ou seja, o iterator retornado pelo find é valido) vamos simplesmente retornar ela, note que criamos um novo TextureResourcePtr_t, assim garantimos que o contador de referências seja incrementado. Este ponto do código mostra de maneira bem sutil o porquê do intrusive_ptr ter sido escolhido ao invés de um shared_ptr (ignorando aqui a questão de ganhos e perdas de como cada um cuida da contagem de referências). Se um shared_ptr fosse utilizado, o gerenciador teria que manter um weak_ptr, realizar locks, etc, isso pesou bastante na decisão e por fim foi decidido utilizar o intrusive_ptr.

Agora que já temos o sistema funcionando, vamos ver como utilizar ele.


O Programa Exemplo

O programa exemplo foi baseado no exemplo do artigo Animação baseada em Tempo, este programa possui duas texturas diferentes, um carro verde e outro azul e toda vez que a barra de espaços é pressioada um novo carrinho é criado (podendo ser verde ou azul, é aleatório). O carrinho percorre a tela e quando sai da mesma ele é destruído. Quando todos os carrinhos de uma mesma cor deixam a tela a textura é finalmente descarregada da memória (de maneira automática).

Para implementar essa lógica foi criada uma classe chamada Sprite, que representa um carrinho e o código do programa principal original foi modificado para utilizar esta nova classe.

Classe Sprite

A classe Sprite quando é instanciada recebe como parâmetro o nome do sprite (textura) que ela deve carregar, ela então requisita essa textura ao gerenciador de recursos e armazena um ponteiro para a mesma. Vamos ver como fica a declaração dela:

#include <string>

#include "TextureResource.h"
#include "RefCounter.h"

namespace pontov
{
    class Sprite_c;

    typedef boost::intrusive_ptr<Sprite_c> SpritePtr_t;

    class Sprite_c: public RefCounter_c
    {
        public:
            Sprite_c(const std::string &name, float startX, float startY);

            inline float GetPosX()
            {
                return fPosX;
            }

            inline void Update(SDL_Surface &dest, Uint32 ticks)
            {
                fPosX += (float)(ticks * 200 / 1000.0);

                ipTexture->Blit(dest, (int)fPosX, (int)fPosY);
            }

        private:
            TextureResourcePtr_t ipTexture;
            float fPosX, fPosY;
    };
}

#endif

Uma das primeiras coisas que temos é a definição de SpritePtr_t, que é o intrusive_ptr usado para “guardar” uma instancia da classe Sprite, ou seja, assim como instâncias de Resource ela é gerenciada por contagem de referências. Um item que deve ficar claro é que não é preciso que a classe Sprite seja gerenciada por smart pointers ou via contagem de referências para usar o sistema, ela é implementada assim apenas para re-utilizarmos o código já criado e cuidarmos da memória de uma maneira mais elegante (e pratica).

Na sequência temos o construtor, que recebe o nome da textura e as posições iniciais do carrinho, um método para obtermos a posição atual do carrinho e o método Update, que atualiza a posição do carrinho e o desenha na surface de destino.

Na parte privada da classe temos o ponteiro para a textura e um par de floats que armazenam a posição do carrinho.

A implementação do carrinho é relativamente simples e temos apenas o construtor, que cuida de carregar a textura e inicializar as coordenadas do carrinho:

#include "Sprite.h"

#include "ResourceManager.h"

namespace pontov
{
    Sprite_c::Sprite_c(const std::string &name, float startX, float startY):
        ipTexture(ResourceManager_c::GetInstance().LoadTexture(name)),
        fPosX(startX), 
        fPosY(startY)
    {
        //empty
    }
}

Programa Principal

Este programa é bem próximo ao programa usado no artigo Animação Baseada em Tempo, a maior modificação foi adicionar um loop principal e gerenciamento de eventos, vamos ver primeiramente o código do loop principal:

static void MainLoop()
{
    using namespace std;
    using namespace pontov;    

    //lista dos sprites ativos
    typedef list<SpritePtr_t> SpriteList_t;
    SpriteList_t sprites;
    
    Uint32 last=SDL_GetTicks();

    bool exit = false;
    while(!exit)
    {             
        Uint32 current=SDL_GetTicks();        
        Uint32 elapsed=current-last;                    
        if(elapsed<10)
            continue;

        last = current;

        SDL_Event ev;

        //checando input
        while ( SDL_PollEvent(&ev) ) 
        {
            switch(ev.type)
            {
                case SDL_QUIT:
                    exit = true;
                    break;
                
                case SDL_KEYDOWN:
                    //ESC?
                    if(ev.key.keysym.sym == SDLK_ESCAPE)
                        exit = true;
                    //criar novo sprite?
                    else if(ev.key.keysym.sym == SDLK_SPACE)
                    {
                        //instanciamos o sprite e escolhemos aleatoriamente uma textura
                        SpritePtr_t ptr(new Sprite_c(rand() % 2 ? "carro_verde.bmp" : "carro_azul.bmp", 0, 0));
                        sprites.push_back(ptr);
                    }
                    break;
            }
        }

        // Limpa a tela
        SDL_FillRect(pScreen_gl, NULL, SDL_MapRGB(pScreen_gl->format, 255, 255, 255));

        //desenhamos os sprites
        for(SpriteList_t::iterator it = sprites.begin(), end = sprites.end(); it != end; ++it)
        {
            SpritePtr_t ptr(*it);

            //atualizando e desenhando
            ptr->Update(*pScreen_gl, elapsed);

            //saiu da tela?
            if(ptr->GetPosX() > WIDTH)
            {
                //removendo ele
                it = sprites.erase(it);

                //se estamos no fin do conteiner, paramos
                //Se nao fizermos essa verificacao o for pode incrementar, end, que dispara assertions em alguns compiladores
                if(it == end)
                    break;
            }
        }
                
        //Atualiza tela
        SDL_UpdateRect(pScreen_gl, 0, 0, 0, 0);
    }
}

O inicio do loop faz apenas o controle de tempo que é explicado em detalhes no artigo Animação Baseada em Tempo. O que temos de novo é a declaração SpriteList_t que é usada para se criar uma std::list com as instancias da classe Sprite que estão ativas.

Logo após temos o loop principal, que cuida primeiramente dos eventos da SDL, o evento mais relevante pare este artigo é o SDL_KEYDOWN, que verifica se “espaço” foi pressionado, caso sim, é criada uma nova instância da classe Sprite que é inserida na lista de sprites ativos.

Após processar os eventos o loop limpa a tela e depois percorre todos os sprites. Para cada sprite primeiramente é chamado o método Update, que como já vimos cuida de desenhar e atualizar a posição do sprite, depois é checado se o sprite saiu ou não da tela, caso ele já esteja fora ele é removido da lista, quando isto o ocorre o único intrusive_ptr que referenciava o sprite é destruído, este é o ponto onde toda a “magica” do nosso sistema ocorre: isso gera uma reação em cadeia (de chamadas de destrutores) que causa a destruição do Sprite, que vai invocar o destrutor do ponteiro intrusivo da textura, este decrementa o contador de referências da textura e caso seja preciso destrói a textura (se o contador chegou a zero), se a textura for destruída, vai cuidar de se auto-remover da lista do gerenciador de recursos.

Por fim o loop principal atualiza a tela e começa tudo de novo…

Agora vamos dar uma olhada na função main que cuida apenas de inicializar a SDL e invocar o loop principal:

int main(int argc, char *argv[])
{
    // Inicializa a SDL
    if ( SDL_Init(SDL_INIT_VIDEO) < 0 ) {
        fprintf(stderr, "Nao consegui inicializar a SDL: %s\n", SDL_GetError());
        exit(1);
    }    
    atexit(SDL_Quit);

    pScreen_gl = SDL_SetVideoMode(WIDTH, HEIGHT, 32, SDL_SWSURFACE);
    if ( pScreen_gl == NULL ) 
    {
        fprintf(stderr, "Falhou inicializacao do video: %s\n", SDL_GetError());
        exit(1);
    }        

    MainLoop();

    exit(0);

    return 0; 
}

Considerações Finais

Neste artigo vimos como podemos usar recursos modernos do C++ e da Boost para gerenciar recursos de um jogo, permitindo assim que um jogo evite manter varias instâncias de um mesmo recurso na memória sem que haja necessidade. Note também que este também é um exemplo da implementação do padrão de projeto Fly Weight.

O sistema aqui mostrado pode evoluir bastante, podemos por exemplo fazer um sistema de cache, de pré cache, etc. Mas estes são tópicos para um outro artigo.

Código Fonte

O código fonte deste artigo pode ser obtido no repositório de código do PontoV via SVN ou baixando o pacote com o código e um executável pronto para Windows clicando-se aqui. O código foi testado apenas em Visual Studio, por favor reporte qualquer problema que venha ocorrer ao se usar outro compilador (no pacote e no SVN estão incluídos os arquivos de projeto do Visual Studio).

Para rodar o programa a partir do Visual é preciso configurar o mesmo para executar a partir do diretório “bin” (onde estão incluídas as imagens), é necessário que a SDL esteja instalada e configurada para o seu compilador, assim como a Boost.


Comentários (2)
  • Bruno Romano
    avatar

    Meus parabéns muito bom o exemplo, ilustra diversas coisas, inclusive exemplos bem práticos de assuntos ja revisados e explicados por vocês. Muito bom este site, tenho visitado sempre procurando aprender mais, alem das técnicas de jogos aprender mais sobre boost, templates, e técnicas de programação.

    Mas tenho uma sugestão dúvida a fazer.
    no método:
    void RefCounter_c::ReleaseRef()
    {
    //Reduzimos o contador
    //Note que jogamos o valor em um temporario, pois como tCount pode ser modificado por threads
    //confiamos apenas no retorno de --
    long count = --tCount;

    //ultima referencia? Entao suicidio!
    if(count == 0)
    delete this;
    }

    não pode criar um método virtual que faz o delete this ? e chamar ele quando for deletar o objeto.
    Assim caso alguem queira que esse objeto faça algo antes de morrer ele pode sobreescrever esse método. Igual acontece com as exception da MFC que vc chama o método Delete.

  • Bruno Crivelari Sanches
    avatar

    Obrigado Bruno.

    Poder criar o método virtual pode, só não vejo necessidade. Se é preciso fazer algo antes do objeto ser destruído basta fazer no destrutor, ele já é virtual para tratar essa situação.

    T+

Escrever um comentário
Your Contact Details:
Gravatar enabled
Comentário:
[b] [i] [u] [url] [quote] [code] [img]   
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
:X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
Security
Por favor coloque o código anti-spam que você lê na imagem.
LAST_UPDATED2  

Busca

Linguagens

Twitter