Jump to content
Leopard20

Code Optimization: onEachFrame vs WaitUntil vs Draw3D

Recommended Posts

Hello everyone.

 

I have a script that I need to run on each frame. The problem is, it can get too heavy sometimes (it runs some code for each element of an array, and if the array gets too big, the code gets slow), enough to reach 1 ms or longer execution time in code performance. I think I read somewhere that codes that run in scheduled environment (such as those called by waitUntil) have a time limit of 3 ms before being stopped by the engine.

What is the best way of balancing performance and making sure the code is executed correctly?

 

1. Using onEachFrame stacked EH

2. Using Draw3d mission EH

3. Using waitUntil (without sleep)

 

I think the first two methods won't get interrupted if the execution time exceeds 3 ms. But it might also mean that the code will slow down the mission even more.

 

I have tested all three methods but they all appear more or less the same.

 

I'd appreciate any help with this.

Share this post


Link to post
Share on other sites

First of all, are you sure your code needs to run on each frame? We don't know what you're speaking about. Neither the code, nor the needed frequency and the locality of your code.

  • Like 1

Share this post


Link to post
Share on other sites
14 minutes ago, pierremgi said:

First of all, are you sure your code needs to run on each frame? We don't know what you're speaking about. Neither the code, nor the needed frequency and the locality of your code.

It's designed for SP. So locality is irrelevant.

 

The code executes some animation for the AI, including rotation, which if not executed on each frame could result in jittery animations; so yes, it needs to be run on each frame.

 

The animation code is not heavy per se (I optimized it as much as possible), it only gets heavy when there are, say 20+ units in the array. This may not happen so many times during a mission, but I'd like to consider the extreme cases as well.

Share this post


Link to post
Share on other sites
Just now, Leopard20 said:

It's designed for SP. So locality is irrelevant.

 

The code executes some animation for the AI, including rotation, which if not executed on each frame could result in jittery animations; so yes, it needs to be run on each frame.

 

Bof, Are you sure you explored all the possibilities like the EH family for anim?

Or,

You can on each frame a simple rotation, but you're you sure to execute that in the right order (see 1st note here) ?

Difficult to answer you on vague information.

  • Like 1

Share this post


Link to post
Share on other sites
19 minutes ago, pierremgi said:

 

Bof, Are you sure you explored all the possibilities like the EH family for anim?

Or,

You can on each frame a simple rotation, but you're you sure to execute that in the right order (see 1st note here) ?

Difficult to answer you on vague information.

Well, the animations are some basic drag, carry and move for the AI. If you've worked with AI before, you should know that performing a continuous anim with AI requires playMoveNow to be executed at succession (not necessarily on each frame, but breaking down the loop into two loops ,one for rotation and one for anim, doesn't seem optimal). Doing the anim with animChanged EH, for example, is possible, but the animation won't be continuous.

