SDL++: Porte do SDL para C++

1. SDL++: Porte do SDL para C++

Thiago Henrique Hüpner
Thihup

(usa Manjaro Linux)

Enviado em 05/06/2016 - 09:44h

Olá pessoal.

Eu comecei um novo projeto: fazer o porte do SDL para C++, e gostaria de saber se alguém está disposto a me ajudar.

Os motivos que me levaram a fazer isto:
* Poder usar os Ponteiros Inteligentes, ao invés de usar os Ponteiros "crus"
* Cada objeto terá as funções correspondente do SDL, por exemplo :

//SDL
SDL_RenderClear(renderer);
//SDL++
renderer->rendererClear()


Toda ajuda será bem vinda.

Grato desde já

Thiago


  


2. Re: SDL++: Porte do SDL para C++

M.
XProtoman

(usa Fedora)

Enviado em 06/06/2016 - 15:12h

Boa tarde a todos,

Olá Thihup, parabéns pela iniciativa, tinha visto o seu tópico por celular, mas só tive tempo de responder agora, tenha certeza que vai facilitar a vida de muita gente.

Boa sorte para você, para o projeto e para todos os participantes.




3. Re: SDL++: Porte do SDL para C++

Paulo
paulo1205

(usa Ubuntu)

Enviado em 06/06/2016 - 21:50h

Eu não tenho tempo de participar de um projeto desses, então desejo sucesso de longe. Se tiver dúvidas, traga aqui para o fórum, e -- quem sabe? -- talvez eu possa ajudar assim, mais difusamente.

Permita-me uma primeira sugestão (que pode fazer mais ou menos sentido, a depender do contexto). Você deu como exemplo do que pretende fazer o seguinte código:

//SDL
SDL_RenderClear(renderer);
//SDL++
renderer->rendererClear();


O que eu sugiro é uma mudança um pouco mais profunda na forma de atribuir nomes aos seus tipos e métodos. Se C possui apenas um espaço para todos os nomes e muito pouco acoplamento entre as funções e os dados que elas podem receber como argumentos, e compensa isso por meio de afixos dados aos nomes (por exemplo: uma função chamada SDL_RedererClear() que recebe argumento do tipo struct SDL_Renderer *), C++ possui a possibilidade de compartimentar espaços de nomes e permite ligar funções especificamente a determinados tipo de dados, fazendo-as “métodos” das classes que definem esses dados.

Valendo-se disso, você poderia transformar a linha em C++ acima, deixando-a com a seguinte cara:

renderer->clear(); 


Por sua vez, a declaração de renderer (que você não mostrou nos exemplos acima) poderia ser, no caso em C++, assim:

sdl::renderer *renderer;  // Sem conflito de nomes porque os namespaces são diferentes  


Para tanto, eis como você poderia montar a classe sdl::renderer:

NOTAS SOBRE O CÓDIGO ABAIXO:

(1) Como sdl::renderer tem dependências de outras classes (uma para envelopar SDL_Window e uma para representar exceções), eu dou uma passada muito superficial nessas coisas também, apenas indicando sua existência e fazendo um ou outro comentário -- não entro nos detalhes porque não cabe aqui, e porque cuidar dos detalhes é justamente o que o seu projeto se propõe a fazer. Além disso, eu indico a declaração de várias classes diferentes como se estivessem num arquivo só. Na implementação real, você poderia partir em vários arquivos de cabeçalhos diferentes.

(2) Eu coloco código no próprio cabeçalho, para que ele fique inline (nesse caso, não é nem mesmo necessário usar a palavra-chave inline). Isso é interessante numa biblioteca que é para ser só uma casquinha em cima da implementação em C e que é voltada para aplicações que tipicamente precisam do melhor desempenho possível. Por outro lado, ter código no cabeçalho faz a compilação mais lenta do que seria se houvesse apenas declarações (o que acaba sendo mais um argumento em favor de separar cada classe ou pequeno grupo de classes correlatas em cabeçalhos diferentes).

