Arma: Les variables

J’ai relu plusieurs documents (Biki, Killzone Kid, Foxhound, les vétérans, ..), qui décrivent le fonctionnement des variables sous Arma, et j’ai finalement décidé de synthétiser l’ensemble. Je ne cherche pas à être exhaustif mais à donner une vision d’ensemble cohérente du fonctionnent des variables sous Arma.

Une variable sous Arma:

  • est nommée par un identifiant (un nom)
  • est une référence vers un espace mémoire contenant la donnée
  • a une portée (privée/globale/publique)

Identifiant
Pour donner un nom à la variable, il faut respecter une normalisation stricte. Il faut utiliser des lettres (a-z)(A-Z), des chiffres(0-9). Il est interdit de commencer le nom par un chiffre.

L’usage du underscore est autorisé, en début de nom il permet également de définir la portée d’une variable à privée. Arma ne différencie pas les minuscules des majuscules, il faut donc être vigilant (aRmA et arma sont au final les mêmes identifiants de variable).

Référence
Une variable sous Arma n’est pas typée, c’est la donnée référencée par la variable qui est typée. Par conséquent, une même variable peut référencer successivement plusieurs type de donnée différents sans provoquer d’erreur.

mavariable = 1;
mavariable = "donne";

mavariable = 1;
typename mavariable; // SCALAR, c'est la donné référencé qui est scalar

mavariable = "donne";
typename mavariable; //STRING, c'est la donné référencé qui est string

Pour identifier le type de la donnée référencée, tu dois utiliser la commande typeName.

_var = 0; hint typeName _var; //SCALAR
_var = ""; hint typeName _var; //STRING
_var = true; hint typeName _var; //BOOL
_var = []; hint typeName _var; //ARRAY
_var = {}; hint typeName _var; //CODE
_var = objNull; hint typeName _var; //OBJECT
_var = scriptNull; hint typeName _var; //SCRIPT
_var = grpNull; hint typeName _var; //GROUP
_var = controlNull; hint typeName _var; //CONTROL
_var = teamMemberNull; hint typeName _var; //TEAM_MEMBER
_var = displayNull; hint typeName _var; //DISPLAY
_var = taskNull; hint typeName _var; //TASK
_var = locationNull; hint typeName _var; //LOCATION
_var = opfor; hint typeName _var; //SIDE
_var = parseText ""; hint typeName _var; //TEXT
_var = configFile; hint typeName _var; //CONFIG
_var = missionNamespace; hint typeName _var; //NAMESPACE

Quand la valeur d’une variable est définie à nil, celle-ci n’a pas de type car elle ne pointe sur aucune donnée.

Passage de paramètres à une fonction (valeur & référence)

En fonction, du type de données référencé par la variable, le moteur d’Arma n’opère pas le passage de paramètres à une fonction de la même façon.

Deux cas de figure:

  • Par valeur : le type de donnée est primitif (SCALAR, STRING, BOOL, ..): La variable est passé en paramètre en tant que valeur, ARMA va créer une copie de la variable à l’intérieur de la fonction. Une modification de la valeur de la variable dans la fonction n’a donc aucun impact sur la variable à l’extérieur de la fonction.
  • Par référence: le type de donnée est complexe (ARRAY, OBJECT, ..): Arma va créer une variable à l’intérieur de la fonction avec une référence qui pointe sur la même donnée.

Par valeur

   myfunction = { 
        _this = false;
    };

    private _array = true
    _array call myfunction;
    hintc format ["%1", _array ];
    // affiche true

Par référence:

   myfunction = { 
        _this pushBack 3;
    };

    private _array = [1,2];
    _array call myfunction;
    hintc format ["%1", _array ];
    // affiche [1,2,3]

Portée
La portée d’une variable correspond à la zone de code dans laquelle une variable est accessible / modifiable. Sous Arma, il y a 3 types de portée, je synthétise car les documents sont très brouillons et cela permet d’avoir une meilleur compréhension de ce que l’on peut faire.

  • privée: accès restreint à la structure de contrôle encadrée par {}, une fonction, ou un script
  • globale: accès restreint à la machine qui exécute le programme
  • publique: accès partiel ou complet à l’ensemble des machines se trouvant sur le réseau

Au regard de ces éléments, il est hautement recommandé d’utiliser quasiment tout le temps les variables privées, et ce pour deux raisons : les performances, et la sécurité.

Les variables privées
Les variables privées n’ont d’existence que dans les scopes ou elles sont déclarées . Cela peut être un script, une fonction, une structure de controle (if, for, foreach, etc).

Les variables privées doivent être déclarée via la commande private.

private _mavariable = "hello";

Les variables privées sont crées dans le scope ou elles sont définies et supprimées à la sortie de ce scope.

   if(true) then {
        _i = 1;
    };
    hint format ["%1", _i]; // _i est égale à nil

