dimanche 21 novembre 2010

Boost asio - Queues de messages

La bibliothèque Asio, intégrée à Boost, permet de gérer des événements de manière asynchrone. Principalement utilisée pour le réseau, elle peut également être très utile pour résoudre des problèmes de programmation concurrente. Un exemple très simple: la gestion de queues de messages.

Prenons donc une classe qui affiche des messages sur la console. Une manière de gérer l'accès concurrent est de mettre un bon vieux mutex autour de l'appel à std::cout, forçant chaque thread à attendre sur des opérations d'entrées sorties potentiellement longues. Une autre manière, souvent plus efficace, est d'ajouter le message sur une queue, laquelle est lue par un thread unique chargé de l'affichage. Comment faire de même avec Asio?


class Logger
{
public:
Logger():
m_work(m_service),
m_thread(boost::bind(&boost::asio::io_service::run,
boost::ref(m_service)))
{
}

~Logger()
{
m_service.stop();
m_thread.join();
}

void log(const std::string & message)
{
m_service.dispatch(boost::bind(&Logger::doLog, this, message));
}

private:
void doLog(const std::string & message)
{
std::cout << message << std::endl;
}

boost::asio::io_service m_service;
boost::asio::io_service::work m_work;
boost::thread m_thread;
};

Cette classe s'utilise simplement en instanciant l'objet Logger, puis en appelant la méthode log. L'appel à log est thread safe, et peut donc être appelé par n'importe quel thread de l'application. L'affichage, lui, ne tourne que depuis le thread interne de la classe. Pas de mutex, pas de conditions, tout est planqué dans Asio.

Remarquez l'objet work, qui indique à m_service qu'il y a toujours quelque chose à faire, pour éviter que m_service.run() ne retourne prématurément.

Et pour l'opération inverse, c'est à dire l'utilisation d'une queue de messages pour faire tourner des calculs potentiellement lourds simultanément? C'est à peine plus complexe, comme par exemple dans cette classe qui calcule le n-ième nombre premier.


class Prime
{
public:
Prime():
m_work(m_service)
{
m_threadGroup.create_thread(boost::bind(&boost::asio::io_service::run,
boost::ref(m_service)));
m_threadGroup.create_thread(boost::bind(&boost::asio::io_service::run,
boost::ref(m_service)));
m_threadGroup.create_thread(boost::bind(&boost::asio::io_service::run,
boost::ref(m_service)));
m_threadGroup.create_thread(boost::bind(&boost::asio::io_service::run,
boost::ref(m_service)));
}

~Prime()
{
m_service.stop();
m_threadGroup.join_all();
}

void nthPrime(size_t n)
{
m_service.dispatch(boost::bind(&Prime::doNthPrime, this, n));
}

private:
void doNthPrime(size_t n)
{
std::vector primes(1, 2);
size_t current = 2;
while(primes.size() < n)
{
current++;
size_t i;
for(i = 0; i < primes.size(); ++i)
{
if(current % primes.at(i) == 0)
break;
}
if(i >= primes.size())
{
primes.push_back(current);
}
}
std::cout << primes.at(n - 1);
}

boost::asio::io_service m_service;
boost::asio::io_service::work m_work;
boost::thread_group m_threadGroup;
};

Faisant tourner 4 threads à partir du bon vieux thread pool de chez Boost, cette classe va empiler les requêtes et n'en faire tourner que 4 maximum à la fois.

L'étape suivant serait d'appeler Logger depuis Prime, afin de n'afficher le résultat que depuis un seul thread. L'on peut ainsi construire un programme à partir de briques de base qui gèrent elles-mêmes l'accès concurrent, sans avoir à se (trop) se préoccuper de l'accès concurrent ou d'interbloquages.

Cette approche est cependant limitée par l'explosion du nombre de threads dans l'application, chaque brique gérant un certain nombre de threads indépendants. Lorsque la charge de travail est lourde, l'on risque de se retrouver avec beaucoup plus de threads tentant de s'exécuter que de processeurs.

L'on peut alors modifier quelque peu son approche, pour partager un même service asio au sein du programme, et laisser à la bibliothèque le soin d'ordonnancer les tâches.

S'assurer que l'accès à certaines ressources demeure séquentiel, comme pour notre Logger, peut se faire grâce aux strands. Démonstration dans un prochain post.

vendredi 19 novembre 2010

Pas assez d'objet?

Je ne suis pas satisfait de mon design autour des concepts principaux d'OpenRailz que sont les sections de rails et les stations. Peut-être ais-je péché par excès d'anti-POO primaire, et, cherchant un style plus fonctionnel, me retrouve avec quantités de collections d'objets. Je cherche donc à centraliser un petit peu, pour une fois, en utilisant le bon vieil héritage multiple (que je pourrais transformer en interface à un moment ou à un autre, c'est à voir).

Le résultat, c'est que plutôt que de séparer ma station de l'entité la représentant, je créé une classe StationEntity qui dérive à la fois de Station et de OsgEntity.

Nous verrons ce que cela donne.

dimanche 14 novembre 2010

Fallout 3 - New Vegas - C'est plié

