lundi, novembre 06, 2006
jeudi, septembre 07, 2006
Touisteur au Caire
J’ai repris l’Ada, après 6 mois de travaux forcés en Java (il faut bien travailler pour son diplôme). En jetant un regard neuf sur mon travail en Ada, je me suis rendu compte que j’avais pris de mauvaises approches pour beaucoup de projets… J’ai donc décidé de reprendre mes activités sur :
- Cairo-Ada et l’intégration GtkAda
- Un générateur de binding OpenGL/Ada automatisé
- Mon parseur iCalendar
L’autre solution, celle explicitée dans cet article dans le GNOME Journal est qu’en GTK+ 2.8 il suffit d’appeler gdk_cairo_create pour récupérer une surface Cairo et dessiner dessus à l’intérieur d’un widget… Mais GtkAda était lié à la version 2.4 de GTK+…
Récemment, j’ai appris que GtkAda était passé à la version 2.8, et bindait désormais la version 2.8.* de GTK+. Un grand pas pour l’humanité. J’ai donc facilement bindé les fonctions gdk_cairo* dans un package Gtk.Cairo_Integration. Ainsi, on peut désormais reprendre l’article et implémenter sans problème la partie Cairo et au lieu d’appeler en C :
gdk_cairo_create()
On notifiera au début de notre paquetage qu’on veut utiliser le package Gtk.Cairo_Integration :
with Gtk.Cairo_Integration;
Et pour récupérer la surface (dans le handler de l’événement Expose), il nous faudra déclarer une constante (une variable fera aussi l’affaire) de type Gdk_Window et récupérer le Gdk_Window du widget courant (l’exemple est sur Gtk_Drawing_Area) :
Cr : Cairo.Cairo_Object;
begin
Cr := Gtk.Cairo_Integration.Cairo_Create(Win);
-- do your drawing stuff
-- ...
Cairo.Destroy(Cr);
Rien de bien compliqué, et aucun dépaysement pour les développeurs Cairo/GTK+ existants. La même chose, dans un vrai langage :-D. Vrai, c’est un peu verbeux, mais Gtk.Cairo comme nom de package entrait en conflit avec Cairo (tout court), le nom du package du binding Cairo...
Le package Cairo_Integration est un binding du fichier gdkcairo.h et toutes les fonctions s’y trouvant sont bindées. Je travaille encore pour intégrer tout cela à la chaîne de compilation de GtkAda, afin de produire un patch utilisable.
Le gros problème désormais reste de nettoyer le binding Cairo… Il reste beaucoup de fonctions mal ou pas bindées, et surtout toute l’API est en un seul package/fichier… difficile à maintenir, mais je réfléchis à un découpage similaire à celui de la documentation mais j’ai encore quelques soucis avec la gestion des types et des packages enfant... Une solution viendra peut-être.
Une fois ce découpage effectué et les fonctions bindées correctement (il manque le binding des fonctions concernant SVG et libsvg par exemple…), je vais faire un patch pour les sources de GtkAda (je ferais un checkout avant) et verrais bien si Act le prend =o).
Mon grand espoir est de parvenir à porter la fameuse horloge de MacSlow ou de produire un widget de qualité graphique comparable.
mercredi, septembre 06, 2006
Outils de travail avec Ada.
Note : cet article date de Mathusalem et je doutais un peu de son intérêt, mais ça fait toujours du bien de rappeler qu'il y a des excellents outils en Ada et qu'il s'agit d'un langage extrêmement ouvert.
Tout d’abord au niveau des éditeurs et IDE
On a d’abord Gnat Programming System (GPS), distribué par AdaCore. Environnement de développement tout intégré, il permet, au-delà de l’édition de code de bonne facture, de gérer des projets, de débugger en ligne, de visualiser les dépendances entre paquetages, entre types, etc… Il est extensible en python (il me semble) et dispose déjà d’un certain nombre d’extensions.
On a aussi XEmacs, l’outil presque ultime pour tout faire. Il dispose d’un mode Ada : coloration syntaxique, templates pour coller des blocs de code, indentation correcte, commentaires… Et j’en oublie sûrement, et je ne m’en sers sûrement pas assez bien pour savoir tout ce dont il est capable. Au-delà de ce mode Ada, on dispose de toutes les fonctionnalités d’emacs : recherche, remplace, buffer de compilation, des milliers d’options, découpage de l’écran à souhait (C-x 1 ou 2 ou 3). De même je suis sûr de passer à côté de certaines options. Mais l’environnement emacs est extrêmement riche pour le développeur, et le logiciel en lui-même est très stable, et je n’ai jamais aucune crainte de perdre des sources non enregistrées à cause d’un crash d’emacs.
On peut même trouver, et j’ignore si c’est installé de façon générique, un mode qui transforme emacs en IDE pseudo-GPS, avec plusieurs fenêtres à gauche de l’écran (parcours de répertoires, buffers ouvert, listes des fonctions/types/paquetages/tasks d’un fichier…). Cela s’appelle ecb il me semble. Très pratique quand un fichier source commence à gonfler en taille et qu’on n’a pas forcément envie de le découper « mieux » tout de suite…
Pour les malades du poignet et ceux qui veulent éviter un syndrôme du canal carpien et au passage perdre aussi en productivité (feeding the trolls), il y a aussi un mode Ada pour vim fonctionne bien aussi avec Ada, et il me semblait qu’à une époque KDevelop proposait vaguement de créer des projets Ada…
Les quelques petites fonctionnalités supplémentaires valent-elle le bloat monstrueux d'Eclipse et l'investissement de 5000USD pour un simple plug-in ?
Les outils de compilation et test
GNAT est un excellent compilateur. Il passe les tests ACATS, est maintenu par certains des meilleurs experts en compilation et Ada (grand fan de Robert Dewar, ses explications sont toujours rayonnantes de concision et de richesse... ok je m'emporte mais quand même, chapeau), et supporte déjà une très grande partie des fonctionnalités d’Ada 2005, en avance.
Par contre, GNAT est désormais GPL (et non plus GMGPL) pour qui ne paie pas le support Act. Cela signifie qu’à moins d’utiliser une version Gnat Pro récupérée ou un Gnat-GCC. (GMGPL veut encore dire que le code est libre, donc copiable, récupérable, diffusable… c’est juste que
Au-delà ce ça, Act livre aussi une version de gdb qui tourne avec GNAT. Avant GCC4.0 et le passage à Ada2005, on pouvait encore travailler avec le gdb de base de gcc, semble-t-il. En attendant que la transition soit terminée, on peut utiliser le gdb fourni par Act, ainsi donc que les outils graphiques qui vont bien, ddd et gvd.
Pour les tests de performance, gprof fonctionne bien avec GNAT, et oprofile est très pratique et peut annoter du code Ada (extrêmement pratique). J'ai déjà fait l'apologie de KCachegrind sur ce blog, alors je me retiendrai.
Les bibliothèques
On trouvera en Ada tout ce qu'il faut pour créer des interfaces graphiques, lire/écrire du XML, intégrer (oui intégrer) un serveur/client HTTP, client POP/Jabber/LDAP, et pour peu qu'on soit intéressé par des applications distribuées, on a le choix : l'annexe E d'Ada95 (génial) et un ORB CORBA (on aime ou n'aime pas, mais c'est disponible). Tout cela de façon portable, partout où GNAT est disponible.
Je passe sur les capacités de liaison vers les langages C, C++, Cobol, qui permettent de récupérer des anciennes bibliothèques et de les réutiliser (mot-clé en Ada), ou de se binder aux dernières nouveautés (Cairo par exemple, ou encore Qt4).
Ce qu'il manque
Ce n'est ici qu'un avis.
Je suis d'abord très très tenté par un générateur automatique de bindings. J'ai regardé et codé pendant quelques heures pour une sortie Ada et Swig paraît un bon point de départ. De mon côté je me penche sur une solution plus simpliste pour le C et le C++ (encore un projet perso jamais commencé jamais fini =oD).
Un plug-in Eclipse avancé avec complétion intéressante, intégrée à GNAT et ses conseils =o), liaison avec le C/C++ automatisée et transparente (il suffit de cliquer-déposer un .so avec les .h correspondants par exemple... aucune idée précise), des capacités de refactoring à la java, une gestion avancée du développement temps-réel embarqué, des assistants de code puissants permettant d'effectuer les opérations pénibles en un clin d'oeil. Et des aides pour l'utilisation de PolyOrb, l'intégration "transparente" de GNADE et tous ces outils qui font d'Eclipse un vrai environnement intégré dont on supporte la lenteur parce qu'il apporte tellement... Ah, évidemment open-source ce plug-in =oD. Que la communauté Ada puisse exprimer ses besoins en les développant =oD.
Cairo-Ada-Gtk... doesn't talk about beer
Récemment (depuis 3-4 mois) je me suis intéressé à Cairo, parce que j’ai vu que GTK+/Gnome avait déjà migré dessus, et que les exemples pour faire des dessins étaient jolis et simple ET ressemblaient énormément à OpenGL. En plus - et c’est surtout ça qui m’enthousiasmait – Cairo était prévu pour être accéléré par la carte graphique et grâce à Glitz, être « posé » par-dessus OpenGL, un peu (mais pas exactement) à la façon de MacOS X (dites Ten, comme dirait un certain enseignant hype que je ne citerai pas).
Travail sur Cairo
L’API ayant l'air très simple à binder, j’ai voulu travailler en Ada, dans un environnement qui me plaisait (pas de C, pitié). J’ai réussi rapidement à binder toutes les fonctions nécessaires pour « porter » les snippets d’exemples disponibles dans la documentation de Cairo. Le binding est on ne peut plus simpliste, j’ai pris les types C, ai transposé les pointeurs en records nulls… et c’est tout. J’aurai pu (et du) aller plus loin, et proposer comme pour PyCairo ou rCairo, ou même Gtkmm (C++), une implémentation « orientée objet », mais la flemme et l’envie de coder par-dessus mon binding ont pris le dessus. Ce sera pour plus tard...
L’utilisation la plus courante de Cairo aujourd’hui est dans les interfaces graphiques (il est quasiment fait pour ça). Le problème étant que l’implémentation d’une UI à partir de Cairo la plus courante est GTK+. Du C. Et pas du C classique, « normal » et « assembleur de haut niveau »-like. Non, du C qui émule des fonctionnalités objet. Via les GObjects et autres jolies fonctionnalités. J’avoue qu’on finit par s’y habituer, mais lire le code source de GTK+, ce n’est pas une sinécure pour un débutant et/ou médiocre en C. Comme disait un de mes anciens collègues « c’est le genre de trucs qui doit bien marcher selon la doc et dans lequel tu dois pas à avoir à mettre les mains ». Les macros sont assez difficiles à suivre intuitivement (sans lire la documentation détaillée), et j’ai vite eu la tête qui explosait.
Pourquoi si dur, alors que tous les programmeurs Gnome et GTK+ y arrivent sans problème. Mais c’est que je n’ai pas dépareillé de mon idée de faire ce projet Cairo en Ada. Il fallait donc que j’insère Cairo dans GtkAda… La voie la plus simple eut été que GtkAda ait suivi l’API Gdk (on va dire une sous-couche de Gtk+), là où est implanté le lien Cairo. A priori, les développeurs de GtkAda sont restés à Gtk+2.6 ou inférieur, et Cairo a été ajouté à la 2.8.
Un widget maison...
Tant pis, dommage, recherche d’une autre piste. Avant de l’intégrer dans GTK, Cairo avait fait l’objet d’une implémentation dans un widget nommé GtkCairo, trouvable en fouillant sur le site de Cairo (décidemment, beaucoup de « Cairo » dans ce post…). Ne connaissant absolument rien de la façon d’écrire un widget en Gtk+ et encore moins en GtkAda, j’ai lu la doc et le code source de plusieurs widget GtkAda, et ai fini, par tâtonnements et segfaults successives (oui ça a beau être de l’Ada, quand on lie avec du C…) par obtenir :
- un widget fonctionnel, sous la forme d’une horloge. Le fond provenant de l’article sur le GNOME Journal à propos de Cairo : Screenshot, Article
- puis une autre horloge (provenant du tutoriel Cairo de GTKmm). Screenshot, Article
- et enfin d’un widget d’animation d’attente infinie… alla MacOsX mais de loin… de Très Loin… Screenshot
Au fur et à mesure que ma connaissance de GTK+ s’améliore, je mets un timeout bien plus raisonnable et apprends à ne redessiner que lorsqu’on a besoin… Et les horloges semblent beaucoup plus rapides. Tout va pour le mieux.
J’ai ainsi continué de m’enfoncer dans l’API Cairo et de la binder au fur et à mesure. Trop d’un coup et le découragement serait venu. Je ne bindais que ce dont je me servais (et en général, toutes les fonctions qui étaient autour).
Prémices d'appli de calendrier
Fort de mon « expérience » joyeuse et fier d’avoir implémenté un widget Cairo en Ada – pour être modeste « bindé du code C », j’ai commencé à développer ce qui m’intéressait. Un widget de calendrier/emploi du temps…
- j’étais, et suis toujours impressionné par iCal
- j’ai utilisé assez longtemps les journaux de KOrganizer/Kontact/KPim pour avoir décidé d’implémenter un début de lecteur iCalendar en Ada (sujet d’un futur post ?).
- C’est simple à dessiner et à modéliser, et c’est très graphique et inspirant.
- J’avais déjà quelques « mock-ups » d’interface… vraiment basiques et laids. Je les partage… Si quelqu’un arrive à en tirer autre chose qu’un bon rire
- Le tout début : la grille, et les débuts d’eventbox. On voit les dégradés partout, l’alpha-blending et l’anti-aliasing en pleine action…
- un jour plus tard, changé d’avis sur les dégradés trop poussés dans les event-boxes, ajouté des titres avec une couleur différente et un dégradé sur la police
- quelques heures après, fait en sorte que les textes des titres rentrent toujours dans le titre des event-boxes (dommage que l’hyphénation ne soit pas gérée toute seule =o)). Désolé pour le blasphème, les paroles d’un morceau que j’écoutais à ce moment…
- un meilleur choix de couleurs, encore un effort sur les polices (peu visible), et surtout refactoring important (la nuit dessus)...
- 10 jours plus tard, après une pause « aaaah je suis allé super loin dans un projet pour une fois, aaaaaah… » de découragement, j’ai décidé d’ajouter une méthode pour dessiner des info-boxes, pour décrire en contexte les événements… Pas si évident qu’il y parait. En fonction des coordonnées pointées, on doit choisir la base de la flèche, et une largeur sympa et éviter les angles droits… Bref, au bout de 3-4 heures d’arrachage de cheveux (manque de concentration surtout), on obtenait ce genre de petites boites.
J’ai mis le projet en pause depuis, entre autre par fainéantise, et aussi parce qu’un de mes camarades codeur fou s’est mis lui aussi à coder un calendrier en Qt… Très vite, très très très vite. Du genre à rendre mes progrès « fulgurants » de 2-3 semaines comme 20 minutes de travail pour lui =o). C'était vraiment du beau travail, avancé et joli... Du coup, j’ai reporté mon attention sur d’autres projets. D’autres projets dont je parlerai peut-être plus tard. =o).
Mais, en attendant, j’ai continué d’implémenter mon infobox vide, et sur un de ces autres projets jamais finis, ça donne quand même envie.
Le prochain article à propos de Cairo parlera des nouveautés et progrès au niveau du binding =o).
Six mois de cours^Wtorture (1/2)
Je n'avais pas posté ici depuis 1/2 siècle , le moment est venu de recommencer un peu. J'ai entre temps brouillonné quelques articles plus ou moins intéressants et je ne vais pas les jeter. Mais avant de poster tout cela, parlons un peu de mon activité durant ces 6 derniers mois...
Mon stage s'est plutôt bien terminé, mon travail a été considéré comme très satisfaisant et ma connaissance des shaders et des technologies 3D a fait un bond conséquent, ainsi que mes compétences en matière de développement, de réflexion et de conception... Le chemin reste long bien sûr. Pour ne rien gâcher, j'ai été très bien noté et rémunéré. Un très, très bonne expérience donc, et cerise sur le gâteau, en Ada...
Je m'étais promis de continuer le développement des projets que j'avais commencés en entreprise et personnellement en Ada : un binding OpenGL et un binding Cairo/GTK+. Le second recevra les honneurs de plusieurs articles très bientôt (le temps de la mise en page) et le premier un peu plus tard. Malheureusement, comme d'habitude mon enthousiasme a été quelque peu écrasé sous la masse de travail des études qui ont repris. J'ai rapidement mis de côté le plaisir du développement Ada personnel pour la "joie" du développement Java forcé.
Parce que voila, le dernier langage pour la pédagogie et pour simplifier les corrections d'exercices, c'est Java. Le thème global des trois projets Java à rendre était cependant toujours frais dans ma tête : Graphisme et Visualisation d'Information étaient à base d'OpenGL. L'un était un exercice de modélisation du campus de la fac' où j'étudie, et les deux autres consistaient en deux applications/composants de visualisation d'informations.
Je vais me contenter de parler du projet de Graphique, et continuerai au prochain article sur la Visu...
Le projet Graphique
La consigne était de modéliser un minimum de bâtiments de mon campus, les voies de circulation, des voitures pour s'y déplacer, un ciel et un sol, et des buttes à l'aide de courbes de béziers, une caméra qui va bien et quelques optimisations (graphe de scène...). Jusque-là, rien de bien pénible ou de surprenant. Le hic, c'est qu'il fallait utiliser Java pour s'interfacer avec OpenGL, via le désormais fameux JOGL. Ce dernier est une usine à gaz et a rendu le développement de ce projet extrêmement pénible et laborieux. La lenteur, la complexité induite par Java/Swing par rapport à une application Ada ou C++ correspondante a rendu le travail sur ce projet une corvée. Pour ma part j'en ai effectué le moins possible graphiquement, me contentant d'aider mes trois courageux (http://blog.gmo-web.info/) binômes (http://jbamassy.blogspot.com) dans leur quête vers la compréhension d'OpenGL et des petites absurdités, incompatibilités, incohérences quotidiennes...
Je m'étais affecté aux tâches de gestion de caméra et de la partie optimisation.
La caméra
J'ai mis un temps fou à créer une caméra qui au final ne fonctionnait pas correctement, mais permettait de se déplacer simplement dans la scène. Dans la recherche du Graal de la caméra j'ai découvert les joies de la gestion de la pile de matrice OpenGL, et ai beaucoup joué avec pour obtenir des mouvements et transformations de façon plus intuitives et pratiques que les transformations classiques. Ainsi, au lieu de reprendre un code de quaternions que je ne comprenais qu'à moitié, j'ai préféré implémenter une caméra un peu originale. Au final, elle était plus que compliquée à réutiliser et absolument inmaintenable, mais ce que j'ai appris ici m'a beaucoup servi pour la phase optimisation.
L'idée était qu'au lieu de donner (position + 3 angles d'Euler) ou un quaternion pour décrire la position et direction de la caméra, j'ai utilisé le contexte courant et appliquait mes transformations directement sur la matrice de caméra courante. Pour ceux qui l'ignorent, dans OpenGL quand on applique une transformation (glRotate, glTranslate, etc.) celle-ci est considérée comme la première transformation effectuée, à l'origine... Donc si vous vous éloignez de l'origine et que vous appliquez une rotation ensuite, OpenGL va d'abord faire la rotation, puis la translation. Pour un débutant en Graphique c'est parfois rebutant, mais j'ai l'habitude depuis le temps.
Le problème est que je souhaitais que la transformation soit effectuée à partir de l'endroit et de l'orientation où se trouvait la caméra. Je voulais l'opération habituelle dans le sens opposé (la multiplication de matrices n'est pas commutative). Après bien des batailles contre le systèmes de matrices d'OpenGL (que j'ai quasiment toutes perdues), j'ai découvert par exemple que les matrices y sont stockées dans le sens différent du sens commun (en maths (en france)): en colonnes/lignes... Ensuite j'ai du appliquer mes magnifiques capacités de calcul et de repérage dans l'espace pour rater complètement toutes les transformations que je tentais, jusqu'à ce que me vienne l'idée (bien tard) de m'imaginer la caméra comme un objet, un point... J'ai fini par trouver des solutions, à grands coups de glLoadMatrix, glMultMatrix, glGetfv()... Bien sûr, Java/JOGL n'aidait pas dans ce capharnaüm, avec sa grande verbosité et son abstraction trop poussée. Et dire qu'un des "grands" arguments contre Ada est sa verbosité...
Les premières implémentations avaient des effets de bords désastreux, car personne dans le projet n'utilisait les glPushMatrix() glPopMatrix() avant de faire joujou avec la transformation courante... Et ensuite je me suis redécouvert un très vieux problème que j'avais rencontré quand j'avais développé à partir de zéro une toute petite bibliothèque de rendu 2D/3D logicielle pour de la stéréoscopie : la précision des nombres à virgule flottante. Usant et abusant des glMultMatrix(), l'erreur ou l'imprécision de chaque opération se répercutait extrêmement vite, en particulier lors des rotations. On voyait progressivement mais très vite l'horizon se pencher alors qu'il s'agissait d'une rotation dans le plan de l'horizon. Le passage à des calculs en précision double a un peu amélioré la situation. Et j'ai laissé le tout tel quel, en priant pour que l'examinateur ne s'en rende pas compte.
Au final je ne suis pas du tout satisfait de ce code, horrible, mais ce qui comptait c'était qu'il fonctionne, pour que je puisse me consacrer à la partie vraiment, vraiment intéressante du projet et qui n'avait plus besoin d'approcher JOGL et consistait majoritairement en de l'algorithmique et de ce fameux number crunching (gros calculs brutaux) dont Java se dit capable de rivaliser avec le C aujourd'hui.
Quatre pommes
Lors de ma soutenance de stage, lorsque j'ai présenté mon travail sur OpenGL et la recherche de performances maximales, via Pixel Buffer Objects, des mécanismes utilisant des textures rectangles, des FrameBuffer Objects pour l'écriture texture-to-texture... Un professeur dans le jury m'a demandé si j'avais recherché des optimisations de plus haut niveau, algorithmiques, en particulier dans le domaine du partitionnement de l'espace (Kd-Tree et autres friandises). Etant un ignare total dans le domaine, j'ai regretté de ne pas m'être penché plus tôt sur ce genre d'idées. Il me fallait corriger mon ignorance au plus tôt.
Je cherchais donc via ce projet à implémenter un algorithme intéressant permettant de réduire le nombre d'objets à afficher dans une scène. On avait survolé rapidement les BSP en cours et je voyais poindre la lourde, peu originale et surtout peu adaptée implémentation basique d'un tel engin... En recherchant d'autres solutions, j'ai découvert les Oct-tree et les Quad-tree. En résumé, il s'agit d'algorithmes très simples de découpage récursifs de l'espace. Ceux-ci sont beaucoup plus adaptés à la modélisation de bâtiments, car leur forme "carrée" simplifie énormément les calculs et leur structure rigide y rend la recherche extrêmement efficace.
L'idée était de gérer le frustum-culling avec un algorithme de détection de collision via un quadtree. Le frustum était projeté sur le sol (ses 6 faces l'étaient), et on utilisait ces quadrilatères pour vérifier la collision avec d'autres objets présents dans la scène.
Le problème premier et pénible était de récupérer le frustum, c'est à dire les coordonnées de ses 8 sommets. Il fallait faire ça à partir de la matrice de caméra. Heureusement j'avais déjà dépensé beaucoup de temps de cerveau à jouer avec les matrices et j'ai rapidement trouvé comment récupérer la position et l'orientation de la caméra, pour ensuite récupérer les 8 sommets à coup de géométrie simplette =oD.
Ensuite et enfin (j'aime garder le plus agréable pour la fin), j'ai implémenté l'algorithme de quadtree en 2 journées (de 22h) et ai optimisé la recherche ainsi que l'occupation mémoire comme j'ai pu, et ai fini de le débugger un autre soir. C'était vraiment agréable d'enfin développer quelque chose qui soit simple, relativement élégant dans le principe, sans la lourdeur pénible des abstractions d'interface graphique... Et surtout de pouvoir tester cet outil directement et de façon interactive avec une petite application Java2D (temps de développement compris dans l'implémentation de l'algorithme :-)) qui a plus tard été intégrée à l'interface finale. Elle sert de radar pour montrer quels objets statiques sont présents dans la scène et montre les frustum et les objets qu'il contient ou coupe, et donc les objets qui sont dessinés et ceux qui ne le sont pas. On visualise le résultat de l'algorithme d'optimisation, à défaut de pouvoir le constater.
En effet, les trois quarts de fonction géométriques que j'ai utilisées, je les ai recodées à la main, à partir de formules pas optimisées du tout. La recherche dans un cas bouclait infiniment et je n'avais pas réussi à reproduire cet Heisenbug. Quelques autres erreurs de conceptions ralentissaient encore ce code, mais priorité a été donnée au bouclage du projet. L'optimisation ralentissait en fait l'application :-D. Etant donnée la complexité de l'algorithme il aurait fallu avoir un nombre conséquent d'objets pour y trouver son compte :-).
Bile-an
Au final, ce projet étalé sur 2 mois m'aura rappelé que j'aime toujours si peu le Java, et que seul Eclipse qui m'a maintes fois sauvé de la dépression, en fait un langage de travail passable. J'aurai tout de même découvert beaucoup d'éléments intéressants, fait beaucoup (beaucoup !) d'erreurs, et donc appris énormément mais que le rapport :
Ce projet nous a quand même valu la meilleure note de la promo... comme quoi...
samedi, novembre 26, 2005
I --------- Love --------- This --------- Compiler !
Introduction, Vite Fait
Oui, ce langage fortement typé, plutôt verbeux, pseudo-limitatif.
Pour le typage fort, ça facilite la détection des erreurs à la compilation, entre autres. En Ada je passe moins de temps à déverminer et plus de temps à coder. Et même si en général on écrit plus de code en Ada qu'en C/C++, on en écrit beaucoup moins qu'en Java. L'API n'est pas aussi complète quye celle de Java, bien qu'on puisse trouver de quoi faire pas mal de choses sur Teh Intarwaybe. Des bindings et des bibliothèques développées from scratch.
Les outils de debugging et de profiling que vous utilisez en C ou pas (c.f mon article sur KCacheGrind) sont utilisables avec Ada.
En plus ! La norme Ada 2005 est en phase de finalisation. Elle apporte pas mal de modifications pour simplifer un peu la vie du programmeur Ada.
- Des nouveaux profils temps-réel
- La possibilité d'avoir des inclusions circulaires de packages (oui c'était un petit souci avec Ada95)
- Des containers (liste, vector, sets, maps)
- Du pseudo héritage multiple via interface
- La notation pointée (pour les vieux critiques qui disaient "tu peux pas faire comme en Java mon_instance.ma_methode()")...
- Les access anonymes : de fonctions/procédures ou de types. Très pratiques, car plus besoin de créer à chaque fois un type pointeur
La compil'
- Confondu Ada, SQL et VB6 ?
- Aide-moi pour la visibilitée et la gestion de portée
framebuffer_object.adb:79:42: use clause would make operation legal
simple_video_handler_types.adb:95:33: "Void_Const_Access" is not visible
simple_video_handler_types.adb:95:33: non-visible declaration at opengl-types.ads:28
- Variable non modifiée, en faire une constante ?
- Pas les bons types pour chaque opérande de l'opérateur "/="
framebuffer_object.adb:367:42: left operand has type "gl_int" defined at opengl-types.ads:16
framebuffer_object.adb:367:42: right operand has type "gl_uint" defined at opengl-types.ads:20
- Une fonction fonction() qui fait return(function));
framebuffer_object.adb:412:14: warning: Storage_Error may be raised at run time
- J'adore celle là (corrige mon orthographe)
framebuffer_object.adb:369:72: possible misspelling of "ct_GL_FRAMEBUFFER_EXT"
- Comment ça mieux que le C ?
- Oublié d'importer un package
- Multiple dispatching
simple_video_handler_types.ads
simple_video_handler_types.ads
simple_video_handler_types.ads
simple_video_handler_types.ads
simple_video_handler_types.ads
simple_video_handler_types.ads
simple_video_handler_types.ads
simple_video_handler_types.ads
simple_video_handler_types.ads
simple_video_handler_types.ads
- Ordre des opérateurs
- Pragma unreferenced
simple_video_handler_types.adb
- Oublié de mettre "body" pour désigner un corps de package...
buffer_lines_list.adb:1:08: missing "body"
- Génial : j'ai mis Const à la place de constant
- <> à la place de /=
Ma vie est belle, grâce aux gens de AdaCore/Act. Merci :-D
vendredi, novembre 04, 2005
KCacheGrind, killer-tool...
Quoi qu'est-ce ?
KCacheGrind est un outil de visualisation des résultats de l'outil callgrind. Callgrind est un outil de profiling qui permet de savoir dans quelles fonctions votre programme passe son temps.
Comme j'ai la flemme de détailler la technique et que google et le man page de callgrind regorgent d'informations, je vais plutôt poster mes screenshots de KCacheGrind, pour montrer à quel point ce logiciel est pratique et agréable.
Après avoir lancé callgrind sur l'exécutable de mon projet, je lance kcachegrind sur le fichier résultat. Et voila l'application qui se lance :
Intrigué, je cherche à comprendre à quoi tout ceci correspond. C'est facile. Les noms des fonctions sont dans les cases du dessin à droite, et dans la liste des fonctions à gauche. Le dessin qu'on voit, c'est en fait une sorte de partition du temps passé dans le programme, entre toutes les fonctions... Comme les fonctions en elles-même en appellent d'autres, on se retrouve avec des poupées russes. C'est très intéressant et même ludique. Je me suis vite pris au jeu de clic-cliquer partout sans trop savoir quoi faire.Je me rappelle que je suis là pour savoir où se trouvent les goulets d'étranglements de mon application. Je clic-clique donc sur les plus gros carrés et clic-clique encore jusqu'à trouver les endroits où ça coince.
Le Boulet...
Là, surprise, je trouve un énorme (!) goulet d'étranglement, qui me saute aux yeux. (Evidemment je n'ai pas pris de screenshot tellement la honte était grande).
Explication : dans mon projet, pour faire du rendu 3d, j'utilise des shaders OpenGL (programmes exécutés par la carte vidéo). Pour les mettre en oeuvre, je me suis contenté de reprendre un package d'exemple présent dans le code que mon tuteur de stage m'avait donné au début du stage. J'ai rajouté des fonctions et des fonctions à ce package, jusqu'à ce qu'aujourd'hui il soit devenu totalement imbitable. Enfin dans Emacs, et sans le mode ECB.
Supposant qu'il s'agissait d'un code ultra-fonctionnel tout bien pensé, je ne me suis pas posé de question. Mais en fait j'ai eu tort. La bêtise que j'ai découverte c'est que je demandais l'index des variables uniformes et des attributs des programmes... A chaque fois ! Pour être clair, tout au long de la vie d'un fragment/vector program, les variables uniform et attribute sont accessibles et modifiables. Mais pour y accéder - en général, 99% du temps , pour les définir (écrire dedans) - il faut disposer de leur numéro d'argument. Oui OpenGL c'est du très bas niveau. Tous les objets qu'on utilise sont définis par des indices.
Ces indices des arguments ne varient pas, ne changent pas, à moins qu'on recompile les programmes... Et mon programme d'exemple, ainsi que mon package de shaders, demandaient à chaque utilisation à OpenGL de nous donner le numéro des variables...
Comme je le disais récemment, en général, les commandes OpenGL sont asynchrones. Ce n'est pas le cas du tout pour les opérations de lecture. Ainsi, mon programme forçait un flush du pipeline OpenGL à chaque modification des variables de shaders...
Tout cela grâce au dessin en boîte. =o)
Quand on ne peut plus...
Ensuite, j'ai trouvé quelques autres idioties sans grandes répercussions sur les performances. Et j'ai fini par me trouver limité par 2 facteurs et j'ai (encore !) eu un surprise.
- Toutes les parties que j'aurais pu optimiser ne constituaient que 5 à 6% du temps d'exécution du programme. Il était totalement stupide de chercher à optimiser ces parties que je croyais critiques, avant de mesurer...
- Je ne pouvais pas faire mieux. Dans le screenshot plus bas, la fonction qui prend presque 13% du temps (Get_Area_Index) n'est qu'une fonction de translation d'adresse. Cette fonction est la routine de base de l'architecture que j'ai implémentée dans ce programme. Et elle ne fait qu'une ligne : une conversion et une division. Et le compilateur l'inline déjà automatiquement (si je me rappelle bien, Gnat permet de sortir une version précompilée du code, où il on peut voir tous les checks, les instanciations silencieuse, les opérations implicites explicitées, et les fonctions inlinées indiquées). Au cas où c'était l'inline qui posait problème (en augmentant la taille du code), j'ai vérifié sans option d'optimisation, le résultat est pire.

