From 7fbbaa2221c0fe04024fcd4523fcc48a57addcd1 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Sat, 17 Oct 2015 01:01:39 +0100 Subject: [PATCH] Optimize AutoTarget.ChooseTarget. Avoid using LINQ for filtering, grouping and dictionary building. Instead, we do these ourselves to reduce overhead. Actors are checked for AutoTargetIgnoreInfo (rather than the trait) since info checks are marginally faster. This check can now be done immediately to allow us to skip such actors right away. The ClosestTo calculation is now only run on the group on actors for the selected armament, rather than all potential armaments. --- OpenRA.Mods.Common/Traits/AutoTarget.cs | 64 +++++++++++++++---------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/AutoTarget.cs b/OpenRA.Mods.Common/Traits/AutoTarget.cs index 52fba3d7a18f..38882c38226c 100644 --- a/OpenRA.Mods.Common/Traits/AutoTarget.cs +++ b/OpenRA.Mods.Common/Traits/AutoTarget.cs @@ -164,43 +164,57 @@ void Attack(Actor self, Actor targetActor, bool allowMove) Actor ChooseTarget(Actor self, WDist range, bool allowMove) { - var inRange = self.World.FindActorsInCircle(self.CenterPosition, range) - .Where(a => - !a.TraitsImplementing().Any(t => t.PreventsAutoTarget(a, self))); + var actorsByArmament = new Dictionary>(); + var actorsInRange = self.World.FindActorsInCircle(self.CenterPosition, range); + foreach (var actor in actorsInRange) + { + if (PreventsAutoTarget(self, actor) || !self.Owner.CanTargetActor(actor)) + continue; + + // Select only the first compatible armament for each actor: if this actor is selected + // it will be thanks to the first armament anyways, since that is the first selection + // criterion + var target = Target.FromActor(actor); + var armaments = attack.ChooseArmamentsForTarget(target, false); + if (!allowMove) + armaments = armaments.Where(arm => + target.IsInRange(self.CenterPosition, arm.MaxRange()) && + !target.IsInRange(self.CenterPosition, arm.Weapon.MinRange)); + + var armament = armaments.FirstOrDefault(); + if (armament == null) + continue; + + List actors; + if (actorsByArmament.TryGetValue(armament, out actors)) + actors.Add(actor); + else + actorsByArmament.Add(armament, new List { actor }); + } // Armaments are enumerated in attack.Armaments in construct order // When autotargeting, first choose targets according to the used armament construct order // And then according to distance from actor // This enables preferential treatment of certain armaments // (e.g. tesla trooper's tesla zap should have precedence over tesla charge) - var actorByArmament = inRange - - // Select only the first compatible armament for each actor: if this actor is selected - // it will be thanks to the first armament anyways, since that is the first selection - // criterion - .Select(a => - { - var target = Target.FromActor(a); - return new KeyValuePair( - attack.ChooseArmamentsForTarget(target, false) - .FirstOrDefault(arm => allowMove - || (target.IsInRange(self.CenterPosition, arm.MaxRange()) - && !target.IsInRange(self.CenterPosition, arm.Weapon.MinRange))), a); - }) - - .Where(kv => kv.Key != null && self.Owner.CanTargetActor(kv.Value)) - .GroupBy(kv => kv.Key, kv => kv.Value) - .ToDictionary(kv => kv.Key, kv => kv.ClosestTo(self)); - foreach (var arm in attack.Armaments) { - Actor actor; - if (actorByArmament.TryGetValue(arm, out actor)) - return actor; + List actors; + if (actorsByArmament.TryGetValue(arm, out actors)) + return actors.ClosestTo(self); } return null; } + + bool PreventsAutoTarget(Actor attacker, Actor target) + { + foreach (var pat in target.TraitsImplementing()) + if (pat.PreventsAutoTarget(target, attacker)) + return true; + + return false; + } } [Desc("Will not get automatically targeted by enemy (like walls)")]