samedi 27 novembre 2010

C++0x - shared_ptr, bind, et for_each

Pour ceux qui utilisaient déjà Boost, rien de fondamentalement nouveau sous le soleil: C++0x fournit enfin en standard une implémentation de pointeur partagé, ainsi que la fonction de bind qui va bien. Le vieux code Boost peut être mis à jour simplement en remplaçant vos boost:: par des std::, hormis cependant une petite subtilité: les paramètres tels _1, _2, ne sont pas dans le namespace global comme avec boost, mais dans le namespace std::placeholders. L'on pourra soit faire un using namespace std::placeholders;, soit, pour éviter les conflits si l'on inclut également Boost, créer un alias afin de réduire la verbosité du code.


#include <iostream>
#include <algorithm>
#include <memory>
#include <vector>
#include <string>

namespace p = std::placeholders;

class Miaou
{
public:
Miaou(const std::string & v):
m_v(v)
{
}

void print()
{
std::cout << m_v << std::endl;
}

private:
std::string m_v;
};

int main()
{
std::vector<std::shared_ptr<Miaou> > v =
{std::shared_ptr<Miaou>(new Miaou("a")),
std::shared_ptr<Miaou>(new Miaou("b")),
std::shared_ptr<Miaou>(new Miaou("c"))};

std::for_each(v.begin(),
v.end(),
std::bind(&Miaou::print, p::_1));

return 0;
}


Avec ces constructions additionnelles de ce côté, et les lambdas de l'autre, ce sont tous les algorithmes qui deviennent bien plus utiles. Je rêve de pouvoir enfin donner à std::accumulate la place qui lui revient!

mercredi 24 novembre 2010

Ça n'aura pas duré longtemps...

mais mon design est pourri. Je vous rappelle l'idée: faire dériver chaque objet (station, section de rail, etc) à la fois d'un objet logique et d'un objet graphique. Le problème, il est que les objets sont inter-dépendants: la création d'une station force la création de sections de rails associés. Donc, quand l'objet logique station est créé, comment fait-il pour créer les objets complets (logique + graphique) pour la section? L'on n'est limité à passer de complexes factories un peu partout. C'est moche, c'est compliqué, et c'est intestable. Bref, à jeter.

Je suis vraiment au carrefour de l'architecture du truc, si je le fais bien, ça ira tout seul, sinon, je risque fort de me lasser à force de me battre avec le code.

C'est pas grave, j'ai des tas d'idées. Dériver de l'objet logique était bien pour la gestion des objets, de leur construction et de leur destruction. Je garde donc l'idée que l'objet logique doit pouvoir posséder l'objet graphique, mais j'essaie de découpler.

Chaque objet logique possède donc un "UserObject", qui dérive d'une interface. En plus de cela, la classe World est munie d'un dispatcheur, qui permet au système graphique de détecter toutes le créations et destructions d'objets logiques. À la création d'un objet logique, le système graphique peut ainsi ajouter un "UserObject" graphique. A la destruction, l'objet graphique s'en va en même temps que l'objet logique.

Le concept de user objets est très courant, comme par exemple dans wxWidgets ou dans Open Scene Graph. J'ai donc bon espoir que l'idée colle également à ma logique.

dimanche 21 novembre 2010

Boost asio - Queues de messages - Un exemple complet

Voilà un exemple complet avec un service unique, et des queues de messages parallélisables (pour Prime) et séquentielles (pour Logger). Dans l'objet Logger, plutôt que de maintenir une référence sur le service, on construit un objet strand, lequel garantit que tous les appels passés par lui seront séquentiels.

Notez la création et la destruction de l'objet m_work. Lorsqu'il est détruit, il assure que les appels à io_service::run vont retourner une fois que tous les messages auront été traités, ce qui permet de terminer l'application une fois que toutes les requêtes auront été complétées. Afin également d'éviter la destruction prématurée des objets Logger et Prime, les appels à boost::bind prennent un shared_from_this qui va garder une référence sur l'objet au sein du message.

Pour compiler sous Unix:

g++ main.cpp -o main -lboost_system-mt -lboost_thread-mt


#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

using namespace boost::assign;

class Service : public boost::noncopyable
{
public:
Service(int nbThreads):
m_work(new boost::asio::io_service::work(m_service))
{
for(int i = 0; i < nbThreads; ++i)
{
m_threadGroup.create_thread
(boost::bind(&boost::asio::io_service::run,
boost::ref(m_service)));
}
}

~Service()
{
m_work.reset();
m_threadGroup.join_all();
}

boost::asio::io_service & getService()
{
return m_service;
}

private:
boost::asio::io_service m_service;
boost::scoped_ptr m_work;
boost::thread_group m_threadGroup;
};

class Logger : public boost::noncopyable,
public boost::enable_shared_from_this
{
public:
Logger(boost::asio::io_service & service):
m_strand(service)
{
}

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

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

boost::asio::io_service::strand m_strand;
};

class Prime : public boost::noncopyable,
public boost::enable_shared_from_this
{
public:
Prime(boost::asio::io_service & service,
const boost::shared_ptr & logger):
m_service(service),
m_logger(logger)
{
}

void nthPrime(size_t n)
{
m_service.dispatch(boost::bind(&Prime::doNthPrime,
shared_from_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::ostringstream str;
str << n << "th prime is " << primes.at(n - 1);
m_logger->log(str.str());
}

boost::asio::io_service & m_service;
boost::shared_ptr m_logger;
};

int main()
{
boost::shared_ptr service
(new Service(3));
boost::shared_ptr logger
(new Logger(service->getService()));
boost::shared_ptr prime
(new Prime(service->getService(), logger));

std::vector values;
values +=
20000, 10000, 5000, 7500, 15000,
17000, 14000, 11000, 21000, 11000;

std::for_each(values.begin(),
values.end(),
boost::bind(&Prime::nthPrime, prime, _1));
}


Asio fournit bien plus qu'une couche d'abstraction au dessus des sockets réseau. Il serait dommage de cantonner cette bibliothèque à la communication client serveur, alors qu'elle fournit un paradigme puissant pour construire des applications événementielles sans avoir à s'arracher les cheveux sur les problèmes bas-niveau.

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)
("" "" )
))