Table des matières

Introduction

Préliminaires

Le système de propriétés que met en avant FlightGear peut paraître complexe au premier abord mais en creusant un peu plus, on se rend compte qu'il s'agit d'un mécanisme très complet et très riche qui permet de modifier très finement le comportement du simulateur. C'est peut-être sa richesse qui donne une impression de complexité mais on se rend compte que cette complexité n'est qu'apparente.

Une bonne compréhension de l'arbre des propriétés permet d'avoir une bonne visibilité de ce qu'il est possible de faire sous FlightGear, de comprendre une grande partie de son fonctionnement.

Une bonne façon de voir les propriétés est de les considérer comme des variables globales 1):

L'arborescence: la notions de noeuds

Les propriétés sont organisées et hiérarchisées afin d'améliorer la lisibilité du code qui les exploite, et aussi permettre à l'utilisateur de retrouver le plus facilement possible la propriété recherchée.

Cette organisation prend la forme d'un arbre, comme un arbre généalogique, ou une arborescence de fichiers et de répertoires (également appelés “dossiers” chez les windowsiens).

Il y a donc une racine, nommée ”/” (slash ou barre de division) et des “branches”, qui se terminent par des “feuilles”. Chaque élément de l'arborescence est appelé un noeud (node in English).


Cette illustration d'une arborescence est tirée de la page Wikipédia sur l'arborescence.
Par exemple Smiles, Happy, Truck sont des noeuds, alors que Vehicles → Whole → Car est une propriété qui a pour valeur le symbole de la voiture rouge. De même Smilies → Happy est une propriété qui a pour valeur le symbole ::Sourire::
La propriété Smiles → Sad → Truck n'existe pas, ne possède donc pas de valeur, et si vous tentez d'y faire référence vous serez éventuellement gratifié d'un chaleureux message vous en informant:

non-objects have no members at ...

Le nommage des noeuds et propriétés

Chaque noeud est identifié par un nom qui lui permet d'être reconnu et de savoir (normalement :-D) à quoi il correspond. Mais alors comment faire pour avoir autant de noms différents? Pas de panique, il est tout à fait possible de donner à plusieurs propriétés et noeuds d'un même niveau des noms identiques, FlightGear 2) ajoutera alors automatiquement un numéro d'index qui les séparera. Ce numéro est alors écrit entre crochets, à la fin du nom.

Il existe une règle implicite dans FlightGear pour le nommage des propriétés et des noeuds:

Accéder aux propriétés

Les propriétés sont classées selon leur appartenance à des catégories. Couramment, les plus intéressantes sont:

Chaque catégorie se rapporte à un équipement ou une fonctionnalité et contient toute une arborescence qui se termine par des feuilles (variables) contenant des valeurs que l'on peut éventuellement venir modifier de plusieurs manières.

le navigateur de propriétés

Lorsque vous lancez FlightGear, vous avez la possibilité d'afficher une fenêtre présentant une arborescence des propriétés pour vous permettre d'y naviguer. Pour celà, sélectionnez Files→Browse Internal Properties.

Un clic sur une des propriétés la sélectionne et il est alors possible d'en modifier la valeur. Un clic en appuyant simultanément sur la touche Ctrl permet d'afficher la valeur de manière continue sur l'écran. Notez que certaines propriétés ne sont pas modifiables manuellement car elles sont mise à jour automatiquement par le modèle de vol (JSBSim par exemple). Si vous essayez de modifier une telle propriété, vous constaterez que FlightGear semble ne pas vouloir prendre en compte la valeur que vous spécifiez. Pas de panique, il s'agit d'un comportement normal.

la méthode des vrais hommes :-)

Notez qu'il existe également (en version CVS) la possibilité de chercher et afficher simplement une ou plusieurs propriétés avec le raccourcis-clavier ”/” (barre de division), il existe ensuite tout un tas d'autres raccourcis pour rechercher la bonne propriété et en faire ce qu'on veut.

Pour activer cette fonctionnalité il faut aller dans Debug→Development Keys et cocher la case “Enable '/'-key property handler” (ce choix est automatiquement sauvegardé à la fermeture de FlightGear).