(3) Eu posso ter feito alguma (ou muita) bobagem neste exemplo, de modo que você pode e deve corrigir meus erros -- até porque tudo nesta postagem é sugestão especulativa.


// th_sdl.h

#include <string>
#include <stdexcept>

#include <SDL2/SDL.h>

namespace sdl { // ou “sdlpp”, ou “th_sdl”, ou o que você preferir.

class exception: public std::runtime_error {
public:
exception(): std::runtime_error(SDL_GetError()) { }
exception(const std::string &msg): std::runtime_error(msg) { }
};

class window { /* ... */ };

/* Outras classes. */

class renderer {
private:
/*
Na verdade, eu gostaria que esta classe fosse uma derivação da estrutura
SDL_Renderer (i.e. “class renderer: public SDL_Renderer { ... };”), mas a libsdl
realmente trata esse tipo como caixa preta, trabalhando apenas com ponteiros
(que não precisam conhecer nenhum detalhe interno) na interface pública com
o usuário. Assim sendo, a única opção é ter um ponteiro desses como
membro de dados da classe.
*/
SDL_Renderer *p_render;

public:
/*
“Conversão” automática para o tipo usado pela libsdl, para permitir seu uso
uso direto como argumento de funções da libsdl. Isso lhe dará um meio de
passar seu objeto diretamente como argumento para funções que você ainda
não tiver convertido ou que não tiver como converter (por exemplo: uma função
implementada por terceiros, fora da própria libsdl, mas usando parâmetros que
são tipos da libsdl). Uma alternativa seria criar um método “get_sdl_ptr()”, mas
acho que isso ficaria mais feio e que não traria ganho nenhum, nem de segurança
nem de desempenho, e muito menos de legibilidade.
*/
operator SDL_Renderer *() const { return p_render; }

/*
sdl::window é outra classe que você vai definir (baseada em SDL_Window).

Note que eu uso sobre ela “conversão” semelhante à que eu mostrei acima.
Se você optar por não fazer a conversão automática, terá de criar um método
get_sdl_ptr() ali também, e usá-lo na chamada nativa a SDL_CreateRenderer().
*/
renderer(const window &wnd, int index=-1, uint32_t flags=0){
p_renderer=SDL_CreateRenderer(wnd, index, flags);
if(!p_renderer)
throw exception();
}

virtual ~renderer(){
SDL_DestroyRenderer(p_renderer);
}

void set_draw_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a){
if(SDL_SetRenderDrawColor(p_renderer, r, g, b, a))
throw exception();
}

void get_draw_color(uint8_t &r, uint8_t &g, uint8_t &b, uint8_t &a){
if(SDL_GetRenderDrawColor(p_renderer, &r, &g, &b, &a))
throw exception();
}

void clear(){
if(SDL_RenderClear(p_renderer))
throw exception();
}

/* ... Outros métodos, mapeando as funções de SDL_Renderer aqui, nos moldes das acima... */

/* Um pouco de criatividade: uma função que seta o fundo com uma cor, mas preserva a cor anterior de desenho. */
void clear(uint8_t r, uint8_t g, uint8_t b, uint8_t a){
uint8_t or, og, ob, oa;
get_draw_color(or, og, ob, oa);
set_draw_color(r, g, b, a);
clear();
set_draw_color(or, og, ob, oa);
}
};


Um problema de ter, agora, um monte de exceções voando pelo código é que pode ser que algum recurso importante alocado pela SDL fique sem ser devidamente devolvido ao sistema se o usuário esquecer de capturar uma dessas exceções.

Corriqueiramente, aplicações com SDL costumam usar atexit() do C para implicar uma chamada a SDL_Quit() antes de a aplicação terminar, mas as funções registradas dessa maneira não são executadas em caso de chamada a abort() do C nem a std::terminate(), que são usadas quando se dispara uma exceção que não é capturada.

