CHAPTER 4: My script doesn't work! What to do?! - Debugging time...

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:

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:

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.  



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