You might have used it already, but Arma 3 has this pretty neat library function BIS_fnc_codePerformance designed by Karel Moricky. And since in Arma you can do the same thing in many different ways, using this function to choose the best way based on code performance is essential. Today I’d like to talk a bit about scheduled and non scheduled environment in Arma.

Remember when you were a kid and you were playing your favourite video game and your mum told you to go do something and you were like “leave me alone” like you had a choice? This is how it works in Arma. Non scheduled environment is just that: script calls that need immediate attention, such as event handler calls. What good is event handler if you get notification of an event sometime after it happened and not immediately when it happens? At this point Arma will suspend its regular scheduled execution and give priority to those non scheduled events.

This is also the reason why you cannot use sleep or uiSleep or waitUntil commands in non scheduled environment. These scripts have to complete as fast as possible to let Arma return back to scheduled environment. This is also the reason any while loop in non scheduled environment will exit after 10,000 iterations. Using sleep command in a script is a good way to test what environment the script is running, in case you don’t know how it was originated. Non scheduled environment will throw an error. How can you not know? With many scripts calling scripts calling scripts it is easy to lose track of the origin. If you call a script from scheduled environment, called script will also run in scheduled environment and so on. With non scheduled environment all scripts will be non scheduled.

Non scheduled environments: CfgFunctions with preInit = 1, all event handler scripts including object init fields, onXXXXXX events (onEachFrame, onMapSingleClick etc.), Arma 3 debug console exec, FSM scripts. Scheduled environments: CfgFunctions with postInit = 1, various event scripts such as initPlayerLocal.sqfinitPlayerServer.sqf etc., and of course the init.sqf. You can start scheduled environment inside non scheduled by using spawn or execVM commands. Because none of them is expected to come back to the script they are executed from, the engine doesn’t wait. If you use call command, then the engine has no choice but to wait for its return. So be wise choosing which command to use when.

The scheduled environment does not guarantee the order or priority of execution of queued scripts. The engine will simply do it when it is feasible and possible. If you have too many non scheduled calls and they also take time to complete, your scheduled scripts can be delayed significantly, we are talking minutes! For the same reason non critical scripts should be run in scheduled environment so that a) they do not get on the way of urgent executions, b) give engine more flexibility for rescheduling when needed.

Having said that, scheduled environment, despite its name, is pretty unpredictable. If you spawn 100 scripts, it is not guaranteed they will execute in the same order they where spawned. Also you cannot predict when they finish. You can however check IF they finished with scriptDone command. And if you have many unoptimised scripts running, especially of a 3rd party addons, and you have no control over what is going on, it is very likely there might be some issues. How to start a non scheduled script from scheduled environment? You can execFSM a FSM script that simply calls a function. In fact I’ve just made one while writing this: call.fsm.

Usage:

[params, function] execFSM “call.fsm”; where params is whatever you pass to your function and function is function variable or code.

Examples:

["hello", {hint _this}] execFSM "call.fsm";
KK_fnc_hello = {hint _this}; ["hello again", KK_fnc_hello] execFSM "call.fsm";

Also I would like to give you some food for thought. I’ve seen some people blindly claiming that scheduled environment adds 0.3 ms to each line of code or something stupid like that. Now this is the reason I started this post mentioning BIS_fnc_codePerformance, do not be afraid to test it yourself. Here are the results of my experiments:

[" a = 10; "] call BIS_fnc_codePerformance; //0.0021ms
[" a = 10; "] spawn BIS_fnc_codePerformance; //0.016ms

In this particular example non scheduled call is almost 7 times faster. But what about if you make it 10 lines of code instead of 1?

[" a = 10; a = 10; a = 10; a = 10; a = 10; a = 10; a = 10; a = 10; a = 10; a = 10; "] call BIS_fnc_codePerformance; //0.013ms
[" a = 10; a = 10; a = 10; a = 10; a = 10; a = 10; a = 10; a = 10; a = 10; a = 10; "] spawn BIS_fnc_codePerformance; //0.1ms

Still about 7 times the difference.

Enjoy,
KK

EDIT: Deathlyrage posted interesting comment below about scheduled execution being fixed to a certain duration. I have tested this and it is indeed the case. A scheduled script will run for exactly 3 milliseconds (3ms) before it gets suspended and then resumed on the next frame, again for another 3 milliseconds and so on. The suspension period can vary. In my 1st test when I tried to measure it with loop, it was 14ms. I then constructed a straight script with 1000 identical lines, the duration of suspension was 50ms. I assume the amount of suspension depends on how busy the script engine is which would manifest itself in FPS rate. To have 50ms suspension my client must have been running at 20 FPS at the time.

Here is the result file with frame number and time next to it for you to examine: frameno_diagticktime.txt

The implications of this is that when your system is very busy your scheduled scripts can and might stop half way and if your system doesn’t recover, some scripts may not even finish.

EDIT2: I’ve redone the test this time logging client FPS too. 20 FPS was indeed the correct assumption: frameno_diagticktime_diagfps.txt