lundi 20 décembre 2010

Bug!

Les rails sont de retour, mais j'ai du rater une étape:



Voyage mouvementé en perspective?

Contrôle

Avec la réorganisation du système d'entités graphiques, construire sa voie ferrée devient plus facile (ou moins frustrant, c'est selon). Voici donc mon petit message de Noël en traverses de chemin de fer:

jeudi 16 décembre 2010

C++0x - Moins de Boost, plus de Standard

Je me suis livré à un petit nettoyage de mes en-têtes précompilées, en enlevant les en-têtes Boost pour lesquelles j'avais un remplacement en C++0x.

Peu de gain, au final, puisque j'économise environ 12Mo sur des précompilations faisant entre 120 et 300Mo, mais c'est toujours ça de pris, sans compter un code plus cohérent. Voici les remplacements:

  • <boost/shared_ptr.hpp> est remplacé par <memory> et son std::shared_ptr

  • <boost/scoped_ptr.hpp> et <boost/scoped_array.hpp> sont remplacés par l'excellent std::unique_ptr

  • <boost/static_assert.hpp> est remplacé par static_assert

  • <boost/optional.hpp> vire à cause des soucis d'aliasing

  • <boost/enable_shared_from_this.hpp> est fourni par std::shared_from_this

  • <boost/thread.hpp> est remplacé par <thread>. Attention cependant, pas encore de groupes de threads dans le standard!

  • <boost/non_copyable.hpp> est remplacé par les suppressions de méthodes (mot-clé delete)

samedi 11 décembre 2010

Bibliothèque piglet