Après 50 heures de jeu, me voilà déjà à la fin... Ou plutôt, une des fins. Fallout 3 New Vegas a tenu toutes les promesses, les quêtes étaient intéressantes, interactives et profondes, les personnages attachants, et l'histoire tout à fait prenante.

J'hésite maintenant entre reprendre une sauvegarde un peu en arrière et essayer d'autres fins, ou tout redémarrer du début avec une ligne très différente. Mais là, tout de suite, je vais plutôt me remettre à coder. Faut que je me remette de mon overdose!

jeudi 4 novembre 2010

Coder efficacement avec Emacs - Partie 5: Scripts utiles

Ou comment sauver un temps précieux avec quelques fonctions simples. Voici deux scripts glanés ici, adaptés par mes soins, qui facilitent la vie. Ajoutez les à votre .emacs, et voyez si ça vous plaît.

Ce script écrit de lui-même les #ifndef / #define / #endif des fichiers en-tête C et C++, en se basant sur le nom du fichier et du répertoire courant.


(defun headerize ()
"Adds the #define HEADER_H, etc."
(interactive)
(let ((flag-name (replace-regexp-in-string
"[\. \(\)]" "_"
(upcase (file-name-nondirectory (buffer-name)))))
(dir-name (upcase(file-name-nondirectory (directory-file-name(file-name-directory (buffer-file-name)))))))
(goto-char (point-max))
(insert "\n#endif\n")
(goto-char (point-min))
(insert (concat "#ifndef __" dir-name "_" flag-name "__\n"))
(insert (concat "#define __" dir-name "_" flag-name "__\n"))
)
)


Ouvrez un fichier, et tapez M-x headerize. Hop, voilà les en-têtes! Le fichier peut être modifié pour ajouter une mention de copyright, un auteur, etc. C'est toujours ça de moins à taper.

J'adore celui-ci: en pressant la touche F9, l'on passe automatiquement du fichier source au fichier d'en-tête, et réciproquement (à condition que les fichiers aient le même nom et soient dans le même répertoire, c'est pas magique non plus!).


(define-key global-map [f9] 'switch-on-extensions)

(defun rewrite-filename (filename rules)
(if rules (or
(let ((current-rule (car rules)))
(and (string-match (car current-rule) filename)
;; The current rule can be applied, return the
;; modified filename
(let ((name (replace-regexp-in-string
(car current-rule) (cadr current-rule)
filename)))
;; We return the name if either it can be
;; created (the rule has 't' for third value) or
;; if it exists already.
(and (or (cadr (cdr current-rule)) (file-exists-p name))
name))))

;; In any other case, we go on looking for other rules
(rewrite-filename filename (cdr rules)))

(error (concat "No rewriting rule applicable for '" filename "'"))))

(defun switch-on-extensions ()
"Switches to a file whose name is derived from the name of the
current buffer. The rewriting rules are specified in
`switch-on-extensions-rules'."
(interactive)
(if (buffer-file-name)
(find-file (rewrite-filename (buffer-file-name) switch-on-extensions-rules))
(error "No file attached to this buffer!")))

(setq switch-on-extensions-rules '(
("\\.cpp$" ".h" t)
("\\.h$" ".cpp" t)
("\\.cc$" ".h" t)
("\\.h$" ".cc")
("\\.ml$" ".mli" t)
("\\.mli$" ".ml" t)
("" "" )
))

dimanche 31 octobre 2010

Fallout 3 - New Vegas

Voici donc la raison de mon silence radio de ces derniers temps (et cela risque de durer un peu plus longtemps!). Grand fan de Fallout 3, je ne pouvais laisser passer l'opus suivant, et la chose est tout à fait à la hauteur de ses ambitions. Pas de révolution, juste un univers encore plus complet et cohérent, une histoire prenante, des personnages déjantés, et des steaks de geckos.

C'en est au point que j'aperçois des radscorpions quand je vais au boulot le matin.

Les quelques crashs et bugs graphiques ne sont pas suffisants pour modérer mon enthousiasme. J'y retourne!

mercredi 20 octobre 2010

Le syndrome des 15 000 lignes

Je suis une fois de plus frappé par ce mal mystérieux qui surgit lorsque que mon application atteint la limite fatidique des 15 000 lignes. Je sais que je suis près d'avoir un système de destinations qui fonctionne, et je suis plutôt satisfait du moteur graphique. Je ne suis pas convaincu par la disposition des rails, mais à la limite cela peut se résoudre plus tard.

Mais voilà, la motivation m'a (momentanément) abandonnée. Je n'arrive plus à écrire la moindre ligne de code. Un petit peu de patience sera donc requise de la part de mes hordes de fans attendant avec avidité un Openrailz jouable :)

samedi 2 octobre 2010

GUI - Itinéraires et propriétés

Le système d'itinéraires commence à tenir la route. J'ai également rajouté un autre panneau pour afficher les propriétés de l'objet courant, comme une gare ou un train.



Ces nouveaux panneaux réduisent la partie 3D à sa portion congrue (et encore, question gui, vous n'avez rien vu!), mais c'est heureusement là que la possibilité de déplacer et de cacher certaines sections va être fondamentale.