touche action
/ valide le noeud et entre au niveau suivant de l'arborescence
tabulation complétion automatique du nom du noeud (ordre alphabétique)
shift + tabulation complétion dans l'autre direction
échappement abandonne et retourne au fonctionnement normal du clavier
entrée affiche la veleur de la propriété dans la fenêtre de FlightGear et dans le terminal
* affiche la valeur de la propriété et de tous les noeuds fils dans le terminal (essayez ”/*” pour rigoler :-D)
! ajoute le suivi de la valeur de la propriété dans une petite fenêtre en haut à gauche
: ouvre le navigateur de propriété directement sur le répertoire de la propriété recherchée
? recherche toutes les propriétés qui contiennent les caractères précédemment tapés
flèches du haut et du bas navigue dans l'historique des dernières propriétés valides cherchées
shift + backspace efface le dernier élément

Le système propose également un code de couleur

couleur signification
blanc syntaxe du chemin correct, mais la propriété n'existe pas (encore)
vert  propriété existante
rouge  erreur de syntaxe
jaune en cours de frappe
magenta recherche

quelques fonctions Nasal bien pratiques

affiche l'arborescence depuis propriété (ou le répertoire la contenant), par défaut propriété est la racine de l'arbre. La valeur “1” indique d'afficher l'arbre en utilisant l'indentation, si elle n'est pas spécifiée (ou égale à 0) l'affichage est “à plat”.

indique chaque changement/création/suppression de la propriété propriété

Les types de propriétés

Les propriétés sont typées, et il existe types différents:

type contenu de la propriété
NONE aucun type défini, cette propriété ne pourra pas être utilisée pour effectuer un calcul numérique
STRING type permettant d'affecter du texte
BOOL si égale à 0 alors est faux, sinon est vrai
INT valeur entière positive ou négative
DOUBLE valeur à virgule flottante

Il existe également un type particulier: ALIAS. Ce type indique que la propriété est un deuxième nom pour une autre propriété (comme sous *NIX, un fichier peut avoir plusieurs noms), donc la propriété possède exactement les mêmes attributs (valeur, type) que “l'original”. Il existe en Nasal deux fonctions permettant de lier/délier des propriétés: alias() et unalias().

Cette gestion des alias peut être très utile pour par exemple la gestion des pannes: un instrument est lié à l'alias de la propriété contenant la valeur à afficher, en cas de panne de l'instrument il suffit de délier l'alias pour faire afficher une valeur différente de celle que l'instrument est sensé afficher.

Manipuler l'arbre des propriétés

Nativement, FlightGear supporte deux langages: le XML (qui structure les données comme des nombres ou des phrases), et le Nasal (qui permet de créer de vrais “sous-programmes” de manière très souple). Ils sont intimement liés à l'arbre des propriétés

Propriétés et XML

Quasiment l'intégralité des fichiers de configuration de FlightGear sont écrit en XML. Une attention profonde montrera que chaque balise contenue dans chaque fichier ”.xml” correspond à un noeud dans l'arbre des propriétés. Ce sont donc les valeurs qui seront passées à FlightGear lors de son lancement, et qui pourront être modifiées plus tard.

Quand FlightGear 3) lit le fichier xml, il modifie (ou éventuellement crée) l'arborescence correspondante dans l'arbre des propriétés.

Propriétés et Nasal

Le Nasal embarqué de FlightGear propose plusieurs possibilités pour créer, modifier, surveiller ou supprimer les propriétés.

fonction(paramètres [,paramètres optionnels=valeur par défaut]) rôle
getprop(propriété) retourne la valeur contenue dans la propriété,
si elle n'existe pas ou ne contient rien, renvoie “rien” (valeur spéciale appelée nil)
setprop(propriété, valeur) met la valeur valeur dans la propriété propriété, attention le type de la propriété sera “NONE” si non-prédéfini
handler = props.globals.getNode(propriété [, création=0]) renvoie un handler 4) vers un noeud de propriété,si création est à 1, et que la propriété n'existe pas, elle sera créée.
handler.getValue() retourne la valeur contenue dans le noeud (cf. getprop())
handler.setValue(valeur) assigne la valeur valeur à la propriété, cf. setprop())
handler.getType() retourne le type de la propriété (DOUBLE, FLOAT, INT, BOOL, STRING, ALIAS ou NONE)
handler.setType(type)  assigne le type type à la propriété
handler.getChild(noeud [, index=0[, création=0]])  renvoie un handler sur un noeud fils
handler.getChildren([noeud]) renvoie tous les noeuds noeuds fils attachés au handler, si noeud n'est spécifié ils sont tous renvoyés. Attention la valeur renvoyée est un vecteur (tableau à une seule dimension) de handlers
handler.getName() renvoie le nom complet de la propriété
handler.removeChild(propriété [, index=0])  supprime la propriété propriété[index]
handler.removeChildren(propriété)  supprime toutes les propriétés propriété
setlistener(propriété, objet [, statut=0]) crée un “callback” sur objet dès que la valeur de la propriété change ou quand elle est créée, dépend de statut. Notez qu'il s'agit d'un appel à un objet et non pas à une fonction

Quelques cas pratiques

Il n'est pas raisonnable de détailler toutes les propriétés présentes sous FlightGear et de toutes façons cela n'aurait pas beaucoup de sens. Il vaut mieux en analyser quelques unes et voir comment on peut les utiliser pour réaliser diverses choses.

L'exemple des interrupteurs

Nous allons nous baser sur un avion que j'affectionne tout particulièrement, le fameux Lightning qui est un excellent jet d'interception des années 70. Les fichiers constituant ce modèle sont particulièrement complets et leur analyse permet d'apprendre beaucoup de choses sur la manière dont les propriétés fonctionnent.

Dans le cockpit de cet avion est situé un commutateur 3 positions sur la console à droite du pilote. Ce commutateur contrôle l'allumage et l'extinction des feux de navigation. Ces feux sont situés aux extrémités des ailes. Un feu de couleur verte pour l'aile droite et un feu de couleur rouge pour l'aile gauche.

Si, avec la souris, vous actionnez ce commutateur, vous allez effectivement allumer ou éteindre les feux de position, ce qui, à priori, est tout à fait souhaitable. Vous pouvez le constater en observant l'avion de l'extérieur.

Maintenant, ouvrez la fenêtre des propriétés en allant dans Files→Browse Internal Properties, puis déplacez-vous dans controls/switches. Faites défiler le contenu de la fenêtre vers le bas de façon à afficher la propriété nav_lights.

Actionnez de nouveau le bouton poussoir (avec la souris) et observez le changement immédiat de la valeur de la propriété (sa valeur passe de 0 à 1, puis revient à 0, puis -1, etc).

Cliquez sur la propriété nav_lights, puis dans la zone de saisie, effacez l'ancienne valeur et entrez une nouvelle valeur, par exemple 1 à la place de 0. Lorsque vous validez en cliquant sur le bouton SET, vous pouvez observer dans le cockpit de l'avion que le bouton poussoir change également d'état dynamiquement.

Maintenant, on va s'intéresser à une autre propriété. Dans la fenêtre des propriétés, déplacez-vous dans sim/model/lightning/lights/. Regardez la propriété nav_lights qui est à 0 (valeur par défaut). Si vous observez votre avion de l'extérieur, vous pouvez constater que les feux de navigation ne sont pas allumés. Maintenant, cliquez sur la propriété nav_lights, effacez la valeur 0 et entrez la valeur 1, puis validez. Observez de nouveau les feux de navigation. Ils sont allumés.

En agissant sur cette propriété, vous avez en fait agi sur un état du modèle ce qui vous permet d'en observer un phénomène direct, l'allumage des feux.

Vous savez maintenant agir directement sur quelques propriétés. Notez cependant que toutes les propriétés ne sont pas aussi triviales que les deux que nous prenons en exemple.

Maintenant, comment se fait-il que lorsque vous actionnez le commutateur Allumage des feux dans le cockpit, les feux s'allument bien à l'extérieur ? Il doit y avoir quelque chose qui lie les deux propriétés que nous venons de voir, à savoir état du commutateur du cockpit et état des feux de position. Dans le monde réel, si on simplifie, on pourrait dire qu'il y a un cable électrique qui relie les deux. Qu'en est-il dans le monde virtuel de FlightGear ? Quelques explications…

FlightGear intègre la possibilité d'exécuter des scripts externes écrits en langage NASaL (Not Another Scripting Language, voir l'excellent tutoriel http://fr.flightgear.tuxfamily.org/doku.php?id=devel:nasal_pour_les_nuls). Grâce à des fichiers .nas contenant du code en nasal, vous allez pouvoir modifier les propriétés, en particulier celles que nous venons de voir. Dans le cas du Lightning, il existe le fichier $FG_ROOT/Aircraft/Lightning/Systems/lightning-lights.nas. Ce fichier est référencé par le fichier $FG_ROOT/Aircraft/Lightning/lightning-set.xml qui chapeaute tous les autres via une directive <nasal>…</nasal>. Bref, passons sur ce détail mais sachez qu'un fichier .nas doit être référencé quelque part pour pouvoir être pris en compte.

Dans ce fichier lightning-lights.nas, on trouve en particulier les lignes suivantes qui permettent de déclarer la fonction NavLights:

var NavLights = func {
var switch = getprop("controls/switches/nav_lights");
var light = getprop("sim/model/lightning/lights/nav_lights");
...

Ces deux lignes permettent de lire les valeurs des propriétés (appel à la fonction getprop) qui nous servent d'exemple ici. Les valeurs sont stockées dans les variables locales (mot clé var) switch et light.

Pour l'instant, aucun lien n'est fait entre ces deux propriétés mais il est facile de le faire en écrivant le code suivant, toujours dans le même fichier .nas:

if (switch == "0" or switch == "nil") {
  setprop ("sim/model/lightning/lights/nav_lights", 0);
}

Cette ligne indique clairement que si le commutateur (switch) vaut 0, alors on positionne la propriété sim/model/lightning/lights/nav_lights à 0, ce qui équivaut à éteindre les feux de navigation. L'écriture de la valeur d'une propriété est réalisée grâce à la fonction setprop.

Remarquez le test particulier:

switch == "nil"

Il permet de tester l'existence de la variable switch. Si elle vaut nil (Not In List, réminiscence du langage Lisp), alors la variable n'existe pas (elle n'a pas été définie). On considère alors que les feux de navigation sont éteints.

Notez également que dans ces quelques lignes de code, vous retrouvez l'arborescence des propriétés que nous avons prise en exemple.

Maintenant, que se passe-t'il si vous voulez allumer les feux de navigation. Vous devez donc traiter le cas où la propriété controls/switches/nav_lights vaut 1, grâce au code suivant:

...
elsif (switch > "0") {
  if (light == nil or light == "0") { 
    light = "1"; # Allumage des feux (effectif à l'appel de **setprop()**)
    lightsec = 1; # Temporisation de 1 seconde
  }
  else {
    light = "0"; # Extinction des feux
    lightsec = 6; # Temporisation de 6 secondes
  }
  setprop("sim/model/lightning/lights/nav_lights", value);
  settimer(Lightning.NavLights, lightsec);
}

Dans ce code, si le commutateur switch est en position “1”, on fait clignoter les feux de position. Pour celà, si la valeur des feux de position light est à “0” (feux éteints), alors on les allume. S'ils étaient allumés, on les éteint.

De plus, on les fait clignoter avec un rapport cyclique de 1 pour 6. Celà signifie que les feux restent allumés 1 seconde et éteints pendant 6 secondes, approximativement. La fonction settimer permet d'indiquer qu'il faut appeler la fonction NavLights au bout de lightsec secondes, c'est-à-dire dans notre exemple soit au bout de 1 secondes si les feux sont allumés, soit au bout de 6 secondes si les feux sont éteints.

Dernière chose très importante ! Vous constatez que tout le code a été écrit dans une fonction qui s'appelle NavLights. Si vous voulez que cette fonction soit appelée dès que le commutateur situé dans le cockpit est actionné, il faut explicitement l'indiquer. Ceci est réalisé grâce à la dernière ligne de code suivante:

setlistener ("controls/switches/nav_lights", NavLights);

Cette ligne permet de “connecter” la propriété controls/switches/nav_lights vers notre fonction. Tout changement qui interviendrait dans la propriété (changement de valeur) résulterait automatiquement en l'appel de la fonction NavLights.

Voilà pour ce premier exemple. Je vous engage à explorer l'arbre des propriété et voir l'influence des changements de certaines valeurs. Je vous engage également à regarder dans les différents fichiers qui constituent les modèles et qui peuvent vous apprendre beaucoup de choses.

1) accessibles par tout le programme
2) , 3) SimGear en vrai
4) une valeur illisible par l'utilisateur qui permet au programme d'accéder directement à la classe