vendredi 25 juillet 2008

Pourquoi je m'embête avec les licenses

Certains auront pu remarquer que mes extraits de code contenaient une en-tête indiquant qu'ils étaient placés sous la WTFPL.

La raison en est que lorsque l'on veut utiliser un bout de code récupéré sur le Net, bien souvent il n'y a aucune indication sur ses conditions d'utilisation. Doit-on considérer qu'il est par défaut libre de droits (même si dans beaucoup de législations, on ne peut pas décider de "placer" son œuvre dans le domaine public)? Est-ce autorisé pour une utilisation non commerciale?

Bref, la plupart du temps, et moi le premier, on choppe le code, on l'utilise, et on oublie très vite d'où il vient.

Ce n'est cependant pas une raison pour que je fournisse du code avec la même désinvolture. Je le dis donc haut et clair, faites ce que vous voulez avec!

Comment afficher une image dans une wxGrid?

Ma grille de commerce sera bien plus jolie si elle contient des images. Cela tombe bien, wxGrid est très extensible, et tout plein de gens l'ont fait avant moi. Par contre, il m'a quand même fallu recoller les bouts!

La seule spécificité du code est le passage par un wxMemoryDC, qui permet donc de faire un blit exactement sur le rectangle au lieu de baver sur les autres cellules si l'on utilise tout bêtement un DrawBitmap.

Voilà le résultat:



Et voilà bien entendu le code.

wxBmpCellRenderer.h


