Affichage des articles dont le libellé est projet. Afficher tous les articles
Affichage des articles dont le libellé est projet. Afficher tous les articles

lundi 7 novembre 2011

Je commence à apprécier Github

Ce site est un vrai petit bijou. Je découvre leur système de tickets, qui permet de lever des bugs ou des feature requests, de les assigner à un développeur et à une release et de les tagger. Ensuite, lorsque l'on corrige le ticket n, il suffit d'avoir "fix #n", "fixes #n", "close #n" ou "closes #n" dans le message de commit. Par exemple, mon dernier commit: "Added possibility to clear all loaded images. Fixes #1." ferme automatiquement le ticket correspondant, en y incluant le commentaire et un lien vers le commit, et ceci, pratiquement immédiatement après le push.

Par contre, étrangement, les statistiques en termes de langages utilisés ont l'air un poil cassé: j'ai commité ce week-end un millier de lignes d'OCaml et un peu de SQL, mais Github indique obstinément 100% de C++.

vendredi 30 septembre 2011

Un client lourd Médoc

Mon projet de documentation Médoc continue de fonctionner impeccablement, du moins du côté de la base de données et du client Web. Par contre, le client lourd en OCaml et LablGTK, chargé de charger les documents dans la base à partir du scanner ou d'un fichier, commence à tomber en ruines.

Pas très ergonomique d'une part, et pas très intégré d'autre part (la plupart des commandes, telles le chargement de fichier ou le scan en lui-même, sont faites à partir de lignes de commandes lancées par le programme). Je me suis donc mis à écrire un client lourd en C++ avec WxWidgets qui soit un peu plus pratique à utiliser. La libsane se chargera de piloter le scanner, libmagick++ sait apparemment transformer un pdf en image, et la libharu sait transformer une image en pdf.

Y'a plus qu'à coller les morceaux.

mercredi 28 septembre 2011

Courir après sa base de données

Julien me parlait de systèmes de suivi de courses type marathon ou trail à base de RFID, et il n'en a pas fallu plus pour enflammer mon imagination. Je pensais à ce à quoi ressemblerait un schéma de base de données enregistrant le passage des coureurs à des points donnés, afin de permettre le suivi par les commissaires de courses, le public, et la publication des résultats et des statistiques pour les coureurs. Et on dirait bien qu'un tel système présente une sacrée dose de défis.

L'on s'intéressera plutôt aux courses de type ultramarathon ou trail, sur chemins ou en pleine nature, où les coureurs doivent s'enregistrer auprès des commissaires de courses à des points de passages déterminés. Le coureur passe donc son RFID au dessus de l'appareil du commissaire de course, et repart.