Uma abordagem típica de frameworks para aplicações C++ é você definir uma classe para representar a aplicação, e supor (ou induzir a) que todas as aplicações com esse framework derivem dessa classe. No seu caso, isso poderia ficar mais ou menos com a seguinte forma.

// th_sdlapp.h

#include <string>
#include <vector>

#include "th_sdl.h"

namespace sdl {

class application {
private:
std::string progname;
std::vector<std::string> args;

public:
application(uint32_t);: application(0, nullptr, flags) { }
application(int, char *const *, uint32_t);
application(const application &) = delete; // Não permite cópia de sdl::application.
virtual ~application();

private:
virtual int exec() = 0; // O usuário terá de derivar desta classe e implementar este método.

public:
virtual int run() final; // Basicamente chama a exec() virtual que você definir, mas com tratamento default de exceções.

std::string getprogname(){ return progname; }
size_t getnargs(){ return args.count(); }
std::string getarg(size_t n){ return args.at(n); }
};


// th_sdlapp.cc

#include <iostream>

#include "th_sdlapp.h"

// Namespace anônimo para um objeto caixa-preta que não vale a pena expor na declaração da classe.
namespace {

unsigned app_obj_initialized=0;

}

sdl::application::application(int argc, char *const *argv, uint32 flags){
if(app_obj_initialized++){
--app_obj_initialized;
throw exception("Only one sdl::application object is allowed pre process.");
}
if(argc<=0 || !argc[0])
progname="Some Great C++ SDL Application"; // Ou vazio, ou outro nome arbitrário qualquer.
if(argc>1){
args.reserve(argc-1);
for(int n=1; n<argc; n++)
args.push_back(argv[n]);
}
if(!SDL_Init(flags))
throw exception();
}

sdl::application::~application(){
SDL_Quit();
--app_obj_initialized;
}

int sdl::application::run(){
try {
return exec();
}
catch(exception &e){
std::cerr << "Exiting due to SDL exception: " << e.what() << '\n';
}
catch(std::exception &e){
std::cerr << "Exiting due to std exception: " e.what() << '\n';
}
catch(...){
std::cerr << "Exiting due to unknown exception.\n";
}
return 1;
}


O fato do método run() ter um tratamento default de exceções não o impede de dar um tratamento mais especializado no método exec() que você há de implementar na classe derivada que representará a aplicação, nem em outras funções que forem invocadas por sua implementação. Os tratadores default são apenas uma salvaguarda para tentar garantir que uma exceção não “vaze” até main(), a fim de garantir que o destrutor da aplicação, que vai desalocar os recursos da SDL, seja efetivamente chamado.

Dito de outra forma, o que se tenta fazer é fazer com que toda a aplicação envelope o uso de recursos da SDL com a técnica de RAII (Resource Acquisition Is Initialization), que é a técnica favorita de gestão de recursos em C++. Ao trazer toda a aquisição de recursos para o construtor e a sua correspondente liberação para o destrutor (de certo modo complicando a implementação da classe), o usuário da classe tem de empreender muito menos esforço (e, consequentemente, fica menos sujeito a cometer erros) para manter tais recursos por conta própria. RAII é o princípio que permeia o projeto da STL (Standard Template Library, que é parte da biblioteca padrão do C++), smart pointers, e também bibliotecas de terceiros voltadas para C++, como Boost, Qt, e muitas outras.

Para usar RAII, e particularmente uma classe derivada da sdl::application apresentada acima, a aplicação tem de tomar alguns cuidados. Um deles é nunca chamar std::exit(), nem tampouco std::abort ou std::terminate, em nenhuma parte do seu código nem em código de terceiros que você vier a usar. A razão é simples: std::exit() tipicamente desvia o fluxo de execução para um ponto posterior ao final da função main(), passando ao largo do código gerado pelo compilador para invocar os destrutores de objetos criados localmente por qualquer função, inclusive a própria main(). Os únicos objetos cujos destrutores são chamados quando se chama std::exit() são aqueles que tenham sido construídos estaticamente, antes da execução de main(). Já std::terminate() e std::abort() normalmente não desconstroem nem mesmo esses objetos estáticos. Assim sendo, você precisa garantir que as únicas possibilidades de você terminar a execução do método exec() que você implementar são por meio de um comando return dentro do corpo do método, ou através do disparo de uma exceção, que pode ser feito por ele mesmo ou por qualquer função por ele chamada, uma vez que o método run() tem tratadores para todas as possibilidades de exceções.

(Você pode até tentar criar algumas caixas pretas para aceitar alguns casos de uso de std::exit() e com uma declaração do objeto da aplicação como global, mas isso será uma hackeada meio suja e trabalhosa. Não acho que valha o esforço -- é mais fácil, e muito mais limpo, adequar-se ao uso de RAII.)

Sabendo tudo isso, eis como a aplicação poderia ser construída.

// my_sdl_app.h
#include "th_sdlapp.h"

class my_sdl_app: public sdl::application {
using application::application; // Herda os construtores de sdl::application.

private:
int exec(); // Esta é a função que você é obrigado a implementar.

private:
// Aqui vão outros dados dados e métodos privados da sua aplicação.

public:
/*
NOTA: Se você tiver usado dados privados acima, não use os construtores
herdados (como indicados no início da declaração da classe) mas sim
os seus próprios construtores e o destrutor!
*/

/*
Outros métodos públicos que de que você possa precisar (o que talvez só
aconteça se você resolver usar o ponteiro “this” como argumento para alguma
função que você empregar).
*/
};


// my_sdl_app.cc
#include "my_sdl_app.h"

/*
Eventualmente você vai querer colocar os construtores (possivelmente tratando
valores de argumentos recebidos do SO) e o destrutor aqui, além
de dados estáticos e outros métodos da sua aplicação.
*/

int my_sdl_app::exec(){
// Aqui é que você implementa o loop de eventos e tratamentos da sua aplicação.
}


// main.cc

#include "my_sdl_app.h"

int main(int argc, char **argv){
my_sdl_app app(argc, argv, SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_AUDIO);
return app.run();
}


Bom, já dei palpite demais. De novo, desejo sucesso ao projeto.


4. Re: SDL++: Porte do SDL para C++

Paulo
paulo1205

(usa Ubuntu)

Enviado em 07/06/2016 - 19:14h

Relendo minha mensagem anterior, identifiquei alguns pontos em que o código podia ser melhorado. Em particular, alterei a declaração da classe base sdl::application e melhorei o texto explicativo que a seguia. Quem tiver se interessado, por favor releia pelo menos essa seção.


5. Re: SDL++: Porte do SDL para C++

Paulo
paulo1205

(usa Ubuntu)

Enviado em 09/06/2016 - 20:12h

Que mal lhe pergunte, o projeto tem algum site, ou algum meio para que se possa acompanhar seu andamento?


6. Re: SDL++: Porte do SDL para C++

Thiago Henrique Hüpner
Thihup

(usa Manjaro Linux)

Enviado em 09/06/2016 - 20:48h

Eu estou avaliando onde hospedarei o projeto e depois direi onde será o site.

Talvez coloque no BitBucket para poder olhar o andamento do projeto.

[]'s

T+

--

Att,

Thiago Henrique Hüpner

http://pastebin.com/ZANutRt4


7. Re: SDL++: Porte do SDL para C++

Thiago Henrique Hüpner
Thihup

(usa Manjaro Linux)

Enviado em 09/06/2016 - 21:39h

Se alguém quiser acompanhar o progresso:

https://bitbucket.org/Thihup/sdl-cat 


[]'s

T+

--

Att,

Thiago Henrique Hüpner

http://pastebin.com/ZANutRt4






Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts