MaHuJa/Sandbox – User

From Bohemia Interactive Community
Jump to navigation Jump to search
No edit summary
(Sandbox replacement: draft of "Armascript considered harmful")
Line 1: Line 1:
This article assumes you are at least slightly familiar with mission editing.
Armascript considered harmful
For a large part, this presents a quick progression along the path. Whenever I introduce some new concept or command, I try to include a link to the wiki page for it.
I recommend you try to figure out why I did what I did, and looking at the referenced pages will help a lot in understanding what it does in the first place.


Introduction to ArmAscript
Cliched title, I know. Armascript is a name I invented to describe the scripting language used in the arma series of games, also known by its file extension sqf, to which the game owes most of its extensibility and power. In short,  its power, already back in Operation Flashpoint, is probably the biggest reason for the success of ArmA. I believe BIS are well aware of this.


=Oneliners in the editor=
It's my observation that the rate of armascripters also being professional programmers is unusually high. Statistically speaking I do not have a big enough sample size, anyone have anything better?


The most basic form of scripting is the oneline scripts in various init fields, trigger on (de-)activation fields, and waypoint activation fields in the editor itself.
One possible factor is that these programmers are drawn to a proper simulation like arma in the first place. (The community overall is also unusually tolerant of bugs.)
I'm focusing on another: The language is unsuitable for new/non-professional programmers.


===Example: Destroying a tank===
ArmAscript, as per arma2, is a huge improvement over the original ofp script language (known by the file extension sqs). The language has certainly evolved since then, with each iteration (ofp,ofp:resistance,arma1,arma2) we've seen large improvements. However, it occurs to me that these improvements constitute evolution rather than design. Thus each improvement, while an improvement, does not always mesh well with the rest of the developments to the language.
A mission requires the player, and his two helpers, to get a tank parked a little behind the engagement area, and reinforce the front with it. However, as they approach the tank, it blows up. Now they have to find and punish the saboteurs. And perhaps go back to the front to help out in any way they can.


These steps can achieve that:
*Create the player squad
*Create the tank, name it "theTank".
*Create a couple saboteurs hiding in some bushes a couple hundred meters nearby - preferrably not with line of sight to the tank so that they could start shooting the player early.
*Create a trigger near to the tank (almost on top of) with both sizes set to 150. Set it to "west" "present". In the "on activation" field, type in the following:
theTank setdamage 1


Congratulations, you've just written your first script.
Here's a few fundamental problems:


''theTank'' is your first use of [[Variables]] - they are essential to pretty much everything you do. Also take a quick look at [[Data Types]] - theTank is of the object type.
* Forced multithreaded programming:
We have to program as though we were making multithreaded programs. A different thread can change things at any time. Worse yet, we are also not given proper synchronization primitives. The best we have is an intrusive hack:


The demonstration mission itself needs a few things more to be considered a complete mission, but which aren't important for this tutorial.
code;
*Briefing, all of that
waituntil { critical_section_code; true};
*Waypoints for the player group
code;
*Notification of changed goals
*Victory condition triggers
*Perhaps an explosive charge beside the tank for realism.


===Customized ammobox===
When I say intrusive, I mean that the code fragment needs to have that final "true" statement. This precludes abstracting it.


Create an ammobox. (Optionally give it the name ammo_one) In its init line, fill in
Apparently the critical_section_code performs a lot better too when run in this fashion. Primarily because the 0.03ms delay isn't present, and also because it will benefit from hardware cache effects. The cost is probably mainly to the framerate, should you take too long.
clearweaponcargo this; clearmagazinecargo this; this addweaponcargo ["m16a2",2]; this addmagazinecargo ["30Rnd_556x45_Stanag",30]
You've just created an ammobox that will contain two m16 rifles and 30 magazines for them. For the "scripting names" of weapons and ammunition, see [[ArmA:_Weapons|here]]. For unofficial addons, the names are usually listed in a readme.


