Jump to content
Sign in to follow this  
dimon

question on optimization

Recommended Posts

what is best of three ways ?

//{_x AddEventHandler ["Killed",{deletevehicle _this;}]} foreach allunits;

/* while {true} do {
   {
       if (!alive _x) then {
           deletevehicle _x;
       };
   } foreach allunits;
   sleep 10;
};  */

/* {
   _x spawn {
       while {true} do {
           if (!(alive _this)) exitwith {
               deletevehicle _this;
           };
       sleep 10;
       };
   };
} foreach allunits; */

Share this post


Link to post
Share on other sites
https://community.bistudio.com/wiki/Code_Optimisation

I'm sure that first variant is better, because event handlers for this purpose intended.

Also this code is more suitable to hide corpses:

{_x AddEventHandler ["Killed", {hideBody (_this select 0)}]} forEach allUnits;

well the question is what principle to use...

And whether the same event handlers in the same while loop?

Schatten,

ну Ð²Ð¾Ð¿Ñ€Ð¾Ñ Ð¸Ð¼ÐµÐ½Ð½Ð¾ какой принцип иÑпользовать...

Рне ÑвлÑÑŽÑ‚ÑÑ Ð»Ð¸ те же обработчики Ñобытий тем же циклом while?

Share this post


Link to post
Share on other sites

I assume you're trying to do cleanup of dead units? This is definitely not a best approach this as more units will be created through the game calling this code block will assign "Killed" event handler (first method) or another thread (third method) onto same unit multiple times. I'd suggest using https://community.bistudio.com/wiki/allDead instead.

Speaking of performance, engine event handlers are always "better" than regular scripted checked, but it all depends on what you're trying to achieve, simply deleting units as soon as they die is definitely not a player-friendly experience as players might want to loot dead units first. Spawning a thread with sleep after unit was Killed might be a solution but personally I prefer to have as less threads as possible. If you're interested in my approach to this task I can share the code.

Edited by SaMatra

Share this post


Link to post
Share on other sites

OK,ok - sorry )

The code I posted is just an example: in order to understand whether a good event handlers and not better in some situations to write a loop with the desired pause. There is an opinion that the event handlers no load carry - which I highly doubt because as I understand it hangs the same cycle with the test conditions.

Addaction - out with the same situation I think there is also a continuous loop with the test conditions.

Код Ñ Ð²Ñ‹Ð»Ð¾Ð¶Ð¸Ð» проÑто Ð´Ð»Ñ Ð¿Ñ€Ð¸Ð¼ÐµÑ€Ð°: чтобы понÑÑ‚ÑŒ так ли хороши обработчики Ñобытий и не лучше ли в некоторых ÑитуациÑÑ… напиÑать Ñвой цикл Ñ Ð½ÑƒÐ¶Ð½Ð¾Ð¹ паузой (еÑли еÑÑ‚ÑŒ Ñ‚Ð°ÐºÐ°Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾ÑÑ‚ÑŒ). СущеÑтвует мнение, что обработчики Ñобытий никакой нагрузки не неÑут - в чем Ñ Ñильно ÑомневаюÑÑŒ ибо к как Ñ Ð¿Ð¾Ð½Ð¸Ð¼Ð°ÑŽ вешаетÑÑ Ñ‚Ð¾Ñ‚ же цикл Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¾Ð¹ уÑловиÑ.

Addaction - из Ñ Ñ‚Ð¾Ð¹ же Ñитуации Ñ Ð´ÑƒÐ¼Ð°ÑŽ, там тоже поÑтоÑнный цикл Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¾Ð¹ уÑловиÑ.

this question came to me once when I accidentally read this post

Edited by Dimon
add

Share this post


Link to post
Share on other sites

Main difference of event handlers is that they execute scripts in non-scheduled environment while your regular script threads (execVM, spawn, addAction, etc.) run in virtual machine or scheduled environment. Having lots of threads doing heavy calculations will seriously postpone execution of other scripting threads, causing so called script lag. Having heavy calculations in event handler called code will not create any script lag but instead will freeze entire game until execution is complete, you need to be very careful with that especially on server machine as server-side freeze will end up with server-wide lag ruining experience for players. SQS and FSMs are bit different but that's another topic.

