Variables: Difference between revisions

From Bohemia Interactive Community
Jump to navigation Jump to search
m (Some wiki formatting)
 
(37 intermediate revisions by 6 users not shown)
Line 1: Line 1:
{{Stub}}
{{TOC|side}}
A [[Variables|variable]] is a "storage container" or "named placeholder" for data. You can read and modify the data once this container is created.<br>
Its "name" is referenced as [[Identifier]].


A '''variable''' is a "storage container" or "named placeholder" for data. You can read and modify the data once this container is created.


== Requirements ==
== Requirements ==
Line 7: Line 8:
The following links guide to the basics to understand this article:
The following links guide to the basics to understand this article:


* [[ArmA: Introduction to Scripting]]
* [[Introduction to Arma Scripting]]
* [[Identifier]]
* [[Identifier]]


== Initialization ==


The first thing you have to do is to find a name for your variable. This name is called [[identifier]]. Read the article [[Identifier]] for more information.
== Initialisation ==


Once you know a name, you have to tell the game engine that you are going to use this identifier. This is called '''initialization'''. Then you may do anything with this variable.
The first thing to do to create a variable is to find its name, also called [[Identifier|identifier]]; this name must be speaking to the reader - keep in mind that code is meant to be read by ''human beings'' (see [[Code Best Practices#Variable format|Code Best Practices - Variable format]]).


Don't worry: This sounds more dramatic than it is. You can initialize any variable by assigning a value to it.
Once a proper name is found, it can be used to '''declare''' (or '''initialise''') the variable:


myVariable = 0;
<sqf>myVariable = 0;</sqf>


Querying undefined (or uninitialized) variables returns [[nil]] (undefined value). When converted to [[String|Strings]] with [[str]], they will always return [[scalar bool array string 0xe0ffffef]] (in [[Armed Assault]]) or [[scalar bool array string 0xfcffffef]] (in [[Operation Flashpoint]]).
Before {{GameCategory|arma2|link= y}}, querying undefined (or uninitialised) variables returns [[nil]] (undefined value); from {{GameCategory|arma2|link= y}} and later, it returns an "Error Undefined variable in expression" error.
{{Feature|informative|
An undefined ([[nil]]) variable converted to [[String]] with [[str]] will return [[scalar bool array string 0xe0ffffef]] (in [[:Category:ArmA: Armed Assault|{{arma1}}]]) and [[scalar bool array string 0xfcffffef]] (in [[:Category:Operation Flashpoint|{{ofp}}]]).<br>
Unless trying to emulate [[isNil]], '''always''' declare your variable before trying to access it.
}}


myString = str(undefinedVar);
myString => "scalar bool array string 0xe0ffffef"


Thus you can't really apply any [[Operators|operations]] on variables before they are initialized with a start value.
== Deletion ==


== Deletion ==
Once created, variables take up space in the computer's memory.<br>
This is not drastic for small variables, but if a big number of very large variables is used, it is recommended to undefine the unneeded ones in order to free up memory.


Once created, variables will take up space in your computer memory. This is not drastic for small variables, but if you use a big number of very large variables, you should undefine any variables that are not needed anymore. This can be done with the command [[nil]].
Variable deletion is done by setting its value to [[nil]]:
<sqf>HugeVariable = nil;</sqf>


hugeVariable = nil;
{{Feature|informative|Local variables are automatically freed (deleted from memory) when their scope is exited, avoiding the need to manually deallocate them.}}


This effectively destroys a variable as if it had never existed.


== Namespace ==
== Scopes ==


Variables are only visible in certain areas (namespaces) of the game. This prevents name conflicts between different variables in different [[Script (File)|scripts]].
Variables are only visible in certain scopes of the game. This prevents name conflicts between different variables in different [[Script File|scripts]].


There are three namespaces:
There are two main scopes:


; local
=== Global Scope ===
: A variable is only visible in the [[Script (File)|script]] in which it was defined.


; global
A global variable is visible from all scripts on the computer on which it was defined.
: A variable is visible on the whole computer where it is defined.
Names given to units in the [[Mission Editor]] are also global variables pointing to those units, which may not be redefined or modified.
<!-- TODO: mention missionNamespace? -->
{{Feature | important | a global variable can be different from one machine to another!<!--
--> To ensure the global variable's proper broadcast, see [[publicVariable]], [[publicVariableServer]] and [[publicVariableClient]].
If the value changes, the broadcast must be reapplied. e.g:
<sqf>
GlobalVariable = 33;
publicVariable "GlobalVariable";


; public
GlobalVariable = 42; // GlobalVariable is now 42 on the current machine but still 33 on the others
: A variable is broadcasted over the network and visible on all computers connected to the network.
publicVariable "GlobalVariable"; // updates the value to 42 for everyone
</sqf>
}}


=== Local Variables ===
=== Local Scope ===


Local variables are only visible in a specific [[Script (File)|script]]. The whole code in this script has access to the variable, that includes also [[Function|functions]] called within the script.
A local variable is only visible in the [[Script File|Script]], [[Function]] or [[Control Structures|Control Structure]] in which it was defined.


The [[identifier]] of variables local to a [[Script (File)|script]] always has to start with an underscore.
{{Feature | Informative | A local variable cannot be broadcast. In order to broadcast a local variable's ''value'', it must be assigned to a global variable first:
<sqf>
private _myLocalVariable = 33;
publicVariable "_myLocalVariable"; // incorrect


_myLocalVariable = 0;
GlobalVariable = _myLocalVariable;
publicVariable "GlobalVariable"; // correct
</sqf>
}}


In functions you should additionally mark variables as local using the command [[private]]. Otherwise you may modify local variables of the calling script that are visible in the function.
=== Local Variables Scope ===


private "_myLocalVariable";
{{Feature|warning|
_myLocalVariable = 0;
Local variables in [[call]]able code (e.g [[Arma 3 Functions Library|Functions]]) should be scoped using the command [[private]],
otherwise you may modify local variables of the [[call]]ing script that are visible in the function.}}
<sqf>
// Since Arma 3 v1.54
private _myLocalVariable = 0;


You may also pass more [[Identifier|identifiers]] to the private command using an [[Array]].
// From Arma 2 until Arma 3 v1.54
local _myLocalVariable = 0;


private ["_myLocalVariable1", "_myLocalVariable2", ...];
// Before Arma 2
private "_myLocalVariable";
_myLocalVariable = 0;


=== Global Variables ===
// Alternative method to private multiple local variables at the same time
private ["_myLocalVariable1", "_myLocalVariable2"];
_myLocalVariable1 = 1;
_myLocalVariable2 = 2;
</sqf>


Global variables are visible on the whole computer where they are defined. Names given to units in the [[Mission Editor]] are also global variables pointing to those units, which may not be redefined or modified.
If a [[private]] variable is initialised within a [[Control Structures|Control Structure]] (i.e. [[if]], [[for]], [[switch]], [[while]]),
its existence will be limited to it and its sub-structures - the variable does not exist outside of the structure and will be seen as undefined.


[[Identifier]]s of global variables ''must not'' start with underscore. Otherwise there are the same rules as for all [[Identifier|identifiers]].


  myGlobalVariable = 0;
<!--
don't judge me
-->
<table style="border-collapse: collapse; font-family: monospace; font-size: 1.1em; line-height: 1.15em; margin: auto; tab-size: 4; -moz-tab-size: 4; white-space: pre">
<tr>
<th colspan="9">Variables<br>lifespan</th>
<th style="font-size: 1.25em">Code</th>
</tr>
<tr style="border-bottom: 0.05em solid grey">
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td style="line-height: 2em">'''{{Color|grey|BEGINNING OF FILE}}''' - e.g <span style="font-size: small"><sqf inline>execVM "script.sqf";</sqf></span></td>
</tr>
<tr>
<td rowspan="28"></td>
<td rowspan="29" style="background-color: orange; border-radius: 10em 10em 0 0; width: .40em"></td>
<td rowspan="28" style="width: .10em"></td>
<td style="width: .40em"></td>
<td rowspan="28" style="width: .10em"></td>
<td style="width: .40em"></td>
<td rowspan="28" style="width: .10em"></td>
<td style="width: .40em"></td>
<td rowspan="28" style="width: 1em"></td>
<td>'''{{Color|darkorange|TAG_GlobalVariable}}''' = "Global variable"; {{cc|Global variable is accessible from any scope}}</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td><wbr /></td>
</tr>
<tr>
<td rowspan="4" style="background-color: darkgreen; border-radius: 10em; margin: .1em"></td>
<td><wbr /></td>
<td><wbr /></td>
<td>[[private]] '''{{Color|darkgreen|_myVariable}}''' = 0;</td>
</tr>
<tr>
<td></td>
<td></td>
<td>[[if]] ('''{{Color|grey|_condition}}''') [[then]]</td>
</tr>
<tr>
<td></td>
<td></td>
<td>{</td>
</tr>
<tr>
<td></td>
<td></td>
<td> '''{{Color|darkgreen|_myVariable}}''' = 1;</td>
</tr>
<tr>
<td></td>
<td rowspan="2" style="background-color: blue; border-radius: 10em"></td>
<td></td>
<td> [[private]] '''{{Color|blue|_myVariable}}''' = 2;</td>
</tr>
<tr>
<td></td>
<td></td>
<td> '''{{Color|blue|_myVariable}}''' = 3;</td>
</tr>
<tr>
<td rowspan="13" style="background-color: darkgreen; border-radius: 10em"></td>
<td></td>
<td></td>
<td>} [[else]] {</td>
</tr>
<tr>
<td></td>
<td></td>
<td> '''{{Color|darkgreen|_myVariable}}''' = 4;</td>
</tr>
<tr>
<td></td>
<td rowspan="1" style="background-color: purple; border-radius: 10em"></td>
<td> [[private]] '''{{Color|purple|_anotherVariable}}''' = 10;</td>
</tr>
<tr>
<td></td>
<td></td>
<td>};</td>
</tr>
<tr>
<td></td>
<td></td>
<td><wbr /></td>
</tr>
<tr>
<td></td>
<td></td>
<td>[[hint]] [[str]] '''{{Color|darkgreen|_myVariable}}'''; {{cc|if '''{{Color|grey|_condition}}''' is [[true]], '''{{Color|darkgreen|_myVariable}}''''s value is '''{{Color|darkgreen|1}}''';}}</td>
</tr>
<tr>
<td></td>
<td></td>
<td> {{cc|if '''{{Color|grey|_condition}}''' is [[false]], '''{{Color|darkgreen|_myVariable}}''''s value is '''{{Color|darkgreen|4}}'''.}}</td>
</tr>
<tr>
<td></td>
<td></td>
<td><wbr /></td>
</tr>
<tr>
<td></td>
<td></td>
<td>[] [[call]] {</td>
</tr>
<tr>
<td></td>
<td></td>
<td> [[hint]] [[str]] '''{{Color|darkgreen|_myVariable}}'''; {{cc|works, as [[call]]ed code runs in the same scope}}</td>
</tr>
<tr>
<td></td>
<td></td>
<td>};</td>
</tr>
<tr>
<td></td>
<td></td>
<td><wbr /></td>
</tr>
<tr>
<td></td>
<td></td>
<td>['''{{Color|darkgreen|_myVariable}}'''] [[spawn]] {</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td> [[hint]] '''{{Color|darkorange|TAG_GlobalVariable}}'''; {{cc|works}}</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td> [[hint]] [[str]] _myVariable; {{cc|throws an [[Debugging Techniques#Common errors|"undefined variable" error]],}}</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td> {{cc|as '''{{Color|darkgreen|_myVariable}}''' does '''not''' exist in this new script}}</td>
</tr>
<tr>
<td rowspan="4" style="background-color: darkgreen; border-radius: 10em"></td>
<td></td>
<td></td>
<td>};</td>
</tr>
<tr>
<td></td>
<td></td>
<td><wbr /></td>
</tr>
<tr>
<td></td>
<td></td>
<td>{{cc|trying to use '''{{Color|purple|_anotherVariable}}''' here would result in an [[Debugging Techniques#Common errors|"undefined variable" error]],}}</td>
</tr>
<tr>
<td></td>
<td></td>
<td>{{cc|'''{{Color|purple|_anotherVariable}}''' being only scoped in the [[else]] block.}}</td>
</tr>
<tr style="border-top: 0.05em solid grey">
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td style="line-height: 2em">'''{{Color|grey|END OF FILE}}''' - '''{{Color|darkorange|TAG_GlobalVariable}}''' keeps on living in [[missionNamespace]]</td>
</tr>
</table>


=== Public Variables ===


Public variables are global variables that are visible on all computers in the network. You can never have true public variables, but you can emulate their behaviour.
{| style="margin: auto"
! style="width: 50%" | Correct
! style="width: 50%" | Incorrect
|-
| <sqf>
private _living = false;
if (alive player) then
{
_living = true;
};


The value of a global variable gets broadcasted over the network using [[publicVariable]]. After the call of this command the variable will have the same value on all [[Client|clients]]. Once you modify the variable though you have to broadcast it manually ''again'' with [[publicVariable]].


== Scope ==
hint format ["%1", _living]; // returns true
If a local variable is initialized within a [[Control Structures]] (i.e. [[if]], [[for]], [[switch]], [[while]]) its scope will stay ''within'' this structure (i.e. outside of the structure it will still be seen as undefined). This does not apply to global or public variables.
</sqf>
<code>if (alive player) then {_living=true}; hint format["%1",_living];</code>
| <sqf>
Returns "scalar bool array string 0xe0ffffef", since the local variable was not initialized ''before'' being used within a control structure.
// -
if (alive player) then
{
private _living = true;
};
// throws an error since the private variable was
// not initialised outside of the if control structure.
hint format ["%1", _living];
</sqf>
|}


<code>_dead=true; if (alive player) then {_dead=false}; hint format["%1",_dead];
</code>
Returns "false", since the variable was initialized before the if...then.<br>
To initialize local variables, so that they are available throughout the whole script (including any control structures), either initialize it via the [[private]] command (e.g. ''private ["_varname"];''), or by assigning a default value to it (e.g. ''varname=0;'').


== Data Types ==
== Data Types ==


Variables may store certain values. The kind of the value specifies the ''type'' of the variable. Different [[Operators|operators]] and [[:Category:Scripting Commands|commands]] require variables to be of different types.
Variables may store values of a certain [[:Category:Data Types|Data Types]] ([[String]], [[Number]], etc). The kind of the value specifies the ''type'' of the variable.
Different [[Operators|operators]] and [[:Category:Scripting Commands|commands]] require variables to be of different types.
 
A variable is not strongly typed and changes its type according to the new data:
<sqf>
private "_myVariable"; // nil
_myVariable = 1; // 1 (Number)
_myVariable = "test"; // "test" (String)
</sqf>


Read the article [[Data Types]] for more information about variable types.


== Multiplayer Considerations ==
== Multiplayer Considerations ==


Storing vars and functions into global vars without securing them with compileFinal is a very bad practice in MP.
Storing functions (or any callable code) into global variables without securing them with [[compileFinal]] (since {{arma3}}) is a '''very''' bad practice in multiplayer.
Basically, if you have a script installed on a server in mission file that uses functions stored in unprotected variables then hackers can overwrite that function attached to a global variable and make it execute code for everyone and do a lot of nasty stuff. So please put all of the code for each function in a SEPARATE file and initialize them like this:
The biggest security risk would be to see it being overridden by a malicious usage of [[publicVariable]], setting potentially dangerous code in it.
<code>myGlobalVarFunction = compileFinal preprocessFileLineNumbers "Dir\where\function\is\fnc_globalVarFunction.sqf";</code>
 
That way, the function can be called and spawned without it being subject to hackers that attempt to overwrite the global variable "myGlobalVarFunction".
The best option is to declare your function in [[Arma 3 Functions Library|CfgFunctions]] so the engine secures it for you.
 
If you want to manually create a global function anyway, the best practice is the following:
<sqf>TAG_MyGlobalVariableFunction = compileFinal preprocessFileLineNumbers "functionFile.sqf";</sqf>
 
{{Feature|informative|
You should ideally run this code locally on every machine.
Using [[publicVariable]]  on a "final" global function (created with [[compileFinal]]) will indeed broadcast the variable '''and''' make it final on other clients as well, but the network can quickly become saturated from sending big pieces of code.
}}


Everyone happy :)


== See also ==
== See also ==


* [[Data Types]], [[Magic Variables]]
* [[Introduction to Arma Scripting]]
* [[Identifier]]
* [[:Category: Data Types| Data Types]]
* [[Control Structures]]
* [[Magic Variables]]
* [[private]] ({{arma3}})
* [[local]] ({{arma2}})
 


[[Category: Scripting_Topics]]
[[Category: Scripting Topics]]

Latest revision as of 17:12, 21 April 2024

A variable is a "storage container" or "named placeholder" for data. You can read and modify the data once this container is created.
Its "name" is referenced as Identifier.


Requirements

The following links guide to the basics to understand this article:


Initialisation

The first thing to do to create a variable is to find its name, also called identifier; this name must be speaking to the reader - keep in mind that code is meant to be read by human beings (see Code Best Practices - Variable format).

Once a proper name is found, it can be used to declare (or initialise) the variable:

myVariable = 0;

Before Arma 2, querying undefined (or uninitialised) variables returns nil (undefined value); from Arma 2 and later, it returns an "Error Undefined variable in expression" error.

An undefined (nil) variable converted to String with str will return scalar bool array string 0xe0ffffef (in Armed Assault) and scalar bool array string 0xfcffffef (in Operation Flashpoint).
Unless trying to emulate isNil, always declare your variable before trying to access it.


Deletion

Once created, variables take up space in the computer's memory.
This is not drastic for small variables, but if a big number of very large variables is used, it is recommended to undefine the unneeded ones in order to free up memory.

Variable deletion is done by setting its value to nil:

HugeVariable = nil;

Local variables are automatically freed (deleted from memory) when their scope is exited, avoiding the need to manually deallocate them.


Scopes

Variables are only visible in certain scopes of the game. This prevents name conflicts between different variables in different scripts.

There are two main scopes:

Global Scope

A global variable is visible from all scripts on the computer on which it was defined. Names given to units in the Mission Editor are also global variables pointing to those units, which may not be redefined or modified.

a global variable can be different from one machine to another! To ensure the global variable's proper broadcast, see publicVariable, publicVariableServer and publicVariableClient.

If the value changes, the broadcast must be reapplied. e.g:

GlobalVariable = 33; publicVariable "GlobalVariable"; GlobalVariable = 42; // GlobalVariable is now 42 on the current machine but still 33 on the others publicVariable "GlobalVariable"; // updates the value to 42 for everyone

Local Scope

A local variable is only visible in the Script, Function or Control Structure in which it was defined.

A local variable cannot be broadcast. In order to broadcast a local variable's value, it must be assigned to a global variable first:
private _myLocalVariable = 33; publicVariable "_myLocalVariable"; // incorrect GlobalVariable = _myLocalVariable; publicVariable "GlobalVariable"; // correct

Local Variables Scope

Local variables in callable code (e.g Functions) should be scoped using the command private, otherwise you may modify local variables of the calling script that are visible in the function.

// Since Arma 3 v1.54 private _myLocalVariable = 0; // From Arma 2 until Arma 3 v1.54 local _myLocalVariable = 0; // Before Arma 2 private "_myLocalVariable"; _myLocalVariable = 0; // Alternative method to private multiple local variables at the same time private ["_myLocalVariable1", "_myLocalVariable2"]; _myLocalVariable1 = 1; _myLocalVariable2 = 2;

If a private variable is initialised within a Control Structure (i.e. if, for, switch, while), its existence will be limited to it and its sub-structures - the variable does not exist outside of the structure and will be seen as undefined.


Variables
lifespan
Code
BEGINNING OF FILE - e.g execVM "script.sqf";
TAG_GlobalVariable = "Global variable"; // Global variable is accessible from any scope
private _myVariable = 0;
if (_condition) then
{
_myVariable = 1;
private _myVariable = 2;
_myVariable = 3;
} else {
_myVariable = 4;
private _anotherVariable = 10;
};
hint str _myVariable; // if _condition is true, _myVariable's value is 1;
// if _condition is false, _myVariable's value is 4.
[] call {
hint str _myVariable; // works, as called code runs in the same scope
};
[_myVariable] spawn {
hint TAG_GlobalVariable; // works
hint str _myVariable; // throws an "undefined variable" error,
// as _myVariable does not exist in this new script
};
// trying to use _anotherVariable here would result in an "undefined variable" error,
// _anotherVariable being only scoped in the else block.
END OF FILE - TAG_GlobalVariable keeps on living in missionNamespace


Correct Incorrect
private _living = false; if (alive player) then { _living = true; }; hint format ["%1", _living]; // returns true
// - if (alive player) then { private _living = true; }; // throws an error since the private variable was // not initialised outside of the if control structure. hint format ["%1", _living];


Data Types

Variables may store values of a certain Data Types (String, Number, etc). The kind of the value specifies the type of the variable. Different operators and commands require variables to be of different types.

A variable is not strongly typed and changes its type according to the new data:

private "_myVariable"; // nil _myVariable = 1; // 1 (Number) _myVariable = "test"; // "test" (String)


Multiplayer Considerations

Storing functions (or any callable code) into global variables without securing them with compileFinal (since Arma 3) is a very bad practice in multiplayer. The biggest security risk would be to see it being overridden by a malicious usage of publicVariable, setting potentially dangerous code in it.

The best option is to declare your function in CfgFunctions so the engine secures it for you.

If you want to manually create a global function anyway, the best practice is the following:

TAG_MyGlobalVariableFunction = compileFinal preprocessFileLineNumbers "functionFile.sqf";

You should ideally run this code locally on every machine. Using publicVariable on a "final" global function (created with compileFinal) will indeed broadcast the variable and make it final on other clients as well, but the network can quickly become saturated from sending big pieces of code.


See also