Whenever you are editing an object, like an ammobox, [[this]] has the special meaning of "this particular object". If you write the same thing for an "ammo_two" the lines there will not change the content of ammo_one. This allows copy/paste to be used to write it once and paste it everywhere, and it will affect the object it has been copied to. Had you written
If the critical section is too large for instant processing, or simply needs to work over time, you have to grab a lock by
clearweaponcargo ammo_one
instead, you'd always be clearing the contents of that first crate unless you changed it every time.


code;
waituntil { _l = lockname; lockname = true; _l==false};
critical_section_code;
lockname=false;
code;


=Script files=
* Distributed programming:
If there's one thing that's harder on programmers than multithreaded programming, it's distributed programming. And once a mission is loaded into multiplayer, the programmer better have it down right. But it gets worse; the "local/global" of many functions are not properly documented. Or depend on the context.


<div style="float:right;border-left-style:double;border-bottom-style:double;border-top-style:thin;border-right-style:thin">
Again, we are provided no synchronization.
There are two formats for script files, [[SQS]] and [[SQF]].<br/>
As for making it ourselves, I've seen a good one: it comes down to putting a string in a variable, publicvariable-ing it (note that these have to happen atomically, see above) and having an EH on the server compile and run it. (Apparently publicvariable on code datatype is horrible on performance, presumably lag-wise. For very small blocks of code.)
Use [[SQF]]: SQS are deprecated and will stop functioning at some point in time.<br/>
Besides, [[SQF]] lends itself to readability and style.
</div>


Obviously, these oneliners are of limited use when you want big things to happen. That's when script files come into play. These files are put alongside the mission.sqm which the editor makes, and get packed into the final pbo that you would give to others. In this section, I'll introduce how to create a system to 'buy weapons'.
Combining the above you can create a session-wide lock, as needed. The fact that you need to, though, speaks for itself. Alternatively, send a string (never code) to be compiled and run at the server, which is really just a way to de-distribute it.


I leave the enemies and all those little details to you.
Another issue again, is the locality of game objects, and how this can magically break code; especially combined with JIP. We have neither the means to override it, nor to detect where else it is. (And by the time our sent code arrives, it may have changed.)


In order to have a place to save these files, you first need to save your mission with a name. Your next job is to find that mission. It's under
* Performance issues
My Documents/ArmA/Missions/''missionname.islandname'' (e.g. My Documents/ArmA/Missions/ScriptTutorial.Sara)
or My Documents/Arma Other Profiles/''Playername''/Missions/''missionname.islandname'' (e.g. My Documents/ArmA Other Profiles/MaHuJa/Missions/ScriptTutorial.Sara)


In that directory, you will find a file called mission.sqm - this is the file produced by the mission editor, and contains the type, placement, name, init script, and so on for every object you've placed, every trigger, marker, and so on. Optionally you could create files like [[briefing.html]] and [[description.ext]] but that's beyond the scope of this tutorial.  
There's one rule above all other rules when it comes to performance in armascript. Don't do it (yourself).
Even for relatively simple mathematical tasks, it'll still be better to contort the game engine to do it for you.  


===The money and income===
This is opposite to most environments where accessing external resources is typically the more expensive way to do something.


Create an empty file called init.sqf (''Init.sqf'' is a special name, and will be run by the game as the mission starts) and open it - notepad will do fine. (MS Word will probably not.) Here's what to put into it:
One recent example I found, which surprised the original programmer; if you want to know if some position is within a tilted rectangle, creating a trigger and a gamelogic, and for each check just move the gamelogic and check the list for that trigger, it's faster than doing the trigonometry by script.
money = 0;
We need to set the money value. Like if we don't set up a bank account before we put money into it, bad things happen.
execvm "earnmoney.sqf";
This will start a script called earnmoney.sqf - which we will create later. ''[[Execvm]]'' is more or less a synonym for ''[[spawn]] [[compile]] [[loadfile]]'' - and the script is runs may continue to run in the background.
removeallweapons player;
This thing will make no sense if the player starts with good enough weapons, will it? (That command also removes magazines.)


