Author Archives: renaud

QML – Tutoriel 4 – Bindings et les méthodes de travail

Bindings + Méthode de travail:
Notions abordées:
-Les syntaxes pour le Binding

Le code de la présentation: https://github.com/obiwankennedy/Learn-Qt
Le code de l’exercice: 04_exercice_binding
La correction sera dans le prochain épisode.

 

Lien youtube: https://youtu.be/A-SMHKkH19U

Supporter le logiciel et la chaîne: https://liberapay.com/Rolisteam/donate
Rolisteam: http://www.rolisteam.org/

QML – Tutoriel 3 – Les attributs

Les attributs:
Notions abordées:
-id
-Propriétés
-Signaux
-Gestionnaire de Signaux
-Gestionnaires et propriétés groupés.
-méthodes
-Enumération

Fichier à télécharger: 03_attributs

Le code de la présentation: https://github.com/obiwankennedy/Learn-Qt

 

Lien youtube: https://youtu.be/tDSVlrsQJU0

QML – Tutoriel 2 – Le positionnement

Le positionnement:
-Apprenez à positionner vos éléments.
Notions abordées:
-Positionnement absolue
-Ancres
-Les positionneurs
-Les layouts
Le code de la présentation: https://github.com/obiwankennedy/Learn-Qt

Ressources du tutoriel: 03_positionnement

 

Lien youtube: https://youtu.be/QDbn1CDjc14

Supporter le logiciel et la chaîne: https://liberapay.com/Rolisteam/donate
Rolisteam: http://www.rolisteam.org/

QML – Tutoriel 1 – Introduction

Introduction:
-Apprenez à installer les outils pour programmer en QML.
-Création d’un projet QML.
-Découverte du premier programme.
-Introduction des types de base du QML (Item, Rectangle, Image, Text, TextInput, TextArea, MouseArea, Timer, Flickable)

 

 

Téléchargez les ressources de l’épisode

Liens Utiles:
Qt: https://www.qt.io/download
RCSE: http://www.rolisteam.org/download.html (Lire attentivement en fonction de vos plate formes)
Le code de la présentation: https://github.com/obiwankennedy/Learn-Qt

Exemple QML:
https://www.youtube.com/results?search_query=qml+example

Lien youtube: https://youtu.be/8moq00ofQPY

Supporter le logiciel et la chaîne: https://liberapay.com/Rolisteam/donate
Rolisteam: http://www.rolisteam.org/

Atelier QML à Pas Sage En Seine 2017

Bonjour,

J’ai organisé un atelier à Pas Sage En Seine en juin 2017.
J’ai enfin le temps de m’occuper de mettre en ligne les éléments de formation.
Je mets à disposition les supports de cet atelier.
Il y a une présentation PDF et un projet regroupant les exercices et les solutions.

La présentation est en réalité une application QML. Son code est disponible ici: https://github.com/obiwankennedy/Learn-Qt
Cela constitue en soit un bon exercice que d’arriver à la lancer sur votre ordinateur.

N’hésitez pas à me contacter pour poser des questions.
Je ferais peut-être une vidéo de la formation.

Liens:

Mettre à jour Postgresql sur debian Stretch

Stretch

Nous allons voir comment mettre à jour votre base de données postgresql après l’update de Jessie à Stretch.
La version de jessie était 9.4. La version pour Stretch est 9.6.

Les commandes suivantes sont exécutées en root. Vous pouvez bien sûr utiliser sudo.

Lister les clusters postgresql installés sur votre machine:

# pg_lsclusters

Si votre installation de postgresql date de Jessie vous aurez un truc comme cela.

Ver Cluster Port Status Owner Data directory Log file
9.4 main    5432 online postgres /var/lib/postgresql/9.4/main /var/log/postgresql/postgresql-9.4-main.log
9.6 main    5433 online postgres /var/lib/postgresql/9.6/main /var/log/postgresql/postgresql-9.6-main.log

 

Si vous n’avez pas fait la mise à jour de votre cluster après la mise à jour de wheezy vers jessie. Vous devriez voir un truc dans ce style la.

Ver Cluster Port Status Owner Data directory Log file
9.1 main    5432 online postgres /var/lib/postgresql/9.1/main /var/log/postgresql/postgresql-9.1-main.log
9.4 main    5433 online postgres /var/lib/postgresql/9.4/main /var/log/postgresql/postgresql-9.4-main.log
9.6 main    5434 online postgres /var/lib/postgresql/9.6/main /var/log/postgresql/postgresql-9.6-main.log

Le port par défaut de postgresql est le 5432.  Le cluster ayant 5432 comme port est votre cluster courant.

Normalement Debian installe la nouvelle version de postgresql à la mise à jour de version et il crée un nouveau cluster dans la foulée.
Si ce n’est pas le cas, vous pouvez l’installer par cette ligne:

# apt-get install postgresql-9.6

Supprimer le cluster postgresql 9.6 créé

Installer une nouvelle version de postgresql sur debian crée un cluster principal (main) qui empêche la commande pg_upgradecluster de fonctionner.

Il faut donc supprimer cette instance dans le but d’obtenir une base de données propre.
Faite très attention, ne vous tromper pas dans la commande.
Ne supprimer surtout pas votre vieille base de données. C’est elle qui contient toutes vos données. 🙂

Nous détruisons la nouvelle; la vide.

# pg_dropcluster --stop 9.6 main

Mettre à jour la vieille base vers le nouveau format

La syntaxe est la suivante:

# pg_upgradecluster -v NouvelleVersion AncienneVersion main

La fonction va créer un nouvelle base main dans le format NouvelleVersion.
Pour ensuite, copier les données de AncienneVersion vers NouvelleVersion.
Cela prendra un peu de temps.

# pg_upgradecluster -v 9.6 9.4 main
Stopping old cluster...
Notice: extra pg_ctl/postgres options given, bypassing systemctl for stop operation
Disabling connections to the old cluster during upgrade...
Restarting old cluster with restricted connections...
Redirecting start request to systemctl
Creating new cluster 9.6/main ...
config /etc/postgresql/9.6/main
data /var/lib/postgresql/9.6/main
locale en_US.UTF-8
socket /var/run/postgresql
port 5434
Disabling connections to the new cluster during upgrade...
Redirecting start request to systemctl
Roles, databases, schemas, ACLs...
Fixing hardcoded library paths for stored procedures...
Upgrading database pbf...
Analyzing database pbf...
Fixing hardcoded library paths for stored procedures...
Upgrading database template1...
Analyzing database template1...
Fixing hardcoded library paths for stored procedures...
Upgrading database livres...
Analyzing database livres...
Fixing hardcoded library paths for stored procedures...
Upgrading database cmdline...
Analyzing database cmdline...
Fixing hardcoded library paths for stored procedures...
Upgrading database systemtest...
Analyzing database systemtest...
Fixing hardcoded library paths for stored procedures...
Upgrading database creatures...
Analyzing database creatures...
Fixing hardcoded library paths for stored procedures...
Upgrading database articles...
Analyzing database articles...
Fixing hardcoded library paths for stored procedures...
Upgrading database postgres...
Analyzing database postgres...
Fixing hardcoded library paths for stored procedures...
Upgrading database campagnes...
Analyzing database campagnes...
Fixing hardcoded library paths for stored procedures...
Upgrading database animations...
Analyzing database animations...
Fixing hardcoded library paths for stored procedures...
Upgrading database asso_member...
Analyzing database asso_member...
Re-enabling connections to the old cluster...
Re-enabling connections to the new cluster...
Copying old configuration files...
Copying old start.conf...
Copying old pg_ctl.conf...
Copying old server.crt...
Copying old server.key...
Stopping target cluster...
Redirecting stop request to systemctl
Stopping old cluster...
Redirecting stop request to systemctl
Disabling automatic startup of old cluster...
Configuring old cluster to use a different port (5434)...
Starting target cluster on the original port...
Redirecting start request to systemctl
Success. Please check that the upgraded cluster works. If it does,
you can remove the old cluster with

pg_dropcluster 9.4 main

Supprimer l’ancienne version

Si la copie s’est bien passée. Vous pouvez supprimer l’ancienne version. Elle est maintenant entièrement copié dans le cluster main 9.6.

# pg_dropcluster 9.4 main

Mettre en ligne ses parties !

Je suis venu vous conter une histoire. Cette histoire est issue d’un désir coupable de frimer un peu.
Non, je n’ai pas réussi à faire quelque chose d’exceptionnel ou d’impossible comme inventer une ampoule qui dure 10 fois plus longtemps que toutes les autres.
Je souhaite juste montrer comment GNU/Linux, le système D et la philosophie OpenSource/libre/DIY peuvent permettre d’automatiser un processus de production vidéo.

Contexte:

Je suis développeur de Rolisteam. J’avais besoin de faire un peu la promotion du logiciel. De plus, en tant que maître de jeu, j’avais envie de garder une trace de ma campagne, histoire
d’en faire profiter d’autres gens.
L’enregistrement des parties en vidéo et audio nous a semblé la meilleure solution pour atteindre ces objectifs.

Le choix des outils:

Après une phase de recherche, mon choix s’est porté sur:

SimpleScreenRecorder (SSR)