Les variables privées ont une durée de vie limitée, elles sont supprimées automatiquement à la fin de l’exécution de leur scope, ce qui permet de limiter la consommation mémoire.

Si tu définis une variable privée dans un scope, cette variable sera également alors défini dans tous les scope fils imbriqué. Si tu veux modifier une variable dans un scope fils et que çà prenne effet dans un scope parent, il faut s’assurer que la variable soit définie dans le scope parent.

_i = 1;
if true then {
    //_i est égale à 1 et définie ici
    if true then {
        //_i est égale à 1 et définie ici
        if true then {
            //_i est égale à 1 et définie ici
        };
    };
};

Par contre, si tu veux que la valeur d’une variable soit accessible uniquement dans le scope fils, alors tu dois utiliser expliciterment la commande private dans le scope fils.

_i = 1;
if true then {
    //_i est égale à 1 et définie ici
    if true then {
        //_i est égale à 1 et définie ici
        if true then {
           private _i = 2;
            //_i est égale à 2 et définie ici
        };
    };
};

Il n’est pas recommandé d’utiliser depuis les dernières versions du moteur, un tableau de variable associé à la commande private pour des raisons de performance.

Les variables globales
Les variables globales sont accessibles de tous scripts sur la machine. Ces variables sont stockés dans des espaces de nom (namespace) en fonctions de leurs spécificités.

Par défaut, au moment de son allocation, une variable globale sera insérée dans le namespace : missionNameSpace.

Ainsi:

mavariable = "hello";

est strictement identique à

with missionNamespace do{
    mavariable = "hello";
};

ou à

missionNamespace setvariable ["mavariable", "hello"];

pour utiliser un autre namespace, tu peux utiliser with et faire pareil:

with uiNamespace do {};
with parsingNamespace do {};
with profileNamespace do {};

Les 4 espaces de noms :

  • missionNamespace: les variables n’existent que le temps de la mission jusqu’au retour lobby
  • parsingNamespace: contient les variables provenant des fichiers description.ext, config.cpp définies via la MACRO __EXEC(variable=value) . Les variables existent le temps qu’Arma est lancé.
  • uiNamespace: contient les variables relatives à l’interface utilisant. Les variables existent le temps qu’Arma est lancé.
  • profileNamespace: contient les variables stockés en dur relative au profil utilisateur selectionné, les variables sont accessibles après le re démarrage d’Arma. Si tu crées un nouveau profil, ou que tu switchs sur un profil alternatif, les variables publiques qui existent dans l’ancien profil n’existeront plus dans le nouveau. La sauvegarde des variables dans profileNamespace est réalisée automatiquement mais tu peux la forcer en utilisant la commande saveProfileNamespace.

Une variable définie dans un espace de nom n’est pas accessible via une autre espace de nom.

myvar = 123;
with uiNamespace do {
    hint str (isNil "myvar"); //true
    myvar = 456;
};

with profileNamespace do {
    hint str (isNil "myvar"); //true
    myvar = 789;
};

with uiNamespace do {
    hint str myvar; //456
};

with profileNamespace do {
    hint str myvar; //789
};
hint str myvar; //123

Il est possible de définir ou de récupérer le contenu d’une variable globale en utilisant les commandes setVariable et getVariable associé avec le namespace à utiliser. La commande getVariable permet également de définir une valeur de retour par défaut, si la variable n’est pas définie pour traiter les erreurs.

missionNamespace getVariable ["mavariable", ""]; // renvoie ""

missionNamespace setVariable ["mavariable","foo"]; 

missionNamespace getVariable ["mavariable", ""]; // renvoie foo

Il est également important de connaitre ces commandes qui sont une véritable optimisation de performance, qui permet par exemple de ne plus faire appel à des commandes comme call compile format pour rendre dynamique l’exécution du code. Le call compile étant assez consommateur en terme de ressource. Voici l’ancienne façon de faire, et la façon la plus correcte ce jour de le faire.

var1 = 1;
var2 = 2;
for "_i" from 1 to 2 do {
    private "_myvar";
    call compile format ["_myvar = var%1;", _i];
    diag_log _myvar;
};

var1 = 1;
var2 = 2;
for "_i" from 1 to 2 do {
    diag_log (missionNamespace getVariable (format ["var%1", _i]));
};

Pour supprimer une variable, il faut utiliser la commande setVariable, le bon namespace, et définir sa valeur à nil.

profileNamespace setVariable ["mavariable", nil];

La commande allVariables permet de récupérer l’ensemble des identifiants des variables définies dans le namespace.

_allVarsUINamespace = allVariables uiNamespace;

Hormis ces espaces de noms, les variables globales peuvent également être attachées à des objets, des véhicules, des contrôles, de groupes, des tasks, des locations, des team members.