Edit: On second thought, using the EH may not be reliable, because I use disableAI "ANIM" and the AI may not change anim at all to trigger the EH (if I don't use disableAI "ANIM", they "refuse" to change animation)

 

All codes are stacked into one EH (or waitUntil), of course. Like this:

 

waitUntil {

 {

 } forEach someUnits;

 false

}

Share this post


Link to post
Share on other sites

1. depends on the complexity of the condition(s)

2. design of the condition(s)

3. complexity of the code you are to execute

 

but yeah use EH/event based execution when possible

  • Like 4

Share this post


Link to post
Share on other sites

Even a 1ms execution time should be fine if nothing much else is happening, since at 60fps one single frame runs for 16.67ms.

Without any examples hard to tell, simple animationstate checks and playmove should be fine for quite some units as long as it's a single eachFrame EH handling this, and not individual eachFrame EHs per unit.

 

Animation related eventhandlers might be the better bet though.

 

Cheers

  • Like 2

Share this post


Link to post
Share on other sites
7 hours ago, Leopard20 said:

2. Using Draw3d mission EH

Only use Draw3D if you actually wanna draw anything or have any other reason to do something after simulation cycle but before rendering.

7 hours ago, Leopard20 said:

I think the first two methods won't get interrupted if the execution time exceeds 3 ms.

correct.

 

 

6 hours ago, Leopard20 said:

it only gets heavy when there are, say 20+ units in the array.

20 is not much.

 

You could split the work to only process up to 20 units per frame, but as you said that would introduce jitter.
Can you show me the addMissionEventhandler line for your EachFrame handler? Just the one line.

You could also show us your code to see if there are more opportunities for optimization.

But if you really need it to run every frame, then there isn't really anything you can do besides running it every frame.

  • Thanks 1

Share this post


Link to post
Share on other sites
4 hours ago, Dedmen said:

Only use Draw3D if you actually wanna draw anything or have any other reason to do something after simulation cycle but before rendering.

correct.

 

 

20 is not much.

 

You could split the work to only process up to 20 units per frame, but as you said that would introduce jitter.
Can you show me the addMissionEventhandler line for your EachFrame handler? Just the one line.

You could also show us your code to see if there are more opportunities for optimization.

But if you really need it to run every frame, then there isn't really anything you can do besides running it every frame.


Alright.

 

I use one of the following loops:

Spoiler

 

1.


["RS_animHandler", "onEachFrame", {
	[] call RS_fnc_animHandler;
}] call BIS_fnc_addStackedEventHandler;

 

2.


addMissionEventHandler ["Draw3D", 
{
	[] call RS_fnc_animHandler;
}];

3.


[] spawn {
	waitUntil {
		[] call RS_fnc_animHandler;
		false
	};
};

 

 

 


Here's the animation function (RS_fnc_animHandler):

Spoiler

 


{
	_animation = _x getVariable ["RS_animation", [[], [], [], []]];
	_animation params ["_positions", "_finalDirection", ["_customCondition", []], ["_customAnimation", []]]; //_positions: positions to pass through; 
	_condFail = (vehicle _x != _x);
	_condSuccess = true;
	_codeFail = {};
	_codeSuccess = {};
	_params = [];
	if !(_customCondition isEqualTo []) then {
		_params = _customCondition select 4;
		_condFail = _params call (_customCondition select 0);
		_codeFail = _customCondition select 1;
		_condSuccess = _params call (_customCondition select 2);
		_codeSuccess = _customCondition select 3;
	};
	if (_condFail) then {
		_params call _codeFail;
		RS_animatedUnits = RS_animatedUnits - [_x];
	} else {
		_x disableAI "MOVE";
		_x disableAI "ANIM";
		if !(_positions isEqualTo []) then {
			_currentPos = getPosASLVisual _x;
			_nextPos = _positions select 0;
			if (_currentPos distance2D _nextPos < 2) then { //_unit has reached the position
				((_x getVariable ["RS_animation", [[], []]]) select 0) deleteAt 0;
			} else {
				_dir = 1; //used only for backward move, such as dragging
				if (_customAnimation isEqualTo []) then {
					_speed = "runsras";
					_stance = if (stance _x == "STAND") then {"erc"} else {
						if (stance _x == "CROUCH") then {"knl"} else {_speed = "sprslow"; "pne"};
					};
					_wpn = if (currentWeapon _x == handgunWeapon _x) then {"pst"} else {"rfl"};
					_anim = format ["amovp%1m%2w%3df", _stance, _speed, _wpn];
					_x playMoveNow _anim;
				} else {
					_customAnimation params ["_anim", "_reverse"];
					_dir = _reverse;
					_x playMoveNow _anim;
				};
				
				//this part rotates the unit
				_watchDir = _nextPos vectorDiff _currentPos;
				_watchDir set [2,0];
				_watchDir = (vectorNormalized _watchDir) apply {_x*_dir};
				if (_watchDir isEqualTo [0,0,0]) then {_watchDir = [0,1,0]};
				_vecDir = vectorDir _x;
				_angle = acos(_vecDir vectorCos _watchDir);
				_turn = 1;
				if (_angle > 1) then {
					_vecDirX = _vecDir select 0;
					_vecDirY = _vecDir select 1;
					_watchDirX = _watchDir select 0;
					_watchDirY = _watchDir select 1;
					if (_watchDirY*_vecDirY >= 0) then {
						if (_watchDirX >= _vecDirX) then {_turn = -1} else {_turn = 1};
					} else {
						if (_watchDirX*_vecDirX >= 0) then {if(_vecDirX >= 0) then {_turn = -1} else {_turn = 1}} else {
							if (abs(_watchDirX)<=abs(_vecDirX)) then {_turn = -1} else {_turn = 1};
							if(_vecDirX < 0) then {_turn =-1*_turn};
						};
					};
					if (_vecDirY < 0) then {_turn =-1*_turn};
					_rotate = if (_angle > 5) then {4} else {_angle};
					_newDir = [_rotate*_turn, _vecDir] call RS_fnc_vectorRotation;
					_x setVectorDir _newDir;
				};
			};
		} else {
			if (_customAnimation isEqualTo []) then {
				if !(_condSuccess) exitWith {};
				if (_finalDirection isEqualTo []) exitWith {
					
					//_x setVariable ["RS_animation", [[], [], _enableAfterFinish]];
					RS_animatedUnits = RS_animatedUnits - [_x];
				};
				if (_finalDirection isEqualTo [0,0,0]) then {_finalDirection = [0,1,0]};
				_vecDir = vectorDir _x;
				_angle = acos(_vecDir vectorCos _finalDirection);
				
				if (_angle > 1) then {
					_turn = 1;
					_vecDirX = _vecDir select 0;
					_vecDirY = _vecDir select 1;
					_watchDirX = _finalDirection select 0;
					_watchDirY = _finalDirection select 1;
					if (_watchDirY*_vecDirY >= 0) then {
						if (_watchDirX >= _vecDirX) then {_turn = -1} else {_turn = 1};
					} else {
						if (_watchDirX*_vecDirX >= 0) then {if(_vecDirX >= 0) then {_turn = -1} else {_turn = 1}} else {
							if (abs(_watchDirX)<=abs(_vecDirX)) then {_turn = -1} else {_turn = 1};
							if(_vecDirX < 0) then {_turn =-1*_turn};
						};
					};
					if (_vecDirY < 0) then {_turn =-1*_turn};
					_rotate = if (_angle > 5) then {5} else {1};
					_newDir = [_rotate*_turn, _vecDir] call RS_fnc_vectorRotation;
					_x setVectorDir _newDir;
				} else {
					if (_condSuccess) then {
						RS_animatedUnits = RS_animatedUnits - [_x];
					};
				};
			} else {
				if (_condSuccess) exitWith {
					_params call _codeSuccess;
					RS_animatedUnits = RS_animatedUnits - [_x];
				};
				_anim = _customAnimation select 0;
				_x playMoveNow _anim;
			};
		};
	};
} forEach RS_animatedUnits;

RS_fnc_vectorRotation (used in above code):


params ["_theta", "_dirMatrix"];
private ["_result", "_row", "_add", "_rotMatrix"];
_rotMatrix = [[cos(_theta), -1*sin(_theta), 0], [sin(_theta), cos(_theta), 0], [0,0,1]];
_result = [];
for "_i" from 0 to 2 do 
{
	_row = _rotMatrix select _i;
	_add = 0;
	for "_j" from 0 to 2 do
	{
		_add = _add + (_row select _j)*(_dirMatrix select _j);
	};
	_result pushBack _add;
};
_result

 

 

 


The anim function should be run in an on each frame loop.
 

The anim function is used for 4 purposes:

1. Simplest: To rotate a unit. Let's call him _unit;
To test this, run this code (make sure the anim function is running in a loop first!)

Spoiler

 


RS_animatedUnits = [];
_dir = (getPosASL _unit) vectorFromTo (getPosASL player);
_unit setVariable ["RS_animation", [[], _dir, [], []]];
 RS_animatedUnits pushBackUnique _unit;

 

 

 

The unit will turn and face the player.

 

 

 

2. Move a unit using a fake anim.To test this, run this code:

Spoiler

 


RS_animatedUnits = [];
_pos = getPosASL _unit;
_positions = [(_pos vectorAdd [0,10,0]), (_pos vectorAdd [10,10,0]), (_pos vectorAdd [10,0,0]), (_pos vectorAdd [-20,0,0])];
_unit setVariable ["RS_animation", [_positions, [], [], []]];
 RS_animatedUnits pushBackUnique _unit;

 

 

 

 

3. Drag (or carry) animations. This is the most complicated and also the most demanding, which also needs _condFail, _condSuccess, etc to be checked at each frame.To test this, synchronize some unit to the original unit (just to make sure it drags the correct unit, not necessary in my original code). Then run this code:

Spoiler

 


_target = (synchronizedObjects _unit) select 0;
(synchronizedObjects _unit) select 0;
_positions = [(_pos vectorAdd [0,10,0]), (_pos vectorAdd [10,10,0]), (_pos vectorAdd [10,0,0]), (_pos vectorAdd [-20,0,0])];
_lastPos = _positions select (count(_positions) - 1);
_mode = selectRandom [1,2];
_target setUnconsious true;
if (_mode == 2) then {
	_cond1 = {
		params ["_unit", "_target"];
		if (_target distance2D _unit > 5) exitWith {true};
		_animS = animationState _unit;
		_target disableAI "MOVE";
		_target disableAI "ANIM";
		_target playMoveNow "AinjPpneMstpSnonWrflDb";
		if (_animS == "amovpercmstpslowwrfldnon_acinpknlmwlkslowwrfldb_2") exitWith {false};
		_fail = ((lifeState _unit == "INCAPACITATED") || (lifeState _target != "INCAPACITATED") || {_animS select [0,5] != "AcinP"});
		_fail
	};
	_code1 = {
		params ["_unit", "_target"];
		detach _target;
		_target disableAI "MOVE";
		_target disableAI "ANIM";
		_target switchMove "AinjPpneMstpSnonWrflDb_Death";
		_target playMoveNow "unconsciousReviveDefault";
		if (lifeState _unit != "INCAPACITATED") then {_unit playMoveNow "AcinPknlMstpSrasWrflDnon_AmovPknlMstpSrasWrflDnon"} else {_unit playMoveNow "unconsciousReviveDefault"};
	};
	_code2 = {
		params ["_unit", "_target", "_lastPos"];
		_unit setVariable ["RS_inCover", true];
		detach _target;
		_target disableAI "MOVE";
		_target disableAI "ANIM";
		_target switchMove "AinjPpneMstpSnonWrflDb_Death";
		_target playMoveNow "unconsciousReviveDefault";
		if (lifeState _unit != "INCAPACITATED") then {_unit playMoveNow "AcinPknlMstpSrasWrflDnon_AmovPknlMstpSrasWrflDnon"} else {_unit playMoveNow "unconsciousReviveDefault"};
		_unit setPosASL _lastPos;
		_target setPosASL _lastPos;
	};
	_unit setVariable ["RS_animation", [_positions, [], [_cond1, _code1, {true}, _code2, [_unit, _target, _lastPos]], ["AcinPknlMwlkSrasWrflDb", -1]]]; 
	_target switchMove "AinjPpneMstpSnonWrflDb";
	_unit switchMove "amovpercmstpslowwrfldnon_acinpknlmwlkslowwrfldb_2";
	_unit playMoveNow "AcinPknlMwlkSrasWrflDb";
	_target attachTo [_unit, [0,0.8,0]];
	_target setDir 180;
	RS_animatedUnits pushBack _unit;
} else {
	_target setAnimSpeedCoef 2.3;
	_unit setAnimSpeedCoef 2;
	_cond1 = {
		params ["_unit", "_target"];
		if (_target distance2D _unit > 5) exitWith {true};
		_animS = animationState _unit;
		_target disableAI "MOVE";
		_target disableAI "ANIM";
		_target playMoveNow "AinjPfalMstpSnonWrflDnon_carried_Up";
		if (_animS == "acinpknlmstpsraswrfldnon_acinpercmrunsraswrfldnon") exitWith {false};
		_unit setAnimSpeedCoef 1;
		_target setAnimSpeedCoef 1;
		//if (animationState _target == "ainjpfalmstpsnonwrfldnon_carried_still" && {abs((getDir _target) - (getDir _unit)) > 5}) then {_target setDir 0; _target attachTo [_unit, [0,0,0]]};
		_fail = ((lifeState _unit == "INCAPACITATED") || (lifeState _target != "INCAPACITATED") || {_animS select [0,5] != "AcinP"});
		_fail
	};
	_code1 = {
		params ["_unit", "_target"];
		detach _target;
		_target disableAI "MOVE";
		_target disableAI "ANIM";
		_target playMoveNow "unconsciousReviveDefault";
		_target setAnimSpeedCoef 1;
		_unit setAnimSpeedCoef 1;
		if (lifeState _unit != "INCAPACITATED") then {_unit switchMove ""} else {_unit playMoveNow "unconsciousReviveDefault"};
	};
	_code2 = {
		params ["_unit", "_target", "_lastPos"];
		_unit setVariable ["RS_inCover", true];
		detach _target;
		_target disableAI "MOVE";
		_target disableAI "ANIM";
		_target setAnimSpeedCoef 1;
		_unit setAnimSpeedCoef 1;
		_target playMoveNow "unconsciousReviveDefault";
		if (lifeState _unit != "INCAPACITATED") then {_unit playMoveNow "AcinPercMrunSrasWrflDf_AmovPercMstpSlowWrflDnon"} else {_unit playMoveNow "unconsciousReviveDefault"};
		_unit setPosASL _lastPos;
		_target setPosASL _lastPos;
	};
	_unit setVariable ["RS_animation", [_positions, [], [_cond1, _code1, {true}, _code2, [_unit, _target, _lastPos]], ["acinpercmrunsraswrfldf", 1]]]; 
	_target switchMove "AinjPfalMstpSnonWrflDnon_carried_Up";
	_target playMoveNow "AinjPfalMstpSnonWrflDnon_carried_Up";
	_unit switchMove "AcinPknlMstpSrasWrflDnon_AcinPercMrunSrasWrflDnon";
	_unit playMoveNow "AcinPknlMstpSrasWrflDnon_AcinPercMrunSrasWrflDnon";
	RS_animatedUnits pushBack _unit;
	_target attachTo [_unit, [0.5,0,0]];
	_target setDir 180;
};

 

 

 

4. Drag and carry action for player. It simply makes AI freeze and constantly play the animation. Kind of similar to the above code.

 

Some questions you probably have regarding this code:

1. Why do I use disableAI "X" on each frame?
Due to some engine bug, when you use disableAI "ANIM", the unit's position doesn't get synchronized correctly at low FPS, and the unit (both the medic and the wounded unit) will get teleported to the original position after finishing their move.

2. Wouldn't it be less demanding to use setDir istead of setVectorDir to rotate the unit?
In terms of performance, it seems better. However, the animation will become jittery for some reason. You can test this to make sure!RS_fnc_animHandler:

Spoiler

 


{
	//private ["_condFail", "_codeFail", "_condSuccess", "_codeSuccess"];
	_animation = _x getVariable ["RS_animation", [[], [], [], []]];
	_animation params ["_positions", "_finalDirection", ["_customCondition", []], ["_customAnimation", []]];
	_condFail = (vehicle _x != _x);
	_condSuccess = true;
	_codeFail = {};
	_codeSuccess = {};
	_params = [];
	if !(_customCondition isEqualTo []) then {
		_params = _customCondition select 4;
		_condFail = _params call (_customCondition select 0);
		_codeFail = _customCondition select 1;
		_condSuccess = _params call (_customCondition select 2);
		_codeSuccess = _customCondition select 3;
	};
	if (_condFail) then {
		_params call _codeFail;
		RS_animatedUnits = RS_animatedUnits - [_x];
	} else {
		_x disableAI "MOVE";
		_x disableAI "ANIM";
		if !(_positions isEqualTo []) then {
			//_currentPos = getPosASLVisual _x;
			_nextPos = _positions select 0;
			if (_x distance2D _nextPos < 2) then {
				((_x getVariable ["RS_animation", [[], []]]) select 0) deleteAt 0;
			} else {
				_dir = 1;
				if (_customAnimation isEqualTo []) then {
					_speed = "runsras";
					_stance = if (stance _x == "STAND") then {"erc"} else {
						if (stance _x == "CROUCH") then {"knl"} else {_speed = "sprslow"; "pne"};
					};
					_wpn = if (currentWeapon _x == handgunWeapon _x) then {"pst"} else {"rfl"};
					_anim = format ["amovp%1m%2w%3df", _stance, _speed, _wpn];
					_x playMoveNow _anim;
				} else {
					_customAnimation params ["_anim", "_reverse"];
					_dir = _reverse;
					_x playMoveNow _anim;
				};
				_watchDir = _x getRelDir _nextPos;
				if (_watchDir > 1) then {
					_turn = -1;
					if (_watchDir <= 180) then {
						_turn = 1;
					};
					_angle = [5,_watchDir] select (_watchDir < 5 || _watchDir > 355);
					_x setDir ((getDir _x)+_turn*_angle)
				};				
			};
		} else {
			if (_customAnimation isEqualTo []) then {
				if !(_condSuccess) exitWith {};
				if (_finalDirection isEqualTo []) exitWith {
					RS_animatedUnits = RS_animatedUnits - [_x];
				};
				_watchDir = _x getRelDir _finalDirection;
				if (_watchDir > 1) then {
					_turn = -1;
					if (_watchDir <= 180) then {
						_turn = 1;
					};
					_angle = [5,_watchDir] select (_watchDir < 5 || _watchDir > 355);
					_x setDir ((getDir _x)+_turn*_angle)
				} else {
					if (_condSuccess) then {	//_x setVariable ["RS_animation", [[], [], _enableAfterFinish]];
						RS_animatedUnits = RS_animatedUnits - [_x];
					};
				};
			} else {
				if (_condSuccess) exitWith {
					_params call _codeSuccess;
					RS_animatedUnits = RS_animatedUnits - [_x];
				};
				_anim = _customAnimation select 0;
				_x playMoveNow _anim;
			};
		};
	};
} forEach RS_animatedUnits;

RS_animatedUnits = [];
_unit setVariable ["RS_animation", [[], (getPosASL player), [], []]];
 RS_animatedUnits pushBackUnique _unit;

 

 

 

Share this post


Link to post
Share on other sites

Oof so much script :o.
Please spoiler tags.

 

34 minutes ago, Leopard20 said:

RS_fnc_vectorRotation

https://github.com/CBATeam/CBA_A3/blob/master/addons/vectors/fnc_vectRotate3D.sqf

 

34 minutes ago, Leopard20 said:

Wouldn't it be less demanding to use setDir istead of setVectorDir to rotate the unit?

no

 

Please take a look a the private keyword. It can make your script faster because the engine doesn't have to look that far for where your local variables are.

 

 

34 minutes ago, Leopard20 said:

RS_animatedUnits = RS_animatedUnits - [_x];

You might be able to use deleteAt with _forEachIndex here.
But keep in mind, if you delete the current element, then your forEach loop will skip the next element inside your array.
Better would be to collect all units that you want to remove, and then remove them all at once after your forEach loop ran through.

 

34 minutes ago, Leopard20 said:

_vecDirX = _vecDir select 0;

_vecDirY = _vecDir select 1;

_watchDirX = _watchDir select 0;

_watchDirY = _watchDir select 1;

Can use params here

_vecDir params ["_vecDirX", "_vecDirY"]

 

Moderator: I've deleted your second post because you double posted 😉 Also you still have fnc_animHandler in there twice.

  • Thanks 1

Share this post


Link to post
Share on other sites
8 minutes ago, Dedmen said:

Please take a look a the private keyword. It can make your script faster because the engine doesn't have to look that far for where your local variables are. 

You mean like declaring the private variables before the loop? (private ["_unit", etc])

Share this post


Link to post
Share on other sites
1 minute ago, Leopard20 said:

You mean like declaring the private variables before the loop? (private ["_unit", etc])

No, that's the private command, that's bad for performance.
The private keyword is used when you first declare a variable.

private _variable = value;

It has the same effect as if you use the private command, but performance wise the keyword is free, while the command is still a call to a scriptcommand.

Share this post


Link to post
Share on other sites
2 minutes ago, Dedmen said:

No, that's the private command, that's bad for performance.
The private keyword is used when you first declare a variable.

 


private _variable = value;

It has the same effect as if you use the private command, but performance wise the keyword is free, while the command is still a call to a scriptcommand.

But according to this article    using private inside a foreach loop is worse than using private ["_var"] before a loop.

Share this post


Link to post
Share on other sites
4 minutes ago, Leopard20 said:

But according to this article    using private inside a foreach loop is worse than using private ["_var"] before a loop.

Yes because that for loop is so short and iterates fast it matters there, yours is neither fast nor short, so the impact is FAR less. 0.004ms per unit, you have bigger problems to worry about.
But yeah if you want you can use private array once, Problem with that is it's not free (which doesn't matter much if you call once) and you loose the readability bonus of seeing where your variables are first defined.

  • Like 2