Voyons donc ce schéma. Les faits sont assez simples: une table contenant chaque détection d'un coureur à un point de passage. Mais qu'est-ce qu'un coureur, et qu'est-ce que c'est qu'un point de passage? Redéfinissons: notre table contient chaque détection d'un RFID par un appareil. Et il ne doit pas être possible de modifier ou d'enlever une ligne: une détection est une détection. Elle s'est effectuée à une certaine heure déterminée par l'appareil, et reçue à une autre heure (parfois beaucoup plus tard s'il y a des soucis de connectivité) par la base de données.

(device_id, rfid, device_timestamp, local_timestamp)

L'on pensera bien sûr aux pannes: si un rfid ou un appareil ne fonctionne plus, le commissaire pourrait se retrouver forcé d'enregistrer les coureurs manuellement via un autre type d'identification, comme un dossard.

(marshal_id, runner_id, marshal_timestamp, local_timestamp)

Ce sont les faits: irréfutables.

Maintenant, tout le problème est de rajouter les tables qui permettront d'associer les appareils aux points de passages, et les RFID aux coureurs.

- Un coureur découvre en milieu de course que son RFID est défectueux, et un commissaire de course lui en fournit un autre
- N'ayant cependant pas rendu son premier RFID, qui s'est tout à coup mis à remarcher, il passe parfois l'un, parfois l'autre aux points de passages
- Deux coureurs échangent un sac, et oublient que leur RFID était dedans

- Un commissaire change de point de passage, et garde avec lui son appareil
- Les commissaires découvrent que l'appareil pour la détection à l'arrivée est en panne, et décident de rapatrier un autre appareil pour l'arrivée, où une détection précise est plus importante

Toute la difficulté est donc de maintenir l'état le plus fréquent (un coureur ayant correctement été enregistré par son RFID par les bon appareils aux bons endroits), tout en permettant un réglage fin de toutes les petites déviations qui surviennent lorsque la réalité débarque avec ses gros sabots dans la théorie.

Peut-on rajouter la positien GPS de chaque appareil à chaque enregistrement pour tenter de les associer automatiquement à un point de passage?
Peut-être des tables se basant sur des plages horaires ou géographiques: de telle heure à telle heure, ou de tel point de passage à tel point de passage, le coureur A avait le RFID f, puis il est passé au RFID g?
Ou tout simplement des tables d'override: tel enregistrement doit être ignoré, tel autre doit être considéré comme ayant détecté le RFID g et non f?

Pas évident.

mardi 27 septembre 2011

Github

J'ai enfin fait le saut, et me voici sur github. Ayant depuis longtemps eu l'envie de m'essayer à git, le logiciel de contrôle de version décentralisé crée par Linus Torvalds, je me suis ouvert un compte sur github, et j'ai commencé à commiter.

Github est particulièrement bien: guidant pas à pas la création du dépôt et la configuration de git, réactif et ergonomique, c'est un petit bijou d'interface. Les performances ont également l'air d'être au rendez-vous.

Utilisant git de manière mono-utilisateur et mono-branche, mon utilisation est très proche de Subversion (en beaucoup plus rapide cependant, mais les dépôts Subversion de tortue de chez Sourceforge n'aident pas non plus), j'espère cependant au moins m'y initier suffisamment pour ne plus être complètement perdu lorsque certains collègues plus dégourdis jonglent avec git-svn sur les dépôts du boulot.

Déjà quelques trucs qui m'ont un petit peu bloqué: il n'est pas possible d'avoir des répertoires vides. La solution standard est de créer un .gitignore vide dans le répertoire, et d'ajouter ce fichier. Également, ajouter un répertoire est récursif, ce qui est souvent bien, mais parfois lourd lorsque ce répertoire contient déjà un build, par exemple. Le plus simple est de commencer par nettoyer tous les fichiers non voulus avant d'ajouter.

samedi 22 janvier 2011

20 000 lignes

Voilà, c'est atteint, le projet fait maintenant très exactement 20032 lignes de code, avec l'ajout hier soir d'une implémentation générique d'un observer (on dit vraiment "patron de conception" pour design pattern???). Je vous en causerai plus avant après nettoyage du code.

Mais, pour en revenir à nos lignes, je pense être plutôt dans une pente descendante: avec l'implémentation de piglet et la simplification annoncée du système d'itinéraires, c'est beaucoup de code qui va jarreter. Les systèmes de contrôle de version étant faits pour ça, j'ai la ferme intention d'oblitérer de vastes quantités de code.

mercredi 19 janvier 2011

Openrailz v0.2 sur Sourceforge

À la demande générale, j'ai publié sur Sourceforge la dernière version du code et des ressources d'OpenRailz dans un beau paquet. Pour Windows, malheureusement, il va falloir être un peu plus patients, et attendre que je trouve la motivation de porter le code.

Pour ceux qui veulent tenter la compilation sous Linux, la difficulté va surtout dépendre de la disponibilités des bons paquets pour votre distribution préférée. Le fichier INSTALL explique comment compiler avec une Debian Squeeze, la seule subtilité étant qu'il faudra compiler soit-même wxWidgets 2.9 (pour l'antialiasing). Pour la compilation en elle-même, un bête omake dans le répertoire principal fera l'affaire, mais il sera plus efficace de suivre d'un peu plus près la documentation, et d'utiliser les en-têtes précompilées ainsi que la compilation en parallèle.

Les ressources sont fournies avec le code, et sont dans le répertoire "data". L'exécutable cherchant par défaut les données dans le répertoire courant, le mieux est de se placer dans le répertoire principal, et lancer OpenRailz en tapant ./debug/bin/openrailz.

C'est là que tout se corse: je n'ai pu tester OpenRailz qu'avec ma carte vidéo, une Radeon HD 4870, en utilisant les drivers propriétaires, et j'ai peu de doutes que bien d'autres cartes ne supportent pas les shaders de la manière dont je les ai écrits. C'est vraiment au petit bonheur la chance. Les fichiers de shaders sont dans le répertoire data/shader, et sont lus au runtime, donc vous pouvez tripatouiller un peu pour tenter de résoudre d'éventuels problèmes.

Si, par le plus grand des hasards, OpenRailz se décidait à démarrer proprement, vous pourrez alors vous amuser un petit peu avec les circuits.

  • La navigation se fait en maintenant le bouton droit pour l'orientation, et le bouton central pour les déplacements

  • Ajoutez une gare, en sélectionnant l'icône "Build station", et en maintenant le bouton gauche à partir d'un endroit du terrain pour orienter le bâtiment

  • Ajoutez quelques autres gares (backspace pour effacer la gare courante)

  • Passez en mode rails avec l'icône "Track layout". Les waypoints des gares apparaissent, en rouge car ils ne peuvent pas être manipulés

  • Sélectionnez-en un, il devient doré. Puis cliquez et maintenez pour créer un autre waypoint. Une fois le bouton lâché, un rail apparaît si un chemin peut-être calculé. Re-cliquez et glissez pour réorienter un waypoint (manipulable, en vert) existant

  • Le train courant est situé dans la première gare que vous avez créée. Passez en mode sélection avec l'icône "Selection tool", et cliquez sur une autre gare. S'il existe un chemin, le train va s'y déplacer!

dimanche 16 janvier 2011

Et maintenant?

Malgré quelques bugs, cette démo de déplacement des trains est une avancée majeure et valide le modèle d'entités graphiques (le bien nommé piglet), le modèle de rails, et le pathfinder. La pose de rails, malheureusement, reste très ardue, mais ce sous-système est suffisamment générique pour être facilement amélioré plus tard, ce qui le rend non prioritaire.

Je peux donc travailler sur l'amélioration du réalisme (accélérations et décélérations), l'interface (simplifier mon modèle d'itinéraires), et enfin m'attaquer à des améliorations de gameplay. Je pense notamment à enfin intégrer l'aspect passagers, avec des gares qui se remplissent de passagers désireux de voyager. L'aspect financier, avec le coût des gares, des rails et des trains, viendra plus tard.

Mais peut-être avant tout ça faut il d'abord trouver enfin un modèle raisonnable pour les callbacks (fonctions de rappel, me dit Wikipedia (!)) entre le moteur et la GUI. L'approche monolithique consistant à faire dériver chaque élément de la GUI d'une interface qui fait tout (sélections de trains et de gares, changements de vitesse, itinéraires, passagers dans les gares, etc) est trop limitée: non seulement cette interface mammouth couplerait tous les composants, mais cela veut dire que des centaines de fois par seconde, chaque train va envoyer sa nouvelle vitesse, chaque gare va envoyer son nouveau nombre de passagers, et bien d'autres événements vont être générés et envoyés à des boites de dialogue qui vont pour la plupart les ignorer.

Inspiré par une approche que j'ai vue au boulot (chut!), je vais tenter quelque chose de plus découplé: permettre à chaque composant de la GUI de s'enregistrer auprès d'un événement donné, en lui passant une fonction de callback, et gérer l'enregistrement dynamiquement au fur et à mesure des besoins de l'utilisateur. Ansi, l'utilisateur cliquant sur une gare, la boite de propriétés de la gare enregistre un callback directement sur un objet représentant le nombre de passagers. Lorsque ce nombre change, la boite de propriétés en est informée, et affiche la nouvelle valeur. Lorsque la gare est dé-sélectionnée, la boite de propriétés se dés-enregistre. Ainsi, pas d'interface monolithique, pas de couplage entre des événements indépendants, et, je l'espère, moins de code à gérer.

mercredi 22 septembre 2010

Feed RSS du projet

Yay, que de fun! J'ai ajouté sur la droite un feed vers les news du projet Sourceforge. C'est plus facile de voir les derniers commits!

jeudi 26 août 2010

Mes bibliothèques

Voici un petit graphique de la structure des bibliothèques du projet, avec le nombre de lignes de code entre parenthèses:



Les bibliothèques mammouth sont les utilitaires bas niveau (c'est une bonne chose), la bibliothèque qui contient la logique (rail) (c'est une bonne chose aussi, puisqu'elle fait partie des tests unitaires), et enfin les utilitaires OpenSceneGraph. Ceux là sont moins évidents à tester, puisque le résultat est la plus part du temps graphique.

Mon dernier commit a déplacé un gros bout de la logique de osgutils vers rail, ce qui va me permettre de tester lourdement mon graphe représentant le réseau et comment s'y déplacer. J'en suis effectivement presque à devoir implémenter ma version de l'algo de Dijkstra, ce qui est toujours amusant.

dimanche 15 août 2010

OpenRailz - Et maintenant?

La démo n'est pas vraiment sortie à un moment particulier du développement. C'est plutôt que j'ai réussi à m'extraire de la léthargie profonde dans laquelle toute mention de développement sous Windows me plonge, et de tenter de compiler. Néanmoins, ça fait toujours du bien de se souvenir d'où on va.


  • Compléter les gares - Ajout de rails au sein de la gare, et génération de waypoints statiques au bout des lignes afin de pouvoir relier la gare au réseau

  • Ajouter les dépôts - Il va bien falloir faire partir les trains de quelque part! Une alternative est de simplement pouvoir faire atterrir le train sur un bout de rail, peut-être m'en contenterai-je pour le moment

  • Gérer enfin les trains de bout en bout - Une fois qu'il y a des trains sur la ligne, et des gares où aller, je n'aurai plus d'excuses pour ne pas implémenter mon pathfinder, et permettre de donner des ordres aux trains

  • Monter en charge - Avec un bout de ligne sur un pauv' carré de terrain et un malheureux train, il n'est pas très difficile d'avoir un bon millier d'images par secondes. Cependant, le moteur tel qu'il est ne tient pas la route dès que l'on rajoute des voies supplémentaires. Il va donc falloir refondre le graphe de scène sous forme de quadtree, de gérer le niveau de détail pour chaque nœud du quadtree, et enfin de créer des nœuds d'états de manière à réduire au minimum les chargements de shaders.

OpenRailz sous Windows

C'était pas de la tarte, mais ça y est, j'ai une version d'OpenRailz qui tourne sous Windows, disponible ici. Je n'ai pas pu tester sur une machine qui n'aie déjà Visual Studio 2010, il est donc possible qu'il faille installer les "redistribuables" pour compléter les DLLs.

Le plus difficile, ça a été de compiler toutes les dépendances sous Visual C++ 2010, car les binaires ne sont généralement pas disponibles. WxWidgets s'est montré particulièrement résistant, me forçant à aller bidouiller les fichiers de projet à la main.

Au menu des problèmes rencontrés lors du port:
- En plus des spécifications pour les constructeurs et les destructeurs, l'initialisation en {} n'est pas non plus implémentée dans VC 2010. Il m'a donc fallu rajouter les push_back et les insert qui vont bien
- Les Property Tree de Boost ne compilent pas! C'est la mauvaise surprise de la journée. C'est particulièrement ennuyeux parce que les ressources sont maintenant définies dans un fichier XML dans le format Boost, et que donc la seule manière de le remplacer soit de le réécrire moi-même, ce qui n'était franchement pas le but de la manœuvre. Pour les besoins de la démo, j'ai codé en dur toutes les ressources, mais je vais avoir besoin d'attendre soit que le compilo évolue, soit que Boost résolve le problème.
- J'utilise ça et là quelques types définis dans cstdint, comme par exemple uint32_t, qui n'ont pas l'air définis par Visual C++ 2010. Je n'ai pas franchement envie de les remplacer par du boost::uint32_t, j'aime bien Boost, mais j'ai quand même l'impression que je devrais pouvoir gérer mes types de base tout seul!
- Quelques différences dans wxWidgets: il faut par exemple appeler wxInitAllImageHandlers pour avoir le support du png, et appeler Realize() sur les barres d'outil pour qu'elles s'affichent, ce qui n'était pas le cas sous Linux. Plus ennuyeux: il semble que cliquer sur un menu, ou déplacer une boite de dialogue, peut bloquer le rendu. J'espère qu'il sera possible de résoudre ces problèmes.

Pour ceux qui auraient 5 minutes pour essayer, dites moi comment ça tourne!

samedi 14 août 2010

Release de la démo

On n'ira pas bien loin avec la démo, mais c'était l'occasion d'écrire un peu de documentation pour la compilation.

OpenRailz version 0.1 vient donc de débarquer chez SourceForge:

Les données

Le code

Comme je le mentionne dans la documentation, en sortir une version pour Windows ne sera pas une mince affaire: en l'état, en plus de la réunion de toutes les dépendances, il me faudra aussi adapter le code pour Visual Studio 2010, lequel implémente un différent sous-ensemble des fonctionnalités C++0x. Par exemple, il va me falloir remplacer toutes les spécifications "default" et "delete" pour les constructeurs et les destructeurs, par les corps de fonction correspondants.

mardi 27 juillet 2010

Développer son MMORPG - Threads

6ème partie (enfin! s'écrie mon public en délire), où j'évoque les tentations du multithreading, et laisse entendre qu'il faut mieux les laisser aux grands garçons (et aux grandes filles).

Ceux qui, comme votre serviteur, auront passé trop de temps sur les forums de développement de jeux vidéos (même stack overflow s'y met: regardez!) auront pu découvrir un certain nombre de questions qui peuvent se résumer ainsi: où est-ce que je met mes threads?

Le jeune scarabée s'empresse de lister alors une série de composants, et leur assigne unilatéralement un thread, histoire de dire que. L'on se retrouve avec un thread par zone, un thread pour la physique, un thread pour les communications, un thread pour le commerce, un thread pour le pathfinding, etc. Côté client, on imagine un thread pour le rendu, un thread pour la GUI, un thread pour la physique, un thread pour la musique, un thread pour les communications avec le serveur, und so weiter.

Arrêtez là, malheureux! Écoutez les paroles de celui qui s'est lamentablement planté avant vous sur exactement le même chemin.

Pas de threads. Nope. N'essayez pas, ce n'est même pas la peine. Non seulement ce n'est pas la peine, mais je soutiens qu'un effort non négligeable est nécessaire pour supprimer, ou du moins abstraire, tout ce qui pourrait avoir besoin d'un thread (mettons, le réseau).

La raison est tout simplement qu'il est horriblement compliqué de gérer la synchronisation des threads. Et qu'en plus, les bibliothèques externes sont rarement thread safe, et qu'il faut donc coder nombre de couches d'abstractions pour garantir que l'on ne va pas exploser en vol. Je ne parle même pas des test unitaires et du débuggage.

Du côté serveur, prévoyez également une simple boucle, éventuellement pilotée par les événements réseau. La bibliothèque boost::asio est particulièrement pratique pour ce cas de figure: elle autorise à faire tourner des routines basées sur un chronomètre d'une part, et sur les messages réseau d'autre part, mais toujours dans le même thread.

Enfin, pour ceux qui s'inquiètent de voir les trois quarts de leur puissance de calcul inutilisés, voici ce que vous pouvez en faire:


  • Faire tourner plusieurs instances du serveur

  • Calculer des statistiques à partir de la base de données

  • Compiler du code pour ajouter des fonctionalités!



Quant à ceux qui se laissent tenter par le chant des sirènes, j'attends avec impatience vos retours d'expérience, statistiques à l'appui. Bon courage!

jeudi 6 mai 2010

Statistiques

Le framework vient juste d'atteindre 10 000 lignes! Voici les statistiques du code. Si la couche ORM (sur laquelle je vais avoir beaucoup de choses à poster) fonctionne correctement, attendez vous à un sacré saut vers le haut, parce que le code autour de la base de données est particulièrement verbeux.


SLOC Directory SLOC-by-Language (Sorted)
3005 utils cpp=3005
813 messages cpp=772,xml=41
746 geom cpp=746
742 unittests cpp=742
533 osgutils cpp=533
532 libweb ml=532
508 wxutils cpp=508
471 server cpp=471
413 sound cpp=413
377 openrail cpp=377
288 orm ml=188,xml=100
237 dsgen ml=237
235 wxosg cpp=235
229 rail cpp=229
202 logserver cpp=202
158 chatserver cpp=158
157 logapi cpp=157
108 doc perl=108
84 wxdemo cpp=84
73 wxosgdemo cpp=73
47 logviewer cpp=47
43 listviewer ml=43
0 authserver (none)
0 bin (none)
0 data (none)
0 lib (none)
0 listserver (none)
0 top_dir (none)


Totals grouped by language (dominant language first):
cpp: 8752 (87.51%)
ml: 1000 (10.00%)
xml: 141 (1.41%)
perl: 108 (1.08%)




Total Physical Source Lines of Code (SLOC) = 10,001
Development Effort Estimate, Person-Years (Person-Months) = 2.24 (26.93)
(Basic COCOMO model, Person-Months = 2.4 * (KSLOC**1.05))
Schedule Estimate, Years (Months) = 0.73 (8.74)
(Basic COCOMO model, Months = 2.5 * (person-months**0.38))
Estimated Average Number of Developers (Effort/Schedule) = 3.08
Total Estimated Cost to Develop = $ 303,171
(average salary = $56,286/year, overhead = 2.40).
SLOCCount, Copyright (C) 2001-2004 David A. Wheeler
SLOCCount is Open Source Software/Free Software, licensed under the GNU GPL.
SLOCCount comes with ABSOLUTELY NO WARRANTY, and you are welcome to
redistribute it under certain conditions as specified by the GNU GPL license;
see the documentation for details.
Please credit this data as "generated using David A. Wheeler's 'SLOCCount'."

dimanche 18 avril 2010

Framework, le retour!

Je suis en train de pousser la modularité jusque dans la GUI, avec des résultats plutôt encourageants. L'idée est de construire la GUI autour de blocs indépendants, chacun correspondant à un wxPanel, possédant un pointeur vers un client afin de pouvoir envoyer des messages, et dérivant d'un dispatcher pour recevoir les messages envoyés par le serveur (le tout basé sur les évènements wxWidgets, ce qui me permet de rester mono-threadé).

J'ai ainsi complété un module permettant de chatter. En théorie, j'aurais pu séparer la fenêtre d'envoi de la fenêtre d'historique, mais cela semble moyennement utile pour le moment.

De manière plus générale, je vois bien l'ensemble des serveurs se référencer auprès d'un contrôleur, auquel les clients peuvent se connecter pour obtenir la liste des serveurs disponibles et leurs capacités. Par exemple, un serveur indiquera au contrôleur qu'il supporte le chat et le commerce, un autre supporte un certain nombre de zones, etc. Chaque élément de la GUI peut alors lister les serveurs disponibles qui la supportent (par exemple, le module de chat listera les serveurs qui ont la capacité chat), et décider auquel se connecter.

Bien sûr, tout cela peut être géré en douce par le client, et rester invisible pour le joueur, qui pourrait n'avoir qu'à choisir son univers de jeu (le contrôleur) et laisser la GUI se connecter à qui il faut.

samedi 20 février 2010

Feuille de route

Plus à usage personnel qu'autre chose, voici ma feuille de route.

Serveur de logs
La base est faite, l'on peut envoyer ses logs sous le format par défaut
- Ajouter la possibilité de créer des formats spécialisés
- Ajouter un visualisateur de logs

Serveur de listes
Les structures de base pour les listes sont à peu près correctes.
- Ajouter des tests unitaires
- Développer le serveur en lui-même
- Ajouter les mécanismes d'acquisition de verrous sur les listes
- Ajouter les mécanismes de signalement de changements
- Créer un éditeur de listes

Serveur d'authentification
Le code est plus ou moins là, mais complétement épars.
- Créer le serveur d'authentification
- Ajouter des systèmes fins de permissions
- Ajouter un éditeur de comptes

Serveur de jeu
Tyrion a démarré sur les chapeaux de roues avec le système de magie. Il va falloir fournir un cadre solide via des APIs vers les autres sous-systèmes, pour permettre un développement aussi aisé que possible.

dimanche 31 janvier 2010

Développer son MMORPG - Architecture

5ème partie, où je soutiens que faire "comme les grands" peut avoir des avantages inattendus.

Je ne dirais pas que le plus grand risque du développeur de fond est d'abandonner son projet, mais plutôt de se laisser perdre dans l'exploration de solutions techniques. Ainsi, l'on se retrouve à démarrer bille en tête sur de la 3D, tout jeter pour passer à la 2D quand les difficultés deviennent insurmontables, puis revenir en 3D lorsque l'on vient juste de découvrir un nouveau moteur graphique, puis décider de passer à un moteur de jeu complet, puis se rendre compte de ses faiblesses et revenir à la 2D...

Il est malheureusement difficile d'échapper à ce genre de cycles, et c'est pour cela qu'il faut s'y préparer et développer en gardant en tête que l'on aura sûrement à changer quelques fondamentaux dans un futur proche. Et avoir de bonnes bibliothèques bien génériques est un pas dans la bonne direction.

Il est très peu probable qu'une refonte, même massive, change beaucoup le code de sérialisation, ou l'authentification, ou les logs, ou les utilitaires de bases de données, etc. Mettre ce code dans des bibliothèques bien séparées (et si possible bien testées), c'est l'assurance d'avoir déjà beaucoup de bon code lorsque l'on sonde de nouvelles possibilités.

Mais l'on peut pousser plus loin: l'on peut séparer de grands ensembles du tronc commun, et construire des services entiers qui soient réutilisables partout. Autant les MMORPGs commerciaux distribuent l'authentification, les logs, les données statiques du monde pour des raisons de performance, autant l'amateur peut construire ces mêmes services comme étant des blocs solides dans une architecture instable, lesquels tourneront d'ailleurs gentiment sur la même machine. Et si le succès est au rendez-vous, il sera d'autant plus simple de distribuer.

Un serveur de logs - C'est pas comme si écrire dans un fichier ou une base de données était très dépendant des fonctionnalités du jeu. Une application indépendante qui tourne en tâche de fond, une API, et basta, voilà une belle brique sur laquelle se poser.

Un serveur d'authentification - Encore un service on ne peut plus générique. Un compte, des permissions sur des applications, des permissions plus précises au sein de ces applications... Au fur et à mesure que l'on développe son jeu, l'on peut enrichir le serveur d'authentification avec des fonctionnalités de type rôles, création de compte via un serveur Web, accumulation de statistiques... qui pourront rester stables au fur et à mesure que l'on travaille sur les modules plus proches du gameplay.

Et autres - Peut-être un serveur qui fournit des données statiques? Peut-être des éléments de GUI?

A vrai dire, l'on pourra sans doute changer de langage en cours de route, ces services resteront (pour peu que les communications soient raisonnablement portables).

Bien sûr, il faut éviter de perdre son temps à généraliser du code, mais si un effort mesuré augmente ses chances d'être encore là après deux ou trois itérations, ça vaut le coup.

jeudi 28 janvier 2010

OMake et fichiers générés

J'ai tout une série de classes C++ générées à partir d'un petit programme ocaml. Comment s'assurer que le bon vieux OMake génère tout dans le bon ordre? Malgré quelques subtilités, c'est plus facile qu'il n'y parait.

Voilà mon fichier OMakefile:


LIBFILES[] =
RequestMsg

GENERATEDFILES[] =
MsgId.h
RequestMsg.h
RequestMsg.cpp

CGeneratedFiles($(GENERATEDFILES))

LIB = ../lib/libmessages

.DEFAULT: $(StaticCLibrary $(LIB), $(LIBFILES))

.PHONY: clean
clean:
rm -f *~ *.o adh

.PHONY: gch
gch:
$(CXX) -c $(CXXFLAGS) All.h

$(GENERATEDFILES): messages.xml ../bin/dsgen
../bin/dsgen messages.xml


En premier, la liste des fichiers pour construire la bibliothèque, comme dans tout OMakefile qui se respecte.

Ensuite, la première subtilité. Je liste tous les fichiers qui seront générés par l'application (l'on peut probablement créer la liste à partir de la première liste de fichiers). Puis l'on indique via la commande CGeneratedFiles que ces fichiers sont générés. OMake va donc chercher dans l'ensemble des make files la règle qui lui indiquera comment construire ces fichiers. Ça tombe bien, tout en bas (au milieu, c'est du standard), il y a une règle qui dit que les fichiers dans $(GENERATEDFILES) peuvent être générés à partir de ma description xml et du programme de génération, et donne la commande à faire tourner pour les construire.

Deuxième subtilité, vu que le programme pour construire lesdits fichiers est lui-même créé par un autre make file du même groupe, j'indique le programme comme dépendance, ce qui veut dire qu'il cherchera à compiler le programme d'abord s'il ne peut pas le trouver.

Ansi, tout changement aussi bien dans mon fichier de description de classes (le "messages.xml") que dans le programme qui génère le code forcera une regénération. Et comme OMake se base sur un hash des fichiers pour déterminer si il y a eu changement, une modification cosmétique du générateur, par exemple, qui ne changerait pas le format de fichiers, se traduira par une execution du générateur, et c'est tout: les fichiers n'ayant pas changé, il est inutile de recompiler la bibliothèque, de lier les exécutables...

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 13 juin 2009

Développer son MMORPG - Choisir un language

Premier épisode d'une petite série qui durera ce qu'elle durera! A travers ces années à essayer un peu tout et n'importe quoi, voilà quelques idées sur le sujet des langages de programmation pour les MMO.

Les forums de développement de jeu regorgent de questions relatives aux langages qui sont les mieux adaptés à tel ou tel type de jeu. Lorsque l'on rajoute par dessus les besoins de performance et la complexité du développement d'un MMO, la question du langage devient particulièrement épineuse.

Avec AdH, on a un peu tout essayé: serveur en ocaml et c++, client en ocaml et c++, outils en ocaml, c++, java. Rajoutez quelques essais web avec PHP et .NET, une couche de SQL d'une épaisseur plus que changeante. Voilà donc ce qui a donné l'impression de marcher:


  • Il n'y a pas de langage parfait, et entre deux choix raisonnables, le mieux est de choisir quelque chose qui soit bien connu par les membres de l'équipe, et qui soit suffisamment utilisé par ailleurs pour trouver tout un tas de ressources sur le sujet. Développer son Tetris ou son Pacman pour apprendre un nouveau langage, c'est une excellente idée. Démarrer un WOW-killer à cloche pied avec l'idée d'apprendre en route, beaucoup moins! Également, il est préférable d'éviter les langages trop ésotériques, et choisir des technologies stables, matures et pour lesquels il sera facile de trouver de l'aide.


  • Choisir un langage, c'est aussi choisir un écosystème. Pour cette raison, il est important d'identifier les bibliothèques disponibles. Pour les graphismes, une implémentation OpenGL bas niveau ne suffit généralement pas: avez vous vraiment envie de développer un graphe de scène, d'écrire le code de chargement pour les objets 3D, ou de devoir convertir toutes vos images dans un seul format? Vous aurez suffisamment à coder comme ça pour vous embêter à écrire une couche de base de données, un moteur graphique, une couche réseau haut niveau, un bon support des threads, des bibliothèques pour charger et jouer les sons, charger les images, et j'en oublie.


  • Un même langage pour le client et le serveur. C'est un point très discuté, et pour cause. Avec un serveur et un client qui ont fondamentalement des buts très différents (graphismes et sons, portabilité, performances brutes pour le client - réseaux, bases de données, montée en charge pour le serveur), il est tentant de choisir deux langages. Notre expérience est qu'avec deux langages et une petite équipe, on se heurte à tout une série de problèmes:

    • Souvent, les membres de l'équipe se spécialisent, et il est difficile de faire bouger des gens du serveur au client et vice-versa

    • Tous les utilitaires bas-niveau doivent être ré-écrits deux fois

    • La communication entre le client et le serveur devient plus complexe, il y a d'avantages de risques liés à la sérialisation des données

    • De manière générale, une perte de temps bien supérieure aux gains potentiels


  • Un langage plus agile pour les outils. L'émergence de nouveaux langages de script tels Python, Ruby, Perl et autres peuvent fournir des opportunités pour développer rapidement des outils. La difficulté est de bien s'assurer qu'ils seront peu couplés avec l'application principale: par exemple, un éditeur de monde avancé aura probablement besoin des mêmes structures que le client et le serveur, et le choix d'un langage différent pourrait au final causer une perte de temps. En revanche, la création rapide de donnés, d'un panneau administratif, ou encore la génération de code, peuvent bénéficier des langages de script. Par exemple, dans AdH, le langage Ocaml est utilisé pour générer le code C++ de sérialisation des messages, ce qui a sauvé beaucoup de temps de développement.


Allez hop, maintenant on s'y met!