Table des matières
Nasal pour les nulsIntroductionNasal? c'est quoi?
C'est un langage de programmation[en] léger et fonctionnel, ce qui permet à FlightGear de l'embarquer dans son propre code source ($FGSOURCE/src/Scripting/*.?xx et $SGSOURCE/simgear/nasal/*.?xx). Il devient alors très simple d'ajouter de nouvelles fonctionnalités globalement (pour l'ensemble du simulateur) ou spécifiquement (pour un ou plusieurs objets, avions ou AI). Le tout sans avoir à recompiler FG et en apportant la souplesse du fichier texte (facile à lire/modifier/tester). Dit-on un script nasal et des scripts nasaux ?Oui, normalement, mais dans FlightGear l'exception confirme la règle, nous dirons donc des scripts nasal Où qu'il y a du nasal sur mon FG?Il y en a un peu partout Mais il y a des endroits privilégiés (quand c'est bien rangé, on s'y retrouve toujours plus facilement ):
Je ne compouend pas bien le france, mais I speak fluide le anglaisÇa tombe bien, there is a dedicated web page just for you on the English wiki of FlightGear[en] (page dont je me suis largement inspiré ici, merci à Nelson M. pour son aide ) Par où on commenceLes pré-requisUne (bonne?) connaissance du système des propriétés de FlightGear est un plus non négligeable. Un peu de syntaxe d'abord
Tout comme dans un forum, dans un courriel, une lettre, enfin à chaque fois qu'on écrit quelque chose qui doit être lu par quelqu'un d'autre, il faut faire attention à ce qu'on écrit et l'écrire de façon intelligible. Voici donc quelques règles à suivre pour que FlightGear comprenne bien ce que vous lui demandez de faire :
nous en reparlerons un peu plus bas
Avec quoi écrire un script?Le plus simplement du monde avec un éditeur de texte des plus basiques. Inutile de sortir OpenOffice.org (ou équivalent pas beau) pour éditer un fichier texte de quelques dizaines d'octets. Voici une liste non-exhaustive:
Il existe également un éditeur intégré à FlightGear (menu Debug → Nasal Console) qui est présent sur 0.9.10 (personne n'en est vraiment sûr) et bien entendu sur la version CVS. Mais il faut vite se rendre à l'évidence, il n'est pas très commode pour éditer du texte. Pour palier à ce manque 2), il existe la possibilité d'éditer son script dans un éditeur (voir plus haut) et de le lancer dans FG après chaque modification en passant par la console (et donc sans avoir à relancer FG).
Attention il faut que FG ait les droit en lecture sur le répertoire où se trouve votre code Nasal, de base il s'agit de $FGROOT/* et $FGHOME/*
Un peu plus sérieusementLes types de variables“Une variable associe un nom (un symbole) à une valeur qui peut éventuellement varier au cours du temps. Plus précisément , une variable dénote une valeur.” Wikipedia France En Nasal, les variables ne sont pas typées, elles peuvent donc contenir indifféremment du texte, un nombre ou une fonction. Elles doivent être déclarées, c'est-à-dire que lors de leur première utilisation, il faut indiquer au système qu'on veut créer une nouvelle variable. L'affectation d'une valeur à une variable s'effectue grâce au signe = (égal) var montexte = "une phrase"; var monnombre = 12; var mafonction = func { print ( montexte ); monnombre += 1; } mafonction (); Ici le code crée trois variables dont une fonction, et exécute la variable-fonction mafonction () qui elle-même fait appel aux deux variables précédemment créées pour afficher le contenu de la variable montexte et ajouter 1 à la variable monnombre (qui désormais vaut 13). Plus loin nous reparlerons plus précisément des fonctions. Il est bien sûr très fortement déconseillé d'ajouter un nombre à une phrase, une phrase à un nombre ou un nombre à une fonction, cela ne donnerait pas du tout le résultat escompté (à moins de savoir ce que vous faites et encore si Nasal vous laisse seulement le faire ) Il est impossible de donner un nom réservé à une fonction, par exemple var var = 1; va générer une erreur. Il est possible d'écrire var mavariable = 1; var mavariable = 2; Il existe des valeurs réservées (on ne peut pas les modifier), la plus importante étant nil, qui signifie “rien, vide”, qui est utile pour faire des tests. Il est également possible de stocker un “tableau” (appelé vecteur) ou un “hash”, dans ce cas les “cases” nécessaires seront automatiquement créées (auto-vivification). var montableau = [3,2,1,0]; # crée un tableau de 4 cases et l'initialise avec les valeurs if (montableau[1] == 2) print ("youpi!"); # le mot "youpi!" sera affiché var montableau = [[5,6],[7,8]]; # crée un tableau à deux dimensions if (montableau[0][1] == 6) print ("c'est énorme!"); # affichera le merveilleux texte var monhash = { unefonctioninterne : func { if (arg[0] == 3) { # variable tableau arg est spéciale, elle contient les paramètres passés à la fonction print ("c'est top"); return; } if (arg[0] < 3) print ("c'est plus..."); else print ("c'est moins..."); }, unefonction : func (valeur = 3) { me.unefonctioninterne (valeur); # le mot réservé "me." signifie "dans cette table de hashage" me.valeurinterne = valeur + 1; }, uneautrefonction : func { if (me.valeurinterne > 10) { me.valeurinterne = 1; }, unevaleur : 108.55, uneautrevaleur : nil }; monhash.unefonction(); # affichera "c'est top" monhash.unefonction(5); # affichera "c'est moins..." Il est aussi possible de créer des tableaux et tables de hashage vides var montableau = []; # crée un tableau vide var monhash = {}; # crée une table de hashage vide le qualificatif "var"
Il est des mails et messages qui comptent dans le monde de l'informatique, celui de Melchior aura certainement sa portée aussi output = func(v) { # missing "var" state = getprop("/sim/foo/state"); # missing "var" do_something(state, v); }
dans lequel ni output, ni state n'ont été déclarées avec le qualificatif “var”. Tout va fonctionner normalement, jusqu'au jour où quelqu'un créera un fichier $FG_ROOT/Nasal/state.nas ou $FG_ROOT/Nasal/output.nas. Ce jour-là vos variables ne seront plus sûres et surtout vont outrepasser l'espace de nommage, et donc leurs fonctions associées, créé par les deux fichiers. Par exemple: props = func { # missing "var" print("I'm props. And I just wiped the whole props namespace."); } ou encore foo = func { # missing "var" props = " :-P "; # missing "var" } ... foo(); exécutez-les, et toutes les méthodes décrites dans $FG_ROOT/Nasal/props.nas (NdT: espace de nommage très important pour FG!!!) seront remplacées par [noms d'oiseaux] au lieu de se contenter d'être locaux. Notez-bien que ceci est également valable pour les variables contenant une fonction. Il y a encore une raison pour laquelle le qualificatif “var” devrait être utilisé, même si on est certain qu'il n'y aura pas d'effet de bord (~ dégâts collatéraux). Le mot réservé “var” rend le code plus lisible et permet aux autres de le comprendre plus facilement (et donc de dénicher et corriger les éventuels bugs). Il indique clairement:“C'est ici que la variable débute sa vie de variable” Les structures de test
Un programme qui exécute à la suite des commandes sans tenir compte de paramètres pour le diriger ou le limiter, peut être utile, mais peu intéressant. Il est souvent nécessaire d'indiquer au système quoi faire dans telle condition, ou quand s'arrêter, etc. Pour ce faire, il existe des structures de test si condition est vrai alors fais ça sinon fais otechose en Nasal, il existe plusieurs façon d'écrire ce “branchement conditionnel” : if ... else ...if (fuel_freeze) return; Dans ce cas, si la variable fuel_freeze est différente de zéro (0 = false; tout le reste = true), alors la fonction return est exécutée, sinon le programme continue à l'instruction suivante. if (selected_tanks == 0) { out_of_fuel = 1; } else { var fuel_per_tank = consumed_fuel / size(selected_tanks); #... le reste du bloc d'instructions } #...la suite du script Ici, si la valeur de la variable selected_tanks est égale à zéro (notez le double == pour le test, à différencier du = simple de l'affectation) alors on met 1 dans la variable out_of_fuel et on passe directement au reste du script, sinon on exécute le bloc d'instructions avant de continuer le reste du script. ... or ...C'est un branchement conditionnel particulier qui permet d'améliorer la lecture du script en écrivant le test de façon intuitive : prop != nil or return;
signifie : “la variable prop est différente de nil ou on arrête”, ce qui est l'équivalent en plus élégant de “si prop est égal à nil alors on arrête”. variable == `h` or variable == 3 or print ( "variable n'est pas égal à h ni à 3" ); On retrouve également or dans le cas de tests multiples. Les tests multiplesTester une variable c'est bien, en tester deux, c'est mieux… \\zieutons le code suivant: if (e == "y" or e == "z") { print ( "e est égal à y ou à z" ); } else { print ( "e n'est pas égal à y ou z" ); } également il y a le test “et” if (e == "y" and e == "z") { print ( "ce texte ne sera jamais écrit" ); } else { print ( "ce texte sera toujours écrit" ); } en effet la variable e ne peut pas contenir à la fois y et z Les test "encadrants"Avant, pour tester si une variable était comprise entre deux valeurs, il fallait utiliser un test multiple if (valeur > valeur_mini and valeur < valeur_maxi) { print ( "valeur est comprise entre valeur_maxi et valeur_mini" ); #notez que les valeurs des variables #ne sont pas remplacées dans l'affichage #du texte, il faut passer par une autre fonction } else { print ( "c'est en dehors des clous!" ); } de nos jours il existe un moyen plus élégant d'écrire cela: if ( valeur_mini < valeur < valeur_maxi) { print ( "valeur est comprise entre valeur_maxi et valeur_mini" ); } else { print ( "c'est en dehors des clous!" ); } L'affectation conditionnelleNasal est un langage complet et propose tout ce que les langages évolués contiennent. Il est possible d'affecter une valeur (à une variable, à un paramètre de fonction, etc.) en incluant un test. Par exemple supposons que nous voulions faire: if (v > 10) { print ( "la variable v est supérieure à 10" ); } else { print ( "la variable v est inférieure à 10" ); } c'est tout à fait correct mais peu élégant, il serait préférable de faire: print ( "la variable v est ", (v > 10)? "supérieure" : "inférieure", " à 10" ); Les branchements conditionnelsDans quelques langages il existe un possibilité très intéressante de tester un variable # en bash case $variable in "youpi" ) fais_un_truc_avec_ca;; "1" | "10") fais_un_autre_truc_avec $variable;; *) ;; #ne fais rien si $variable n'est ni égal à "youpi", ni à "1", ni à "10" esac #en C switch (variable) { case 1: fais_un_truc(variable); break; case 10: fais_un_autre_truc(); break; default : break; # ne fais rien } en Nasal ce n'est pas possible, il vous faudra donc planter une forêt d'if if (variable == "youpi") fais_un_truc_avec_ca(); if (variable == 1 or variable == 10) fais_un_autre_truc_avec(variable); ou d'elsif if (me.stage == 1) { cprint("", "1: press start button #1 -> spool up turbine #1 to N1 8.6--15%"); setprop("/controls/rotor/brake", 0); engines.engine[0].ignitionN.setValue(1); engines.engine[0].starterN.setValue(1); me.next(4); } elsif (me.stage == 2) { cprint("", "2: move power lever #1 forward -> fuel injection"); engines.engine[0].powerN.setValue(0.13); me.next(2.5); } elsif (me.stage == 3) { cprint("", "3: turbine #1 ignition (wait for EGT stabilization)"); me.next(4.5); } elsif (me.stage == 4) { cprint("", "4: move power lever #1 to idle position -> engine #1 spools up to N1 63%"); engines.engine[0].powerN.setValue(0.63); me.next(5); } [code en provenance de Aircraft/Models/bo105.nas] Les bouclesCe ne sont pas de fonctions toutes prêtes pour vous faire réussir du premier coup des figures avec votre avion favori (mais elles peuvent être utiles pour vous aider à les faire) : Au lieu d'écrire var mafonction = func (n) { //faire un truc avec n }; mafonction(1); mafonction(2); mafonction(3); # je continue? , il est plus sensé et pratique et lisible et… j'en passe, d'utiliser les boucles qui permettent de faire répéter à l'ordinateur une commande autant de fois que nécessaire/souhaité. for (variable = 0; variable < 100; variable += 1)Cette boucle fait du trois en un
Une manière spéciale d'écrire une boucle for: var i = 0 for (; 1; i += 1) { #faire quelque chose avec i i < 100 or return; } cette boucle tournera 100 fois, cet exemple est là pour montrer que la syntaxe de cette structure est plutôt souple. while (variable < 100)cette boucle tournera tant que variable sera inférieur à 100. Attention, c'est à vous d'incrémenter la valeur de variable (contrairement à for (;;)). Elle permet cependant une utilisation pratique : #première forme var i = 0; { print ( "première forme" ); i += 1; } while ( i < 10 ); #deuxième forme i = 0; while ( i < 10 ) { print ( "deuxième forme" ); i += 1; } La différence réside dans l'ordre d'exécution. Dans la première forme, le bloc d'instruction est exécuté avant le test, donc sera exécuté une fois de plus que la deuxième forme. foreach (var element; ensemble)Cette boucle permet de parcourir chacun des éléments d'un ensemble en les affectant à la variable element. Il n'y a aucune certitude sur l'ordre dans lequel les éléments seront traités. forindex ()Il s'agit d'une forme élégante et simplifiée de la boucle for qui s'applique sur une variable de type tableau. me.data = ["a","b","c","d","e", "f","g","h","i","j", "k","l","m","n","o", "p","q","r","s","t", "u","v","w","x","y","z"]; me.cible = "r"; forindex (var i; me.data) if (me.data[i] == me.cible) { printf ("%s est la %d lettre de l'alphabet", me.cible, i+1); break; } } est l'équivalent de for (var i; i < size(me.data); i+=1) Tip: dans la boucle for ci-dessus la fonction “size()” est utilisée pour connaître la taille en éléments du tableau me.data, il serait judicieux de placer cette valeur (constante) dans une variable afin d'éviter d'appeler “size” à chaque itération de la boucle. settimer ()Il n'est pas toujours judicieux d'utiliser ces boucles avec FlightGear, au risque de voir le simulateur tout bêtement s'arrêter le temps que la boucle ait terminé ce qu'elle avait à faire (autre débat que celui qui nous intéresse ici). Pour palier à ce “léger” désagrément, il y a des solutions, en fait une seule mais c'est déjà pas mal et ça permet même de faire mieux . Voyons en détail ce bout de code récupéré sur le wiki anglais: var boucle = func { print("affiche ce message toutes les deux secondes"); settimer(boucle, 2); } boucle(); # lance la boucle
On y voit qu'une fonction (boucle ()) a été déclarée, elle contient une autre fonction settimer (boucle, 2) qui fait appel à l'objet boucle toutes les deux secondes. et voilà on a une boucle simple, sans compteur, qui ne gêne pas le fonctionnement de FlightGear, qui… ne s'arrête jamais c'est pas vraiment ce qu'on veut (enfin pas toujours). var boucleid = 0; var compteur = 0; var boucle = func (id) { print("affiche ce message toutes les deux secondes"); id == boucleid or return; compteur < 10 or return; compteur += 1; settimer(boucle, 2); } boucle(id); # lance la boucle # on fait le reste pendant que la boucle tourne boucleid += 1; La boucle ici tournera 10 fois ou alors sera arrêtée par le script avant (en changeant la valeur de boucleid) L'espace de nommage (TODO trouver un meilleur titre)
Je ne sais pas si c'est FlightGear qui permet au Nasal de partager les espaces de nommage des fichiers inclus dans $FGROOT/Nasal et $FGHOME/Nasal (ailleurs encore?), ou si Nasal sait le faire tout seul… Toujours est-il que cette possibilité existe et est très utilisée car très pratique. var phrase = "du texte sympa"; var une_fonction = func { print (phrase); }
a.une_fonction (); a.phrase = "une phrase encore plus sympa"; a.unefonction (); ainsi le script b.nas va utiliser la fonction définie dans l'espace de nommage défini par a.nas. D'oùl'intérêt de suivre les recommandations détaillées plus haut sur l'utilisation du qualificatif “var”. Les Listeners (lisseneurze)
Les “écouteurs” (ou “sniffeurs” ?) sont des <langage technique>fonctions de rappelfr (ou “callback functions”)</langage technique> attachées à des propriétés de FG. C'est-à-dire qu'à chaque fois qu'une propriété est modifiée/créée/supprimée, le listener appelle la fonction qui lui est associée. Les fonctions callback et surtout... leurs paramètresLes fonction de rappel peuvent prendre de zéro à quatre paramètres, les deux premiers doivent être des noeuds de propriétés, le troisième est un opérateur, le quatrième est un événement concernant les enfants du noeud de propriétés HELP j'y pige que dalle setlistener ()C'est une fonction qui permet de créer un listener (ou écouteur, ou sniffeur) var listener_id = setlistener(<propriété>, <fonction> [, <startup=0> [, <runtime=1>]]);
Un exemple basique de listener: setlistener("/sim/signals/exit", func { print("à bientôt !") }); ceci permet d'afficher sur le terminal le texte “à bientôt !” dès qu'on écrit quelque chose dans la propriété ”/sim/signals/exit”. Ce code aurait également pu s'écrire de la façon suivante: var mafonction = func { print("à bientôt !") }; setlistener("/sim/signals/exit", mafonction() }); ou encore var mafonction = func { print("à bientôt !") }; setlistener("/sim/signals/exit", mafonction() }, 0, 1); ou encore… Une utilisation plus poussée des listeners est bien sûr possible, mais je vous laisse la découvrir via la doc complète[en]. removelistener ()
Nous avons créé des listeners, c'est bien… Et on peut en créer autant qu'on le souhaite sans la moindre détérioration des performances de FlightGear (c'est Melchior qui le répète inlassablement ). Cependant il peut être utile de supprimer des listeners devenus inutiles, ou qui ne doivent servir qu'une seule fois. var L = setlistener("/une/propriete", func { print("Je ne serai affiché qu'une fois"); removelistener(L); }); Cet exemple montre le cas d'un listener qui ne sera utilisé qu'une seule fois, lors du changement de la valeur de la propriété /une/propriete. Les fonctions fournies par ...... le code source
props.nasglobals.nasdebug.nasstring.nasaircraft.nasflightplan.naslead_target.nasscreen.nasatc-chatter.nasfuel.nasmaterial.nasstartup.nascontrols.nasgeo.nasmath.nasmultiplayer.nastrack_target.nasgui.nasprop_key_handler.nastutorial.nasdynamic_view.nasio.nasview.nas |