Lou Montana/Sandbox – User

From Bohemia Interactive Community
Jump to navigation Jump to search
m (Some page presentation / copy-paste introduction from Code Optimisation)
m (Closer to the final page format?)
Line 1: Line 1:
__NOTOC__
[[Category: Sandbox]]
[[Category: Sandbox]]
{{Informative | Future [[Code Best Practices]] page}}
{{Informative | Future [[Code Best Practices]] page}}
{{Important | This is at the moment '''only a list of topics''' and absolutely not the final render. Many single entries will result in a chapter.}}
{{wip}}
 


----
----


{{SideTOC}}
{{Stub}}


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


== Getting started ==
== Getting started ==
In the domain of development, any rule is a rule of thumb.
If a rule states for example that it is better that a line of code doesn't go over 80 characters, it doesn't mean that any line '''''must not''''' go over 80 characters; sometimes, the situation needs it.
If you have a good structure, '''do not''' change your code to enforce a single arbitrary rule.
If you break many of them, you may have to change something; again, this is according to your judgement.
With that being said, let's go!


=== Prerequisites ===
=== Prerequisites ===


* Interest!
* An Arma game
* An Arma game
* Passion
* An advanced text editor such as Visual Studio Code or Notepad++ (see [[Debugging Techniques#Code Edition|Debugging Techniques' Code Edition chapter]] for useful plugins)
* Some English knowledge, helped if needed by a translator tool like [https://www.google.com/translate Google Translate] or [https://www.deepl.com/ DeepL]
* Some English knowledge, helped if needed by a translator tool like [https://www.google.com/translate Google Translate] or [https://www.deepl.com/ DeepL]
* Access to this wiki is a plus (the Swiss flag is, too)
* Access to this wiki is a plus (the Swiss flag is, too)
* Tutorial/example article (YouTube tutorials, )
* Tutorial/examples (YouTube tutorials, community tutorials, [[:Category:Example_Code|Example Code]])
* Google-Fu (a.k.a search engine skills)
* Google-Fu (a.k.a search engine skills)
* Advanced text editor (Such as Notepad++ or Visual Studio Code)




== Rules ==
== Best practices ==


In the domain of development, any rule is a rule of thumb. If a rule states for example that it is ''better'' that a line of code doesn't go over 80 characters, it doesn't mean that any line '''''must not''''' go over 80 characters; sometimes, the situation needs it. If you have a good structure, '''do not''' change your code to enforce a single arbitrary rule. If you break many of them, you may have to change something. Again, this is according to your judgement.
=== Code format ===


With that being said, here are the three basic rules to get yourself in the clear:
* Whatever you do about your code's format, '''be consistent'''.
* Choose an indentation format '''and stick to it'''. There is not especially one indent better than another (''but there sure are terrible ones''), again the important point here being ''consistency''.
** Common indentation styles are:
*** [https://en.wikipedia.org/wiki/Indentation_style#K&R_style K&R style] indenting
*** [https://en.wikipedia.org/wiki/Indentation_style#Allman_style Allman style] indenting
** Use empty space. Line return, spaces before and after brackets, if this improves readability, use it: space is free.
** indent with two/four spaces '''or''' one tab. Do not mix these.
** ''One-lining'' (putting everything in one statement) memory improvement is most of the time not worth the headache it gives when trying to read it. Don't overuse it.
* {{Inline code|<span style{{=}}"background-color: #FCC">'''0 {{=}}''' </span>''myCommand''}} is "useful" only for editor fields that for no apparent reason refuse commands returning a value.<br><!--
--> You do '''not''' need it in script files.


# [[#Make it work|Make it work]]
=== Variable format ===
# [[#Make it readable|Make it readable]]
# [[#Optimise then|Optimise then]]


=== Make it work ===
* Name your variables and functions properly:<!--
--> While [[SQF syntax|SQF]] ''is'' (non-noticeably) impacted by variable name length, this should not take precedence on the fact that code must be readable by a human being.
** Your variable names must have a meaning: variables like '''_u''' instead of '''_uniform''' should not exist. '''_i''' is an accepted iteration variable name (e.g in [[for]] loops).
** It is recommended to use [https://en.wikipedia.org/wiki/Camel_case camel-case] your variables;<!--
--> camel-casing (namingLikeThis) your variables and functions make the code naturally more readable, especially for long names.
* Prefix your public variables and [[setVariable]] with your tag in order to avoid any conflict about other mods, scripts or mission variables.
* Make your variables '''private''' thanks to the [[private]] or [[params]] keyword in order to avoid accidental upper-scope overwriting of variables of the same name.
* Defined constants must be UPPERCASE_WITH_UNDERSCORES (e.g {{Inline code|#define SOME_CONST}}).


{{quote|Premature optimization is the root of all evil.|[https://en.wikipedia.org/wiki/Donald_Knuth Donald Knuth]}}
Your first goal when coding is to make your code do what you want it does. A good way to reach this objective is to read and getting inspired by other people's code. If you understand it by reading it once, it is probably a good source of inspiration.


* When starting from scratch if you know what you want but miss the specific steps to get to your point, it is a good practice to write down in your native language what you want to do. E.g ''Get [[allUnits|all the units]] [[distance|near]] [[locationPosition|the city]], and [[forEach|for each]] [[side|west]] soldier in them, [[setDamage|add 30% damage]]''.
=== Code structuration ===
* Use [[Arma_3_Startup_Parameters#Developer_Options|-showScriptErrors]] startup parameter and '''make sure your code doesn't throw errors.''' Not only will your code run slower but it may also ''not work at all''. Be sure to read the error, isolate the issue and sort it out [[:Category:Scripting Commands|thanks to this Wiki]].
* Read your [[Crash_Files|Arma RPT]] (report) to read more details about the error that happened in your code.


=== Make it readable ===
* '''DRY:''' '''D'''on't '''R'''epeat '''Y'''ourself. If you write the same code block or the same logic many times, export this code as a function and use parameters with it.
** If your code has too many levels, it is time to split and rethink it<!--
--> (e.g {{Inline code|[[if]] (something) [[then]] { [[if]] (somethingElse) [[then]] { [[if]] (anotherSomething) [[then]] { {{codecomment|/* etc */ }} }; }; };}}...)
** Do NOT use [[PreProcessor_Commands#Macros|macros]] as functions - these hinder readability. Use functions instead.
* Using {{codecomment|comments}} in your code must not explain ''what'' the code does, but ''why'' it is done this way (if needed).<!--
--> Your code organisation combined to your variable names must be enough to be read by a human.


Whether you are cleaning your code or a different person's, you must understand the code without twisting your brain:
=== Code/Files organisation ===


* While [[SQF syntax|SQF]] ''is'' (non-noticeably) impacted by variable name length, this should not take precedence on the fact that code must be readable by a human being. Variables like '''_u''' instead of '''_uniform''' should not be present.
* Use [[Arma 3 Functions Library#Adding a Function|CfgFunctions]] to declare the functions that will be called frequently.
* ''One-lining'' (putting everything in one statement) memory improvement is most of the time not worth the headache it gives when trying to read it. Don't overuse it.
** One Functions directory, with sub-directories if needed.
* Indentation is important for the human mind, and space is too. Space is free, use it.
* Use [[Event Scripts]] as needed.
* Same goes for line return; it helps to see a code block wrapping multiple common instructions instead of having to guess where it starts and stops.
* Don't put '''any code''' in a unit's init box ''but eventually'' [[local]] commands for this specific unit - '''all''' unit's init boxes are run client-side on client connection.
* Do you see the same code multiple times, only with different parameters? Now is the time to write a function!
* If you have a lot of [[if]]..[[else]], you may want to look at a [[switch]] condition, or again break your code in smaller functions.
* Is your function code far too long? Break it in understandable-sized bites for your own sanity.
* Finally, camel-casing (namingLikeThis) your variables and commands will naturally make the code more readable, especially for long names.


{{Informative| '''_i''' is an accepted variable standard for a [[for]]..[[do]] iteration}}


See the following code:
== Examples ==
_w=[]; {_w pushbackunique primaryweapon _x} foreach((allunits+alldeadmen) select{_x call bis_fnc_objectside==east});


The same example is far more readable with proper spacing, good variable names and intermediate results:
_weaponNames = [];
_allUnitsAliveAndDead = allUnits + allDeadMen;
_allEastAliveAndDead = _allUnitsAliveAndDead select { _x call BIS_fnc_objectSide == east };
{ _weaponNames pushBackUnique primaryWeapon _x } forEach _allEastAliveAndDead;
<!--
<!--
EDITOR'S NOTE: ^ code examples are not linking commands on purpose! This allows for a fair comparison of both syntaxes' readability.
 
EDITOR'S NOTE: Formatting Code examples are not linking commands on purpose! This allows for a fair comparison of both syntaxes' readability.
-->
-->
 
{| class="bikitable"
==== Constants ====
! <big>Bad Example</big>
Using a hard-coded constant more than once? Use preprocessor directives rather than storing it in memory or cluttering your code with numbers. Such as:
! <big>Good Example</big>
a = _x + 1.053;
|-
b = _y + 1.053;
! colspan="2" |
And
<small>
_buffer = 1.053;
=== Good Practice Examples ===
a = _x + _buffer;
</small>
b = _y + _buffer;
|-
Becomes
| <code>_unit = player;</code>
<span style="color: purple; font-weight: bold;">#define BUFFER 1.053</span> {{codecomment|// note: no semicolon}}
| <code>'''private''' _unit = player;</code>
_a = _x + BUFFER;
|-
_b = _y + BUFFER;
| <code>private _uB = allUnits select { side _x == blufor };</code>
{{Informative | Using the '''#define''' macro only works within the current [[SQF]] ''file''. Such definition will not propagate anywhere else.}}
| <code>private '''_bluforUnits''' = allUnits select { side _x == blufor };</code>
'''Global''' "constants" can be defined ''via'' a [[Description.ext]] declaration, though, and accessed using [[getMissionConfigValue]] command:
|-
 
| <code>finalAssault = true; publicVariable "finalAssault";</code>
Declaration in [[Description.ext]]:
| <code>'''PREFIX_'''finalAssault = true; publicVariable "'''PREFIX_'''finalAssault";</code>
<syntaxhighlight lang="cpp">
|-
var1 = 123;
| <code>player setVariable ["MoneyInPocket", true];</code>
var2 = "123";
| <code>player setVariable ["'''PREFIX_'''MoneyInPocket", true];</code>
var3[] = {1,2,3};
|-
rand = __EVAL(random 999);
! colspan="2" |
</syntaxhighlight>
<small>
 
=== Format Examples ===
Usage in code:
</small>
[[hint]] [[str]] [[getMissionConfigValue]] "var1"; // 123 {{codecomment|// 0.0007 ms}}
|- style="vertical-align: top"
[[hint]] [[str]] [[getMissionConfigValue]] "var2"; // "123" {{codecomment|// 0.0008 ms}}
|
[[hint]] [[str]] [[getMissionConfigValue]] "var3"; // [1,2,3] {{codecomment|// 0.0017 ms}}
<code>if (alive player && damage player >= 0.9) then {
[[hint]] [[str]] [[getMissionConfigValue]] "rand"; // constant random, for example 935.038 {{codecomment|// 0.0007 ms}}
hint "very damaged";
 
};
The [[getMissionConfigValue]] command searching [[Description.ext]] from top to bottom,
if (alive player && damage player >= 0.5 && damage player < 0.9) then {
it is better for a matter of performance to put all your definitions at the top of the file.
hint "quite damaged";
 
};
=== Optimise then ===
if (alive player && damage player > 0 && damage player < 0.5) then {
 
hint "slightly damaged";
Once you know what is what, you can understand your code better.
};
* Use private variables instead of global variables (preceded with an underscore) as much as possible
if (alive player && damage player == 0) then {
* You were iterating multiple times on the same array?
hint "pristine state";
** You should be able to spot your issue now.
};
* Are you using [[execVM]] on the same file, many times?
if (not alive player) then {
** Store your function in memory to avoid file reading every call with {{Inline code|_myFunction {{=}} [[compile]] [[preprocessFileLineNumbers]] "myFile.sqf";}}
hint "dead";
* Is your variable name far too long?
};</code>
** Find a smaller name, according to the variable scope:
|
e.g
<code>if (not alive player) exitWith { hint "dead"; };<br>
{ _opforUnitUniform {{=}} [[uniform]] [[_x]]; [[systemChat]] _opforUnitUniform; } [[forEach]] _allOpforUnits;
private _playerDamage = damage player;
becomes
switch (true) do {
{ _uniform {{=}} [[uniform]] [[_x]]; [[systemChat]] _uniform; } [[forEach]] _allOpforUnits;
case (_playerDamage >= 0.9): { hint "very damaged"; };
 
case (_playerDamage >= 0.5): { hint "quite damaged"; };
{{Informative | See [[Code Optimisation]] for more information.}}
case (_playerDamage > 0)   : { hint "slightly damaged"; };
 
default { hint "pristine"; };
 
};</code>
== Best practices ==
|}
 
=== Code format === // move that in Make it Readable maybe - it's too late, brb later (:
 
* Some general coding tips from [https://www.topcoder.com/blog/coding-best-practices/ here]: standards, line lengths, etc
** Variable names should indicate what they store / are used for.
 
* Format, indentation, no one-line, spacing, line returns
* Be consistent (space/tab indentation, (camel)casing, [https://en.wikipedia.org/wiki/Indentation_style#K&R_style K&R style] / [https://en.wikipedia.org/wiki/Indentation_style#Allman_style Allman style] indenting)
* Use comments frequently to explain ''not'' what the code does, but ''why'' it is done this way.
 
=== Make reusable functions ===
 
* Don't copy and paste the same code, make functions
* Don't make macros to replace SQF code
 
=== Variables ===
 
* Prefix your public variables and [[setVariable]] with your tag
* PRIVATE (or params) your variables
* Use {{Inline code|#define SOME_CONST}} for constant values instead of variables
 
=== Code location ===
 
* '''Nothing''' in init box ''but'' [[local]] commands for this specific unit - '''all''' the init boxes are run client-side on client connection
* {{Inline code|<span style{{=}}"background-color: #FCC">'''0 {{=}}''' </span>''myCommand''}} is "useful" only for editor fields that for no apparent reason refuse commands returning a value.




== Final words ==
== Final words ==


* Learn from others' scripts but don't steal code and pretend it's yours — be a decent human being.
* Learn from others' scripts but don't steal code and pretend it's yours — be a decent human being. Stealing code and its consequences:
** It soils your reputation and devaluates your actions once it is found out — and it ''always'' get found out. DMCA's are filled on Steam every day.
** It makes people in the community get less helpful and more reluctant in giving advices. It can also prevent them to ''release'' an interesting feature!
* Don't try to obfuscate your code: it is considered rude, especially since you learnt from others.
* Don't try to obfuscate your code: it is considered rude, especially since you learnt from others.
** Obfuscated code only makes it ''harder'' to get, but does not make it ''protected''.
** Obfuscated code is also slower on compilation (and, depending on the quality of code and obfuscation, on execution too).
* Have fun!
* Have fun!

Revision as of 14:47, 11 September 2019


Template:wip


Template:SideTOC Template:Stub

Category: Scripting Topics

Getting started

In the domain of development, any rule is a rule of thumb. If a rule states for example that it is better that a line of code doesn't go over 80 characters, it doesn't mean that any line must not go over 80 characters; sometimes, the situation needs it. If you have a good structure, do not change your code to enforce a single arbitrary rule. If you break many of them, you may have to change something; again, this is according to your judgement.

With that being said, let's go!

Prerequisites

  • Interest!
  • An Arma game
  • An advanced text editor such as Visual Studio Code or Notepad++ (see Debugging Techniques' Code Edition chapter for useful plugins)
  • Some English knowledge, helped if needed by a translator tool like Google Translate or DeepL
  • Access to this wiki is a plus (the Swiss flag is, too)
  • Tutorial/examples (YouTube tutorials, community tutorials, Example Code)
  • Google-Fu (a.k.a search engine skills)


Best practices

Code format

  • Whatever you do about your code's format, be consistent.
  • Choose an indentation format and stick to it. There is not especially one indent better than another (but there sure are terrible ones), again the important point here being consistency.
    • Common indentation styles are:
    • Use empty space. Line return, spaces before and after brackets, if this improves readability, use it: space is free.
    • indent with two/four spaces or one tab. Do not mix these.
    • One-lining (putting everything in one statement) memory improvement is most of the time not worth the headache it gives when trying to read it. Don't overuse it.
  • 0 = myCommand is "useful" only for editor fields that for no apparent reason refuse commands returning a value.
    You do not need it in script files.

Variable format

  • Name your variables and functions properly: While SQF is (non-noticeably) impacted by variable name length, this should not take precedence on the fact that code must be readable by a human being.
    • Your variable names must have a meaning: variables like _u instead of _uniform should not exist. _i is an accepted iteration variable name (e.g in for loops).
    • It is recommended to use camel-case your variables; camel-casing (namingLikeThis) your variables and functions make the code naturally more readable, especially for long names.
  • Prefix your public variables and setVariable with your tag in order to avoid any conflict about other mods, scripts or mission variables.
  • Make your variables private thanks to the private or params keyword in order to avoid accidental upper-scope overwriting of variables of the same name.
  • Defined constants must be UPPERCASE_WITH_UNDERSCORES (e.g #define SOME_CONST).


Code structuration

  • DRY: Don't Repeat Yourself. If you write the same code block or the same logic many times, export this code as a function and use parameters with it.
    • If your code has too many levels, it is time to split and rethink it (e.g if (something) then { if (somethingElse) then { if (anotherSomething) then { /* etc */ }; }; };...)
    • Do NOT use macros as functions - these hinder readability. Use functions instead.
  • Using comments in your code must not explain what the code does, but why it is done this way (if needed). Your code organisation combined to your variable names must be enough to be read by a human.

Code/Files organisation

  • Use CfgFunctions to declare the functions that will be called frequently.
    • One Functions directory, with sub-directories if needed.
  • Use Event Scripts as needed.
  • Don't put any code in a unit's init box but eventually local commands for this specific unit - all unit's init boxes are run client-side on client connection.


Examples

Bad Example Good Example

Good Practice Examples

_unit = player; private _unit = player;
private _uB = allUnits select { side _x == blufor }; private _bluforUnits = allUnits select { side _x == blufor };
finalAssault = true; publicVariable "finalAssault"; PREFIX_finalAssault = true; publicVariable "PREFIX_finalAssault";
player setVariable ["MoneyInPocket", true]; player setVariable ["PREFIX_MoneyInPocket", true];

Format Examples

if (alive player && damage player >= 0.9) then { hint "very damaged"; }; if (alive player && damage player >= 0.5 && damage player < 0.9) then { hint "quite damaged"; }; if (alive player && damage player > 0 && damage player < 0.5) then { hint "slightly damaged"; }; if (alive player && damage player == 0) then { hint "pristine state"; }; if (not alive player) then { hint "dead"; };

if (not alive player) exitWith { hint "dead"; };
private _playerDamage = damage player; switch (true) do { case (_playerDamage >= 0.9): { hint "very damaged"; }; case (_playerDamage >= 0.5): { hint "quite damaged"; }; case (_playerDamage > 0)  : { hint "slightly damaged"; }; default { hint "pristine"; }; };


Final words

  • Learn from others' scripts but don't steal code and pretend it's yours — be a decent human being. Stealing code and its consequences:
    • It soils your reputation and devaluates your actions once it is found out — and it always get found out. DMCA's are filled on Steam every day.
    • It makes people in the community get less helpful and more reluctant in giving advices. It can also prevent them to release an interesting feature!
  • Don't try to obfuscate your code: it is considered rude, especially since you learnt from others.
    • Obfuscated code only makes it harder to get, but does not make it protected.
    • Obfuscated code is also slower on compilation (and, depending on the quality of code and obfuscation, on execution too).
  • Have fun!