Il est pratique, simple et la qualité vidéo est plutôt bonne. Le seul problème c’est l’enregistrement audio. Impossible d’enregistrer en même temps mon micro et
les voix de mes camarades à moins de configurer pulseaudio ou l’usage de Jack. Je n’avais pas envie de changer toute la configuration son de ma bécane. C’est peut-être facile à faire mais j’avais pas envie de me lancer la dedans. De plus, la qualité audio de SSR est moins bonne que Teamspeak (je trouve en tout cas).

Teamspeak:

Ok, ce n’est pas libre mais pour des raisons de qualité et d’habitude des joueurs, on est resté sur cette solution pour l’audio.
Les fruits de l’enregistrement

Grâce à ces outils, j’ai réalisé les enregistrements.
J’avais donc un fichier son et une vidéo par partie. Dans un premier temps, j’ai fusionné les deux fichiers avec ffmpeg pour obtenir la vidéo de la partie (avec le son).

ffmpeg -i video.mp4 -i audio.wav -c:v copy -c:a aac -strict experimental output.mp4

Améliorer l’expérience visuelle

Souhaitant offrir une meilleure expérience pour les éventuels spectateurs, j’ai codé un plugin à teamspeak qui envoie sur dbus le statut de la voix de chaque joueur.

J’ai crée une application qui écoute ces messages dbus, quand le joueur parle, le portrait de son personnage s’affiche en couleur. Quand il est silencieux le portrait du personnage s’affiche en niveau de gris. Il m’a fallu plusieurs tests et étapes pour arriver à ce fonctionnel final.
Les joueurs ne voulant pas montrer leur visage par webcam. Cela semblait la meilleur solution de suivre facilement les conversations.

Vous trouverez le code ici: https://github.com/obiwankennedy/GameVisualisationHelper/tree/cops/display

Cette application a apporté une contrainte supplémentaire sur les vidéos. Le son et l’image doivent être synchronisés avec précision. Ce n’était pas le cas avant car les éléments de l’écran pouvait être en avance ou en retard par rapport à la voix, il n’y avait aucun repère visuel pour le remarquer.

Caler le son et l’image

J’ai d’abord pensé merger les deux fichiers dans un éditeur vidéo.
J’ai essayé PitiVi, OpenShot et Kdenlive. Les deux premiers agonisent dans d’atroces souffrances après le chargement de fichiers supérieurs à 3h. Kdenlive s’en sort mieux. Il n’agonise qu’une fois sur deux. J’ai installé les versions de ma distribution et j’ai fait de report de bug mais je pouvais pas attendre la résolution des problèmes.

Dans ce contexte, trouver le bon timing pour synchroniser l’audio sur l’image, c’est compliqué. Le drag and drop de fichier de 3h fait assez mal aux logiciels d’édition vidéo. Sans parler de la précision pour les déplacements. Bref, pas pratique.

Je me suis dit “c’est idiot, il faudrait synchroniser le début de l’enregistrement de l’audio et la vidéo”.
Je n’ai pas le code source de Teamspeak mais il est possible de créer un plugin (ou de modifier celui que j’ai créé) et j’ai le code source de SimpleScreenRecorder (SSR).

J’ai donc décidé d’exposer l’API de SSR sur DBUS et mon plugin teamspeak envoie des commandes dbus. Vive Dbus !
En gros, j’ai étudié le code de SSR pour identifier la fonction qui démarre l’enregistrement. J’ai créé la petite tambouille pour l’exposer (ainsi que la fonction pour mettre l’enregistrement en pause) sur dbus.

Si vous voulez voir comment faire cela: http://renaudguezennec.eu/index.php/2011/03/10/introduction-a-dbus-avec-qt4/

Pour le coup, après quelques essais et des modifications sur l’ensemble des participants à l’affaire. J’ai une solution qui tourne bien. Je peux utiliser ffmpeg pour fusionner mes fichiers vidéos avec le son et cela correspond parfaitement.

ffmpeg -i video.mp4 -i audio.wav -c:v copy -c:a aac -strict experimental output.mp4

Une étape d’accomplie

J’ai fait un pull request à l’auteur de SSR. Mon but était de montrer comment faire car l’auteur de SSR n’est pas formé à Dbus. Il est très intéressé mais clairement je n’ai pas le temps de généraliser l’usage de Dbus dans SSR, ni lui d’ailleurs.

Ma version est accessible ici : https://github.com/obiwankennedy/ssr
Ma pull request : https://github.com/MaartenBaert/ssr/pull/399

Montage des génériques

Après ces étapes, j’ai des vidéos de mes parties assez brutes. Idéalement, il me reste à ajouter un générique de début et de fin.

