Monday, 18 October 2010

La trappola del C++ (seconda parte)

«Non è facile portare un satellite in orbita»
Lo Space Shuttle Atlanits (STS-27)
Immagine fornita da Wikimedia Commons





Nella prima parte di questo articolo ho iniziato ad affrontare il problema della realizzazione di un motore grafico 2D in OpenGL e C++ presentando quali sono le classi che potrebbero concorrere alla soluzione del problema.
Voglio ribadire un'altra volta che non sto dicendo che il C++ sia il Male assoluto: voglio solo sottolineare che spesso la gente lo utilizza senza capire bene a quali problemi va incontro e che le caratteristiche del C++ (astrazione, velocità, ecc.) non sono necessarie nella stragrande maggioranza dei casi.

Proviamo a vedere, sulla base dell'esempio scorso, la dichiarazione della classe Camera.

//le dimensioni dello schermo le teniamo costanti
const int SCREEN_WIDTH=1024;
const int SCREEN_HEIGHT=768;

class Camera{
    public:
        Camera(int width,int height);
        void SetCameraPosition(int x, int y)
        void DrawImageAt(Image* img, int x, int y):
        void DrawMap(Map* map);
    private: 
        //coordinate dell'obiettivo
        int camera_x;
        int camera_y;
  
        //dimensioni dell'obiettivo
        int camera_width;
        int camera_height;
};

Commentiamo questa dichiarazione: il costruttore creerà una nuova istanza di Camera e, magari, preparerà pure una finestra OpenGL; DrawImageAt disegnerà l'oggetto Image passato come parametro alle coordinate x,y; DrawMap, invece, utilizzerà i dati contenuti nell'oggetto mappa per disegnare la porzione di mappa attualmente "inquadrata" dall'oggetto Camera. Il metodo SetCameraPosition sposterà l'inquadratura della telecamera modificando i membri camera_x e camera_y.
Pensiamo a cosa significa avere una classe che gestisce una telecamera virtuale in OpenGL: possiamo disegnare immagini, visualizzare mappe, spostare tutto con un'animazione. Non è affatto male per un motore grafico 2D. Si tratta solo di implementare tutto ciò che giace sotto.

Allocare gli oggetti Image a partire da un file non è una tragedia: ricorrendo alla buona libreria DevIL (scritta apposta per caricare immagini da utilizzare in un contesto OpenGL) ce la caveremo in poco tempo.
La classe MediaCache può essere scritta con poca fatica ricorrendo all'uso di un template <vector> della Standard Template Library

Lo ammetto: al momento non sembrano sussistere particolari difficoltà, a parte forse la scelta delle librerie: valutare licenze, dimensioni e funzionalità porterà via solo un po' di tempo. Con un po' di pazienza e di buona volontà si può sicuramente terminare questo progetto in una settimana. E' un progettino semplice (al massimo arriverà a 1000 righe di codice), ma si può trasformarlo in un inferno aggiungendo un solo requisito.

Ammettiamo di aver terminato e testato il motore grafico e proviamo a pensare a cosa bisognerà fare per renderlo portabile. Se pensate che la portabilità sia un problema da poco, chiedetevi come mai su Linux ci siano pochi giochi :P

Pensiamo a come abbiamo inizializzato la finestra OpenGL: probabilmente siamo ricorsi a uno dei tutorial presenti sul celeberrimo NeHe. Proviamo a confrontare il codice usato per chi lavora con Windows+Visual Studio con quello di chi utilizza Ubuntu+SDL. Magari di poco, ma è diverso. Renderlo portabile significherebbe inserire dei #define per distinguere il codice win32 da quello Linux. Per un paio di header file possiamo farlo: ma se immaginate che siano dieci o quaranta, la voglia inizierà a scemare.
Non basta, però, aver modificato i sorgenti: bisogna sicuramente modificare il makefile C++. Anzi, probabilmente a questo punto diventa più comodo sostituirlo con i tool automake o con cmake.
Fortunatamente le funzioni presenti in OpenGL e DevIL sono portabili, ma lo stesso non si può dire per le routine di gestione degli eventi (mouse e tastiera): più di qualcuno sconsiglia l'uso delle API di I/O di OpenGL e, quindi, ci si appoggia su DirectInput o qualche altra libreria.
Questo progetto, lo ripeto, è piccolo e queste difficoltà sono facilmente aggirabili: però pensate a cosa vuol dire lavorare su un programma C++ serio (e con serio parlo di ALMENO 5000 righe di programma).

La mancanza di una libreria standard più ampia è una seccatura perché:
  1. Usare i thread significa ricorrere alle API presenti nel sistema operativo o cercare una libreria opportuna;
  2. Lo stesso discorso vale per i socket;
  3. Idem per quanto riguarda le interfacce grafiche
La situazione è sicuramente cambiata da quando la Nokia ha acquisito le librerie QT e le ha rilasciate con licenza BSD.

Conclusioni del post
Se dovete lavorare con programmi C++ portabili, che prevedano l'uso di grafica, socket, threads, ecc. prendete seriamente in considerazione l'idea di eleggere tutto il framework QT a libreria standard del C++. Ovviamente se questo non è il vostro caso, ricordatevi che dovrete comunque appoggiarvi alle routine del sistema operativo in cui vi trovate (che potrebbero essere scritte in C)
Chiudo qui la seconda parte, ma vi do le "succose anticipazioni" della terza parte.

  • I casi di Mozilla Firefox e Blender 3D: programmi di successo in C++ e portabili "col trucco"
  • KDE e Skype: cosa si può fare con le librerie QT
  • Requisiti funzionali e Debug
  • Considerazioni su Cocoa e l'Objective-C
(continua domani nella terza parte)

No comments: