Scripting Example – Arma Reforger
Lou Montana (talk | contribs) m (Text replacement - "lang="cpp">" to "lang="C#">") |
Lou Montana (talk | contribs) m (Text replacement - "[[OFPEC tags" to "[[Scripting Tags") |
||
(11 intermediate revisions by 3 users not shown) | |||
Line 4: | Line 4: | ||
For starters, we are aiming at "get the smallest value from the array" functionality. | For starters, we are aiming at "get the smallest value from the array" functionality. | ||
== Create the Script == | |||
=== New File === | |||
* [[Arma Reforger:Resource Manager|Resource Manager]]: '''Resource Browser''' > "Create" button > Script | |||
* [[Arma Reforger: Script Editor| Script Editor]]: {{Controls|RMB}} (on a directory in the Projects window) > "Add New Script..." | |||
* [[Arma Reforger: Script Editor| Script Editor]]: {{Controls|Ctrl|N}} or Plugins > Script Wizard: | |||
{{Feature|important| | |||
'''Always''' prefix the filename and the classname with a [[Scripting Tags|modder tag]] (e.g {{hl|ABC_}}, {{hl|AR15_}}, etc) in order to avoid class conflicts: | |||
* {{hl|'''TAG_'''MyFile.c}} | |||
* {{hl|class '''TAG_'''MyFile}} / {{hl|class '''TAG_'''MyFile : ParentFile}} | |||
Methods and values are not concerned by this. | |||
}} | |||
{{Feature|important| | |||
Scripting modding can only happen in '''Modules''', defined in {{armaR}}'s {{hl|.gproj}} ({{hl|Enfusion Workbench -> Workbench -> Options -> Game Project -> Script Project Manager Settings > Modules}}): | |||
{{{!}} class{{=}}"wikitable valign-top-row-2" | |||
! Module | |||
! core | |||
! gameLib | |||
! game | |||
! workbench | |||
! workbenchGame | |||
{{!}}- | |||
! Directories | |||
{{!}} | |||
* Core | |||
{{!}} | |||
* GameLib | |||
{{!}} | |||
* Game | |||
* GameCode | |||
{{!}} | |||
* Workbench | |||
{{!}} | |||
* WorkbenchGame | |||
{{!}}} | |||
Scripts placed '''outside of those folders''' will be simply '''ignored!'''}} | |||
== Get the Smallest Value == | == Get the Smallest Value == | ||
Line 11: | Line 52: | ||
==== Creation ==== | ==== Creation ==== | ||
Let's begin by creating the object and its method that will host the code. | Let's begin by creating the object and its method that will host the code. | ||
< | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
void Method() | void Method() | ||
Line 18: | Line 59: | ||
} | } | ||
} | } | ||
</ | </enforce> | ||
The method exists now. It is important to name objects, methods and variables properly, therefore it has to be more precise than "Method": | The method exists now. It is important to name objects, methods and variables properly, therefore it has to be more precise than "Method": | ||
< | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
void GetMinValue() | void GetMinValue() | ||
Line 27: | Line 68: | ||
} | } | ||
} | } | ||
</ | </enforce> | ||
==== Parameters ==== | ==== Parameters ==== | ||
The method is empty, takes no parameters and returns nothing. Since we want to provide a list of numbers from which to extract a result, let's add a parameter: | The method is empty, takes no parameters and returns nothing. Since we want to provide a list of numbers from which to extract a result, let's add a parameter: | ||
< | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
void GetMinValue(array<int> numbers) | void GetMinValue(array<int> numbers) | ||
Line 38: | Line 79: | ||
} | } | ||
} | } | ||
</ | </enforce> | ||
Here, one could pass a {{hl|null}} array to the method, which could create a NULL pointer exception on code run; as an author it is our task to cover such case. There are two ways to do such check: | Here, one could pass a {{hl|null}} array to the method, which could create a NULL pointer exception on code run; as an author it is our task to cover such case. There are two ways to do such check: | ||
< | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
void GetMinValue(array<int> numbers) // the numbers array can be null | void GetMinValue(array<int> numbers) // the numbers array can be null | ||
{ | { | ||
if (!numbers) // or if (numbers == null) | if (!numbers) // or if (numbers == null) | ||
return; // a null numbers is intercepted and stops the code | return; // a null numbers is intercepted and stops the code | ||
} | } | ||
} | } | ||
</ | </enforce> | ||
< | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
void GetMinValue(notnull array<int> numbers) // the numbers array cannot be null | void GetMinValue(notnull array<int> numbers) // the numbers array cannot be null | ||
Line 59: | Line 98: | ||
} | } | ||
} | } | ||
</ | </enforce> | ||
The latter version would make the engine throw an exception before the method is actually called. This is the version with which we are going in this tutorial's scope. | The latter version would make the engine throw an exception before the method is actually called. This is the version with which we are going in this tutorial's scope. | ||
==== Return Value ==== | ==== Return Value ==== | ||
The method is supposed to return the array's smallest value: it should then return a number. | The method is supposed to return the array's smallest value: it should then return a number. | ||
< | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
int GetMinValue(notnull array<int> numbers) | int GetMinValue(notnull array<int> numbers) | ||
Line 71: | Line 110: | ||
} | } | ||
} | } | ||
</ | </enforce> | ||
{{Feature|informative|Some methods use an {{hl|out}} parameter to provide the result to an already-assigned variable for memory management purpose. This is not going to be the case here for the sake of clarity.}} | {{Feature|informative|Some methods use an {{hl|out}} parameter to provide the result to an already-assigned variable for memory management purpose. This is not going to be the case here for the sake of clarity.}} | ||
Line 120: | Line 159: | ||
Following the earlier code conception, here is how it would translate to code: | Following the earlier code conception, here is how it would translate to code: | ||
< | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
int GetMinValue(notnull array<int> numbers) | int GetMinValue(notnull array<int> numbers) | ||
{ | { | ||
if (numbers.IsEmpty()) | if (numbers.IsEmpty()) | ||
return 0; | return 0; | ||
int result = numbers[0]; | int result = numbers[0]; | ||
Line 135: | Line 172: | ||
{ | { | ||
if (number < result) | if (number < result) | ||
result = number; | result = number; | ||
} | } | ||
Line 143: | Line 178: | ||
} | } | ||
} | } | ||
</ | </enforce> | ||
=== Commenting === | === Commenting === | ||
Line 161: | Line 196: | ||
! Good Example | ! Good Example | ||
|- style="vertical-align: top" | |- style="vertical-align: top" | ||
| < | | <enforce> | ||
int i = 0; // initialises i to 0 | int i = 0; // initialises i to 0 | ||
i++; // increments | i++; // increments i by 1 | ||
</ | </enforce> | ||
| < | | <enforce> | ||
// int i = 0; | // int i = 0; | ||
// i++; | // i++; | ||
Line 172: | Line 207: | ||
Print(i); | Print(i); | ||
} | } | ||
</ | </enforce> | ||
|} | |} | ||
Line 194: | Line 229: | ||
''result'' is set to the array's first value, but {{hl|foreach}} then goes and compares every array element to it, including itself | ''result'' is set to the array's first value, but {{hl|foreach}} then goes and compares every array element to it, including itself | ||
< | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
int GetMinValue(notnull array<int> numbers) | int GetMinValue(notnull array<int> numbers) | ||
{ | { | ||
if (numbers.IsEmpty()) | if (numbers.IsEmpty()) | ||
return 0; | return 0; | ||
int result = numbers[0]; | int result = numbers[0]; | ||
Line 209: | Line 242: | ||
{ | { | ||
if (numbers[i] < result) // and here (number → numbers[i]) | if (numbers[i] < result) // and here (number → numbers[i]) | ||
result = numbers[i]; // and here (number → numbers[i]) | result = numbers[i]; // and here (number → numbers[i]) | ||
} | } | ||
Line 217: | Line 248: | ||
} | } | ||
} | } | ||
</ | </enforce> | ||
So, are we done now? | So, are we done now? | ||
Line 226: | Line 257: | ||
The {{hl|Count}} method is called on every {{hl|for}} loop; since the array is not changing its size between each loops, it is considered wasting resource. | The {{hl|Count}} method is called on every {{hl|for}} loop; since the array is not changing its size between each loops, it is considered wasting resource. | ||
< | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
int GetMinValue(notnull array<int> numbers) | int GetMinValue(notnull array<int> numbers) | ||
{ | { | ||
if (numbers.IsEmpty()) | if (numbers.IsEmpty()) | ||
return 0; | return 0; | ||
int result = numbers[0]; | int result = numbers[0]; | ||
Line 241: | Line 270: | ||
{ | { | ||
if (numbers[i] < result) | if (numbers[i] < result) | ||
result = numbers[i]; | result = numbers[i]; | ||
} | } | ||
Line 249: | Line 276: | ||
} | } | ||
} | } | ||
</ | </enforce> | ||
But: technically, {{hl|foreach}} is still faster. | |||
<enforce> | |||
class TAG_TutorialExample | |||
{ | |||
int GetMinValue(notnull array<int> numbers) | |||
{ | |||
if (numbers.IsEmpty()) | |||
return 0; | |||
int result = numbers[0]; | |||
foreach (int i, int number : numbers) // we use foreach's index | |||
{ | |||
if (i > 0 && number < result) // here - one bool check is faster than two results[] .Get method call | |||
result = number; | |||
} | |||
return result; | |||
} | |||
} | |||
</enforce> | |||
The code flow is now optimal, and there is nothing more to be done. | The code flow is now optimal, and there is nothing more to be done. | ||
Except perhaps an {{hl|int.MIN}} check, but… this would be overkill and not worth it! | Except perhaps an {{hl|int.MIN}} check, but… this would be overkill and not worth it! | ||
An optimisation should be a balance between time spent and performance gained. | An optimisation should be a balance between time spent and performance gained. | ||
Something 50% optimal but working will always be better than something 98% optimal but unreleased! | Something 50% optimal but working will always be better than something 98% optimal but unreleased! | ||
{{Feature|informative|For further performance advices, see [[Arma Reforger:Scripting Performance|Scripting Performance]].}} | {{Feature|informative|For further performance advices, see [[Arma Reforger:Scripting Performance|Scripting Performance]].}} | ||
== Get the Average Value == | == Get the Average Value == | ||
Line 265: | Line 318: | ||
In this section the {{hl|GetMinValue}} method code above will be reused. | In this section the {{hl|GetMinValue}} method code above will be reused. | ||
< | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
int GetAverageValue(notnull array<int> numbers) | int GetAverageValue(notnull array<int> numbers) | ||
Line 272: | Line 325: | ||
} | } | ||
} | } | ||
</ | </enforce> | ||
But an average is most likely a '''floating point''' number, so {{hl|float}} will be used here. | But an average is most likely a '''floating point''' number, so {{hl|float}} will be used here. | ||
< | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
float GetAverageValue(notnull array<int> numbers) | float GetAverageValue(notnull array<int> numbers) | ||
Line 283: | Line 336: | ||
} | } | ||
} | } | ||
</ | </enforce> | ||
=== Code Setup === | === Code Setup === | ||
Line 311: | Line 364: | ||
Here is the code translation: | Here is the code translation: | ||
< | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
float GetAverageValue(notnull array<int> numbers) | float GetAverageValue(notnull array<int> numbers) | ||
{ | { | ||
if (numbers.IsEmpty()) | if (numbers.IsEmpty()) | ||
return 0; | return 0; | ||
int sum = 0; | int sum = 0; | ||
Line 331: | Line 382: | ||
} | } | ||
} | } | ||
</ | </enforce> | ||
=== Testing === | === Testing === | ||
Line 337: | Line 388: | ||
For demonstration purpose, both methods will be setup inside the {{hl|TutorialExample}} class: | For demonstration purpose, both methods will be setup inside the {{hl|TutorialExample}} class: | ||
< | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
int GetMinValue(notnull array<int> numbers) | int GetMinValue(notnull array<int> numbers) | ||
Line 344: | Line 395: | ||
if (numbers.IsEmpty()) | if (numbers.IsEmpty()) | ||
return 0; | return 0; | ||
int result = numbers[0]; | int result = numbers[0]; | ||
foreach (int i, int number : numbers) | |||
{ | { | ||
if ( | if (i > 0 && number < result) | ||
result = number; | |||
result = | |||
} | } | ||
Line 373: | Line 422: | ||
} | } | ||
} | } | ||
</ | </enforce> | ||
== Debug Console == | == Debug(Remote) Console == | ||
The following code can be used in the Debug Console to try and see the result of previous methods - be sure to set the Console's running environment to "Game": | The following code can be used in the Debug(Remote) Console to try and see the result of previous methods - be sure to set the Console's running environment to "Game": | ||
< | <enforce> | ||
array<int> values = { 18, 10, 5, 4, 5, 10, 33, 42, 666 }; // setup any wanted value here | array<int> values = { 18, 10, 5, 4, 5, 10, 33, 42, 666 }; // setup any wanted value here | ||
TutorialExample tutorialExample = new TutorialExample(); | TutorialExample tutorialExample = new TutorialExample(); | ||
Line 385: | Line 434: | ||
float avgValue = tutorialExample.GetAverageValue(values); | float avgValue = tutorialExample.GetAverageValue(values); | ||
PrintFormat("Array %1: min value is %2, average is %3", values, minValue, avgValue); | PrintFormat("Array %1: min value is %2, average is %3", values, minValue, avgValue); | ||
</ | </enforce> | ||
{{Feature|informative| | {{Feature|informative| |
Latest revision as of 12:01, 2 October 2024
This tutorial aims to provide scripting logic basics. The abstract project is to find certain values (smallest, biggest, average, etc) from a list of numbers.
For starters, we are aiming at "get the smallest value from the array" functionality.
Create the Script
New File
- Resource Manager: Resource Browser > "Create" button > Script
- Script Editor: (on a directory in the Projects window) > "Add New Script..."
- Script Editor: Ctrl + N or Plugins > Script Wizard:
Get the Smallest Value
Method Setup
Creation
Let's begin by creating the object and its method that will host the code.
The method exists now. It is important to name objects, methods and variables properly, therefore it has to be more precise than "Method":
Parameters
The method is empty, takes no parameters and returns nothing. Since we want to provide a list of numbers from which to extract a result, let's add a parameter:
Here, one could pass a null array to the method, which could create a NULL pointer exception on code run; as an author it is our task to cover such case. There are two ways to do such check:
The latter version would make the engine throw an exception before the method is actually called. This is the version with which we are going in this tutorial's scope.
Return Value
The method is supposed to return the array's smallest value: it should then return a number.
Code Setup
Conception
The goal here, as decided earlier, is to get the smallest value from the provided array of numbers. To begin, let's start with writing human code before writing machine code, to lay the logic properly:
- have a "current best result" value
- read each element of the list
- if an element is a better candidate (smaller) than the stored one, use this element instead
- when the end of the list is reached, return the best value
This is fine. But what happens if the list has no values? In that case, we must decide for a default value. Depending on the method's role, default values can change:
- methods will return -1 to indicate an error or a "not found"
- methods will return 0 to state "no value = default value"
We are going for the latter here as we are returning a value.
The end logic:
- have a "current best result" value initialised to zero
- if the list is empty, return the current best value
- read each element of the list
- if an element is a better candidate (smaller) than the stored one, use this element instead
- when the end of the list is reached, return the best value
But!
What if the array has only one element that is 10? The return value would be 0 here, which is an invalid result. Back to the drawing board:
- if the list is empty, return 0
- have a "current best result" initialised to the array first element's value
- read each element of the list
- if an element is a better candidate (smaller) than the stored one, use this element instead
- when the end of the list is reached, return the best value
No more pitfalls in sight, code can be started!
Writing
Following the earlier code conception, here is how it would translate to code:
Commenting
One can note that the above code does not contain any comments.
In the past, commenting has been taught to be extensive, to cover the maximum number of lines of code; on the other hand, some people consider commenting as the last resort.
The balance is somewhere in-between, somewhat closer to the latter:
- a comment should explain why the code is written this way
- a comment should not tell what the code does; code should be self-explanatory
- as a last resort in the event of a complex piece of code, a comment can be used to describe what the code actually does - or at least its intention
Bad Example | Good Example |
---|---|
// int i = 0;
// i++;
for (int i = 1; i < 10; i++) // starts from 1 to not have 0-based index miscalculation
{
Print(i);
} |
Optimisation
The basic rules of coding are as follow:
- Make it work
- Make it readable
- Optimise then
Once the code works, optimisation can be accessed.
What is wrong with this code; it takes the wanted arguments, it returns the expected result, it is fast?
Almost nothing. If we want to care about performance impact, go "straight to the point", avoid useless operations (thus saving cycles):
Remaining issue:
result is set to the array's first value, but foreach then goes and compares every array element to it, including itself
So, are we done now?
No!
As improvement has been done, another loss has been introduced in the process: numbers.Count().
The Count method is called on every for loop; since the array is not changing its size between each loops, it is considered wasting resource.
But: technically, foreach is still faster.
The code flow is now optimal, and there is nothing more to be done.
Except perhaps an int.MIN check, but… this would be overkill and not worth it!
An optimisation should be a balance between time spent and performance gained.
Something 50% optimal but working will always be better than something 98% optimal but unreleased!
Get the Average Value
Method Setup
In this section the GetMinValue method code above will be reused.
But an average is most likely a floating point number, so float will be used here.
Code Setup
Conception
- have a "sum" variable set to zero
- read each element of the list
- add each element to the sum variable
- when the end of the list is reached, return the sum divided by the array size
But!
What if the array has no elements? "sum" would remain a valid value (zero), but then the division could be by zero, which is a forbidden operation in computer science.
- if the list is empty, return 0
- have a "sum" variable set to zero
- read each element of the list
- add each element to the sum variable
- when the end of the list is reached, return the sum divided by the array size
Code writing can be started.
Writing
Here is the code translation:
Testing
For demonstration purpose, both methods will be setup inside the TutorialExample class:
Debug(Remote) Console
The following code can be used in the Debug(Remote) Console to try and see the result of previous methods - be sure to set the Console's running environment to "Game":
The code logs to the Output window something like this:
Array 0x000001C150656B48 {18,10,5,4,5,10,33,42,666}: min value is 4, average is 88.1111