Again, it all depends on what you want to achieve and how much priority it has to have. If you want to avoid data race conditions or have precise on each frame code then non-scheduled execution is a must. I guess body clean up is not that important precision-wise and executing it in ScriptVM threads should be fine, personally I prefer to have single thread iterating through list of something instead of attaching new thread to each item in the list.

Share this post


Link to post
Share on other sites

OK, let's move closer to the issue.

There was a dispute that would be better :

1. Hang on every unit event killed

2. At death unit ( using the same event handler ) to drive them in one array and then in one cycle to process the entire array.

To have the opportunity to work with this unit after his death:

for example, before deleting check the distance to the player

1. for the first option, spawn the function

			_deadunit=_this select 0;
		sleep 60;
		waitUntil{sleep 5;({isPlayer _x} count ((getPosATL _deadunit) nearEntities ["man",50]))<1};//count playableunits
		hideBody _deadunit;
		deleteVehicle _deadunit;

2.In the second option we have in the mission already hanging loop which periodically with a pause of 20 seconds checks the array

while {true} do
{
sleep 20;
_cnt=count FFA_OBJECTSTOCLEAR;
for "_i" from 0 to _cnt - 1 step 1 do 
{		
	_item=FFA_OBJECTSTOCLEAR select _i;
	_veh=_item select 0;
	if !(alive _veh) then
	{
		_deathtime=_item select 1;
		if (_deathtime==0) then
		{
			_deathtime=diag_tickTime;
			FFA_OBJECTSTOCLEAR set [_i,[_veh,_deathtime]];
		};
		if ((diag_tickTime -_deathtime)>120 && {(({isPlayer _x} count ((getPosATL _veh) nearEntities ["man",50]))<1} ) then
		{
			deleteVehicle _veh;
			FFA_OBJECTSTOCLEAR set [_i,objNull];
		};
	};		
};	
FFA_OBJECTSTOCLEAR=FFA_OBJECTSTOCLEAR-[objNull];
};

Ок, перейдем ближе к вопроÑу.

Произошел Ñпор что будет лучше :

1. Вешать на каждого юнита Ñобытие killed

2. При Ñмерти юнита ( Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ того же обработчика Ñобытий ) загонÑÑ‚ÑŒ их в один маÑÑив и потом в одном цикле обрабатывать Ñтот веÑÑŒ маÑÑив.

Чтобы поÑвилаÑÑŒ возможноÑÑ‚ÑŒ работать Ñ Ñтим юнитом поÑле его Ñмерти:

допуÑтим перед удалением проверÑÑ‚ÑŒ раÑÑтоÑние до игрока

1. Ð´Ð»Ñ Ð¿ÐµÑ€Ð²Ð¾Ð³Ð¾ вариант spawn функцию

2.Во втором варианте у Ð½Ð°Ñ Ð² миÑÑии уже виÑит цикл который периодичеÑки Ñ Ð¿Ð°ÑƒÐ·Ð¾Ð¹ 20 Ñекунд проверÑет маÑÑив

Edited by Dimon

Share this post


Link to post
Share on other sites

Not THAT much of a difference performance-wise honestly since all your code is run in threads (scheduled environment) in the end, even when Killed event handler is executed, you spawn another thread which does sleep and waitUntils. For this kind of task (body cleanup) it all comes down and depends on how it would be better for you as mission designer to manage dead units cleanup, add event handler onto each unit manually, add each unit to "watch list", or simply cycle through allDead command returned array as I suggested. If go nitpicking about performance differences, first approach is worse because it creates many smaller scripting threads, second is worse because it regularly runs "array - array" command (FFA_OBJECTSTOCLEAR=FFA_OBJECTSTOCLEAR-[objNull]) which is fairly expensive if you have very long array.

By the way since I had this piece of code for cleanup at hand, going to post it anyway:


while {true} do {
{
	// If no cleanup timer set, set it
	if(_x getVariable ["cleanup_at", 0] == 0) then {
		_x setVariable ["cleanup_at", diag_tickTime + (switch(true) do {
			case (_x isKindOf "CAManBase"):	{120}; // Dead units - 2 minutes
			default				{300}; // Vehicle wrecks - 5 minutes
		})];

	};

	// Check if timer ran out
	if(diag_tickTime > _x getVariable ["cleanup_at", 0]) then {
		_dead = _x;

		if(switch(true) do {
			// Example of additional condition, delete body anyway if it is right at base so they wouldn't stockpile there as players can die and respawn there, fnc_isOnBase is not provided in this example code
			case (_dead call fnc_isOnBase): {true};

			// Check for players nearby, if there is > 0 players nearby, have switch return false
			case ({
				// If player is within 20 meters, exit "count" loop with result of 1
				if(_dead distance _x < 20) exitWith {1};
			} count (playableUnits) > 0): {false};

			// Default is true => delete the body\wreck
			default {true};
		}) then {
			deleteVehicle _dead;
		} else {
			// Switch condition returned false, means we should not delete body yet and timer has to start again.

			_dead setVariable ["cleanup_at", 0];
		};
	};
} forEach (allDead);

// Next cycle through dead units in 10 seconds.
sleep 10;
};

Code supposed to be run once in scheduled environment (execVM, spawn)

Pastebin for better readability: http://pastie.org/private/lyjhmlor0h7krjxc0ey4uw

Edited by SaMatra

Share this post


Link to post
Share on other sites

Thanks ,SaMatra, for the script and for the clarification.

---------- Post added at 13:05 ---------- Previous post was at 12:45 ----------

The next question follows from the first.

What would be better from the point of performance in situations when it is necessary to check something (for example to compare who has more in the trigger/marker)

1. We hang on every unit handler killed spawn with functions in which there is a one-time comparison of the number of living units. (of course adding a check on the already running function to avoid triggering at the same time a large number of runs the function when multiple event killed.)

_new AddEventHandler ["Killed",{_this spawn FFA_EVENT}];

FFA_EVENT

if (FFA_EVENT_EVENT) exitWith{};
FFA_EVENT_EVENT = true;
if ((('man' countType list SENSORENEMYSIDE) < 15) && {(('tank' countType list SENSORENEMYSIDE) < 2)} && {(('car' countType list SENSORENEMYSIDE) < 2)} && {(FFA_TOWNGUARDSSPAWNED)} && {(isNull FFA_RADIO)} && {(isServer)} && {(FFA_SERVERSTARTED)} && {(FFA_TNDEF == 1)}) then 
{
[] spawn ffa_func_deftown;
};
FFA_EVENT_EVENT = false;

2. We hang in the game loop to check and compare the number of living units.

while{true} do {
sleep 10;
if ((('man' countType list SENSORENEMYSIDE) < 15) && {(('tank' countType list SENSORENEMYSIDE) < 2)} && {(('car' countType list SENSORENEMYSIDE) < 2)} && {(FFA_TOWNGUARDSSPAWNED)} && {(isNull FFA_RADIO)} && {(isServer)} && {(FFA_SERVERSTARTED)} && {(FFA_TNDEF == 1)}) then 
{
[] spawn ffa_func_deftown;
}; 
};

like the first method at first glance better, but a large number were hanged handlers be better than one while?

specify in advance that the accuracy and speed checks are not important.

Что будет лучше Ñ Ñ‚Ð¾Ñ‡ÐºÐ¸ производительноÑти в Ñитуации когда надо проверить что-то (допуÑтим Ñравнить кого больше в триггере/маркере)

1. Вешаем на каждого юнита обработчик killed Ñ spawn функции в которой идет одноразовое Ñравнение количеÑтва живых юнитов. (еÑтеÑтвенно добавив проверку на то запущена ли уже функциÑ, чтобы избежать Ñрабатывание одновременно большого кол-ва запуÑков функции при множеÑтвенном Ñобытии killed.)

2. Вешаем в игре цикл который будет проверÑÑ‚ÑŒ и Ñравнивать количеÑтво живых юнитов.

как бы первый ÑпоÑоб на первый взглÑд лучше, но большое количеÑтво повешенных обработчиков будет ли лучше чем один while Ñ Ð¿Ð°ÑƒÐ·Ð¾Ð¹ 5 Ñек допуÑтим?

заранее оговорюÑÑŒ, что точноÑÑ‚ÑŒ и ÑкороÑÑ‚ÑŒ проверки не важна

Edited by Dimon

Share this post


Link to post
Share on other sites

I'd say that having one constantly working thread is better than spawning many threads just to close most of them right away. So I'd go with second approach. On the other hand you can call instead of spawn in "Killed" event handler, it will do the check right away within current frame, looking at code it shouldn't cause any performance issues.

Edited by SaMatra

Share this post


Link to post
Share on other sites

I drew the correct conclusion that the fewer concurrent threads - the better?

But if I still, for some reason, I would be forced to use an event handler on each unit (for example to find the killer and to reward him), then it is better to use a handler in full...

Thanks again for the script, remove, and with your permission, it just optimized (although all this stuff)

while {true} do {
{	
		if(_x getVariable ["cleanup_at", 0] == 0) then {
			_x setVariable ["cleanup_at", diag_tickTime + (
				call
				{
					if (_x isKindOf "CAManBase") exitwith {120}; // Dead units - 2 minutes
					300; // Vehicle wrecks - 5 minutes
				}
			)];

		};

		if(diag_tickTime > _x getVariable ["cleanup_at", 0]) then {
			_dead = _x;

			if(

				call {
					//case (_dead call fnc_isOnBase): {true};
					if ({ if(_dead distance _x < 20) exitWith {1};} count (playableUnits) > 0) exitwith {false};
					true;
				}
			) then {
				deleteVehicle _dead;
			} else {
				_dead setVariable ["cleanup_at", 0];
			};
		};
} forEach (allDead);
sleep 10;
};

Ñ Ñделал правильный вывод, что чем меньше параллельных потоков - тем лучше?

Ðо еÑли Ñ Ð²Ñе равно, по каким то причинам, вынужден буду иÑпользовать обработчик Ñобытий на каждом юните (допуÑтим Ñ Ñ†ÐµÐ»ÑŒÑŽ найти killer и наградить его), то уже тогда лучше иÑпользовать обработчик по полной мере...

Еще раз ÑпаÑибо за Ñкрипт ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¸ Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ñ‡ÑƒÑ‚ÑŒ его оптимизировал (Ñ…Ð¾Ñ‚Ñ Ñто вÑÑ‘ мелочи)

Edited by Dimon

Share this post


Link to post
Share on other sites

Yes, the less script threads you have, the better. Of course it also matters how your threads work and what they do.

Sure, feel free to use cleanup script as you like. Using call instead of switch really gives very small performance increase but I just prefer switch for better readability.

Share this post


Link to post
Share on other sites

What would be better?

	{
	FFA_HOUSESCLIENT= FFA_HOUSES;
	(owner _x) publicVariableClient "FFA_HOUSESCLIENT";
} foreach playableUnits;

or

	FFA_HOUSESCLIENT= FFA_HOUSES;
	publicVariable "FFA_HOUSESCLIENT";

Share this post


Link to post
Share on other sites

As far as I heard publicVariableClient actually sends packets to all clients just applies them on one due to improper implementation - http://feedback.arma3.com/view.php?id=22695 Might not be actual in A3 and just in A2 but according to comments might have been addressed in 1.63. I'd use publicVariable if FFA_HOUSES is static and not going to change. If it is changing dynamically and array increases or decreases in size I'd go with pvc and when array updates instead of sending entire array again I'd just send what changed in it to all clients and when JIP joined, send them full final array with pvc once.

Edited by SaMatra

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
Sign in to follow this  

×