vendredi 31 juillet 2009

Développer son MMORPG - Les bibliothèques

4éme opus, où l'on parlera de tout ce qu'il ne faut surtout pas coder soi-même. Moins on écrit de code, et plus on peut utiliser celui des autres (à condition que cela soit de la qualité!), et mieux on se porte.

Combien de lignes de code, pour un MMORPG? Des dizaines de milliers? Des centaines de milliers? Il est hors de question de tout écrire soi-même. Si l'on veut avoir la moindre chance de sortir quelque chose un jour, il va falloir lourdement utiliser toutes les bibliothèques possibles, et se servir au maximum de tout ce code écrit par d'autres. Vu que choisir ses bibliothèques est à peine aussi cornélien que de choisir son langage, voici quelques pointeurs (C++! Hein, pointeurs, C++! Bon, heu, je prends mon manteau, hein?).

À travers le spectre
Pourquoi donc ne pas avoir une seule bibliothèque (on parlerait d'ailleurs plutôt de plate-forme, d'ailleurs) qui soit spécialisée dans le MMO? Ça tombe bien, cela existe, mais il faudra alors entrer dans le moule: ces plate-formes proposent en fait la modification et l'extension d'un jeu de base, fonctionnant sur certains principes immuables, et il sera probablement impossible, par exemple, de transformer une plate-forme fantasy en un space opera. d

D'un autre côté, l'on peut s'orienter vers des petites bibliothèques très spécialisées, donnant une grande liberté, mais ajoutant autant au travail d'intégration.

Enfin, au milieu, il y a les bibliothèques de jeux intégrées (comme par exemple Ogre) qui proposent un moteur graphique complet, un support des sons et des entrées utilisateurs, avec en général une bonne portabilité sur de nombreux systèmes d'exploitations.

Pour AdH, l'on a beaucoup tergiversé entre les deux dernières options, pour au final s'orienter vers des bibliothèques spécialisées, les moteurs de jeux complètement intégrés s'étant avérés trop contraignants. Point bonus, il est plus facile de changer une bibliothèque qui ne convient pas que d'avoir à remplacer tout un framework.

Qu'est-ce qu'il ne faut surtout pas faire soi-même

  • Les couches bas-niveau de portabilité - Le genre de code qui vous permet d'afficher une fenêtre graphique et de lire le clavier à la fois sous Linux et sous Windows est généralement ennuyeux au possible à coder, en plus d'être difficile. Et puis, ce n'est pas cela qui va limiter votre imagination. Regardez la bonne vielle SDL, qui a des bindings dans à peu près tous les langages.

  • La bibliothèque graphique - N'écrivez pas votre graphe de scène, malheureux! Ni les routines de chargement de ressources (images, modèles 3D). Du peu que je connais de DirectX, c'est probablement utilisable directement, mais pour OpenGl, trouvez-vous un bon moteur graphique. AdH utilise OpenSceneGraph, mais il en existe des tonnes dans tous les langages.

  • La couche réseau - Il va falloir pouvoir compter dessus, donc inutile de se fatiguer avec les sockets. Utilisez la couche de haut niveau de votre plate-forme, voire une bibliothèque client-serveur complète

  • Le XML - Si vous en mettez, n'écrivez surtout pas votre parseur! Aujourd'hui, il y a pléthore de générateurs de code, ou encore de sérialiseurs qui peuvent par exemple utiliser la réflexion


Pour bien choisir

