jeudi 22 mai 2008

wxWidgets et les sockets

Me voilà très favorablement impressionné par le support réseau de wxWidgets, totalement intégrée dans la gestion d'évènements. Je trouve de manière générale qu'une des difficultés de l'intégration du réseau est de gérer correctement l'aspect asynchrone. L'on peut coder soi-même son petit thread et sa queue de messages, mais c'est vite lassant, et ne résout pas le problème de manière totalement satisfaisante. Doit-on aller regarder si un message attend dans la queue de temps en temps? Ou traiter le message de manière préemptive, au risque d'avoir à coller des mutex à travers tout le code? On se retrouve vite à coder une mauvaise implémentation d'un système d'évènements. Pourquoi donc ne pas utiliser wxSocket?

Dans l'utilisation que j'en fait, j'ai une liste de sockets que j'ouvre les uns après les autres, et qui vont aller sonner, de fait, chacun des serveurs de la liste, affichant dans le client l'état du serveur (s'il est up, le nombre de connectés, une courte description...). Je démarre donc toutes mes connections, et j'attends les évènements. Pas de threads, pas de select, juste une méthode qui sera appelée à chaque fois qu'il y a quelque chose d'intéressant. Si j'ai bien compris, l'on peut même dire au système: "Préviens moi quand tu as reçu tant d'octets", ce qui colle particulièrement bien avec mon protocole.

La seule chose qui me chagrine... Pour différencier les clients, qui sont passés à la méthode de gestion de l'évènement, il faut leur passer un "ClientData", horrible void * qu'il faut caster pour savoir de quoi il retourne. Mais je serais bien en peine de trouver un autre mécanisme plus élégant.

mercredi 21 mai 2008

Autosatisfecit

Petit post en forme d'autosatisfecit, mon wrapper de zlib arrive en deuxième position dans google France sur la recherche "zlib c++". Youpie!

J'en profite pour mentionner que le code est en prod, et que personne ne s'en est plaint jusqu'à présent. Le code est également présent dans AdH, et compresse les messages qui font plus de 100 octets. Autant la charge mémoire et CPU ne m'inquiète absolument pas, autant, avec les moyens du bord, c'est la bande passante qui va rapidement s'étioler...

dimanche 18 mai 2008

Grilles

Voilà une petite capture de ce à quoi ressemble la grille de commerce pour le moment. Désolé pour les en-têtes en anglais, je suis encore loin d'avoir décidé comment gérer la localisation.



Cette grille sera en lecture seule, et contiendra pour chaque marchandise les prix du marché. Le joueur peut y ajouter des prix, et quand le marché se croise, les marchandises et les sous sont échangés. Le tout devra fonctionner en temps réel, ce qui risque de créer d'intéressants problèmes si trop de joueurs sont actifs au même moment!

lundi 12 mai 2008

Boostons notre C++

Java a sa bibliothèque standard, C# a .NET, et C++ a boost.

Boost, c'est bien, mangez-en! Boost, c'est:

* exhaustif, la plupart des fonctionnalités dont on a toujours besoin y sont
* modulaire, on peut prendre ce qui nous intéresse, et laisser le rèste
* construit sur des concepts robustes, comme l'orienté fonctionnel, le typage fort
* géré par la communauté à travers un process de peer-review, quiconque peut proposer de nouveaux ajouts
* distribué sous une licence extrêmement permissive

Gare cependant à l'indigestion, on peut facilement s'y perdre. Voilà donc la petite liste des librairies de boost que j'utilise tout le temps.

* boost::shared_ptr - Les compteurs de référence sont très utiles, mais gare à les utiliser à bon escient, c'est à dire lorsque std::auto_ptr ou boost::scoped_ptr ne sont plus adaptés.
* boost::bind - Indispensable pour gérer ses closures dans les algorithmes de la bibliothèque standard. Par contre, se fait une spécialité de cracher les erreurs de compile les plus imbitables qui soient.
* boost::noncopyable - Bien pratique pour rendre une classe non copiable. Quand bien même c'est très facile de modifier soit-même sa classe, c'est encore plus simple (et plus lisible pour le suivant) de dériver de boost::noncopyable
* BOOST_FOREACH - Bien qu'étant une macro, c'est une reine de lisibilité. Très pratique lorsque j'ai 5 ou 6 conteneurs imbriqués les uns dans les autres.
* boost::lexical_cast - Transformer les chaines en flotants ou en entier sans avoir à se battre avec les vieilles atoi et atof, c'est bien vu. Encore mieux, étant une fonction template, l'on pourra parfaitement intégrer par exemple lexical_cast à sa classe de vecteurs 3D de types génériques.
* boost::posix_time - Je tremble à chaque fois que je dois gérer des dates dans mon code. posix_time et ses amis boost::gregorian et autres font leur boulot, et je tremble (un peu) moins.
* boost::optional - Un peu comme noncopyable, c'est surtout une bonne manière de commenter le code, en donnant plus de sens qu'un pointeur