Now, create a new blank file called earnmoney.sqf and choose one of the following:
And don't even think of using trigonometry to measure the distance between two coordinates; the distance command does this better. But wait, you need to create two objects (gamelogic preferred) and setposasl them, or the result will be wrong if that area of terrain isn't flat. (Well, unless they were world coordinates and that was exactly what you wanted.)


Alternative 1: Time is money
* The difficulty of getting things right
while {true} do {
  sleep 60;
  money = money + 50;
  hint format ["You now have %1 dollars.",money];
}
It uses [[while]] (which requires [[do]]), [[sleep]] [[hint]] and [[format]] functions.


Alternative 2: Travel is money
Bugs will occur in any program, in any language. However, the nature and rate of bugs will vary depending on the language. While there are clearly languages which are harder to get right (see brainfuck), I find armascript ranks quite low. This is especially true given the complications I noted above. And the lack of debugging tools make this even worse. diag_log was no less than a breakthrough.  
while {true} do {
  _pos = position player;
  sleep 60;
  _lump = player distance _pos;
  money = money + _lump/2;
  hint format ["You now have %1 dollars.",money];
}
The latter will force the player to move about, even though he is unarmed and there are enemies about. This also uses the [[position]] and [[distance]] functions, as well as the [[player]] constant. If you haven't read it already, it's time for [[Data Types]] too, seeing how the above not only uses numbers, but also the position data type.
Please note the use of indentation, everything that's part of the loop.


Try to run it. It if works, you should see the money counter in the upper right updating every minute. If it shows an error, you probably made an error in copying it (or I messed up). If you typed it, common pitfalls are:
* Non-linear syntax
*while uses {} - if uses ()
*you missed a ; at the end of a command


===Buying===
Armascript is an operator-based language. This means that each command is in the form of
op B;
A op B;
This makes a lot of sense for  -a, a+b, a=b, and so on.
It's also a fairly close match to the usual object-oriented notations, {player setpos somepos} instead of player.setpos(somepos)
When a command needs more than two operands, that problem is solved by making an array out of B. It's almost decent, as workarounds go.


I wish i knew how to make those pretty dialogs and all, but I don't. And they're anything but simple so they're not good for a tutorial like this. So I'm going to use actions - those menu items in the lower right of the screen.
However, it's also being used where less appropriate. As a particular example,
{
...
...
...
} foreach whatever;
In order to know the context of the block, you need to read below it. Then you can go back up and understand what it's for. Most languages are, and I dare say for a reason, made such that a human reading it from top to bottom can understand it. As a workaround, I usually copy the foreach to a comment on top of the block, but such comments tend to fall out of sync with their original.


First, let's introduce the arrays


* No static code checking


=Dump for later use=
The only way you can check your code, even for syntax, is to run it. Even the compile command will not catch (all) your syntax errors.
weapons = ["m16a2","m4","m4aim","m24"];
Squint is a good attempt, but falls short in several areas; some of which, due to the nature of armascript, it cannot fix. For example the surprisingly plentiful code that, for armascript reasons, must be contained in "strings" rather than {blocks}.
weaponscost = [100, 200, 250, 500];
stanag = "30Rnd_556x45_Stanag";
ammo = [stanag,stanag,stanag,"5Rnd_762x51_m24"];


[] execvm "moneytime.sqf"
Going over that line by line:
*''money = 0;'' sets a [[variable]] called money to 0. Otherwise it would be nil, which has a meaning along the lines of "not a number".
*''weapons = ["m16a2","m4","m4aim","m24"];'' creates an [[array]]




I'm going to introduce you to a good habit: Gather all 'magic' values in one place.
Is armascript, in its current condition, salvageable?


