Jump to content
Lemonwired

Clear Roads Script - Optimization Required

Recommended Posts

Hi All,

 

First time on these forums. I've created a unoptimized script that clears roads of obstacles to make it easier for AI to navigate past the clutter.

 

It works to clear the roads but takes so damn long to execute. Any ideas here?

 

Basically, the script works by looking for all roads within a radius then obtains all the nearest objects on that road and hides them.

 

//0 = [player,1000,10] execvm "clearroad.sqf"; //here's what to put in the init.sqf
//item 1 is the object that will be the center e.g. player
//item 2 is the radius where all the roads that will be impacted e.g. 1000m around the player
//item 3 is the area of the road that will be cleared e.g. 10 meters of road to be cleared


_pos = _this select 0;
_radius = _this select 1;
_radius2 = _this select 2;

_roadList= getpos _pos nearroads _radius;

_notobjects = [];
_i = 1;
_arraycount = count _roadList;

while {_i < _arraycount} do {
_arr = nearestObjects [getpos (_roadList select _i), [], _radius2];
_arr2 = nearestterrainObjects [getpos (_roadList select _i), [], _radius2];
_notobjects = _notobjects + _arr + _arr2;
_i = _i + 1;
};

{hideobject _x} foreach _notobjects;

 

 

kind regards,

Lemon

 

Share this post


Link to post
Share on other sites

So you need to run this script once or more times? 1000 meters for roads is slow..

Share this post


Link to post
Share on other sites

Well one trick to make it faster is to put the code in EachFrame EH. 

 

eachFrameEH = addMissionEventHandler ["EachFrame",
{
 removeMissionEventHandler ["EachFrame",eachFrameEH];
 
 // Code here
_pos = player;
_radius = 1000;
_radius2 = 10;

_roadList= getpos _pos nearroads _radius;

_notobjects = [];
_i = 1;
_arraycount = count _roadList;

while {_i < _arraycount} do {
_arr = nearestObjects [getpos (_roadList select _i), [], _radius2];
_arr2 = nearestterrainObjects [getpos (_roadList select _i), [], _radius2];
_notobjects = _notobjects + _arr + _arr2;
_i = _i + 1;
};

{hideobject _x} foreach _notobjects;
                        
                        
}];

 

Run that in init.sqf for example

  • Like 1
  • Thanks 1
  • Confused 1

Share this post


Link to post
Share on other sites

Just another query on this, I have inputted both nearestObjects & nearestterrainObjects to run on the same road to ensure ALL types of objects are cleared. Does anyone know if this is overkill? 

 

For example, can nearestObjects clear terrain objects as well?

 

I'm looking to use this on any terrain.

 

Kind regards,

Lemon

 

Share this post


Link to post
Share on other sites

@gc8 Why are you using EachFrame? Not a trick at all. That will run x times per frame. while loop will run every second per frame. Not ideal at all, especially with a while loop inside.

 

@Lemonwired No need to run while loop and set _i so just use for loop:

for "_i" from 0 to (count _roadList) do
{
  _arr = nearestObjects [getPos (_roadList select _i), [], _radius2] append nearestTerrainObjects [getPos (_roadList select _i), [], _radius2];
  _notobjects pushBack_arr;
};

Not tested but you get the idea.

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

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

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

 

I also recommend using params command.

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

params ["_pos", "_radius", "_radius2"];
// You can set default values like so:
params
[
	["_pos", [0, 0, 0]],
	["_radius", 100],
	["_radius2", 200]
];

Not all roads can be hidden. I don't see any of your script being a reliable solution. If you are using a pre-defined path for AI vehicles then there are other ways to make AI follow road.

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

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

 

  • Thanks 1

Share this post


Link to post
Share on other sites

Thanks HazJ!

 

I was just looking into using "append" & "For" instead of "_notobjects = _notobjects  + _arr;" and "while".

 