Share this post


Link to post
Share on other sites
3 minutes ago, Dedmen said:

Yes because that for loop is so short and iterates fast it matters there, yours is neither fast nor short, so the impact is FAR less. 0.004ms per unit, you have bigger problems to worry about.
But yeah if you want you can use private array once, Problem with that is it's not free (which doesn't matter much if you call once) and you loose the readability bonus of seeing where your variables are first defined.

Do you have any ideas to make it faster?! I personally think it's already optimized enough! 😉

Share this post


Link to post
Share on other sites
6 minutes ago, Leopard20 said:

Do you have any ideas to make it faster?! I personally think it's already optimized enough! 😉

Read my above posts ^^
Mostly minor things I guess, but maybe enough. I can't see anything huge to optimize.

Share this post


Link to post
Share on other sites
36 minutes ago, Dedmen said:

You might be able to use deleteAt with _forEachIndex here.
But keep in mind, if you delete the current element, then your forEach loop will skip the next element inside your array.
Better would be to collect all units that you want to remove, and then remove them all at once after your forEach loop ran through.

Actually, I tried both of these before.

 

Using deleteAt, as you said, caused the forEach to skip the next element.

 

I also tried defining an array _toDel and using pushback to put _foreachindices inside it to delete later (using deleteAt). But the code performance was worse by ~10%.