/* 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
#include

class wxBmpCellRenderer : public wxGridCellRenderer
{
public:
wxBmpCellRenderer(const wxBitmap & bmp);
void Draw(wxGrid& grid,
wxGridCellAttr& attr,
wxDC& dc,
const wxRect& rect,
int row,
int col,
bool isSelected);

wxSize GetBestSize(wxGrid& grid,
wxGridCellAttr& attr,
wxDC& dc,
int row,
int col);

wxGridCellRenderer *Clone() const;
private:
wxBitmap m_bmp;
};


wxBmpCellRenderer.cpp

/* 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 "wxBmpCellRenderer.h"

wxBmpCellRenderer::wxBmpCellRenderer(const wxBitmap & bmp):
m_bmp(bmp)
{
}

void wxBmpCellRenderer::Draw(wxGrid& grid,
wxGridCellAttr& attr,
wxDC& dc,
const wxRect& rect,
int row,
int col,
bool isSelected)
{
wxGridCellRenderer::Draw(grid,attr,dc,rect,row,col,isSelected);
wxMemoryDC memdc;
memdc.SelectObject(m_bmp);
dc.Blit(rect.GetX(), rect.GetY(), rect.GetWidth(), rect.GetHeight(),
&memdc, 0, 0);
}

wxSize wxBmpCellRenderer::GetBestSize(wxGrid& grid,
wxGridCellAttr& attr,
wxDC& dc,
int row,
int col)
{
return wxSize(m_bmp.GetWidth(), m_bmp.GetHeight());
}

wxGridCellRenderer * wxBmpCellRenderer::wxBmpCellRenderer::Clone() const
{
return new wxBmpCellRenderer(m_bmp);
}

jeudi 24 juillet 2008

Les ordres

C'est parti, j'ai commencé à coder le système d'ordres. Après avoir fortement typé mes prix et mes quantités, j'ai pu coder un ordre comme ayant un identifiant personnel, un identifiant d'objet, un prix et une quantité. Pour les actions sur les ordres, c'est on ne peut plus simple: on ajoute un ordre, ou on le supprime!

Maintenant, il me reste à trouver une bonne structure de données pour maintenir les ordres du côté du serveur (avec probablement de la persistance dans la base de données), et une bonne structure de données pour envoyer les données de marché vers les clients.

dimanche 20 juillet 2008

Des nombres fortement typés

Je viens de terminer mon dernier test unitaire (vive la Boost Test Library!) sur ma classe de nombres fortement typés. L'idée est, comme pour les identifiants, d'éviter de s'emmêler les pinceaux en comparant ou effectuant d'autres opérations sur des nombres qui n'ont rien à voir. C'est à dire que quand j'ajoute des choux et des carottes, je veux que le compilo gueule bien fort.

Le souci, c'est qu'il faut ajouter à notre classe de nombres forts tous les opérateurs généralement présents sur les nombres, et il y en a un paquet. Heureusement, vive la Boost Operators Library vient à notre rescousse, en déduisant tout un tas d'opérateurs à partir d'une liste restreinte à implémenter.


template<typename D, typename T>
class Number : boost::operators<Number<D, T> >
{
public:
Number();
Number(const Number & n);
Number(const T & value);

bool operator<(const Number & n) const;
bool operator==(const Number & n) const;
Number & operator+=(const Number & n);
Number & operator-=(const Number & n);
Number & operator*=(const Number & n);
Number & operator/=(const Number & n);
Number & operator%=(const Number & n);
Number & operator|=(const Number & n);
Number & operator&=(const Number & n);
Number & operator^=(const Number & n);
Number & operator++();
Number & operator--();

T getValue() const;

private:
T m_value;
};


Et avec ceci, les opérations +, -, *, / et les autres sont toutes définies gentiment.

jeudi 17 juillet 2008

Le marché - Flux de données

Pour avoir un peu côtoyé ce genre de code, je sais qu'il est parfois un poil complexe d'afficher tous les prix d'une marchandise donnée dans une liste achat/vente. La raison en est que de nouveaux prix vont devoir s'insérer, ou des prix visibles disparaitre en faisant remonter toute la pile. Il faut donc gérer au niveau du client un registre local des prix, mis à jour par le serveur, et qui peut afficher la profondeur de prix voulue par l'utilisateur. De plus, l'utilisateur peut reconnaître certains prix particuliers, comme les siens, ou ceux entrés par un groupe d'utilisateurs particuliers. Ceci veut dire que le serveur doit envoyer une vue légèrement différente à chaque utilisateur, en cachant les identités des prix pour lesquels l'utilisateur n'a pas de permissions.

Bien entendu, tout un tas de monde s'est heurté au même souci avant moi, et c'est pour cela que la plupart des logiciels utilisés dans les échanges séparent d'un côté les ordres de l'utilisateur, et de l'autre les données de marché. Ainsi, plutôt que de voir la liste des prix, avec par exemple ses propres prix surlignés, l'utilisateur voit le marché, qui est l'agrégation des prix les plus hauts (bas pour les ordres de vente), et la liste de ses propres ordres. Le serveur envoie donc la même vue du marché à tous, et seulement les mises à jour de leurs propres ordres à chaque utilisateur.

Vous l'aurez deviné, c'est bien cette deuxième méthode que j'ai l'intention d'utiliser dans AdH!

mardi 8 juillet 2008

Actions longues

Enfin, c'est implémenté! Ce système permet maintenant à une action de s'effectuer sur la durée. Le joueur démarre donc une recherche, et en reçoit le résultat 30 secondes plus tard. S'il démarre une autre recherche, ou change de lieu, la recherche courante est annulée.



Tout est dans la base de données, il est donc possible pour chaque lieu d'y ajouter la possibilité d'y rechercher des choses. Chaque recherche est définie par le temps passé à chercher, et d'une liste d'objets potentiellement trouvables avec leurs probabilités respectives. Il est ainsi possible de donner 1 chance sur 1000 de trouver une perle naturelle sur la plage!

dimanche 6 juillet 2008

Ils font rire les gosses mes static*

[*] Point bonus à ceux qui pigent la vanne

Le mot-clé "static" est une cochonnerie. Ou plutôt, un outil bien spécifique utilisé à tort et à travers dans le mauvais code. En fait, l'on pourrait probablement dire que "static", c'est le SUV du C++. Non, vous n'en avez pas besoin pour aller chercher le pain. Ni même pour aller en vacances à Biarritz. En revanche, si vous écumez les chemins de terre, on peut commencer à discuter.

Ecumons donc les chemins de terre!
Tout d'abord, et afin de tout compliquer, le mot-clé "static" a plusieurs utilisations.

  • Tout d'abord, et c'est un héritage du C, une variable globale "static" n'est visible que dans son unité de compilation. Cette utilisation est tombée en désuétude avec l'apparition des namespaces, et notamment des namespaces anonymes.

  • Ensuite, et c'est encore du C, une variable statique dans une fonction ou une méthode agit comme une variable globale visible uniquement dans son scope, initialisée la première fois que la fonction est appelée, et gardant sa valeur d'un appel à l'autre.

  • Enfin, et cette fois ci c'est du vrai C++, les méthodes et attributs d'une classe peuvent être déclarés static, c'est à dire relatifs à une classe et non à une instance.

J'ai trouvé que la variable statique dans une fonction était particulièrement utile pour débugger son programme. Par exemple, je cherche à afficher pour chaque appel de la fonction quelles étaient ses paramètres:


int f(int a, int b)
{
static n = 0;
n++;
std::cout << "Appel" << n << " - " << a << ", " << b << std::endl;
...
}


Ceci affichera, par exemple:

Appel 1 - 3, 7
Appel 2 - 7, 43
...
Appel 254 - 4, 25

Voilà, mais par contre, ce n'est pas particulièrement utile en production!

Une deuxième utilisation est d'utiliser une variable statique comme cache. Par exemple, imaginons que la fonction f soit très gourmande en calcul, mais que par ailleurs elle soit uniquement dépendante de ses paramètres, et qu'elle soit souvent appelée avec les mêmes. L'on peut faire la chose suivante:


int f(int a, int b)
{
typedef std::map<std::pair<int, int>, int> CacheT;
static CacheT cache;
CacheT::const_iterator it = cache.find(std::make_pair(a, b));
if(it != cache.end())
{
return it->second;
}
else
{
int result = calcul_long(a, b);
cache.insert(std::make_pair(std::make_pair(a, b), result));
return result;
}
}


Et voilà, un beau cache ajouté à la fonction, sans rien cochonner autour! Par contre, cette technique montre assez rapidement ses limites, particulièrement lorsque le code est inclus dans un service censé tourner 24h/24. Il est en effet impossible de vider le cache, ou d'en tirer des statistiques. Même si l'on implémente un cache plus malin qui ne garde que les n dernières entrées, la taille des entrées est fixée à la compilation. Ou alors, il faut la passer systématiquement en paramètre de la fonction. Si c'est le cas, pourquoi ne pas passer le cache lui-même, ce qui sera bien plus propre. Et que l'on ne vienne pas me parler de singleton, les variables globales, c'est le mal!


En parlant de variables globales, causons un peu des méthodes et attributs statiques. Sur le sujet, je dirais que c'est rarement nécessaire, et souvent dangereux, en dehors des cas où ils sont utilisés à travers des templates. Par exemple, lors de l'implémentation d'une factory qui prend mes messages ayant des identifiants.


class LoginMessage
{
...
static int getId() {return 1};
};

class LogoutMessage
{
...
static int getId() {return 2;}
};


Quand j'enregistre mon message dans ma factory en passant le message comme un paramètre template, je peux en extraire l'identifiant.


template<typename T>
registerMessage()
{
m_messageMap.insert(T::getId(), ...); // ... est par exemple un pointeur vers une fonction qui renvoie un nouvel objet T
}


Et je peux donc enregistrer mes messages ainsi:


factory.registerMessage<LoginMessage>();
factory.registerMessage<LogoutMessage>();


Voilà, j'en ai écrit un peu plus que ce que j'aurais imaginé. Probablement la frustration de devoir maintenir du code plein d'horreurs statiques dans tous les sens. Alors, "static", évitez-le quand vous le pouvez, utilisez-le quand vous le devez!

samedi 5 juillet 2008

Wrapper zlib, un panégyrique

Il semblerait bien que mon wrapper C++ de la zlib soit un particulièrement recherché, si j'en crois mes stats. Je tiens donc à préciser que le bout de code en question tourne joyeusement en production au boulot, et que je l'utilise également dans le code réseau d'AdH. Pour l'instant, pas de bugs en vue, et une utilisation plutôt sobre de la mémoire.

En regardant les autres implémentations sur le net, l'on s'aperçoit que beaucoup sont centrées autour des fichiers, ce que je trouve plutôt étonnant. Je ne vois en effet pas vraiment l'intérêt de s'amuser à compresser ou décompresser un fichier à partir d'un programme C++. Si par exemple l'on est inquiet de la taille que prennent les logs, pourquoi ne pas avoir une tâche quotidienne qui appelle un bon vieux tar bz2? Si ce sont des données binaires, sont-elles si grosses qu'elles vaillent la peine de se fatiguer à les compresser, pour un gain généralement assez faible? Si c'est du XML, est-ce la peine de perdre le gros avantage de pouvoir l'éditer à la main? Bien sûr, il y a des cas où utiliser directement zlib pour compresser un fichier aura un avantage, mais à ce point?

En l'occurrence, j'utilise mon wrapper dans deux cas. D'une part, pour sauver des fichiers XML dans une base de données. Nos utilisateurs veulent sauver des frames XML de tailles qui s'approchent du mégaoctet, et la base n'a pas beaucoup de place libre. La compression permet de gagner jusqu'à 20 fois l'espace, et nos utilisateurs, au lieu de faire sauter la base au bout de 1000 entrées, ce qui risque d'arriver dans l'année, pourront insérer jusqu'à 20 000 entrées, ce qui nous permet de souffler un peu. Et j'en conviens, avoir seulement 1 gigaoctet d'espace dans la base, c'est pas malin, mais que voulez-vous, c'est corporate...

D'autre part, et c'est là que la simplicité prime, compresser les paquets que l'on envoie à travers le réseau résulte en un très bon gain de performances, et permet de voir venir des structures beaucoup plus grosses. Bien sûr, n'oubliez pas que le gain dépend de la taille des données, et qu'il vous faut trouver la limite à partir de laquelle il est intéressant de compresser! Décidément, c'est la journée des backlinks, j'en avais causé ici.

Et dans ces deux cas, je ne peux m'empêcher que l'approche fonctionnelle, une fonction pour compresser, une autre pour décompresser, est de loin la plus simple et la plus élégante! Pourquoi aurait-on à créer un objet, et s'embêter avec deux ou trois couches d'abstraction? Ou pire, utiliser des méthodes statiques! Beurk...

Du simple (deux petites fonctions), du fonctionnel (fonctions libres, pas d'effets de bord), du standard (istream, ostream, vous pouvez même passer un fichier si ça vous fait plaisir).

Oh, et j'ai reçu mon WTF mug! Cool, non?