We can live with it as it is; but we will suffer the cost of doing so. (Fewer programmers, each working under bad conditions -> Less added value.) How many projects have not been completed and released because its maker gave up on armascript? At some point, I considered the idea of making a C++ compiler backend, that would accept a rather large subset of C++, and turn it into armascript. We also do not want to switch to an alternative that's just plain worse, even should it have prettier syntax. There's also the issue of how much work implementing it will be for BIS. (The parts they cannot leave to the community, that is.)


=Misc=
There are many minor annoyances with armascript, which can be fixed; the language can evolve further. However, there is a limit to how much you can do without breaking existing code, and the above list are fundamental issues, any fix to which WILL break code. And lots of it.
 
Thus, to fix it, we will need to deprecate it, such that it will only be used for backward compatibility. Similarly to how sqs was deprecated in favor of sqf.
 
 
 
What features do we need of a replacement?
We should consider every feature armascript has, look at WHY that feature is there, and compile a list of those requirements. Only when we have the most fundamental parts down, can we begin looking at what we would replace it with.
Points that must be covered:
-Multithreading: .03, atomicity/pre-emptive,
-Distributed
-Events
-Localization
 
Clean upgrade path?
If possible, make it viable to have a program do the (bulk) conversion of armascript to the new solution.
Recycle as many resources as possible?
 
What is a realistic timeschedule?
Probably ArmA 3. For the beginning of the deprecation. No less than the release of a full expansion.

Revision as of 11:37, 7 March 2011

Armascript considered harmful

Cliched title, I know. Armascript is a name I invented to describe the scripting language used in the arma series of games, also known by its file extension sqf, to which the game owes most of its extensibility and power. In short, its power, already back in Operation Flashpoint, is probably the biggest reason for the success of ArmA. I believe BIS are well aware of this.

It's my observation that the rate of armascripters also being professional programmers is unusually high. Statistically speaking I do not have a big enough sample size, anyone have anything better?

One possible factor is that these programmers are drawn to a proper simulation like arma in the first place. (The community overall is also unusually tolerant of bugs.) I'm focusing on another: The language is unsuitable for new/non-professional programmers.

ArmAscript, as per arma2, is a huge improvement over the original ofp script language (known by the file extension sqs). The language has certainly evolved since then, with each iteration (ofp,ofp:resistance,arma1,arma2) we've seen large improvements. However, it occurs to me that these improvements constitute evolution rather than design. Thus each improvement, while an improvement, does not always mesh well with the rest of the developments to the language.


Here's a few fundamental problems:

  • Forced multithreaded programming:

We have to program as though we were making multithreaded programs. A different thread can change things at any time. Worse yet, we are also not given proper synchronization primitives. The best we have is an intrusive hack:

code; waituntil { critical_section_code; true}; code;

When I say intrusive, I mean that the code fragment needs to have that final "true" statement. This precludes abstracting it.

Apparently the critical_section_code performs a lot better too when run in this fashion. Primarily because the 0.03ms delay isn't present, and also because it will benefit from hardware cache effects. The cost is probably mainly to the framerate, should you take too long.

If the critical section is too large for instant processing, or simply needs to work over time, you have to grab a lock by

code; waituntil { _l = lockname; lockname = true; _l==false}; critical_section_code; lockname=false; code;

  • Distributed programming:

If there's one thing that's harder on programmers than multithreaded programming, it's distributed programming. And once a mission is loaded into multiplayer, the programmer better have it down right. But it gets worse; the "local/global" of many functions are not properly documented. Or depend on the context.

Again, we are provided no synchronization. As for making it ourselves, I've seen a good one: it comes down to putting a string in a variable, publicvariable-ing it (note that these have to happen atomically, see above) and having an EH on the server compile and run it. (Apparently publicvariable on code datatype is horrible on performance, presumably lag-wise. For very small blocks of code.)