Car en plus, il y a du choix! Voici les critères, par ordre de priorité, que nous retenons pour faire notre choix:

  • La licence - Si vous ne pouvez pas légalement utiliser la bibliothèque, inutile d'aller plus loin! En fonction de vos choix, vous pourrez utiliser du GPL, du LGPL, du BSD, ou encore sortir votre porte-monnaie pour acheter une licence commerciale.

  • Portable - Puisque nous voulons tourner sur le maximum de plate-formes, il est hors de question de choisir une bibliothèque qui ne tourne que sous un système d'exploitation

  • Mature - Vous n'allez pas attendre l'année prochaine que la fonctionnalité dont vous avez absolument besoin sorte enfin. Un tiens vaut mieux que deux tu l'auras, choisissez donc une bibliothèque qui fait déjà tout ce dont vous avez besoin

  • Simple - Inutile d'essayer d'intégrer une usine à gaz. Les meilleures bibliothèques sont celles qui peuvent se marier très facilement à votre système, sans forcer une architecture. La bibliothèque doit faire exactement ce qu'on lui demande, avec le minimum d'intégration

  • Maintenue - Rien de plus ennuyeux que d'utiliser des vieilles versions de bibliothèques, utilisant des compilateurs antédiluviens, et présentant des bugs qui ne seront jamais résolus. Au contraire, une bibliothèque bien maintenue va évoluer avec les outils du jour, et les petits problèmes rencontrés ont une plus grande chance de se voir résolus un jour. N'hésitez pas à contribuer si vous avez trouvé un défaut!


L'équipe AdH utilise une règle simple et qui marche la plupart du temps: n'utiliser que les bibliothèques se trouvant dans les dépôts Debian testing. Ansi, c'est l'assurance que la bibliothèque est suffisamment mature, qu'elle est maintenue, qu'elle est libre, et qu'elle sera facile à installer! Il ne reste plus qu'à vérifier l'existence d'une version pour Windows, et roule ma poule.

samedi 18 juillet 2009

Création dynamique des îles

C'est fait, l'on peut maintenant créer les îles dynamiquement! Dans la console, l'on tape par exemple:

/island create here 1457 Minos
et l'île Minos, créée à partir de la racine 1457, apparaît sous vos pieds.

Il me reste cependant à gérer l'UTF-8, car pour l'instant, l'on ne peut pas créer l'île de Ré!

mercredi 8 juillet 2009

Vive les en-têtes précompilées!

J'adore cette fonctionnalité de gcc, d'ailleurs présente, si mes souvenirs sont justes, dans le compilo de Microsoft, bien avant son arrivée dans le compilateur GNU.

L'idée est simple: dans n'importe quel programme d'une taille un peu conséquente, beaucoup de fichiers en-tête sont inclus dans pratiquement toutes les unités de compilation (pensez à la bibliothèque standard, par exemple). Le compilateur passe donc beaucoup de temps à lire et à parser les mêmes fichiers. Pourquoi donc ne pas mouliner ces en-têtes souvent utilisées, une seule fois, vers un format efficace, utilisé ensuite dans chaque unité de compilation.

Dans gcc, c'est d'une simplicité enfantine.

  • Créez un fichier en-tête (par exemple All.h), mettez-y toutes vos en-têtes souvent utilisées.

  • Dans tous vos fichiers source, incluez en premier All.h, et enlevez toutes les inclusions déjà présentes dans All.h

  • Vérifiez que tout compile gentiment

  • Puis, compilez All.h comme un bête fichier source, avec les mêmes options de compilation que tout le rèste. Par exemple, pour le c++:
    g++ -c All.h

  • Admirez le fichier All.h.gch qui vient d'être généré, et compilez vos sources normalement!



Voilà ce que je mets dans mon All.h, pour le serveur AdH:

#ifndef __ALL_H__
#define __ALL_H__

#include <istream>
#include <ostream>
#include <stdexcept>
#include <cmath>
#include <sstream>
#include <vector>
#include <set>
#include <iostream>

#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/static_assert.hpp>
#include <boost/operators.hpp>
#include <boost/optional.hpp>
#include <boost/scoped_array.hpp>
#include <boost/foreach.hpp>
#include <boost/noncopyable.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/bind.hpp>
#include <boost/variant.hpp>
#include <boost/scoped_array.hpp>
#include <boost/timer.hpp>
#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <boost/random.hpp>
#include <boost/function.hpp>
#include <boost/program_options.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/regex.hpp>

#include <pqxx/pqxx>

#endif


Il me semble avoir déjà dit ici que j'aimais bien Boost.

Voilà les temps de compilation du serveur.
  • Tout seul: 26.16 secondes

  • Avec l'en-tête précompilée:12.92 secondes


