Scripting Example – Arma Reforger
Lou Montana (talk | contribs) m (Text replacement - " " to " ") |
Lou Montana (talk | contribs) (Add create script step) |
||
Line 3: | Line 3: | ||
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 [[OFPEC 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. | |||
}} | |||
Line 12: | Line 28: | ||
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> | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
void Method() | void Method() | ||
{ | { | ||
} | } | ||
} | }; | ||
</enforce> | </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> | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
void GetMinValue() | void GetMinValue() | ||
{ | { | ||
} | } | ||
} | }; | ||
</enforce> | </enforce> | ||
Line 32: | Line 48: | ||
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> | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
void GetMinValue(array<int> numbers) | void GetMinValue(array<int> numbers) | ||
{ | { | ||
} | } | ||
} | }; | ||
</enforce> | </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> | <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> | ||
<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 | ||
{ | { | ||
} | } | ||
} | }; | ||
</enforce> | </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. | ||
Line 65: | Line 79: | ||
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> | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
int GetMinValue(notnull array<int> numbers) | int GetMinValue(notnull array<int> numbers) | ||
{ | { | ||
} | } | ||
} | }; | ||
</enforce> | </enforce> | ||
Line 121: | Line 135: | ||
<enforce> | <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 147: | ||
{ | { | ||
if (number < result) | if (number < result) | ||
result = number; | result = number; | ||
} | } | ||
return result; | return result; | ||
} | } | ||
} | }; | ||
</enforce> | </enforce> | ||
Line 195: | Line 205: | ||
<enforce> | <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 217: | ||
{ | { | ||
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]) | ||
} | } | ||
return result; | return result; | ||
} | } | ||
} | }; | ||
</enforce> | </enforce> | ||
Line 227: | Line 233: | ||
<enforce> | <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 245: | ||
{ | { | ||
if (numbers[i] < result) | if (numbers[i] < result) | ||
result = numbers[i]; | result = numbers[i]; | ||
} | } | ||
return result; | return result; | ||
} | } | ||
} | }; | ||
</enforce> | </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. | ||
Line 266: | Line 268: | ||
<enforce> | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
int GetAverageValue(notnull array<int> numbers) | int GetAverageValue(notnull array<int> numbers) | ||
{ | { | ||
} | } | ||
} | }; | ||
</enforce> | </enforce> | ||
Line 277: | Line 279: | ||
<enforce> | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
float GetAverageValue(notnull array<int> numbers) | float GetAverageValue(notnull array<int> numbers) | ||
{ | { | ||
} | } | ||
} | }; | ||
</enforce> | </enforce> | ||
Line 312: | Line 314: | ||
<enforce> | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
float GetAverageValue(notnull array<int> numbers) | float GetAverageValue(notnull array<int> numbers) | ||
Line 330: | Line 332: | ||
return sum / numbers.Count(); // numbers.Count() cannot be zero here as it is checked line 5 | return sum / numbers.Count(); // numbers.Count() cannot be zero here as it is checked line 5 | ||
} | } | ||
} | }; | ||
</enforce> | </enforce> | ||
Line 338: | Line 340: | ||
<enforce> | <enforce> | ||
class | class TAG_TutorialExample | ||
{ | { | ||
int GetMinValue(notnull array<int> numbers) | int GetMinValue(notnull array<int> numbers) | ||
Line 372: | Line 374: | ||
return sum / numbers.Count(); | return sum / numbers.Count(); | ||
} | } | ||
} | }; | ||
</enforce> | </enforce> | ||
== Debug Console == | == Debug Console == |
Revision as of 14:51, 27 February 2023
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.
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 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 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