Optimization Help

A refuge for those migrating from the fallen DXEditing.com and a place for general discussion relating to Deus Ex editing (coding, mapping, etc).
Post Reply
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Optimization Help

Post by Cybernetic pig »

Code: Select all

////////////////////////////
// Cyber: check for nearby environmental hazards
////////////////////////////

singular function checkForHazards(GC gc)
{
local DamageTrigger DT;
local ZoneInfo ZI;
local Cloud CL;
local string threatType;
local string threatDam;
local int typecastIt;
local Actor acti;

 ForEach Player.RadiusActors(class'DamageTrigger', DT, 512)
  {
    threatType = (string(DT.damageType));
    typecastIt = (int(DT.damageAmount));
    threatDam = (string(typecastIt));
    acti = DT;
  }
 if (acti == None)
 {
 ForEach Player.RadiusActors(class'ZoneInfo', ZI, 1024)
  {
    if (ZI.bPainZone)
    {
    threatType = (string(ZI.DamageType));
    threatDam = (string(ZI.DamagePerSec));
    acti = ZI;
    }
  }
 }
 if (acti == None)
 {
  ForEach Player.RadiusActors(class'Cloud', CL, 512)
  {
    if (CL.Damage > 1)
    {
    threatType = (string(CL.damageType));
    typecastIt = (int(CL.Damage));
    threatDam = (string(typecastIt));
    acti = CL;
    }
  }
 }
  if (threatType != "" && threatType != "shot")
  {
    if (bHazardRefresh)
       Player.PlaySound(sound'hazardwarn',SLOT_None);
    if (threatType == "Exploded")
    {
      threatType = "Fatal";
      threatDam = "Fatal";
    }
  bHazardRefresh=False;
  DrawThreatDetectionAugmentation(gc, acti, threatType, threatDam);
  }
  else
  bHazardRefresh=True;
}
This function is called every tick when the conditions are met. I'm worried about it being a costly function. It runs fine in-game but of course it all adds up. So, my questions are:

1. would one ForEach RadiusActors loop looking for class'actor' be more suitable than three ForEach loops looking for the more specific actor classes as above?

2. Is there any way I can optimize this at all?

3. on the unreal wiki ForEach loop page it says the following under the "Pitfalls" section:

"Pay special attention when you modify the list in some way. Adding items to the list, for example spawning actors during an AllActors loop, can create an infinite loop where the engine continues to create actors until it runs out of memory or crashes for other reasons!"