Probablement un peu injuste, puisque par exemple je n'inclurais pas boost/regex.hpp dans chaque fichier. Mais la différence est quand même significative, ne serais-ce que pour ne pas avoir à s'embêter à choisir soigneusement les fichiers à inclure dans chaque source, et se retrouver avec des listes d'inclusions standards longues comme le bras dans chaque fichier.

Petite remarque: ma préférence quand à choisir ce qui va dans les en-têtes précompilées est d'y mettre toutes les dépendances extérieures. Les entêtes libpqxx ou wxWidgets y appartiennent, mais ma bibliothèque d'utilitaires non!

Et une dernière pour la route: assurez vous qu'il y a de l'espace disque. Mon gch fait 113 megs!

mardi 7 juillet 2009

Rhaaaaa!

C'est le cri primal du programmeur qui a passé de trop nombreuses heures à traquer des bugs stupides.

C'est en effet le pompon: en 3 jours, pas moins de 3 bugs majeurs qui sont venus tout bousiller. Reprenons dans l'ordre:

- Tout d'abord, ce stupide état OpenGL qui me cassait la couleur de mes îles et leur réflexion, décrit dans le post précédent

- Ensuite, le plan de clipping qui commençait à faire des siennes, coupant mon décor de manière différente en fonction de la taille de la mer (je ne vois toujours pas le rapport!), qu'il a fallu calmer à coups de osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR

- Enfin, un bug encore plus stupide: j'ai voulu charger dynamiquement les îles en threadant le calcul des altitudes et des normales, parce que cela prend un bon bout de temps. Mais surprise, les performances étaient épouvantables! Après un bon moment et beaucoup de recherches, j'ai fini par me rendre compte que tous les threads restaient sur le même CPU, et que cela était dû à OpenSceneGraph qui force l'affinité des threads graphiques, c'est à dire indique à chaque thread quel processeur utiliser, améliorant les performances de l'ordonnanceur du système d'exploitation qui n'a pas à se poser de questions. Mais là où tout part en vrille, c'est que les threads fils du thread principal se retrouvent avec la même affinité, et tout le monde se retrouve sur le même CPU, délaissant totalement les autres!

Ces bugs sont résolus, mais de nombreux problèmes subsistent. En premier, l'agrandissement de la zone d'eau cause maintenant une pixelisation excessive de la texture de reflet, ce qui, plus simplement, veut dire que c'est moche. De toutes façons, mon eau, si elle rendait plutôt bien à courte distance, est bien trop réfléchissante lorsque l'on prend de l'altitude. Ce ne sont plus des îles en Méditerranée, ce sont des tas de farine sur un miroir!

samedi 4 juillet 2009

Archipels

Pfiou! Il a fallu se battre avec OpenSceneGraph, mais voilà enfin un archipel digne de ce nom.

Tout d'abord, il semblerait que charger un fichier 3D mette la machine à états OpenGL dans une configuration bizarre, qui me cassait à la fois mes couleurs et ma réflexion. Après de longues batailles, j'ai fini par contourner le problème en m'assurant que tous mes objets "spéciaux" seraient affichés en premier.

Ensuite, il m'a fallu me battre avec mes îles, et la taille de la grille pour les accueillir.

Mais bon, on s'approche de la création dynamique des îles, et bientôt, les Dieux feront surgir des îles fertiles des océans furieux!

jeudi 2 juillet 2009

Permissions

Je viens juste d'ajouter un petit système de permissions, afin de contrôler les commandes des joueurs.

Comme créer un éditeur de monde me semble au dessus de nos forces, la création du monde se fera en temps réel à partir du client, à travers certains avatars possédant des pouvoirs supplémentaires, tels des dieux, des demi-dieux, ou autres créatures magiques. La première commande permet à l'avatar de savoir où il se situe dans les coordonnées du monde:

/where
renvoie par exemple:
1@-57.446,-418.098,66.9607
pour l'avatar possédant la permission "locateSelf".

Plus tard, il sera possible de créer des îles en définissant ses coordonnées et quelques paramètres de base, et tous les joueurs de la zone devraient voir apparaître le nouvel archipel.