Don't worry, it's normal. An algorithm is usually a complex construct
that does not forgive even minor mistakes, the occasions for them it gives
as many, as characters, it contains. SQF scripts in Arma 3 are no
exception. Hence, a significant part of the scripter's work with the code
is absorbed by testing and successive "debuggung" his script, i.e.
removing the errors detected successively. A school of patience and
humility in the face of our own fallibility.
Debugging in practice is a series of tests, log analysis, logical
inference and, if necessary, gradual exclusion of possible causes until
the actual one is found.
1. About testing SQF scripts
Each run of the scenario containing the script is a test. However, to
effectively determine and remove the causes of errors, it is better to
test scripts in "laboratory", sterile conditions, not during casual
gameplay. What does it mean? This means testing only with what is
necessary for the script to run.
We start the game without any mods (unless the script requires them, but
it's better not to require mods at the testing stage). We test the script
in a scenario in the form of an open folder (not packed into a .pbo file).
We run the script in the editor, using the "preview" function. The
scenario should contain only the minimum of elements necessary for the
script to work. The scenario should not run any other scripts. All in
order to be as clear as possible in the event of an error, that the reason
lies in the piece of code being tested, and not, which is possible, in
someone else's mod or other script. Well, the key to success in debugging
our algorithm is to accurately determine the place in the code, that
causes the error and the reason, why the error occurs there. The more
potential sources of error, the more difficult it is "to corner". It's
usually better to focus on finding one error at a time. Sometimes a whole
series of errors have one, common cause. Tracking several bugs at once
makes easy to get lost.
A valuable source of information about errors is user feedback. If we
publish our script, it is worth asking for information about errors.
Different man will often do something, that would never occur to us during
testing... Because he is different. Hence, our seemingly polished version
1.00 quickly grows into comments like "it doesn't work for me!" They
should be expected and we should use the oportunity to ask users for
details of the problems.
Warning! For testing, we start the game without the -noLogs parameter (Arma 3 launcher option "No logs"). This parameter prevents placing logs in the RPT file, and we need them badly. It is turned off by default and should remain so. Let's also remind about this other people, who we ask for tests.
2. RPT file
The scripter's best friend. A bunch of them (for Windows 10) can be found
here:
Instead of "Rydygier", will be the name of the current user. Note - the
AppData folder is by default hidden and you need to change this in the
settings or work around by creating a shortcut to it. Inside you will find
a number of RPT files - from the last 10 launches of the game. In essence,
these are plain text files.
The game logs are saved in them - backstage messages about what is
happening in the gutters of the "engine" of the game. Most of them are
uninteresting murmurs and bormotans, and because sometimes a lot of it
accumulates, you need to be able to recognize and pick out those logs,
that concern scripts. Thus, also for reading RPT files, an advanced text
editor (eg Notepad++) is useful, which allows quick searching for keywords
such as "Error".
3. Our first error
Depending on the game settings, the error may appear on the screen when it
occurs - this applies especially to running scenarios in the editor (the
so-called preview). Here is our first error, waving to us, let's wave to
him too:
Everything, we see here except "|#|" is simultaneously saved to the RPT
file. The window on the screen is displayed briefly, but its content can be
found among the logs. For this error, the logs look like this:
11:09:22 Error in expression <Side = side player;
while {not (isNull _arty) and {(alive _arty)}} do
{
_gunn>
11:09:22 Error position: <_arty) and {(alive _arty)}} do
{
_gunn>
11:09:22 Error Undefined variable in expression: _arty
11:09:22 File C:\Users\Rydygier\Documents\Arma 3\missions\Skrypt_Bum.Malden\init.sqf..., line 7
11:09:22 Error in expression <"
_handle = [] spawn
{
sleep 1;
_arty = arty1;
_mySide = side player;
while {no>
11:09:22 Error position: <arty1;
_mySide = side player;
while {no>
11:09:22 Error Undefined variable in expression: arty1
11:09:22 File C:\Users\Rydygier\Documents\Arma 3\missions\Skrypt_Bum.Malden\init.sqf..., line 4
If we search the RPT file for the phrase "Error in expression" or "Error
position" (or their equivalent in the language in which we start the
game), each element found will correspond to one error log. Here we see,
as it turns out, two (only the last of the series can be seen on the
screen). Note: this does not mean the same number of mistakes in the
script. For code executed repeatedly/cyclically, each execution of one
incorrect line will generate a new log. Often, some are a consequence of
others, i.e. the code fails in many places due to an error in one place.
This is our case.
The errors relate to the script, we created in the previous chapter. We
can see, that the RPT log reports the use of the undefined variable
arty1 at line 4 of the init.sqf file. It also
helpfully gives us a fragment of our code, where the script failed because
of this error (this will not always be the place, that is the actual
source for the error!). This fragment can be used to search for a place in
our script, if the line number is missing in the log. Above we see the
error in line 7: the undefined _arty variable used. This is a
consequence of the original error, because we defined this variable as
follows:
_arty = arty1;
So if arty1 is undefined, the same fate befalls to variable
_arty, because underneath is hidden variable arty1.
Therefore, we focus on the variable arty1. Why is it undefined? We remember, that it is not defined in the script,
it comes from the name of the mortar set on the map. So there will be the
cause of the error. And indeed!
"Arty11" instead of "arty1" is entered by mistake. Therefore the variable
arty1 is not defined. We found the source of the error! It
remains to remove the excess "1" and... we can continue testing.
4. Editing the script for debugging purposes
Often, in order to understand, where the problem comes from, we want to know what is happening, e.g. with a variable in a specific place in the code - what value it has. We can either display this value on the screen, e.g. using the command:hintSilent
or save it to the RPT file:
diag_log
In both cases, we also need the command, e.g. a
format
that converts any value into text, and thus something, that can be displayed or saved as log.
For example, if we want to find out, what the target and ally arrays
contain in our artillery script just before choosing the target, we can do
it like this:
diag_log format ["cele: %1 sprzymierzeni: %2",_targets,_allies];
Examples of RPT logs obtained in the test:
Global variable values and expressions can also be tracked using the console in the editor ("Watch" section), which is sometimes sufficient and can replace the code editing:
If after testing the scenario in the logs we do not find "test1", it means that for some reason this line, and thus further in this portion of the code, namely:
are never reached. In this way - finding with similar additions a line, at which the script get stuck - we can find out the answer to the question "why this is not happening?" - where "ceases to happen", so where is the problem.
diag_log format ["cele: %1 sprzymierzeni: %2",_targets,_allies];
Examples of RPT logs obtained in the test:
11:15:30 "cele: [] sprzymierzeni: [B Alpha 1-1:1 (Rydygier),arty1G]"
11:15:52 "cele: [O Alpha 1-1:1,O Alpha 1-2:1,O Alpha 1-3:1] sprzymierzeni: [B Alpha 1-1:1 (Rydygier),arty1G]"
11:16:00 "cele: [O Alpha 1-2:1,O Alpha 1-3:1] sprzymierzeni: [B Alpha 1-1:1 (Rydygier),arty1G]"
Note, that the time intervals between logs are smaller, than 30 seconds per cycle (we expect one log per cycle in this case). In this test, I used the editor function to compress time: everything was happening at an accelerated pace. Sometimes this allows you to significantly speed up testing, but it should be remembered, that this also multiplies the load on the processor, which affects some scripts, sensitive to so-called timing.
Global variable values and expressions can also be tracked using the console in the editor ("Watch" section), which is sometimes sufficient and can replace the code editing:
The Execute section will execute the code entered in it (after pressing
"LOCAL EXEC"), which can also be useful, for example, for quickly testing
ideas or checking the state of affairs during simulation
(gameplay).
Sometimes we just want to check, if and when the script "reaches" the
indicated line. Then all you need is:
If after testing the scenario in the logs we do not find "test1", it means that for some reason this line, and thus further in this portion of the code, namely:
are never reached. In this way - finding with similar additions a line, at which the script get stuck - we can find out the answer to the question "why this is not happening?" - where "ceases to happen", so where is the problem.
5. Errors difficult to find
There are also such errors, that will not leave a log in RPT (or this log will not help us find the reason), and are not easy to detect by the methods described in section 4. Faults being their source can also be difficult to notice, especially in the extensive and complex script. These include some cases of unpaired or misaligned brackets, especially curly braces.
Notepad++ editors provide the option to count characters in a given file.
In this way, we can count the opening and closing brackets of a given type.
If their quantity is different, we probably have a problem with unpaired
brackets. However, in this way we will not learn much more than that, nor
will we detect a problem with brackets misplaced.
Remains tedious browsing the script line by line? You can try, but if the code is long, it will be a hassle during which we can still overlook, what we are looking for. Fortunately, we have one more trick up our sleeve. We can use /**/ symbols, which "inactivate" the portion of the code enclosed by them (becomes a "comment" invisible to the game engine) or two slashes - // - which turn off the code at single line - a part following the slashes. In this way, we can selectively deactivate batches of the code selected in a way, so that the rest without them is still properly executed. If we turn off half the code and the sought error disappears, we know that the reason lies in the disabled half. Otherwise, we know it is in the still active half. Then we focus on the half, that contains the cause and repeat the procedure: we turn off one part of it, etc. In this way, we are gradually narrowing the search area, cornering the defective line by the elimination until the suspicious part of the script is small enough to easily search it.
How to choose portions of code to turn off to not break the execution of the rest? By respecting the logic and syntax.
If we turn off the part, that defines the variables used by the code left active - we will cause new errors - logical. An example of such error:
When it comes to respecting the syntax, do not "cut" the code through the middle of the expression - command syntax:
Cut between complete expressions:
Remains tedious browsing the script line by line? You can try, but if the code is long, it will be a hassle during which we can still overlook, what we are looking for. Fortunately, we have one more trick up our sleeve. We can use /**/ symbols, which "inactivate" the portion of the code enclosed by them (becomes a "comment" invisible to the game engine) or two slashes - // - which turn off the code at single line - a part following the slashes. In this way, we can selectively deactivate batches of the code selected in a way, so that the rest without them is still properly executed. If we turn off half the code and the sought error disappears, we know that the reason lies in the disabled half. Otherwise, we know it is in the still active half. Then we focus on the half, that contains the cause and repeat the procedure: we turn off one part of it, etc. In this way, we are gradually narrowing the search area, cornering the defective line by the elimination until the suspicious part of the script is small enough to easily search it.
How to choose portions of code to turn off to not break the execution of the rest? By respecting the logic and syntax.
If we turn off the part, that defines the variables used by the code left active - we will cause new errors - logical. An example of such error:
WRONG |
When it comes to respecting the syntax, do not "cut" the code through the middle of the expression - command syntax:
WRONG |
WRONG |
WRONG |
WRONG |
WRONG |
Cut between complete expressions:
CORRECT |
CORRECT |
CORRECT |
CORRECT |
6. Logic errors
It also happens, that the script is written flawlessly and still does not
do, what we planned. The reason may be logic errors - the code correctly
reflects the logic, we devised, but that logic is not correct. Maybe we just
forgot to include some important element of our concept in the script. Or
maybe we've misconstructed the condition. If the code without an error from
the syntax side does not do, what we planned, it remains to rethink and
modify its logical construction. A logical problem may be the result of not
only a mistake at the conceptual stage, but also a simple error, which
admittedly does not spoil the SQF syntax, but spoils the intended logic. A
simple example:
Our script with such a line will work without errors, but the mortar will
almost never fire. All due to mistake in the condition: it is 2000, it
should be 200. In the above form, the code will execute a firing line only,
if there is no ally within a radius of up to 2 kilometers from the target
while in Arma 3 such distance often excludes any knowledge about the enemy.
We have unwittingly expanded the "security buffer" ten
times.
Thank you for your attention and good luck!
Rydygier
7. End of the beginning
This concludes the series dedicated to the basics. I hope, you have managed to convey enough to make the beginning of your SQF scripting adventure as easy and painless as possible. If not - please leave any questions, wishes, complaints and complaints in the comments.Thank you for your attention and good luck!
Rydygier
No comments:
Post a Comment