La bibliothèque piglet pour "Platform Independent Graphical Layer Entity Tools" (désolé, pas pu m'empêcher...) sépare enfin le système d'entités, lequel reçoit un lifting à la mesure de son importance. J'ai des pages couvertes de diagrammes de classes, mais l'idée est d'avoir plusieurs types d'entités, contenant chacun un objet graphique abstrait. L'implémentation OSG s'effectue en implémentant l'interface d'objet graphique, et la modification d'objets s'opère via un visiteur (après toutes ces années, j'ai enfin compris qu'un visiteur n'était qu'un double dispatch un peu bidouillé, mieux vaut tard que jamais...).

Dans l'immédiat, ça ne va pas donner beaucoup de différences à l'écran (et ça commence à faire un moment, me hurlera ma horde de fans déchaînés, qui n'aura pas tort), mais cela permettra enfin, je l'espère, d'implémenter de manière à peu près correcte des objets qui s'animent en temps réel, notamment les voies lors de l'ajustement des waypoints.

C'est reculer pour mieux sauter, et c'est probablement la meilleure manière d'arriver quelque part. Le système d'entités a démarré très simplement, et a fait un boulot admirable jusqu'ici. Les besoins autour de l'affichage des objets graphiques sont maintenant plus clairs, et surtout j'ai une implémentation qui marche. C'est donc maintenant, ni trop tôt et ni trop tard, qu'est le moment idéal pour retravailler le module en profondeur.

Sinon, et sur un tout autre sujet, je pense enfin pouvoir me débarrasser des derniers boost::shared_ptr du code et les remplacer par des std::shared_ptr, avec l'espoir, entre autres, de virer quelques fichiers d'en têtes de ma pré-compilation pour retarder du même coup le moment où il va me falloir investir dans quelques barrettes de RAM.

mardi 7 décembre 2010

C++0x - Chronomètres

Enfin, et c'est pas trop tôt, une interface C++ pour mesurer le temps. Celle-ci vient de plus avec d'intéressantes fonctionnalités. Mais voyons d'abord un petit programme d'exemple:


#include <chrono>
#include <iostream>

int main()
{
auto t1 = std::chrono::system_clock::now();
usleep(100000);
auto t2 = std::chrono::system_clock::now();

std::cout << (t2 - t1).count() << std::endl;

return 0;
}

Tout bête! Remarquez l'utilisation de auto pour ne pas avoir à m'ennuyer à définir le complexe type retourné par la méthode statique. Cependant, et c'est là que je trouve que le standard fait très fort, les types fournis permettent de s'affranchir totalement des problèmes d'unités souvent rencontrés dans ces cas là: quelle est la précision fournie par la bibliothèque? Et quelle est la précision voulue par l'utilisateur? C'est là que le type duration (ici implicite) de (t2 - t1) donne toute sa mesure. Regardons plutôt:

#include <chrono>
#include <iostream>

int main()
{
auto t1 = std::chrono::system_clock::now();
usleep(100000);
auto t2 = std::chrono::system_clock::now();

std::chrono::nanoseconds n = (t2 - t1);
std::cout << n.count() << std::endl;

std::chrono::microseconds u = (t2 - t1);
std::cout << u.count() << std::endl;

std::chrono::duration<double> d = (t2 - t1);
std::cout << d.count() << std::endl;

return 0;
}

Là où ça devient magique, c'est que la valeur est automatiquement convertie dans l'unité demandée. Le programme affiche:

100059000
100059
0.100059

Ces types std::chrono::nanoseconds et std::chrono::microseconds sont des typedef sur le type duration, lequel prend comme paramètres templates le type de base, et un ratio (avec un défaut de 1, pour les secondes).

Et, cerise sur le gâteau, g++ refusera même de compiler une conversion non exacte. La ligne
std::chrono::seconds s = (t2 - t1);
renvoie le message d'erreur suivant:
error: static assertion failed: "the resulting duration is not exactly representable"
En effet, le typedef std::seconds prenant en type de base un entier, la conversion perd en précision. Pour que cela marche, il faut utiliser un double, comme dans l'exemple précédent.

Qui a dit que le C++ n'était pas intuitif? :)

lundi 6 décembre 2010

C++0x - std::atomic

Les variables atomiques dans c++0x fournissent (enfin!) une manière standard pour protéger les types de base, avec un support automatique des instructions atomiques quand elles sont disponibles. Voyons le programme suivant avec g++4.4.


#include <thread>
#include <iostream>

void increment(int & counter)
{
for(size_t i = 0; i < 10000; i++)
{
counter++;
}
}

void decrement(int & counter)
{
for(size_t i = 0; i < 10000; i++)
{
counter--;
}
}

int main()
{
int counter;

std::thread t1(&increment, std::ref(counter));
std::thread t2(&decrement, std::ref(counter));

t1.join();
t2.join();

std::cout << counter << std::endl;

return 0;
}

C'est prévisible, le programme va afficher des nombres totalement farfelus en sortie, car counter n'est pas protégé.

Maintenant, protégeons counter:

#include <cstdatomic>
#include <thread>
#include <iostream>

void increment(std::atomic<int> & counter)
{
for(size_t i = 0; i < 10000; i++)
{
counter++;
}
}

void decrement(std::atomic<int> & counter)
{
for(size_t i = 0; i < 10000; i++)
{
counter--;
}
}

int main()
{
std::atomic<int> counter;

std::thread t1(&increment, std::ref(counter));
std::thread t2(&decrement, std::ref(counter));

t1.join();
t2.join();

std::cout << counter << std::endl;

return 0;
}

Magie de la technologie moderne, le programme affiche systématiquement un beau 0 tout rond. Un appel de la méthode is_lock_free() sur counter (atomic_is_lock_free() sur gcc 4.5, plus avancé au niveau des standards) vous dira si votre architecture supporte les instructions atomiques.

Voilà, c'est à peu près tout! A noter cependant que l'en-tête atomic a fait son arrivée dans gcc 4.5. Avec cette version, le programme d'exemple (de mémoire), serait (et avec les lambdas, s'il vous plait!):

#include <atomic>
#include <thread>
#include <iostream>

int main()
{
std::atomic<int> counter;

std::thread t1([&counter]()
{for(size_t i = 0; i < 10000; i++) counter++;});
std::thread t2([&counter]()
{for(size_t i = 0; i < 10000; i++) counter--;});

t1.join();
t2.join();

std::cout << counter << std::endl;

return 0;
}

mercredi 1 décembre 2010

C'est mieux

Les UserObject et les callbacks semblent donner un design un peu plus raisonnable. L'envers de la médaille, c'est que je suis revenu d'un bon en arrière, et je me retrouve à ré-implémenter la création des waypoints (va falloir que je trouve bon un nom en français).

Rendez-vous dans quelques semaines pour redécouvrir Openrailz pareil qu'aujourd'hui :)