Not sure what you meant by "Not all roads can be hidden". The purpose of this is to clear the road of obstacles, which custom terrains like 'Mogadishu' that have to much clutter for AI to navigate around. What I had previously worked but was extremely slow to do all the hiding of objects. I also have the AI dynamically moving to different locations across the entire map (similar to Arma 2's Warfare/CTI), the issue is they get stuck behind a wreck in the middle of the road for example.

 

Right now, I'm not on my gaming desktop to test, but I'm looking to use nearobjects instead of nearestobjects because sorting shouldn't be required. And I'm getting rid of nearestterrainobjects.

 

Also, please note, this will just be for singleplayer.

 

Here's the script I have now (calling it cleanroads.sqf or whatever), I'll test it when I get home:

_pos = _this select 0;
_radius = _this select 1;
_radius2 = _this select 2;

_roadList= getpos _pos nearroads _radius;

_notobjects = [];

for "_i" from 0 to (count _roadList) do
{
  _arr = getPos (_roadList select _i) nearObjects _radius2;
  _notobjects append _arr;
};
{hideobject _x} foreach _notobjects; 

I'll give feedback once tested for anyone else that wants to use a script to remove road obstacles from their missions.

Share this post


Link to post
Share on other sites

You'll need nearestTerrainObjects to get certain map objects. You can also use forEach loop.

  • Thanks 1

Share this post


Link to post
Share on other sites
16 minutes ago, HazJ said:

You'll need nearestTerrainObjects to get certain map objects. You can also use forEach loop.

 

😭 I was hoping I could get away with it. Thanks again HazJ.

 

Here's the latest script I'll soon test:

_pos = _this select 0;
_radius = _this select 1;
_radius2 = _this select 2;

_roadList= getpos _pos nearroads _radius;

_notobjects = [];

for "_i" from 0 to (count _roadList) do
{
  _arr = getPos (_roadList select _i) nearObjects _radius2 append nearestTerrainObjects [getPos (_roadList select _i), [], _radius2];
  _notobjects append _arr;
};
{hideobject _x} foreach _notobjects; 

 

Share this post


Link to post
Share on other sites

Use params as I suggested. Change:

_pos = _this select 0;
_radius = _this select 1;
_radius2 = _this select 2;
params ["_pos", "_radius", "_radius2"];

Or:

// You can set default values like so:
params
[
	["_pos", [0, 0, 0]],
	["_radius", 100],
	["_radius2", 200]
];

Might just be easier to use a forEach in first place rather than create the array then iterate through and hideObject, etc...

Share this post


Link to post
Share on other sites
6 hours ago, HazJ said:

Why are you using EachFrame? Not a trick at all. That will run x times per frame. while loop will run every second per frame. Not ideal at all, especially with a while loop inside.

 

The each frame EH is removed immediately so it runs only once

  • Confused 1

Share this post


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

 

The each frame EH is removed immediately so it runs only once

And the benefit from doing so will be what exactly?

 

Cheers

  • Like 1
  • Haha 1

Share this post


Link to post
Share on other sites
1 hour ago, Grumpy Old Man said:

And the benefit from doing so will be what exactly?

 

The code is run almost immediately

Share this post


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

 

😭 I was hoping I could get away with it. Thanks again HazJ.

 

Here's the latest script I'll soon test:


_pos = _this select 0;
_radius = _this select 1;
_radius2 = _this select 2;

_roadList= getpos _pos nearroads _radius;

_notobjects = [];

for "_i" from 0 to (count _roadList) do
{
  _arr = getPos (_roadList select _i) nearObjects _radius2 append nearestTerrainObjects [getPos (_roadList select _i), [], _radius2];
  _notobjects append _arr;
};
{hideobject _x} foreach _notobjects; 

 

Doubt this will work, since append doesn't return anything.

 

49 minutes ago, gc8 said:

The code is run almost immediately

Why not just a simple call?

Got any numbers on that to compare eachFrame+instant removal / call / execVM?

Anyway I doubt the time until the snippet is being executed is relevant here, since the snippet itself takes quite a while to finish.

 

Testing this (fixed to make it work):

params
[
	["_pos", player],
	["_radius", 100],
	["_radius2", 10]
];

_roadList= getpos _pos nearroads _radius;

_notobjects = [];

for "_i" from 0 to (count _roadList) do
{
	_objs = getpos (_roadList select _i) nearObjects _radius2;
	{hideObjectGlobal _x} foreach _objs;
	_terrObjs = nearestTerrainObjects [getPos (_roadList select _i), [], _radius2];
	{hideObjectGlobal _x} foreach _terrObjs;
};
//Result:
//0.669565 ms

The culprit here is that you neither need the for loop (no _i is needed), nor the assignment of all roads and nearby objects to variables,

and also can skip the additional forEach loop to hide objects, and condense all operations down to as few as possible:

params
[
	["_centerPos", getPosATL player],
	["_roadRadius", 100],
	["_objectRadius", 10]
];

_centerPos nearRoads _roadRadius apply {
	getPos _x nearObjects _objectRadius apply {hideObjectGlobal _x};
	nearestTerrainObjects [getPos _x, [], _objectRadius] apply {hideObjectGlobal _x};
};
//Result:
//0.330003 ms

This works in less than half the duration of the previous snippet.

Tested on Altis, Lakka at [12345.4,15669.5,0.00154877].

 

To further increase speed you could use map center as _centerPos, check all roads on the entire map and run this as custom function from CfgFunctions during preInit.

Doubt you'll notice a wait of more than 1-2 seconds once upon mission start.

 

Cheers

  • Like 5
  • Thanks 1

Share this post


Link to post
Share on other sites
10 minutes ago, Grumpy Old Man said:

Got any numbers on that to compare eachFrame+instant removal / call / execVM?

 

I have tested that but don't have any numbers now. if scripts get laggy I put them to eachFrame to make them run faster

 

What may happen when doing this is a lag spike (if the script is heavy) as the engine is trying to run the code in single frame

Share this post


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

 

I have tested that but don't have any numbers now. if scripts get laggy I put them to eachFrame to make them run faster

 

What may happen when doing this is a lag spike (if the script is heavy) as the engine is trying to run the code in single frame

Worst case is that the scripts you put into the eachFrame EH will run on the next frame.

At 60fps this means it can take up to 16.67ms until the snippet is actually run.

Compare that to the 0.33ms runtime of the snippet above, doubt "call" delays this in any order of significance.

 

Cheers

Share this post


Link to post
Share on other sites
8 minutes ago, Grumpy Old Man said:

Worst case is that the scripts you put into the eachFrame EH will run on the next frame.

 

Not if you remove the EH as first thing inside the EH

Share this post


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

 

Not if you remove the EH as first thing inside the EH

What GOM meant was that registering the event frame 1 means the first execution might be delayed until frame 2, hence the delayed execution. 

 

Generally OnEachFrame helps by running the code in unscheduled but other than that I see no real improvement. Take for example my Voronoi generation. A scheduled run would take 5-8s but an unscheduled pre-init run is <1s. If you can get away with a pre-init exec it should be preferred IMO. 

  • Like 1

Share this post


Link to post
Share on other sites

@mrcurry Yes I believe pre-init is also fast (But function dependencies is a problem). You only need to use tricks like this when your mission is running so many scripts that the script execution gets longer and longer. I once had script, which do to the amount of other scripts running, took about two minutes to run. So I needed solution to fix this and eachFrame helped there

Share this post


Link to post
Share on other sites

@Grumpy Old Man I thought append was the same as _arr1 + _arr2 hehe. Just faster?

Share this post


Link to post
Share on other sites

Huge thanks @Grumpy Old Man !

 

This crazy amount of optimisation was exactly what I was after, I'll test this when I get the chance.

 

By the way, with the script it will hide editor place objects including the player if near a road. So I'll need to throw in something that prevents editor placed units from being deleted, perhaps the following:

nearObjects ["Man", _roadRadius]?

 

How could I incorporate that into Grumpy Old Man's code?

_EXCLUDE = nearObjects ["Man", _roadRadius];

_centerPos nearRoads _roadRadius apply 
{
getPos _x nearObjects _objectRadius - _EXCLUDE apply {hideObjectGlobal _x}; 
nearestTerrainObjects [getPos _x, [], _objectRadius] apply {hideObjectGlobal _x}; 
};

Thanks in advance all! Everyone has been so extremely helpful.

  • Like 1

Share this post


Link to post
Share on other sites
13 hours ago, HazJ said:

@Grumpy Old Man I thought append was the same as _arr1 + _arr2 hehe. Just faster?

It's similar in function and faster but not in outcome, + returns the resulting array while append modifies the first array without returning anything on its own:

_arr = [1,2,3] + [4,5,6]
systemChat str _arr;//prints [1,2,3,4,5,6]

_arr = [1,2,3] append [4,5,6]
systemChat str _arr;//prints nothing

_arr1 = [1,2,3];
_arr = _arr1 append [4,5,6];
systemChat str _arr1;//prints [1,2,3,4,5,6]

In the case of the snippet above it's not needed to store any of the retrieved roads and objects in an array, so I used apply to simply hide all objects returned from the respective commands.

Apply also doesn't run on an empty array, in case a road doesn't have any nearby objects this could throw errors needing another if check.

 

1 hour ago, Lemonwired said:

Huge thanks @Grumpy Old Man !

 

This crazy amount of optimisation was exactly what I was after, I'll test this when I get the chance.

 

By the way, with the script it will hide editor place objects including the player if near a road. So I'll need to throw in something that prevents editor placed units from being deleted, perhaps the following:

nearObjects ["Man", _roadRadius]?

 

How could I incorporate that into Grumpy Old Man's code?

Thanks in advance all! Everyone has been so extremely helpful.

 

You can do an isKindOf check (only needed for nearObjects) to exclude infantry objects from being removed:

params
[
	["_centerPos", getPosATL player],
	["_roadRadius", 100],
	["_objectRadius", 10]
];

_centerPos nearRoads _roadRadius apply {
	(getPos _x nearObjects _objectRadius select {!(typeOf _x isKindOf "Man")}) apply {hideObjectGlobal _x};
	nearestTerrainObjects [getPos _x, [], _objectRadius] apply {hideObjectGlobal _x};
};

Cheers

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites

Thanks again Grumpy Old Man,

 

Works brilliantly and much more faster.

 

Regards,

Lemon

  • Like 1

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

×