Où donc que ça coince ?
L'image ci-dessous montre juste en haut à gauche le temps passé dans chacun des objets ELF en jeu pendant l'exécution de mon programme.

Comme on peut le voir (en haut à gauche) Mon programme passe son temps dans l'API que j'ai codée. Ou en tout cas, il y a énormément plus d'appels à des fonctions de mon API pour la gestion des données à afficher que pour l'affichage même (libGL.so, libGLCore.so). C'est une surprise, je pensais que le facteur limitant serait le GPU, et que c'était à cause de lui que ça ramait... En fait, le GPU va très bien, c'est dans la mémoire système et sur le CPU qu'on travaille beaucoup trop. Avec un peu de recul, le pourquoi du comment est évident.
On casse tout et...
Ainsi, à la recherche des fonctions primitives qui prennent plus de 10% du temps, je n'ai pas pu en trouver une seule dans l'API qui puisse être regroupée, factorisée, améliorée, et qui en vaille la peine. Je n'ai pas trouvé comment faire mieux. Mais l'idée que mon programme passe plus de temps sur le CPU que le GPU me turlupinait, alors j'ai mis en cause toute mon architecture.
- Je pensais devoir stocker toutes les données pour connaître l'état exact à l'instant T des informations que je venais de dessiner
- je pensais aussi devoir avoir différents niveaux de granularités et j'avais axé la performance sur la granularité la plus petite. Ainsi pour dessiner un gros paquet d'informations (la majorité des arrivées... mais je l'ignorais) cela me prenait 4 à 5 fois plus de travail - recherche, translation d'adresse, mise à jour mémoire application puis mémoire vidéo - que ce que je pouvais faire en fait. Mais le problème était que mon architecture était de base trop prévue pour être flexible.
J'ai donc décidé de tout casser il y a de ça une semaine. Je change totalement la structure des données, et axe désormais mes traitements sur les gros chunks, pousse le maximum d'informations vers la carte vidéo (quitte à faire les fonctions bien chiantes pour la relecture et le débug...).
C'est pour ça que j'ai porté le code de gpgpu.org en Ada, pour avoir les outils et m'entraîner avec ces techniques.
Mais sinon, KCacheGrind ?
Tout de même fasciné par l'outil j'ai continué à m'en servir, ne serait-ce parce que je le trouve sympa, et que la procrastination est une tentation à laquelle il est difficile de résister =oD...
Je me suis intéressé aux petites fonctionnalités et à une autre, beaucoup plus cool.
Les listes des callees et de callers avec leurs statistiques assorties, permettent de naviguer dans les poupées russes de haut en bas, et de bas en haut sans trop repasser par le graphiques des boîtes imbriquées qui sont parfois trop denses et ne contiennent pas de statistiques globales d'une fonction par exemple. Voila ce que ça peut donner :
Et puis j'ai découvert, LE truc qui fait la différence et qui rend l'outil plus qu'utile, mais fun. C'est l'affichage du VRAI callgraph (graphe d'appel) sous forme de graphe noeud->liens. Si le système de poupées russes est très intuitif pour les performances des différents blocs, on trouvera utile le graphe d'appel pour savoir qui conduit à quoi, en quelle proportion, et si on a des cycles, des fonctions "globales"...
Je pense qu'une ou deux images feront plus court ici que des pages d'explications (et puis je fatigue). Voila ce qu'on peut obtenir à partir du même code et du même exécutable (et du même log il me semble).

