Jump to content
BEAKSBY

Guidelines on when to add sleep delays and how long?

Recommended Posts

Hi Folks,

I 've noticed while scripting the importance of adding some delays using sleeps.

For example if I do not include the sleep 0.5; below, the data in the array does not get passed to the funtion.

// Send reward to player
[[goldValue, _box], "DNA_fnc_addMoney", (side player)] spawn BIS_fnc_MP; 
sleep 0.5;
deleteVehicle _box;

Also, I need to add sleep delays to my while loops and after deleting an object if any further code follows it in the script.

Does anyone have some other helpful tips or guidelines on when/where to add sleep delays and how long they should be?

It's been mostly trial and error so far, but I don't want to over-delay something or forget to add a delay and risk having data lost...etc.

Share this post


Link to post
Share on other sites

It all depends on the functions being ran or the commands you use within a loop. Certain functions take time to process and looping them continuously with little time between each call will eat up performance. It is more efficient to go event-based for performance-heavy calls; calling only when needed.

Looping with no delay will flood the CPU with redundant instructions, eating up time that can be spent on other useful executions. For example, executing "hintSilent" infinitely fast is pointless due to the player not needing or seeing all the updates that take place sub-frame, due to their framerate and other factors. If you need something to update rapidly, take the client's framerate into account.

You can also use "waitUntil" to pause a script until a certain condition is met, this removes the uncertainty of a sleep duration as code execution time may vary beyond the expected sleep time. Also keep in mind the difference between "call" and "spawn". Calls will wait until the resulting function completes execution or returns a value before executing the next statement. Spawns run code asynchronously (similar to execVM or a new thread) and will execute the next statement in your script regardless if the spawned code has completed execution.

Regarding your code; let's say the server is lagging as a reward is being sent to players, the arguments are the amount of gold and the box object. Some players may not receive the packet in time and the _box object will be null as it has been deleted before they could get the packet containing the reward. This results in a possible script error (due to an undefined var - _obj), possibly bugging their money received, or even corrupting their own money variable, depending on your code. A possible solution is to reposition (teleport) the box away (perhaps to [0, 0, 0]) and delete it after a minute (which should be plenty of time for clients to receive the packet(s) and execute their code).

I recommend reading into Big-O so you can better understand the efficiency of algorithms based on the size of the input received: http://en.wikipedia.org/wiki/Big_O_notation

I hope this helps.

Edited by zooloo75

Share this post


Link to post
Share on other sites

Thanks for the advice and work=around solution!

I also read up on KK's blog and thought about using onEachFrame instead of the while loop. http://killzonekid.com/arma-scripting-tutorials-loops/2/

But I'm confused with "This command will spawn a script loop {} so that the rest of your code in the same thread as onEachFrame can carry on. The engine will also only ever spawn 1 instance of onEachFrame so any subsequent call of this command will overwrite previous call."

In my script, there are mutiple boxes being created and waiting for a condition to be met...but didn't think I could use onEachFrame for every box created.

here's the full script:

GoldCollect.sqf

// Get variables from GoldDrop.sqf
private ["_a", "_box", "_smoke", "_chemlight"];
_box = _this select 0;
_a = true;

while {_a} do {
sleep 1;
if ((player distance _box) < 10) exitWith {

	// Send reward to player
	[[goldValue, _box], "DNA_fnc_addMoney", (side player)] spawn BIS_fnc_MP; 
	sleep 0.5;

	// Delete smoke and light
	deleteVehicle _box;
	_a = false;
};
};

Share this post


Link to post
Share on other sites

I don't see the reason to use onEachFrame for that.

If possible, I'd use a trigger for that, but here's a scripted alternative including zooloo's suggestion:

_box = _this select 0;

waitUntil {sleep 1; (player distance _box < 10)};
_box setPosATL [0,0,0];
[[goldValue, _box], "DNA_fnc_addMoney", (side player)] spawn BIS_fnc_MP; 
sleep 60;
deleteVehicle _box;

Share this post


Link to post
Share on other sites

Yes, thanks cuel...

...since waitUntil runs on every frame anyways.

I can delete the sleep 1 as I don't want a player crashing into the box (if they're driving fast iside a vehicle) and over tax the CPU even if multiple boxes exist.

Share this post


Link to post
Share on other sites

It won't run on every frame if sleep is there. Sleep command suspends a thread.

Using sleep 1 would help with optimization but if you have a reasonable amount of boxes it won't be a problem

Share this post


Link to post
Share on other sites

I would also add that SQF is not truly multithreaded, meaning that there is only one engine thread processing SQF statements. It uses a scheduler to determine what executes when. It can also be interrupted to execute non-scheduled code, such as eventhandlers firing.

Sleep does not just tell the thread to pause, it tells the engine to move on and start executing from the next thread. The engine uses sleep to queue up when it should jump to the next thread. However, the more you suspend that thread, the more others will get time to execute. You must prioritize waits and loops to balance which are time sensitive and which are not.

Thus, if you have a waitUntil loop, which, without sleep, runs every frame (max dedicated server framerate is 50), the engine must stop and return to the thread with this loop every frame for a few milliseconds. If no other SQF threads are running, this is fine. You will not see a measurable performance impact for using the 'distance' command every frame.

You will see a performance impact if 10 threads want to execute 100 of lines of code every frame, because the engine maintains a .3ms delay between commands (in scheduled environments). 10 threads * 100 lines/thread * .3ms/line = .3 seconds; you would be better off executing each thread every few seconds. Due to this delay, performance is dependant upon framerate, but any improvement in performance from a high framerate is negated by running every frame.

Finally, you also don't want to use sleep frivolously. Many functions are better left to run all the way through, even if they execute thousands of lines. Making the engine jump from thread to thread using sleep in the name of optimization can just make things worse. Sometimes, optimization is not about using fewer resources and more about taking less time so the engine can move on.

Share this post


Link to post
Share on other sites

You will see a performance impact if 10 threads want to execute 100 of lines of code every frame, because the engine maintains a .3ms delay between commands (in scheduled environments). 10 threads * 100 lines/thread * .3ms/line = .3 seconds; you would be better off executing each thread every few seconds. Due to this delay, performance is dependant upon framerate, but any improvement in performance from a high framerate is negated by running every frame.

The 3ms delay is the maximum amount of time scheduled environment script can run before getting suspended until the next frame, not the delay between commands. :)

It was introduced to get rid of the ARMA1 script lag spikes

https://community.bistudio.com/wiki/Code_Optimisation#The_3ms_run_time

The 3ms run time

A scheduled script runs for exactly 3ms before it is put in suspension to be resumed on the next frame, again for another 3ms and so on until the script is finished. The amount of suspension depends on FPS. At 20 FPS the duration of suspension for example is 50ms.

This means that if scheduled script cannot be completed under 3ms, the execution can stretch for undefined amount of time, subject to engine load, FPS and other non scheduled scripts running at the same time. A while true loop with sleep started in scheduled environment therefore has little chance to follow with exact interval.

Scheduled scripts always start with slight delay, subject to engine load.

http://killzonekid.com/arma-scripting-tutorials-code-performance/

Share this post


Link to post
Share on other sites
The 3ms delay is the maximum amount of time scheduled environment script can run before getting suspended until the next frame, not the delay between commands. :)

It was introduced to get rid of the ARMA1 script lag spikes

https://community.bistudio.com/wiki/Code_Optimisation#The_3ms_run_time

http://killzonekid.com/arma-scripting-tutorials-code-performance/

Thank you for correcting me, as I was still thinking with old information. I am very certain that the wiki page you linked actually used to say .3ms per command; however, I see this was a misconception that arose from benchmarking frames and engine time. It seems possible that in ArmA 2 with older hardware that some scripts executed 10 times per frame, thus the .3ms myth.

The last part of KK's blog that you linked shows why trying to reduce the delay to Xms/command does not work. In those 3ms, his experiment code executed an average of 205 times per frame, giving .015ms/command. This is not a fixed number, and is actually Xms/command/thread. So increasing the number of lines/frame that execute in 3ms is the determining factor in how many frames a script takes to run.

Now the question: how many threads can reasonably run every frame? At 60 frames/s => 16.7 ms/frame => 5.56 threads/frame at 3 ms/thread. I assume anything more than that can delay the scheduler. Applying this to waitUntil loops, you could have 5 waitUntil loops taking the full 3ms allowed without any performance impact. Any threads that take less than 3ms subtract linearly from the 16.7 ms/frame, so you could have dozens of threads execute a few commands every frame. Also, every thread can expect to be suspended for 16.7 ms.

All of this is apparently independent of the length or complexity of those threads. That only matters for how long a thread is running. Very interesting, and it's all part of the fun of SQF and ArmA.

Share this post


Link to post
Share on other sites

Yeah, I'd say it's very related to what commands that are actually being executed (using nearObjects on the entire map as an example)

Share this post


Link to post
Share on other sites
Yeah, I'd say it's very related to what commands that are actually being executed (using nearObjects on the entire map as an example)

I agree that that would slow down the thread a lot, but the engine would still have to move on to the next thread in 3ms. Are you saying that something like that will make the engine break the 3ms rule? How would it interfere with other threads other than taking the full 3ms its thread is allowed?

It's true commands like that require more processing and thus have a performance impact. If one thread reduces the framerate the engine can calculate/render at then all threads slow down. However, a loss of 5 or even 10 fps is not that much out of 50 fps or so. Some commands seem to freeze the engine until they can be processed though, e.g. simulWeatherSync. I assume this is done because they need more than 3ms?

Anyway, isn't most of this theory based upon experiments using lots of simple commands, e.g. KK's tests. The execution time of one small command e.g. 'distance' is too short for the accuracy of diag_tickTime to measure. What exactly is the difference when we using a scripting command that is essentially a very complex engine-based function. Do the results of using nearObjects with a radius of 10,000 meters still support the 3ms rule?

Share this post


Link to post
Share on other sites

Thanks again folks,

My original post regarding while loops is part of a larger problem I'm having with Code Optimization. My game is nearing completion, but I'm experiencing issues in MP in terms late data packets transfers. I'm going through all my scripts now and applying https://community.bistudio.com/wiki/Code_Optimisation, https://community.bistudio.com/wiki/Multiplayer_framework and other similar links as well as all your input, suggestions and advice.

3 more questions;

1) How and can I recompile if I change a funcion or init file, from within a started game operating in LAN?

2) Is there much of difference in performance if I combine scripts into one large function or break them up and chain them together (where appropriate) with the call function?

3) Does converting scripts into functions and using call compile preprocessFileLineNumbers "filename.sqf" have the same effect in terms of performance as configuring a funtion within CfgFunctions class? I assume the latter is compiled in the same manner as the former and therefore should not have any significant advantages?

thank-you

Edited by BEAKSBY

Share this post


Link to post
Share on other sites
Thanks again folks,

My original post regarding while loops is part of a larger problem I'm having with Code Optimization. My game is nearing completion, but I'm experiencing issues in MP in terms late data packets transfers. I'm going through all my scripts now and applying https://community.bistudio.com/wiki/Code_Optimisation, https://community.bistudio.com/wiki/...ayer_framework and other similar links as well as all your input, suggestions and advice.

3 more questions;

1) How and can I recompile if I change a funcion or init file, from within a started game operating in LAN?

2) Is there much of difference in performance if I combine scripts into one large function or break them up and chain them together (where appropriate) with the call function?

3) Does converting scripts into functions and using call compile preprocessFileLineNumbers "filename.sqf" have the same effect in terms of performance as configuring a funtion within CfgFunctions class? I assume the latter is compiled in the same manner as the former and therefore should not have any significant advantages?

thank-you

1. Use compile instead of compileFinal, e.g.:

SomeFunction = compileFinal preprocessFileLineNumbers "SomeFunction.sqf";
// or
SomeFunction = compile preprocessFileLineNumbers "SomeFunction.sqf";

You can't change the init.sqf during a mission because it is hard-coded and preprocessed and compiled once at mission start. You can run it again though:

https://community.bistudio.com/wiki/runInitScript

You can also just use a proxy init function, e.g. Init2.sqf, that is not compiled finally.

2. This depends, if the functions are just SQF commands that used to be all together, then the only overhead to using call is that engine has to define a lower scope. However, if all those functions check arguments, print debug, etc. something that was previously done once, the time can add up if they are called a lot.

For what that lower scope overhead is, try this:

f_append = {
   _array set [_i, _i];
};

_array = [];
_sTime = diag_tickTime;
for "_i" from 0 to 10000 do {
   _array set [_i, _i];
   // call f_append;
};
_eTime = diag_tickTime;
player sidechat str (_eTime - _sTime);

I get 66.8ms seconds with just 'set', and 127.ms with the function. Also note the function uses the variables directly; it would take more time if arguments were passed. However, the percent increase of the overhead time will decrease as more commands are added. You probably won't see a significant difference if you call a 100 line function vs. run the code directly.

3. I assume so...unless the engine has some internal methods that are not in the SQF API. However, I recall that the wiki claims that cfgFunctions are compiled with compileFinal. Somewhere here:

https://community.bistudio.com/wiki/Functions_Library_%28Arma_3%29

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×