Targeter - Count vs Any and readability considerations

I’m leaving these considerations simply because this course is in beta and so the dev in me is kind of code reviewing it as I go. You won’t hurt my feelings by telling me to shove off :slight_smile:

In the SelectTarget method, couple of things:

First - when using the Count property on a collection, it is generally seen as not as optimal as calling the Any() method instead. The Any method when it enumerates over the collection stops when it encounters the first item.

if (targets.Count == 0) return;

consider using: if (!targets.Any()) return;

The count property does fire off compiler warnings for me in Visual Studio.

I’m super nitpicky in game code with performance simply because I’ve had to refactor and hunt down the most evil bugs that are very difficult to track and these little things add up over time.

Second: from a readability standpoint if SelectTarget is going to return a bool indicating success or fail, consider renaming it to TrySelectTarget. SelectTarget as a function name is misleading if its returning a TRUE or a FALSE whereas TrySelectTarget is clearer that it will attempt to select a target, and the TRUE/FALSE values have more meaning coming back.

Of course that doesn’t change functionality but from a code readability and maintenance standpoint, those little things go a very very long way.

6 Likes

I think you are talking about the Count() extension method here. In which case Any() could indeed be preferable when dealing with an IEnumerable. Though when there is no condition being used they perform really similar.
However when we are dealing with a collection with a Count property, the Count is always preferred since it just uses a size backing field that is incremented with the Add() method. The property is completely avoiding any extra processing that a Count() or Any() could potentially perform.

As a side note; Any() does allocations which might not be desired, especially in a game.

Linq is expensive to use, especially in the older frameworks.

I would use linq queries sparingly to not have potential performance issues later.

When .NET Core (and .NET 5+) is used, the performance for linq queries is much better.

Performance Improvements in .NET 6 - .NET Blog (microsoft.com)

The performance is better, but you also have to consider that it creates garbage which needs to be collected, and the garbage collector is also expensive. You can use linq, but try not to use it in places that may be called frequently. Like update methods.

1 Like

I have been using certain Linq statements in Unity for years and years and have never had a problem. It is true SOME linq statements can be beasts, and that is true both in unity and in general commercial software so that I agree with. I would agree that it also depends on where the Linq statements are going and what level of monstrosity is created with it heh.

To just double check my sanity I have an update loop in a game long published that uses three Any statements. I went through and profiled them, and then replaced them with Count() and let them run for a while (they are in a core update method of a controller).

The any statement was more performant (not by a huge amount, the collection its looping through has between 2,000 and 10,000 elements). The garbage collector did not deviate from what it normally is in either case.

The any method simply transforms into a for loop and at the sign of a first run, breaks out and returns true. In fact, if I write a function that does just that, the performance is nearly identical in the profiler.

To Mogitu: yes I was referring to Count(). You are correct, the Count property is already calculated so you can also just evaluate that number. That is probably preferable over Any since it doesn’t require any transforming of the code to do a loop once.

Not all Linq clauses are created equal (for example, a Where() is much faster than an OrderBy). Where things can really get expensive, though, is in method chaining. Each call to a Linq clause puts a copy of the source of that expression on the heap. This is actually necessary because Linq is not destructive, like say myList.RemoveAt(1);

So a simple Linq clause isn’t a huge problem in an Update loop (except for OrderBy, OrderBy should NEVER be called every frame, the garbage collector will bog your game down fast!), but that also depends on how many scripts will be using Linq in the Update loop…

I once foolishly wrote a FindNearestTarget() method for a game… it had three possible results: If a player was in range, it will ALWAYS select the player. If an npc detects a unit from another faction, the nearest of those units is targeted, and if nothing is in range, null…

So I wrote a simple Linq expression for this:

private GameObject FindNearestTarget()
{
    return FindObjectsOfType<BaseController>().
              Where(o=>o.Team!=Team).
              Where(r=>Vector3.Distance(d.transform.position, transform.position)<patrolRange).
              OrderBy(t=>t.Team).
              ThenBy(d=>Vector3.Distance(d.transform.position, transform.position)).
              FirstOrDefault();
}

So in this one method chained Linq statement, I’ve got 5 distinct piles of results put into the heap where they will be disposed of by the GC all at once.
First, we get all the BaseControllers (could be PlayerController or AIController, each has a common BaseController ancestor)
We then ensure that it’s not this unit, and filter out units that are out of patrol range.
Since the player is team 0, The OrderBy(t=>t.Team) ensures that all Players are the first things in the list. This ensures that the Player will ALWAYS be selected if he is in patrol range, even if an enemy NPC is closer.
Then the sorted list is subjected to a ThenBy sort… the ThenBy preserves the order of the Faction, but where there are multiple factions, it sorts based on the new field, in this case the distance from the enemy to the player.
Finally, we return either the first element in that list or null.

So silly me, I’m testing this with four enemies, with 2 in each enemy faction and one player (who doesn’t call this method at all)… The game is performing brilliantly, lag free, Then I got past the prototyping phase and I was dealng with 100 enemies in a randomly generated gaming envionment. Things started out great until you actually started playing, and the frame rate dropped from 200+_ to about 6… well… more like the high teens except when the Garbage Collector did it’s thing.

Point being, be careful. Profile, even if it is a Debug/timestamp type of debug (Log with current time on the logged script in the Debug).

2 Likes

Privacy & Terms