Celles-là, je les utilise moins souvent, mais elles n'en sont pas moins utiles au moment où on en a besoin:
* boost::assign - Très pratique pour rapidement remplir des conteneurs, je l'utilise tout le temps dans mes tests unitaires
* boost::unit_test - En parlant de tests unitaires, voilà une belle bibliothèque qui, pour n'en être pas exhaustive, n'en remplit pas moins mes besoins. Bonus pour la quantité minimum de code de mise en place nécessaire grâce à auto_unit_test
* boost::asio - des concepts très intéressants dans cette petite nouvellement arrivée et qui propose un cadre plutôt bien conçu pour gérer ses connections réseaux et évènements asynchrones.

En revanche, je déconseille fortement boost::iostreams::zlib! Utilisez plutôt mon wrapper dans le post précédent :)

samedi 3 mai 2008

Zlib et C++

Depuis longtemps j'avais tenté d'éviter ce moment, mais finalement je m'y suis mis: écrire une couche d'abstraction c++ au dessus de zlib. En suivant pas à pas la documentation, et en remplaçant les concepts C par des concepts C++ (principalement l'utilisation des streams), j'ai fini par obtenir un bout de code qui me semble fonctionner correctement, sans trop avoir à se casser la tête.

Ce qui m'y a poussé, c'est d'abord que j'avais besoin d'un bout de code comme cela pour le boulot. Je me servais de boost::iostream, mais soit je l'utilise mal, soit il reste des bugs dans cette bibliothèque de boost (ce qui n'est impossible, en voyant la mailing-list), mais Purify m'indiquait de nombreuses fuites mémoires dues à la compression. Le code étant trop complexe pour un fix rapide, et ne pouvant de toutes façons pas patcher boost à travers l'ensemble de la boite, je me suis mis à écrire ce wrapper minimaliste.

C'est quand même une certaine déception que ces nouvelles bibliothèques de Boost aient autant de problèmes. Leur wrapper zlib fuit et n'accepte de toutes façons pas les valeurs négatives pour le paramètre "window" permettant de supprimer l'en-tête et le hash final, le rendant incompatible avec le cryptokit ocaml. Et la bibliotèque d'encodage en base 64 a des soucis de fin d'encodage (les '=' qui doivent indiquer comment se finit le flux). Quand on voit le temps qu'il faut pour compiler tout ça...

Comme cadeau bonus, voilà mon code, que vous pouvez bien entendu utiliser comme bon vous semble. Bien entendu, je ne garantis rien, donc si en l'exécutant, vous coupez l'alimentation oxygène de l'IIS, il ne faudra vous en prendre qu'à vous même. Na!


/* This program is free software. It comes without any warranty, to
* the extent permitted by applicable law. You can redistribute it
* and/or modify it under the terms of the Do What The Fuck You Want
* To Public License, Version 2, as published by Sam Hocevar. See
* http://sam.zoy.org/wtfpl/COPYING for more details. */

#include "zlib.h"
#include <string>
#include <istream>
#include <sstream>
#include <stdexcept>
#include <iostream>

namespace
{
const int chunk = 256 * 1024;
}

void compress(std::istream & input, std::ostream & output, int level = 8, int window = 15)
{
unsigned char in[chunk];
unsigned char out[chunk];
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;

if(deflateInit2(&strm,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
window,
level,
Z_DEFAULT_STRATEGY) != Z_OK)
{
throw std::runtime_error("deflateInit");
}

int flush;

do
{
input.read(reinterpret_cast<char*>(in), chunk);
strm.avail_in = input.gcount();
flush = (input.eof() || input.fail()) ? Z_FINISH : Z_NO_FLUSH;
strm.next_in = in;
do
{
strm.avail_out = chunk;
strm.next_out = out;
deflate(&strm, flush);
output.write(reinterpret_cast<char*>(out), chunk - strm.avail_out);

}
while (strm.avail_out == 0);
}
while(flush != Z_FINISH);

deflateEnd(&strm);
}

void decompress(std::istream & input, std::ostream & output, int window = 15)
{
unsigned char in[chunk];
unsigned char out[chunk];
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
if(inflateInit2(&strm,
window))
{
throw std::runtime_error("inflateInit");
}

int ret;
do
{
input.read(reinterpret_cast<char*>(in), chunk);
strm.avail_in = input.gcount();
if(strm.avail_in == 0)
{
inflateEnd(&strm);
throw std::runtime_error("unexpected end of stream");
}
strm.next_in = in;
do
{
strm.avail_out = chunk;
strm.next_out = out;
ret = inflate(&strm, Z_NO_FLUSH);
if(ret == Z_STREAM_ERROR ||
ret == Z_NEED_DICT ||
ret == Z_DATA_ERROR ||
ret == Z_MEM_ERROR)
{
inflateEnd(&strm);
throw std::runtime_error("inflate");
}
output.write(reinterpret_cast<char*>(out), chunk - strm.avail_out);

}
while(strm.avail_out == 0);
}
while(ret != Z_STREAM_END);
inflateEnd(&strm);
}


int main()
{
std::istringstream input("aaa");
std::ostringstream output;
compress(input, output);
std::cout << output.str() << std::endl;

std::istringstream input2(output.str());
std::ostringstream output2;
decompress(input2, output2);
std::cout << output2.str() << std::endl;

return 0;
}