Share this post


Link to post
Share on other sites
2 minutes ago, Leopard20 said:

I also tried defining an array _toDel and using pushback to put _foreachindices inside it to delete later (using deleteAt).

Wouldn't have worked anyway. When you delete the first index, the next indicies would move around and when you remove the next you'd remove the wrong one.

 

2 minutes ago, Leopard20 said:

But the code performance was worse by ~10%.

Don't understand why pushing a number to an array is more expensive than copying and iterating a bigger array.

Share this post


Link to post
Share on other sites
Just now, Dedmen said:

Don't understand why pushing a number to an array is more expensive than copying and iterating a bigger array

Maybe I should've used: 

RS_animatedUnits = RS_animatedUnits - _toDel

instead?

  • Thanks 1

Share this post


Link to post
Share on other sites

What I often do is that I make the code functions and call them each at different time in EachFrame

 

Something like this:

 

timer = 0;

addMissionEventHandler ["EachFrame",
{


if(timer == 1) then
{
 call testFunction1;
};

if(timer == 30) then
{
 call testFunction2;
};


timer = timer + 1;

if(timer > 60) then { timer = 0; };

}];

 

Then the functions run fast and the load is distributed to more than just one frame

Share this post


Link to post
Share on other sites
4 minutes ago, gc8 said:

Then the functions run fast and the load is distributed to more than just one frame

->

4 hours ago, Dedmen said:

You could split the work to only process up to 20 units per frame, but as you said that would introduce jitter.

 

11 hours ago, Leopard20 said:

The code executes some animation for the AI, including rotation, which if not executed on each frame could result in jittery animations; so yes, it needs to be run on each frame.

 

 

10 minutes ago, Leopard20 said:

Maybe I should've used: 

RS_animatedUnits = RS_animatedUnits - _toDel

instead?

Yes. Also isn't measuring the speed on that code quite hard? With every execution the state changes, AI's finish animations, or go into new ones. AI's are removed from the loop altogether.

Share this post


Link to post
Share on other sites

@Dedmen Yeah I know you already discussed that but I just wanted to share an example to anyone reading this thread 🙂

 

  • Like 1

Share this post


Link to post
Share on other sites
1 hour ago, Dedmen said:

->

 

 

 

Yes. Also isn't measuring the speed on that code quite hard? With every execution the state changes, AI's finish animations, or go into new ones. AI's are removed from the loop altogether.

Yes. But if I pause the game, and call the fnc manually, it won't change.

 

@gc8

Actually, I use something similar in another waitUntil loop I use for unit tasking!

(I don't use sleep, instead, I record the time every time the code executes and make measurements against that)

waitUntil

{

if (time - _lastTime > 1) then {

_lastTime = time;

};

false

}

Share this post


Link to post
Share on other sites

@Leopard20 That's another way. But usually my coding projects end up quite big and I find the scheduled code to be running too slow. Which is why I usually in these bigger projects end up putting the heaviest code in to the EachFrame handler, so that it runs faster and without significant delay.

Share this post


Link to post
Share on other sites

We seem to have gotten slightly off topic! I still haven't found the answer to my initial question. So to recap, which one of the three methods mentioned is best for an on each frame code? (in this case, mine, which I have now fully posted!)

Let's assume for the time being that the code is fully optimized!

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

×