Combining the above you can create a session-wide lock, as needed. The fact that you need to, though, speaks for itself. Alternatively, send a string (never code) to be compiled and run at the server, which is really just a way to de-distribute it.

Another issue again, is the locality of game objects, and how this can magically break code; especially combined with JIP. We have neither the means to override it, nor to detect where else it is. (And by the time our sent code arrives, it may have changed.)

  • Performance issues

There's one rule above all other rules when it comes to performance in armascript. Don't do it (yourself). Even for relatively simple mathematical tasks, it'll still be better to contort the game engine to do it for you.

This is opposite to most environments where accessing external resources is typically the more expensive way to do something.

One recent example I found, which surprised the original programmer; if you want to know if some position is within a tilted rectangle, creating a trigger and a gamelogic, and for each check just move the gamelogic and check the list for that trigger, it's faster than doing the trigonometry by script.

And don't even think of using trigonometry to measure the distance between two coordinates; the distance command does this better. But wait, you need to create two objects (gamelogic preferred) and setposasl them, or the result will be wrong if that area of terrain isn't flat. (Well, unless they were world coordinates and that was exactly what you wanted.)

  • The difficulty of getting things right

Bugs will occur in any program, in any language. However, the nature and rate of bugs will vary depending on the language. While there are clearly languages which are harder to get right (see brainfuck), I find armascript ranks quite low. This is especially true given the complications I noted above. And the lack of debugging tools make this even worse. diag_log was no less than a breakthrough.

  • Non-linear syntax

Armascript is an operator-based language. This means that each command is in the form of op B; A op B; This makes a lot of sense for -a, a+b, a=b, and so on. It's also a fairly close match to the usual object-oriented notations, {player setpos somepos} instead of player.setpos(somepos) When a command needs more than two operands, that problem is solved by making an array out of B. It's almost decent, as workarounds go.

However, it's also being used where less appropriate. As a particular example, { ... ... ... } foreach whatever; In order to know the context of the block, you need to read below it. Then you can go back up and understand what it's for. Most languages are, and I dare say for a reason, made such that a human reading it from top to bottom can understand it. As a workaround, I usually copy the foreach to a comment on top of the block, but such comments tend to fall out of sync with their original.


  • No static code checking

The only way you can check your code, even for syntax, is to run it. Even the compile command will not catch (all) your syntax errors. Squint is a good attempt, but falls short in several areas; some of which, due to the nature of armascript, it cannot fix. For example the surprisingly plentiful code that, for armascript reasons, must be contained in "strings" rather than {blocks}.


Is armascript, in its current condition, salvageable?

We can live with it as it is; but we will suffer the cost of doing so. (Fewer programmers, each working under bad conditions -> Less added value.) How many projects have not been completed and released because its maker gave up on armascript? At some point, I considered the idea of making a C++ compiler backend, that would accept a rather large subset of C++, and turn it into armascript. We also do not want to switch to an alternative that's just plain worse, even should it have prettier syntax. There's also the issue of how much work implementing it will be for BIS. (The parts they cannot leave to the community, that is.)

There are many minor annoyances with armascript, which can be fixed; the language can evolve further. However, there is a limit to how much you can do without breaking existing code, and the above list are fundamental issues, any fix to which WILL break code. And lots of it.

Thus, to fix it, we will need to deprecate it, such that it will only be used for backward compatibility. Similarly to how sqs was deprecated in favor of sqf.


What features do we need of a replacement? We should consider every feature armascript has, look at WHY that feature is there, and compile a list of those requirements. Only when we have the most fundamental parts down, can we begin looking at what we would replace it with. Points that must be covered: -Multithreading: .03, atomicity/pre-emptive, -Distributed -Events -Localization

Clean upgrade path? If possible, make it viable to have a program do the (bulk) conversion of armascript to the new solution. Recycle as many resources as possible?

What is a realistic timeschedule? Probably ArmA 3. For the beginning of the deprecation. No less than the release of a full expansion.