On peut voir la représentation verticale du graphe d'appel.
Quelques notes :
- La fonction qu'on a sélectionné pour être le centre d'intérêt du graphe est un peu ombrée.
- On a une carte générale du graphe et une vue un peu plus zoomée.
- On peut changer l'inclinaison du graphe :
- de gauche à droite
- du haut vers le bas (le screenshot ci-dessus)
- le mode circulaire (le screenshot ci-dessous)
- On peut réduire le graphe en sélectionnant des fonctions plus bas dans le graphe.
- On peut continuer à parcourir le graphe d'appel, toutes les informations se mettent en correspondance. Si, par exemple, on choisit un élément dans le graphe d'appel, les poupées russes vont se mettre sur la fonction sélectionnée.

Petit détail un peu ennuyeux
J'ai découvert la fonctionnalité des jolis graphes assez tard (quand ils ne m'étaient plus vraiment utiles). KCacheGrind me disait que si je voulais pouvoir les afficher, il fallait dot et graphviz... J'avais occulté ce problème car l'outil me contentait. Et quand j'ai finalement voulu l'essayer je me suis retrouvé face à ce petit problème : graphviz, qui est un outil qui m'a l'air assez connu, n'est pas packagé par la communauté fedora linux, ou en tout cas ne se trouve pas dans leur repository de base.
Le problème s'est vite résolu, car sur le site de graphviz, on peut trouver tous les paquets rpm pour Fedora Core 4 (la bonne en plus) dont on a besoin. Ils s'installent sans problème et KCacheGrind fonctionne très bien ensuite, aucun problème.
C'est juste dommage, ceux qui n'ont pas Teh Intarwayb au boulot toute la journée, ou qui ont seulement accès à un miroir Fedora, sont condamnés à se priver d'une fonctionnalité de cet outil, pourtant si agréable. =o)
Rien de bien méchant, n'est-ce pas ?
Conclusion
Cet outil, il est bien, stable, intuitif et simple. Que demander de plus ?
Si vous avez encore un doute, c'est un outil KDE, donc derrière c'est du Qt. Voila qui devrait aider à convaincre le dernier récalcitrant blond un peu frisé au fond :-D
lundi, octobre 31, 2005
General Purpose GPU
Gpgpu Didactique
Les gens de gpgpu.org travaillent dans ce sens. Le site dispose d'un nombre important d'articles, résumés de recherches, ou encore présentations (ppt/pdf) concernant l'utilisation d'un GPU pour des calculs plus "généraux" que juste du dessin 3D.
Bien qu'il faille quelque peu fouiller pour trouver les meilleurs articles, les présentations sont très didactiques et complètes, introduisent à l'architecture des cartes video de façon bien plus didactique et "généraliste" que la plupart des articles de programmeurs de jeux video que j'ai pu lire (et j'en ai lu des dizaines).
Une fois toute l'architecture des cartes video digérée, on peut passer très vite à la pratique, en utilisant les classes mises à disposition sur leur site, en C++/OpenGL. Le code est extrêment clair, simple, et didactique, commenté à profusion et une connaissance quasiment nulle du C++ (comme la mienne) permet tout de même de comprendre sans aucun problème comment faire !
Parce qu'en général, en ayant fait un peu de 3d, on a des structures assez rigides dans la tête et même si on voit des possibilités se dégager avec les Shaders, on a pas forcément l'intuition pour comprendre les concepts de base de GpGPU. Par exemple, le rendu multi-passe était pour moi un vrai casse tête sans nom, ainsi que le render-to-texture, ou encore leur notion de kernel ou de stream. (noyau et flux me semblent des traductions fort appropriées =o)).
Maintenant, avec leur présentation, remâchée, relue plusieurs fois, je peux définir :
- un stream : un ensemble de données séparées, traitables en parrallèle, et n'ayant aucune dépendance interne.
- un kernel : une opération qu'on applique sur un élément d'un flux.
- Texture = Array : Une texture correspond au concept de tableau dans la programmation "classique". On alloue une texture pour faire tenir nos données.
- Fragment Program = Computational Kernel : un programme de fragment peut-être pensé comme un petit kernel de calcul qui est appliqué en parrallèle à plusieurs fragments (éléments d'un stream) en même temps.
- One-to-one Pixel to Texel Mapping:
- Data-Dimensioned Viewport : On a besoin (en général) d'un mapping "bijectif" de un pixel vers un texel. Ceci afin de s'assurer que l'on a effectué le calcul demandé sur chaque élément de la texture. En configurant notre viewport avec les dimensions de notre texture "destination" et en dessinant un Quad (rectangle en OpenGL) ajusté à la taille de l'écran, on s'assure que chaque pixel de notre texel (=o)) est généré et traité dans le fragment program... (oui, on dirait une traduction google mais non, je suis juste nul en anglais).
- Orthographic Projection : On définit en général un matrice de projection orthogonale avec un repère [-1;1] sur les axes X et Y. Ca permet d'avoir une correspondance simple entre les pixels et les texels.
- Viewport-Sized Quad = Data Stream Generator : pour pouvoir exécute des fragments programs, on a besoin de générer des pixels. Dessiner un Quad de la taille du viewport génère un fragment pour chaque pixel de notre texture de destination. Chaque fragment est traité identiquement par le fragment program.
- Copy To Texture = feedback : nous avons juste invoqué notre calcul en appliquant un fragment program sur une quad de la taille du viewport. Les résultats sont maintenants dans le framebuffer. Pour les stocker, on copie les données du framebuffer dans une texture. Ainsi on peut réutiliser ces résultats comme source pour l'affichage ou pour d'autres calculs.
Et non, seulement le code est clair mais il est didactique. Il reprend dans l'ordre toutes les étapes nécessaires pour dessiner quelque chose avec OpenGL, plaquer une texture, charger/compiler/linker un shader... Et on voit même où poser nos propres améliorations pour faire ce qu'on veut.
En plus, le code est gavé de commentaires. Et bien qu'il y en ai presque un par ligne, ce ne sont pas des commentaires ASM. Leurs commentaires permettent presque de comprendre le code sans le lire =o).
Bref, pour ma part, rien de trop, rien ne manque. Chapeau.
Great expectations
Les drivers 80.x de NVidia sont sortis récemment pour windows. Apparemment, la nouveauté que j'attendais le plus est implémentée et a l'air de fonctionner (pas trop vu de plaintes sur les forums NVidia).
Il s'agit de drivers OpenGL multithreadés. Oui, c'est une nouveauté, aussi étonnant que ça peut paraître. OpenGL est basé sur un modèle de machine à état. On pourrait imaginer que les fonctions de l'API sont des fonctions statiques du contexte OpenGL. Les états sont globaux.
Plus clair, un exemple. Quand on veut sélectionner une texture à appliquer, on lie le contexte courant à un numéro de texture (réservé plus tôt). On est contraint de faire ceci pour que le serveur sache que le client souhaite utiliser cette ressource. Les Texture Objects, les Vertex Buffer Objects, Pixel Buffer Objects, et Framebuffer Objects sont en général des objets serveurs. Cela évite le transfert client->serveur quand on veut s'en servir. Donc quand on veut sélectionner un Vertex Buffer, il faut appeler glBindBuffer. Tous les threads appelant du programme appelant OpenGL partageront cet état.
A priori, NVidia avait commencé à travailler sur ce problème avec leur bibliothèque tls (thread local s*). Mais j'ai essayé plusieurs fois avec les tâches Ada (qui sont codées par dessus les pthreads dans Gnat sous Linux), et j'arrivais toujours à provoquer une erreur OpenGL voire de gros plantages.
Asynchrone
Si leur bibliothèque est désormais bien multitâches (et donc que je vais pouvoir utiliser l'hyper-threading de mon P4 et les fonctionnalités de parallélisme d'Ada), j'espère aussi qu'ils ont rendu certaines commandes totalement asynchrones.
Mais certaines fonctions malheureusement ne renvoient pas ou ne permettent pas de continuer pendant qu'elle s'exécutent. Par exemple, glReadPixels est une commande assez lourde (lecture de pixels, transfert, grosse utilisation de ressources et de bande passante). Avec une extension récente, glReadPixels() a été rendue asynchrone, grâce aux possibilités de DMA (dites ce que vous voulez transférer, et partez faire ailleurs). Cela fait gagner un temps fou quand on fait du rendu multi-passe, sur des ensembles différents. Le tout est d'éviter de mapper (rendre accessible en lecture/écriture aléatoire) une zone serveur.
Comme je dois travailler sur énormément d'éléments assez petits (qui ne rempliraient pas le cache de vertex/fragment processing de façon efficace), j'ai 2 solutions : tout casser mon code pour coder une sorte d'algo d'allocation pour utiliser le cache au maximum (sans même être sûr que c'est le point limitant) ou alors laisser OpenGL s'occuper de remplir son cache en me voyant appeler 600 fois le même programme sur différentes zones.
Bref, un peu d'espoir de pouvoir essayer bientôt de faire joujou avec ces nouvelles "features".
En attendant, je me prends la tête pour mon truc de rendu multi-passe en pensant pouvoir faire exploser les 320 millions de points par seconde de mon ancien moteur. Ou au moins rendre l'archi plus propre, en évitant la tonne de calculs inutiles que je faisais jusque là =o).
Auto-Conversation, Never-starting-projects
Je viens juste de commencer à travailler sur un petit module Ada pour lire/écrire du iCalendar. Juste un peu de lecture du format et une petite idée sur une architecture simple pour implémenter le parseur... Passer par Aflex-Ayacc serait bien mais trop lourd, et je ne comprends rien à AdaGOOP... Alors en attendant, je vais tester les expressions régulières de GNAT qui ont l'air de ressembler de loin à Snobol (que je ne connais pas mieux), et voir ce qu'on peut en sortir. Et si un jour ma motivation est grande je regarderai du côté de AdaGOOP (qui manque furieusement d'exemples didactiques à mon sens).
Bah, le bon gros vrai projet intéressant et technique, ce serait un xslt-processor en Ada... Mais bon... Depuis quand je sais coder ? Il me suffirait peut-être de regarder le code d'un parseur simple, basé sur SAX... Sans trop de récursivité-mal-de-crâne...
Si ça se trouve je vais même prendre goût aux bindings C et au Python et porter plein de trucs vers Ada, tellement c'était pas trop dur avec OpenGL. =o)
Comment ça digression ?
Edit - Pour Jb
Jb, (dit aussi Monsieur Borborygme dans les milieux huppés du vinaigre Puget(tm)) m'a posé une question intéressante (je déforme à peine le propos histoire de nettoyer les erreurs de grammaire les plus grossières)...
Pour te répondre, si on a besoin de travailler avec des passes dépendantes, il faut appliquer une bonne partie des cours théoriques de système qu'on a vu en 3ème année de FIIFO. Parallélisme maximal, dépendances des données (pas de dépendances de processeur/registres... le parrallélisme du GPU est un parallélisme des données), conflits... Tout ça.
Est-ce que j'ai bien compris : les rendus multi-passes pourront se faire en parallèle ? Mais si elles sont dépendantes ces passes ?
T'as vu je fais bien le mec qui suit et qui est un peu au courant mais complètement à côté de la plaque (ma question est toutefois vrai)
Je ne vais pas mettre de Cokpit, ce serait mal placé. Dr Lionel et Mr Twouisteux, je m'en doutais un peu. A bientôt sur une autre passerelle de Massy...
Hé oui. A digression, digression et demi (quoiqu'il s'agisse plus de hors-sujet durable de ma part que d'un refus de te prendre au sérieux).
J'avoue que d'avoir réalisé avec brio =o) le projet de rattrapage de système aide un peu =o).
Venez voir ce document (pdf) pour des informations plus détaillées et plus poussées sur le rendu multi-passes avec des passes dépendantes.
N'hésitez pas pour les questions.
jeudi, octobre 20, 2005
Python : first sight
Jusque-là je travaillais sur d’anciens headers datant de 2003, qui me donnaient entière satisfaction, car j’ignorais l’avancée récente des technologies graphiques. Avec la découverte de gl_framebuffer_object et gl_pixel_buffer_object, et autres sucreries, il m’a fallu me remettre en cause.
Un binding OpenGL ?
En C pour travailler avec OpenGL il « suffit » de faire #include
Supposons qu’en C on ait la fonction :
En Ada on déclarera (si on le fait à la main) :
Il faudra ensuite écrire dans le source Ada
Les problèmes
Il fait plus de 5000 lignes et c’est un header C, avec les sympathiques instructions au préprocesseur pour définir des constantes et garder du code ou pas… Vraiment du code comme je l’aime : efficace - cà ! en performance c’est sûr… -, et compréhensible.
des redéfinitions de types C (typedef), préfixés en GL, collé. Par exemple : GLint, GLuint, GLubyte, GLfloat… des définitions de constantes « en dur » à l’aide de #define. Cela veut dire qu’en C ou en C++, à la précompilation le nom des constantes qu’on aurait pu insérer dans notre code sera remplacé « textuellement » par la valeur définie. Les constantes ont pour charte d’être entièrement en majuscules et les mots sont séparés par des caractères underscore ‘_’. Par exemple : GL_TRUE, GL_FALSE, GL_STREAM_DRAW. - des fonctions. Préfixées en gl (minuscule) et ensuite avec un style « dromadaire ».
Par exemple : glDrawBuffers, glCreateShaderProgram.
Il faut donc que le processus de génération du binding soit automatisé au maximum.
Mais encore une fois, il va être nécessaire de passer un script, mais sur du code Ada cette fois-ci.
Bien sûr les types ne sont pas convertis comme il faut par défaut. Il faut virer la dépendance à stddef.h
d’un bon système pour détecter les expressions régulières de pouvoir faire relire le code =o) de pouvoir coder assez vite sans avoir besoin de la vérification apportée par le typage de fonctions de traitement de chaînes de caractères et d’entrées-sorties, simples et directes
compilation d’une regex parcours du fichier source match de la regex reformatage ou autre
En tout cas, l'argument "je n'arrive pas à recruter des programmeurs Ada qualifiés" est franchement fallacieux. L'apprentissage d'Ada est très rapide. En 2 semaines on est formé à l'essentiel. Et ensuite, des revues de code, quelques petits projets simples proches du sujet du travail. En général c'est plus que suffisant. Ada et Eiffel sont, par leurs contraintes et la qualité de leurs compilateurs, des langages qui s'apprennent presque "tout seul". En tout cas, très rapidement.
