All global variables must start with the ACE prefix followed by the component, separated by underscores. Global variables may not contain the `fnc_` prefix if the value is not callable code.
Example: `ace_component_myVariableName`
_For ACE this is done automatically through the usage of the `GVAR` macro family._
- All stringtables shall follow the format as specified by [Tabler](https://github.com/bux/tabler){:target="_blank"} and the [translation guidelines]({{ site.baseurl }}/wiki/development/how-to-translate-ace3.html) form.
The family of `GVAR` macros define global variable strings or constants for use within a module. Please use these to make sure we follow naming conventions across all modules and also prevent duplicate/overwriting between variables in different modules. The macro family expands as follows, for the example of the module 'balls':
| Macros | Expands to |
| -------|---------|
|`GVAR(face)` | `ace_balls_face` |
|`QGVAR(face)` | `"ace_balls_face"` |
|`QQGVAR(face)` | `""ace_balls_face""` used inside `QUOTE` macros where double quotation is required. |
|`EGVAR(leg,face)` | `ace_leg_face` |
|`QEGVAR(leg,face)` | `"ace_leg_face"` |
|`QQEGVAR(leg,face)` | `""ace_leg_face""` used inside `QUOTE` macros where double quotation is required. |
There also exists the `FUNC` family of Macros:
| Macros | Expands to |
| -------|---------|
|`FUNC(face)` | `ace_balls_fnc_face` or the call trace wrapper for that function. |
|`EFUNC(leg,face)` | `ace_leg_fnc_face` or the call trace wrapper for that function. |
|`DFUNC(face)` | `ace_balls_fnc_face` and will ALWAYS be the function global variable. |
|`DEFUNC(leg,face)` | `ace_leg_fnc_face` and will ALWAYS be the function global variable. |
|`QFUNC(face)` | `"ace_balls_fnc_face"` |
|`QEFUNC(leg,face)` | `"ace_leg_fnc_face"` |
|`QQFUNC(face)` | `""ace_balls_fnc_face""` used inside `QUOTE` macros where double quotation is required. |
|`QQEFUNC(leg,face)` | `""ace_leg_fnc_face""` used inside `QUOTE` macros where double quotation is required. |
The `FUNC` and `EFUNC` macros shall NOT be used inside `QUOTE` macros if the intention is to get the function name or assumed to be the function variable due to call tracing (see below). If you need to 100% always be sure that you are getting the function name or variable use the `DFUNC` or `DEFUNC` macros. For example `QUOTE(FUNC(face)) == "ace_balls_fnc_face"` would be an illegal use of `FUNC` inside `QUOTE`.
Using `FUNC` or `EFUNC` inside a `QUOTE` macro is fine if the intention is for it to be executed as a function.
ACE3 implements a basic call tracing system that can dump the call stack on errors or wherever you want. To do this the `FUNC` macros in debug mode will expand out to include metadata about the call including line numbers and files. This functionality is automatic with the use of calls via `FUNC` and `EFUNC`, but any calls to other functions need to use the following macros:
`QUOTE()` is utilized within configuration files for bypassing the quote issues in configuration macros. So, all code segments inside a given config should utilize wrapping in the `QUOTE()` macro instead of direct strings. This allows us to use our macros inside the string segments, such as `QUOTE(_this call FUNC(balls))`
The family of path macros define global paths to files for use within a module. Please use these to reference files in the ACE3 project. The macro family expands as follows, for the example of the module 'balls':
Functions shall be created in the `functions\` subdirectory, named `fnc_functionName.sqf` They shall then be indexed via the `PREP(functionName)` macro in the `XEH_preInit.sqf` file.
The `PREP` macro allows for CBA function caching, which drastically speeds up load times. **Beware though that function caching is enabled by default and as such to disable it you need to `#define DISABLE_COMPILE_CACHE` above your `#include "script_components.hpp"` include!**
Braces `{ }` which enclose a code block will have the first bracket placed behind the statement in case of `if`, `switch` statements or `while`, `waitUntil`&`for` loops. The second brace will be placed on the same column as the statement but on a separate line.
- Opening brace on the same line as keyword
- Closing brace in own line, same level of indentation as keyword
**Yes:**
```cpp
class Something: Or {
class Other {
foo = "bar";
};
};
```
**No:**
```cpp
class Something : Or
{
class Other
{
foo = "bar";
};
};
```
**Also no:**
```cpp
class Something : Or {
class Other {
foo = "bar";
};
};
```
When using `if`/`else`, it is encouraged to put `else` on the same line as the closing brace to save space:
```js
if (alive player) then {
player setDamage 1;
} else {
hint ":(";
};
```
In cases where there are a lot of one-liner classes, it is allowed to use something like this to save space:
Putting the opening brace in its own line wastes a lot of space, and keeping the closing brace on the same level as the keyword makes it easier to recognize what exactly the brace closes.
Ever new scope should be on a new indent. This will make the code easier to understand and read. Indentations consist of 4 spaces. Tabs are not allowed.
All code shall be documented by comments that describe what is being done. This can be done through the function header and/or inline comments.
Comments within the code shall be used when they are describing a complex and critical section of code or if the subject code does something a certain way because of a specific reason. Unnecessary comments in the code are not allowed.
Good:
```js
// find the object with the most blood loss
_highestObj = objNull;
_highestLoss = -1;
{
if ([_x] call EFUNC(medical,getBloodLoss) > _highestLoss) then {
There shall be no magic numbers. Any magic number shall be put in a define either on top of the `.sqf` file (below the header), or in the `script_component.hpp` file in the root directory of the component (recommended) in case it is used in multiple locations.
Magic numbers are any of the following:
- A constant numerical or text value used to identify a file format or protocol
- Distinctive unique values that are unlikely to be mistaken for other meanings
- Unique values with unexplained meaning or multiple occurrences which could (preferably) be replaced with named constants
Parameters of functions must be retrieved through the usage of the `param` or `params` commands. If the function is part of the public API, parameters must be checked on allowed data types and values through the usage of the `param` and `params` commands.
Usage of the CBA Macro `PARAM_x` or `BIS_fnc_param` is deprecated and not allowed within the ACE project.
Functions and code blocks that specific a return a value must have a meaningful return value. If no meaningful return value, the function should return nil.
All private variables shall make use of the `private` keyword on initialization. When declaring a private variable before initialization, usage of the `private ARRAY` syntax is allowed. All private variables must be either initialized using the `private` keyword, or declared using the `private ARRAY` syntax. Exceptions to this rule are variables obtained from an array, which shall be done with usage of the `params` command family, which ensures the variable is declared as private.
The initialization expression in a `for` loop shall perform no actions other than to initialize the value of a single `for` loop parameter.
### 6.10. Increment expression in `for` loops
The increment expression in a `for` loop shall perform no action other than to change a single loop parameter to the next value for the loop.
### 6.11. `getVariable`
When using `getVariable`, there shall either be a default value given in the statement or the return value shall be checked for correct data type as well as return value. A default value may not be given after a `nil` check.
Bad:
```js
_return = obj getvariable "varName";
if (isnil "_return") then {_return = 0 };
```
Good:
```js
_return = obj getvariable ["varName", 0];
```
Good:
```js
_return = obj getvariable "varName";
if (isnil "_return") exitwith {};
```
### 6.12. Global Variables
Global variables should not be used to pass along information from one function to another. Use arguments instead.
Bad:
```js
fnc_example = {
hint GVAR(myVariable);
};
```
```js
GVAR(myVariable) = "hello my variable";
call fnc_example;
```
Good:
```js
fnc_example = {
params ["_content"];
hint _content;
};
```
```js
["hello my variable"] call fnc_example;
```
### 6.13. Temporary Objects & Variables
Unnecessary temporary objects or variables should be avoided.
### 6.14. Commented out Code
Code that is not used (commented out) shall be deleted.
### 6.15. Constant Global Variables
There shall be no constant global variables, constants shall be put in a `#define`.
### 6.16. Logging
Functions shall whenever possible and logical, make use of logging functionality through the logging and debugging macros from CBA and ACE3.
### 6.17. Constant Private Variables
Constant private variables that are used more as once shall be put in a `#define`.
### 6.18. Code used more than once
Any piece of code that could/is used more than once, shall be put in a function and it's separate `.sqf` file, unless this code is less as 5 lines and used only in a [per-frame handler](#waituntil).
This is a large open source project that will get many different maintainers in its lifespan. When writing code, keep in mind that other developers also need to be able to understand your code. Balancing readability and performance of code is a non black and white subject. The rule of thumb is:
- When improving performance of code that sacrifices readability (or visa-versa), first see if the design of the implementation is done in the best way possible.
- Document that change with the reasoning in the code.
Avoid the usage of scheduled space as much as possible and stay in unscheduled. This is to provide a smooth experience to the user by guaranteeing code to run when we want it. See [Performance considerations, `spawn` & `execVM`](#avoid-spawn--execvm) for more information.
This also helps avoid various bugs as a result of unguaranteed execution sequences when running multiple scripts.
All ACE3 components shall be implemented in an event driven fashion. This is done to ensure code only runs when it is required and allows for modularity through low coupling components.
Event handlers in ACE3 are implemented through the CBA event system (ACE3's own event system is deprecated since 3.6.0). They should be used to trigger or allow triggering of specific functionality.
More information on the [CBA Events System](https://github.com/CBATeam/CBA_A3/wiki/Custom-Events-System){:target="_blank"} and [CBA Player Events](https://github.com/CBATeam/CBA_A3/wiki/Player-Events){:target="_blank"} pages.
When a key value pair is required, make use of the hash implementation from ACE3.
Hashes are a variable type that store key value pairs. They are not implemented natively in SQF, so there are a number of macros and functions for their usage in ACE3. If you are unfamiliar with the idea, they are similar in function to `setVariable`/`getVariable` but do not require an object to use.
The following example is a simple usage using our macros which will be explained further below.
```js
_hash = HASHCREATE;
HASH_SET(_hash,"key","value");
if (HASH_HASKEY(_hash,"key")) then {
player sideChat format ["val: %1", HASH_GET(_hash,"key"); // will print out "val: value"
};
HASH_REM(_hash,"key");
if (HASH_HASKEY(_hash,"key")) then {
// this will never execute because we removed the hash key/val pair "key"
};
```
A description of the above macros is below.
| Macro | Use |
| ------|-------|
|`HASHCREATE` | Used to create an empty hash. |
|`HASH_SET(hash,key,val)` | Will set the hash key to that value, a key can be anything, even objects. |
|`HASH_GET(hash,key)` | Will return the value of that key (or nil if it doesn't exist). |
|`HASH_HASKEY(hash,key)` | Will return true/false if that key exists in the hash. |
|`HASH_REM(hash,key)` | Will remove that hash key. |
A hashlist is an extension of a hash. It is a list of hashes! The reason for having this special type of storage container rather than using a normal array is that an array of normal hashes that are similar will duplicate a large amount of data in their storage of keys. A hashlist on the other hand uses a common list of keys and an array of unique value containers. The following will demonstrate its usage.
```js
_defaultKeys = ["key1", "key2", "key3"];
// create a new hashlist using the above keys as default
_hashList = HASHLIST_CREATELIST(_defaultKeys);
//lets get a blank hash template out of this hashlist
_hash = HASHLIST_CREATEHASH(_hashList);
//_hash is now a standard hash...
HASH_SET(_hash,"key1","1");
//to store it to the list we need to push it to the list
HASHLIST_PUSH(_hashList, _hash);
//now lets get it out and store it in something else for fun
//it was pushed to an empty list, so it's index is 0
_anotherHash = HASHLIST_SELECT(_hashList,0);
// this should print "val: 1"
player sideChat format["val: %1", HASH_GET(_anotherHash,"key1")];
//Say we need to add a new key to the hashlist
//that we didn't initialize it with? We can simply
Hashes and hashlists are implemented with SQF arrays, and as such they are passed by reference to other functions. Remember to make copies (using the `+` operator) if you intend for the hash or hashlist to be modified with out the need for changing the original value.
When adding new elements to an array, `pushBack` shall be used instead of the binary addition or `set`. When adding multiple elements to an array `append` may be used instead.
Good:
```js
_a pushBack _value;
```
Also good:
```js
_a append [1,2,3];
```
Bad:
```js
_a set [ count _a, _value];
_a = a + _[value];
```
When adding an new element to a dynamic location in an array or when the index is pre-calculated, `set` may be used.
When adding multiple elements to an array, the binary addition may be used for the entire addition.
`createVehicle(Local)` used with a non-`[0, 0, 0]` position performs search for empty space to prevent collisions on spawn.
Where possible `[0, 0, 0]` position shall be used, except on `#` objects (e.g. `#lightsource`, `#soundsource`) where empty position search is not performed.
This code requires ~1.00ms and will be higher with more objects near wanted position:
While is only allowed when used to perform a unknown finite amount of steps with unknown or variable increments. Infinite `while` loops are not allowed.