DX code uses ForEach AllActors(class'actor somewhat commonly, and also spawns actors somewhat commonly, such as fragments. So how come we don't suffer frequent crashes if the above is true?
Hanfling
MJ12
Posts: 406
Joined: Sun Oct 04, 2009 6:54 pm

Re: Optimization Help

Post by Hanfling »

You will just get infinite loops if you do sth. like foreach AllActors( Class'Fragment', Fragment ) { Spawn( Class'Fragment' ); }.

Despite that your code just detectst the first thing and skips all others, for optimisation you could use all kind of approaches. One approach is to either use an array or linked list to store a list of all actors of that class. E.g. add some DamageTriggerList variable to your gameinfo class, add a NextDamageTrigger variable to your DamageTriggers and in PreBeginPlay of the DamageTrigger add it to the list. Afterwards you could just walk through that list. As you probably don't need the overlap RadiusActors checks, you could just go ahead and not compare the Distance itsself when going through your DamageTriggerList, but the square of the Distance, so you would avoid one (expansive) Sqrt(). However, in UC VSize() might be still faster due to overhead.

But actually you don't need to calculate the distance to the player for NM_StandAlone, because it was already calculated during ULevel::Tick() and stored in inside the DistanceFromPlayer variable. So you could just walk through that linked list of DamageTriggers and check the DistanceFromPlayer variable.

For the Clouds, you should probably ignore the HalonGas ones, fastest way would be to check if ( CL.DamageType=='HalonGas' ) instead of introducing another class check there.

Your ZoneInfo will probably not do what you thing it would do. The position of the ZoneInfo actor inside a zone is arbitrary, and is in no relation to the actual zone volume, so you should screw that approach. Also you miss checking if the PainZone is actually enabled. As using Zone/FootZone/HeadZone is probably no option either as you already get the damage and probably don't need to be warned off, you probably need some more complicated model checking your surounding. For uc you could move around dummy actors (probably slow), for C++ you can use UModel::PointRegion() to determine the Zone of some Location.

And when you call it a lot of times, and want it fast, use C++, especially if you start to do math in your function.

/edit:
You might also be able use the AI event system for this kind of stuff.
I demand my DXE User ID back. Launcher for DeusEx, Rune, Nerf, Unreal & Botpack. HX on Mod DB. Revision on Steam.
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Optimization Help

Post by Cybernetic pig »

Your ZoneInfo will probably not do what you thing it would do. The position of the ZoneInfo actor inside a zone is arbitrary, and is in no relation to the actual zone volume, so you should screw that approach
Yeah I saw that coming and had planned to move the Zone info where necessary or add dummy damage triggers. Luckily each pain zone is relatively small so it shouldn't be hard to sort.

Alright, thanks again. I'll see what I can do with these suggestions.
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Optimization Help

Post by Cybernetic pig »

For the Clouds, you should probably ignore the HalonGas ones, fastest way would be to check if ( CL.DamageType=='HalonGas' ) instead of introducing another class check there.
Already there: if (CL.Damage > 1). Although halon gas deals 0 damage so I'll change that to > 0.

Not sure what to do when there is more than one trigger of a different damage type (say, EMP & shocked together). I could either have it draw more than one hazard at a time or cycle through all nearby hazards periodically.

For those that a curious, I'm implementing this for a new augmentation of sorts - the default augmentation "IFF" can now be upgraded, and it acts as a HUD upgrade aug.

LVL1: IFF (reticule turns red/green based on friend or foe. Default behavior).
LVL2: Crosshairs are displayed at all times when a weapon is drawn, not just when aiming at decoration and NPCs.
LVL3: Environmental hazards are detected.
LVL4: player light levels are displayed (Light Gem of sorts).

All this is already done except the finishing up of the environmental hazard detection. I may need to add more features too, because it wouldn't be especially appealing to invest in this aug in comparison to most of the others which are very empowering.
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Optimization Help

Post by Cybernetic pig »

Got a problem:

Code: Select all

////////////////////////////
//  cyberP: check for nearby environmental hazards
////////////////////////////

singular function checkForHazards(GC gc)
{
local DamageTrigger DT;
local ZoneInfo ZI;
local Cloud CL;
local string threatType;
local string threatDam;
local int typecastIt;
local Actor acti;

 ForEach Player.RadiusActors(class'DamageTrigger', DT, 512)
  {
   if (DT.bIsOn)
   {
    threatType = (string(DT.damageType));
    if (DT.damageInterval != 0)
       typecastIt = (int(DT.damageAmount/DT.damageInterval));
    else
       typecastIt = (int(DT.damageAmount));
    threatDam = (string(typecastIt));
    acti = DT;
   }
  }
 if (acti == none)
 {
  ForEach Player.RadiusActors(class'Cloud', CL, 512)
  {
    if (CL.Damage > 0)
    {
    threatType = (string(CL.damageType));
    if (CL.damageInterval != 0)
        typecastIt = (int(CL.Damage/CL.damageInterval));
    else
        typecastIt = (int(CL.Damage));
    threatDam = (string(typecastIt));
    acti = CL;
    }
  }
 }
  if (threatType != "" && threatType != "shot")
  {
    if (bHazardRefresh)
       Player.PlaySound(sound'hazardwarn',SLOT_None);
    if (threatType == "Exploded")
    {
      threatType = "Fatal";
      threatDam = "Fatal";
    }
    else if (threatType == "Shocked")
    {
      threatType = "Electrical";
    }
    else if (threatType == "TearGas")
    {
      threatDam = "5"; //CyberP: technically it is 3, but clouds often group together and deal massive damage
    }
    else if (threatType == "EMP")
    {
       threatType = "EMP Field";
    }
    else if (threatType == "PoisonGas")
    {
       threatType = "Poison Gas";
    }
    else if (threatType == "Burned")
    {
       threatType = "Fire";
    }
  bHazardRefresh=False;
  DrawThreatDetectionAugmentation(gc, acti, threatType, threatDam);
  }
}
With dummy damage triggers placed where necessary it works great, only with one problem: damage triggers appear to occasionally be detected from further than 512 unreal units away. ForEach RadiusActors does check the specified radius on all axis right; 512 x,y & z?
Hanfling
MJ12
Posts: 406
Joined: Sun Oct 04, 2009 6:54 pm

Re: Optimization Help

Post by Hanfling »

RadiusActors actually test if |Actor.Location - Loc| < |Radius + Actor.CollisionRadius|. Idea behind it is probably that it's a fast approximation to see if the ball and Collision cylinder overlap.
I demand my DXE User ID back. Launcher for DeusEx, Rune, Nerf, Unreal & Botpack. HX on Mod DB. Revision on Steam.
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Optimization Help

Post by Cybernetic pig »

Well, that shouldn't result in issues then...strange.
Cybernetic pig
Illuminati
Posts: 2284
Joined: Thu Mar 08, 2012 3:21 am

Re: Optimization Help

Post by Cybernetic pig »

Oh right, because some dam triggers have a large collision radius. :facepalm:
Post Reply