Pour le faire, j’ai fait un programme en QML avec deux animations qui se courent après. C’est pas jolie mais cela fait le job. Le générique dure ~10 secondes, je l’ai enregistré avec SSR aussi.
J’ai utilisé Kdenlive pour caler une musique libre dessus.

A la fin de cette étape, j’ai ma petite vidéo de générique de début, idem pour le générique de fin et mes épisodes (plus de 75).
Vous l’avez compris, l’étape ici est de créer des vidéos contenant les génériques.
J’ai cherché un peu dans ffmpeg pour arriver à cela. C’est une simple fonction de concaténation des vidéos.
J’ai écrit ma petite commande, ça marche.

cd /racine/des/videos/
OPENING=/chemin/vers/generique/debut.mp4
ENDING=/chemin/vers/generique/fin.mp4
video=/chemin/vers/videos.mp4
LIST_FILE=/tmp/mylist.txt
videoExtLess=\`echo $video | awk -F '.' '{print $1}'\`
echo "file '$OPENING'" > $LIST_FILE
echo "file '$video'" >> $LIST_FILE
echo "file '$ENDING'" >> $LIST_FILE
ffmpeg -safe 0 -f concat -i /tmp/mylist.txt ${videoExtLess}_avec_generiques.mp4
Concat avec ffmpeg

Du moins, je croyais que cela marchait. En vérité, cela cassait la synchronisation son/image. Ce fut très embêtant.
Je retourne à la case départ “logiciel de montage vidéo” (Kdenlive). Il y a un peu moins de manipulation précise à faire. Je colle les trois fichiers: générique de début, l’épisode, générique de fin et c’est parti.
Ça a fonctionné un temps.

Extraction du son et traitement

Une fois la vidéo complète avec les génériques. J’en extrait le son pour la diffusion en podcast, vraiment facile avec ffmpeg.

ffmpeg -i videos.mp4 -codec:a libmp3lame -qscale:a 2 output.mp3

Avant de sortir l’épisode en podcast, je le re-travaille un peu avec audacity pour supprimer les silences et pour normaliser le son (avec le filtre compresseur du logiciel).
Cela raccourcit l’épisode d’environ 30 mins et le son est bien meilleur.

Traitement sur la vidéo

Je souhaite effectuer les mêmes traitements sur les vidéos. J’ai bien lutté pour trouver une solution.

Normalisation

Pour la normalisation, j’ai trouvé un petit script python: ffmpeg-normalize.

ffmpeg-normalize -vu -p normalized-episode40.mp4 Episode_40.mp4

Supprimer les moments inutiles

Couper la vidéo quand il y a du silence fut bien plus complexe. Aucun logiciel de montage vidéo n’offre de filtre pour cela (je n’ai pas trouvé en tout cas) et j’allais pas le faire à la main. Cela m’aurait pris trop de temps.

J’ai donc cherché à droite à gauche et c’est Ryzz (Merci à lui) sur Linuxfr.org qui m’a envoyé vers une bonne piste: MoviePy

Un module python pour faire de l’édition vidéo. Il y a même un exemple utilisant MoviePy pour créer le résumé d’un match de foot grâce au son des supporters.

J’ai donc créé un script python pour couper les silences. J’en ai profité également ajouter les génériques avec MoviePy.
Quand mon script fut prêt j’avais déjà sorti 20 épisodes. J’en avais donc 40 en stock à refaire. J’ai donc lancer le script sur les 40 restant. Après 3 jours d’exécution. J’avais
tous mes épisodes prêts.

Si vous souhaitez voir le code du script et des explications techniques:
http://renaudguezennec.eu/index.php/2017/03/03/montage-video-en-python/

La réduction des silences est moins efficace qu’audacity mais il y a un gain indéniable.

Voilà, le degrés maximum que j’ai atteins dans l’automatisation.

Aller encore plus loin

Dans tout ce processus, il me manque encore des choses pour vraiment automatiser toute la chaîne.

Le premier point, c’est audacity. Il n’est pas possible d’utiliser audacity en ligne de commande. Je me suis un peu renseigné, il y a eu des tentatives mais c’est très complexe à mettre en place.
J’ai regardé un peu le code, espérant trouver un moyen d’appliquer les deux filtres dont j’ai besoin. Le code est peu lisible avec des define partout pour différencier les OS. Bref, un cauchemar à maintenir. Du coup, j’hésite à mis mettre vraiment ou rester en édition manuelle.

Ensuite, il me reste à automatiser la partie “mise en ligne” sur youtube et sur le wordpress.
Je suis certains que c’est possible mais pour l’instant, je n’ai pas pris le temps de le faire.
Si vous voulez voir le résultat:

Le wordpress pour écouter les épisodes: http://blog.rolisteam.org/
La playlist youtube des épisodes : https://www.youtube.com/playlist?list=PLBSt0cCTFfS5fi3v1LtB9sfeA8opY-Ge1
Les premiers n’ont pas bénéficié de tous les outils. Il y a clairement une marque de progression dans les épisodes jusqu’à l’épisode 20 environ.

Montage Vidéo en python

Mon besoin

Dans le cadre de la promotion de Rolisteam, je diffuse en ligne des enregistrements de parties.
Ces enregistrements nécessitent différents traitement afin d’être rendu plus audibles et intéressants.
Les taches à réaliser sont les suivantes:

1 – Associer la piste audio et la piste vidéo
2 – Ajouter le générique de début et de fin.
3 – Couper la vidéo en fonction des silences
4 – Améliorer le son (le compresser/normaliser)

Pour réaliser la tache 1, une simple commande ffmpeg suffit. J’ai facilement créé un script bash pour l’automatiser.
La tâche 2 peut également être réalisée par ffmpeg avec l’option concat mais cela ne s’est pas passé comme prévu.
La solution de replie fut kdenlive, un logiciel de montage vidéo sur linux (Un des rares qui ne plante pas tout le temps quand on lui donne à monter une vidéo de trois heures).

L’étape 3 fut bien plus complexe à réaliser. Il n’y a aucun outil clé en main pour faire cela dans ffmpeg ou kdenlive. C’est très probablement faisable avec ces outils mais je n’ai pas trouvé comment. Je ne me voyais pas couper les moments de silence à la main.
Je commençais à désespérer quand Ryzz sur Linuxfr.org a évoqué le package MoviePy. Un module de manipulation vidéo pour python avec un exemple d’emploi proche de mon objectif.

Créer un résumé automatique d’un match de foot: http://zulko.github.io/blog/2014/07/04/automatic-soccer-highlights-compilations-with-python/
La doc de l’API: http://zulko.github.io/moviepy/index.html

Le code python: MoviePy

Voila un outil pour manipuler le son et la vidéo en codant avec python. Outil parfait pour réaliser les actions 2 et 3 de façon automatique.
Mes fichiers d’entrées sont biens rangés dans des dossiers, cela rend l’automatisation plus facile.

Pour la dernier étape, j’ai trouvé un petit script python “ffmpeg-normalize” qui fait cela. Ce n’est clairement pas aussi puissant que les filtres d’audacity mais cela suffit.

Première étape, Parcourir les dossiers, trouver fichier associant la vidéo et le son pour y ajouter les génériques.

for subfolder in sorted(os.listdir(rootFolder)):
if("_done" not in subfolder):
for subfile in os.listdir(os.path.join(rootFolder,subfolder)):
if(("mp4" in subfile)and("Partie" in subfile)):
link = os.path.join(rootFolder,subfolder)
video = os.path.join(link,subfile)
dest = subfile.replace(".mp4","_ending.mp4",1)
destination = os.path.join(link,dest)
videoclip = VideoFileClip(video)
#concatenation of opening, video and ending
finalclip = concatenate_videoclips([opening,videoclip,ending])
finalclip.write_videofile(destination,fps=25)
Parcours des dossiers

Je ne fais que reconstruire le chemin pour arriver jusqu’à l’épisode.

Ensuite, l’autre partie intéressante (grandement inspiré de l’exemple sur le foot), est de découper le film en échantillon d’une seconde. Le volume de chaque échantillon est calculé.
Il faut stocké le volume de chaque échantillon.

#split resulting video in audio subclip
clip = VideoFileClip(destination)
cut = lambda i: clip.audio.subclip(i,i+1).to_soundarray(fps=22000)
volume = lambda array: np.sqrt(((1.0*array)**2).mean())
volumes = [volume(cut(i)) for i in range(0,int(clip.audio.duration-2))]
final_times= []
Les volumes

Le dernière étape consiste à regrouper les périodes de temps qui doivent être sauvegardé. Dans un tableau à deux éléments, je conserve le début et la fin de chaque période à conserver.

i = 1
duo = []
start = -1
end = 0
sumVideo = 0
#identify all part with sounds. What we keep.
for vol in volumes:
if(( vol == 0.0 )and (start!=-1)):
end = i-1
duo = [start,end]
final_times.append(duo)
sumVideo += (end-start)
start = -1
if((start == -1)and (vol>0.0)):
start=i
i=i+1
Séparation des duos

Quand l’ensemble des duos sont identifiés, il suffit de les concatener dans le fichier de sortie.

finalpath = destination.replace("_ending","_cutted")
print finalpath
print final_times
#concatenate all kept parts.
final = concatenate_videoclips([clip.subclip(t[0],t[1])
for t in final_times])
#write the file
final.write_videofile(finalpath,fps=25)
Sauvegarde des échantillons

Code complet: http://www.renaudguezennec.eu/file/cutVideos.py

Créer un WebService en C++ avec Qt

Un petit article pour évoquer la possibilité de faire un webservice, en C++/Qt.

Mon besoin est simple, je veux fournir une interface web pour réaliser des lancer de dés avec le système de lancement de dés DiceParser, issu de Rolisteam.

DiceParser est écrit en C++ avec Qt5. A partir de ce constat, je pouvais soit faire un serveur web en C++, soit faire un site web en python/php/whatever pour lancer une commande système et utiliser dice, le client en ligne de commande de DiceParser.

La deuxième solution est  la plus facile mais clairement un peu «sale» et niveau sécurité ce n’est pas idéal. Le «challenge» se trouve dans la première méthode.
Je n’avais pas envie de réinventer la roue donc j’ai cherché une solution technique pour satisfaire mon besoin. Un composant C++/Qt qui permet de créer un serveur http avec possibilité d’être notifier à chaque requête. J’ai trouvé un composant qui réalise cela.

Le dépôt git du composant : https://github.com/azadkuh/qhttp.git

Implémentation du serveur

  m_server = new qhttp::server::QHttpServer(this);
    m_server->listen( // listening on 0.0.0.0:8080
            QHostAddress::Any, port,
            [=](qhttp::server::QHttpRequest* req, qhttp::server::QHttpResponse* res)
            {
                req->collectData(1024);

		// Ici mettre le code d'analyse de la requête

        });
    if ( !m_server->isListening() ) {
            qDebug() << "failed to listen";

        }
    else
    {
        qDebug()<< "Server is On!!";
    }
Instancier le serveur web

Nous créons une instance du serveur, nous démarrons l’écoute sur le port (80 par défaut pour le protocole http).
L’écoute est réalisé par une fonction lambda, cela n’a rien d’obligatoire, mais c’est plus minimaliste.

Notre fonction est notifiée à la réception d’une requête http.

Récupérer les informations d’une requête

// Récupère les données
req->collectData(1024);

// Récupère l'url (les données du GET)
QString getArg = req->url().toString();

// Découpage des arguments
getArg=getArg.replace("/?","");
QStringList args = getArg.split('&');
QHash<QString,QString> m_hashArgs;
for( auto argument : args)
{
    QStringList keyValue = argument.split('=');
    if(keyValue.size()==2)
    {
        m_hashArgs.insert(keyValue[0],keyValue[1]);
    }
}

// recherche dans la table des arguments la présence de fonction.
if(m_hashArgs.contains("cmd"))
{
	// répondre à la requête reconnue.
}
Découpage

Ici, nous analysons la requête pour identifier les actions à réaliser. Nous cherchons le paramètre cmd, pour trouver la commander à identifier. Le travail préalable est des créer une tableau associatif (dans une QHash) pour conserver les paramètres et leur valeur.

Une fois la commande de dés identifiés, il faut l’exécuter.

Exécuter la commande et répondre


                    QString result = startDiceParsing(QUrl::fromPercentEncoding(m_hashArgs["cmd"].toLocal8Bit()));
                    

                    res->setStatusCode(qhttp::ESTATUS_OK);
                    res->addHeader("Access-Control-Allow-Origin", "*");
                    res->addHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
                    res->addHeader("Access-Control-Allow-Headers", "x-requested-with");


                    QString html("<!doctype html>\n"
                             "<html>\n"
                             "<head>\n"
                             "  <meta charset=\"utf-8\">\n"
                             "  <title>Rolisteam Dice System Webservice</title>\n"
                             "  <style>.dice {color:#FF0000;font-weight: bold;}</style>"
                             "</head>\n"
                             "<body>\n"
                             "%1\n"
                             "</body>\n"
                             "</html>\n");

res->end(html.arg(result).toLocal8Bit());

La première action est de convertir la commande pour la transformer en données lisibles. Elle est actuellement encodé en mode pourcent. Qt fournit une méthode statique pour faire la conversion. Le resultat de cette conversion est ensuite envoyé à une fonction qui exécute la commande et donne le résultat.

Il faut ensuite générer la réponse. Le premier truc à définir est le code réponse du protocole http. Le module qhttp propose des raccourcis pour cela.

Ensuite, la réponse doit contenir 3 paramètres dans les headers afin d’être accessible par des frameworks Ajax/javascript/Web 2.0 (vous ressentez mon dédain envers ces techno ?).
Après la définition des headers, il faut créer le code html (ou autre) de la réponse.

Créer une page web cliente du service

<!DOCTYPE html>                                                                                               
<html>      
<head>                       
<meta charset="utf-8"> 
<meta name="Author" content="Renaud GUEZENNEC"/>                                                            
  <title>Rolisteam Dice System Webservice</title>  
  <style>.dice {color:#FF0000;font-weight: bold;}</style>                                                     
  <script type="text/javascript" src="js/jquery-3.1.1.min.js" ></script>            
  <script type="text/javascript">  
    function displayResult() {                                                                                                         
      var request = $.ajax({   
        method: "get",    
        url: "http://127.0.0.1:8085/",  
        dataType: "html",  
        async: false,
        data: {cmd: $('#cmd').val()} 
      });                
      request.done(function(data){  
        var str ="<p>" 
        str = str.concat(data,"</p>");                                                                        
        $('.diceresult').prepend(str);    
      }); 
    }   
  </script>  
</head>       
<body>    
<form method="POST" action=""/> 
<input id="cmd" name="cmd"/>  
<input id="roll" name="roll" type="button" onclick="javascript:displayResult(); return 0;" value="Roll"/>
</form>                
<a href="index.html">Clear</a> 
<div class="diceresult">
</div>      
</body> 
</html>
Page cliente

Le travail ici est assez simple. Il faut créer un formulaire pour permettre à l’utilisateur de saisir la commande de dés. J’ai utilisé JQuery comme framework javascript pour récupérer la commande et envoyer la requête de mon webservice. Quand la requête s’est bien passée, le résultat s’affiche dans la page sans recharger la page.

Le lien vers le code source: https://github.com/Rolisteam/DiceParser/tree/master/webserver
Le webservice fait partie de projet DiceParser: https://github.com/Rolisteam/DiceParser

rand() moi un entier !

Cet article est une synthèse de plusieurs conférences, de la documentation C++ et de mon expérience sur la génération de nombre aléatoire en C++.

Mon besoin

Je suis le développeur de Rolisteam. Un logiciel pour faire du jeu de rôle en ligne. Il intègre une solution badass (oui, je m’envoie des fleurs) de lancer de dés. J’ai passé beaucoup de temps entre juillet 2014 et février 2015 pour produire un langage de script interprété de lancement de commandes de dés. Vous pouvez y jeter un œil sur ce lien: DiceParser. Bref, j’ai crée tout un système pour lancer les dés et faire des opérations sur les résultats: tri, relance, filtre, explosion, arithmétique, gestion des priorités mathématiques et il est même capable de générer l’arbre d’exécution avec dot.

L’aléatoire

Pour la partie aléatoire dans tout le système, j’ai réutilisé le code historique de rolistik (l’ancêtre de rolisteam).
Certains utilisateurs sont venus me voir pour me demander de vérifier le système d’aléatoire car ils avaient fait des tests et avez déterminé que Rolisteam favorisait les résultats haut; ou bas selon les personnes.
J’avais droit à un tableau de statistique réalisé sur quelques dizaines de lancer de dés.
Argument qu’il est facile de détruire avec une étude statistique plus riche en lancer. DiceParser fonctionne en ligne de commande, donc très facile à «scripter». Bref, des gens avaient des doutes sans réelle preuve.
Du coup, je me suis un peu renseigné s’il n’y avait pas des méthodes plus «C++» avec le C++11, C++14 et/ou C++17.

L’existant

Quand il s’agit de générer des nombres aléatoires, la première implémentation qu’on apprend est souvent celle ci :

int resultat = std::rand()%MAX+debut;
usage classique de rand();

Je pense que tous les étudiants ont implémenté cette méthode, dans leurs premières années d’études.  Elle est facile à comprendre. Elle est disponible dans beaucoup de langages. Elle vient du C. Elle met en avant le modulo, ce qui lui donne peut-être une valeur pédagogique. Elle suffit dans de très nombreux cas.

Nous allons voir un peu ce qui cloche avec cette méthode.

Les défauts

1/ Pauvre entropie
Déjà, rand() ou std::rand() sont définis dans le “man” comme une solution pauvre pour obtenir de l’aléatoire. Il est précisé que les vieilles implémentations de rand() ou les implémentations sur d’autres systèmes fournissent un aléatoire pauvre sur les bits de poids faibles. Cela explique peut-être pourquoi certains utilisateurs sous Windows se plaignent.
Donc l’entropie de rand n’est pas bonne. Il existe une autre méthode: random() qui fonctionne normalement mieux, avec un période beaucoup plus grande.

2/ Erreur de répartition
Ces deux méthodes génèrent un nombre entre 0 et RAND_MAX. Maintenant, nous souhaitons obtenir des résultats sur un dés à 100 faces. (C’est un format de dés courant en Jeux de rôle).
Imaginons que RAND_MAX vaut 32767.

Si je découpe la valeur maximum en tranche de 100 cela donne ceci:

[0 : 99] => 100 valeurs
[100 : 199] => 100 valeurs
[200 : 299] => 100 valeurs
:
:
[32700 : 32767 ] => 67 valeurs.

Nous avons 327 tranches complètes et une dernière de 67.  L’usage du modulo vient faire la correspondance entre la valeur tirée dans l’espace de valeurs désirées. Cela favorise les résultats entre 0 et 67. Il y a une chance supplémentaire de tomber en dessous de 67. La différence est probablement négligeables mais tout de même, cela me pose problème d’avoir cela dans mon logiciel.

3/ Problème de portabilité

La valeur RAND_MAX vaut au minimum 32767 dans la norme. Elle diffèrent en fonction des implémentations, nous avons vu également que la portabilité de la méthode rand() n’est pas garantie.
Cette somme de problèmes vient conforter les utilisateurs de Rolisteam dans leur idée de problème relatif au système de dés. Deux problèmes de portabilité se cumulent.

Le constat de ces erreurs m’a poussé à chercher une solution moderne pour générer ces nombres.

Dans les API C++ est arrivé un nouveau “module” avec le C++11. Il s’agit de random.

#include <random>

Ce module présente toute une série de classes pour générer des nombres aléatoires et les répartir.
Il propose un nombre intéressant d’algorithmes ou de moteurs différents pour la génération. Je ne suis pas un expert en génération pseudo-aléatoire de nombre. Du coup, le nom des algorithmes ne me parle pas.
Cependant, il semble que deux éléments sortent du lot: le std::random_device et le std::mt19937.

Le std::random_device

Il est défini dans la documentation comme: Un générateur de nombre aléatoire non déterministe. En gros, cela signifie qu’on ne peut pas prévoir son comportement.
D’habitude, vous initialisez votre méthode aléatoire par un seed. Il peut être intéressant pour des tests automatiques ou une résolution de bug de pouvoir rejouer une séquence entière de nombre aléatoire.
Pour atteindre cet objectif, il suffit de mettre la même seed. Donc vous avez là une méthode de nombre aléatoire qui est déterministe. random_device n’a pas de seed. Il n’a pas besoin d’être initialiser. Il est donc impossible de lui faire “jouer” deux fois la même chose. Dans mon cas, cela me satisfait complètement.

C’est facile à mettre en place:

std::random_device rd;
std::uniform_int_distribution<int> dist(1, 10);
int randomNumber = dist(rd);
std::cout <<  randomNumber << std::endl;

On génère une nombre et on le distribue entre 1 et 10. Cela suffit. Il y a rien besoin de plus. C’est propre, élégant etc.
J’ai implémenté cette méthode dans rolisteam 1.8, malgré les avertissements qu’on trouve à droit à gauche. Cela donne de bons résultats sous Linux. J’ai fait une version pour Windows et là, c’est le drame.
L’entropie du module sous windows est de zéro. Cela signifie qu’il renvoie toujours le même résultat. Bref, les dés étaient toujours de la même valeur.
La solution du random_device ne marche pas. Elle n’est pas portable.

Le std::mt19937

Il reste donc la méthode du std::mt19937. Elle fait toujours partie de l’API de <random>. Voici l’implémentation finale dans rolisteam.

// dans le constructeur de ma classe dé
auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count();
m_rng = std::mt19937(quintptr(this)+seed);

//dans la fonction lancer (roll())
std::uniform_int_distribution<qint64> dist(m_base,m_faces);
qint64 value = dist(m_rng);
L’implementation dans rolisteam.

On peut voir qu’elle est plus proche de l’ancienne méthode car Il est nécessaire de l’initialiser avec un seed. Pour cela, il est également possible d’utiliser une API C++11: <chrono>.

 #include <chrono>
…
auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count();
m_rng = std::mt19937(quintptr(this)+seed);
Politique Agricole Commune

 

Autres méthodes:

Conclusion:

J’espère avoir fait le tour des nouvelles API pour la génération aléatoire de nombres entiers en C++. Il est important de garder à l’esprit que l’API <random> fournit bien sur bien plus de chose que ce que j’ai montré. Je n’ai pas fait d’étude statistique pour démontrer que c’est mieux maintenant. Dans tous les cas, j’ai implémenté le device_random dans mon lecteur audio maison, et je découvre plein de nouvelle musique de ma collection.

Les sources:

CppCon 2016: Cheinan Marks “I Just Wanted a Random Integer!”:
https://www.youtube.com/watch?v=4_QO1nm7uJs
Stephan T. Lavavej – rand() Considered Harmful:
https://channel9.msdn.com/Events/GoingNative/2013/rand-Considered-Harmful