Let’s start from the beginning. In Arma there are 3 concepts of script:

  • script –  an ancient implementation used as early as OFP. It is probably better known as .sqs script. One of the ways to instruct the engine that the script you want to execute is .sqs script is by using exec command and path to the .sqs file.
  • VM script – main implementation of the scripting language. The VM scripts could be either executed with execVM command together with path to .sqf file or spawn command, in which case the code could be written inline.
  • FSM script –  another script implementation that combines the use of visual block building algorithms and .sqf code. For this you need FSM editor. The FSM scripts are invoked with execFSM command and path to FSM file.

I’m going to focus more on .sqs today and why it is “old but gold”. But first let me remind you how script scheduler works. I have talked about it in the past in my blogs, but it would not hurt to repeat a few things. Actually what I said before was related to how engine deals with VM scripts.

So let’s say we have a bunch of spawned scripts. The engine has to somehow manage the whole lot. There is no multithreading or parallel execution, so all scripts need to be executed in sequence. But if you start executing script after script and wait until previous script finished before starting next script, it might take awhile before last launched script is executed, especially if some prior scripts have some demanding operations.

So in order to make it more fair for other scripts launched later and to distribute priorities more evenly, the scheduler is used and each script is added to the scheduler. This is why those scripts are called scheduled. The scheduler itself is primitively simple. All script are placed in a queue and each script gets time stamp. After that the scripts are executed in order they are positioned in the queue.

But here is the trick, a script is only allowed to run for 3 milliseconds or less. If the script is completed within allocated time, it is removed from the queue. If not, it is interrupted and suspended, time stamp updated and the next script in the queue is started. After the end of the script simulation is reached, the queue is rearranged and the oldest scripts are placed at the beginning of the queue, then the process starts over.

This way you pretty much guaranteed no one script will be left behind and every script is treated equally. However there is this little problem associated with running scheduled scripts. How fast a particular script will complete is unknown. It highly depends on how other scripts are doing and what they are doing. Also any unscheduled event will get priority over scheduled scripts and will delay the whole scheduled queue until unscheduled script is completed.

Another downside is that if you have 2 scheduled scripts sharing same global variable, you cannot know which one will change it first and even more so that the variable will not change some several times during script execution. Also considering that most of the scripts will run in scheduled environment, you pretty much need powerful CPU that can process the scripts as fast as possible, so there is no backlog. And indeed, Arma games were always performing better with better CPUs rather than with better graphics.

So what do you do if it is critical that your script runs from beginning to end with no interruption? Well the obvious thing is you make sure it runs in unscheduled environment. Any event handler script will run in unscheduled environment. You can also start an FSM script from scheduled environment and make it run your unscheduled script. Or, you can write your script in .sqs.

Differently to .sqf scripts, .sqs scripts do not get interrupted (unless you have explicitly added suspension in the code) and run from the beginning to the end once launched. It might take longer to launch .sqs script in comparison to .sqf or .fsm script, but once it started it will complete. On one hand this is good news, on the other hand if your .sqs script has some heavy operations and takes long time to complete it will drag performance down, as it will make other scripts wait. Nevertheless it is one useful functionality.

Let’s take addAction for example. This command has 2 params that could be scripts – condition, which has to return true for action to display, which runs in unscheduled environment almost every frame, and the actual script to be executed on action activation. This script runs in scheduled environment. When addAction is compiled, compiler will check what is that you pass as a script. If it is string reference to .sqf file or just code {…}, it will be treated as VM script, if it is string reference to .sqs script, it will be treated as just script.

I have made a simple comparison as a proof of concept, one addAction calling .sqf script (VM) and one calling .sqs script (nonVM). I also made sure that I have some heavy operations everywhere to put some load on scheduler and then made both to display [frames, time]. As you can see from the video above, .sqf script was interrupted many times and it took 200 frames and over 6 seconds to complete it, while .sqs script was completed in less than a second all in one frame. Clearly if you have critical section in your addAction script, this is where .sqs can come handy.

Here is the code I used for testing:

//ingame debug console [] spawn { while {true} do { allMissionObjects ""; }; }; player addAction ["VM", "vm.sqf"]; player addAction ["nonVM", "nonvm.sqs"]; //vm.sqf _frameNo = diag_frameNo; _tickTime = diag_tickTime; for "_i" from 1 to 100 do { allMissionObjects ""; }; hint str [diag_frameNo - _frameNo, diag_tickTime - _tickTime]; //nonvm.sqs _frameNo = diag_frameNo _tickTime = diag_tickTime for "_i" from 1 to 100 do {allMissionObjects ""} hint str [diag_frameNo - _frameNo, diag_tickTime - _tickTime]

As you can see .sqs code is slightly different from .sqf, so I might as well explain the difference here. One thing that makes .sqs different is that each statement is 1 line. You cannot use ; to separate statements in the same line because ; is used for comments. Basically if you can fit your expression in one line great, if not, you have to start using .sqs commands like goto and then it gets tricky. There is also no sleep or waitUntil commands. Instead you use ~ or @. Remember if you explicitly add suspension to .sqs script like this, then it will no longer complete in one go. Might as well use normal .sqf if you are planning adding some waiting.

Also .sqs scripts have special variable _time, that seems to contain time, which indicates how long the .sqs script had to wait before it got its turn to run. Pretty sure this could be used for performance profiling. Anyway, for more differences between .sqs and .sqf have a look at this BIKI page.


EDIT: Well, as I was trying to write something complicated in .sqs I realised 2 things:

  1. It is not going to work for me, .sqs is just too limited
  2. You do not have to write in .sqs but can have the same “run beginning to end” behaviour as .sqs

So this is what I did. I wrote my function elsewhere in normal SQF and in .sqs file I put a single line:

_this call KK_sqfFunctionIWrote

What it does is it forces your SQF script behave like SQS script. Works like a charm and faster than SQS!