Les variables publiques
Une façon simple de partager une variable entre les différentes machines est de l’initialiser dans le fichier init.sqf en tant que variable globale. Cependant au cours de la mission, les machines ne partageront plus la valeur qui évoluera sur chacune des machines. Pour permettre ce partage, il existe les variables publiques.

Les variables publiques sont des variables globales qui sont broadcastés sur le réseau et qui vont donc écraser la valeur des variables globales se trouvant sur les machines distantes.

Comme ce mécanisme se fait silencieusement, BIS a également ajouté des Event Handler addPublicVariableEventHandler qui se déclenchent uniquement quand une variable globale est écrasée par une nouvelle valeur provenant du réseau.

Outre le fait, que ce mécanisme est assez simple et efficace, il soulève plusieurs problèmes de sécurité et de performance. Sécurité, tes fonctions déclarées dans des variables globales peuvent être écrasés par ce mécanisme si tu ne les protèges pas contre l’écrasement avec un compilefinal.

Performance, car l’usage abusif des variables globales augmente considérablement la charge mémoire si elles ne sont pas libérées, et cela également peut occasionner une surconsommation de ton trafic réseau.

Pour publier une variable globale sur le réseau, tu peux utiliser les commandes publicVariable, publicVariableClient, publicVariableServer.

// machine 1
myvar = "somevar";
// machine 2
hint format ["%1", myvar]; //"any"
// machine 3
hint format ["%1", myvar]; //"any"

// machine 1
myvar = "somevar";
publicVariable "myvar";
// machine 2
hint format ["%1", myvar]; //"somevar"
// machine 3
hint format ["%1", myvar]; //"somevar"

Il faut néanmoins limiter clairement le nombre de variables publiques broadcaster car cela peut avoir une impact surtout lors des connexions des nouveaux joueurs. A ce moment l’ensemble des variables peuvent être retransmises pour synchroniser le nouvel arrivant.

Il existe également une autre méthode en utilisation la commande classique setVariable avec un troisième paramètre à true, et en attachant à des objets partagés sur le réseau des variables. Il faut cependant prendre en compte plusieurs points / limitations. Les objets sous ARMA sont streamés et donc pas forcément visible par l’ensemble des joueurs. Broacaster un objet, ne broadcast pas les variables qui lui sont attachées.

//computer 1
myobj = (typeOf player) createVehicle (position player); 
myobj setVariable ["lastCreated", time];
hint str (myobj getVariable ["lastCreated", 0]); // temps depuis qu'à commencé la mission
publicVariable myobj;

//computer 2
hint str (myobj getVariable ["lastCreated", 0]); //"0"

Pour rendre la variable publique, il faut procéder de cette façon avec la commande setVariable :

//computer 1
myobj = (typeOf player) createVehicle (position player); 
myobj setVariable ["lastCreated", time, true];
hint str (myobj getVariable ["lastCreated", 0]); //time since mission start
publicVariable myobj;
//computer 2
hint str (myobj getVariable ["lastCreated", 0]); //time since mission start

Les variables privées magique

Je t’invite à relire l’article sur les variables magiques. A l’exécution de certaines commande, le moteur définit automatiquement des variables privées qui existent uniquement dans le scope de la commande.

{
    {
        hint str _x; //_x est égale à 1
    } forEach _x; //_x est égale à [1,2,3]
} forEach [[1,2,3],[4,5,6],[7,8,9]];

Comme tu peux le voir dans l’exemple, la variable _x dans la première boucle n’as pas la même valeur que dans la boucle imbriquée. Cela permet de sécuriser le fonctionnement des boucles imbriquées. Cependant, si tu as besoin d’utiliser la variable _x de la première dans la deuxième boucle, tu devras assigner la valeur de _x dans une autre variable et la passer dans la boucle imbriquées.

{ 
    _x2 = _x;
    {
        hint str _x; //_x est égale à 1
        hint str _x2; //_x2 est égale à [1,2,3]
    } forEach _x; 
} forEach [[1,2,3],[4,5,6],[7,8,9]];

Il n’y a rien à dire de plus sur ces variables, hormis qu’il faut les connaitre ce qui permet de construire du code plus efficace.

Les variables globales magiques
Un dernier mot sur les variables magiques qui sont réservées et assignées par le moteur. La variable player par exemple est différente sur l’ensemble des clients et sur le serveur vaut null. Ces variables ne peuvent pas être écrasées par vos propres valeurs.

true false time enemy friendly sideLogic sideUnknown sideFriendly sideEnemy playerSide east west civilian resistance independent opfor blufor nil objNull grpNull netObjNull locationNull taskNull controlNull displayNull teamMemberNull player

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Photo Google

Vous commentez à l’aide de votre compte Google. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s