From e3aa2dc6c04bb4b0ea644a35abbe1ac4aeb36ec9 Mon Sep 17 00:00:00 2001 From: Vapre Date: Thu, 4 Aug 2022 21:42:14 +0200 Subject: [PATCH] HitShape, query trait via actor cached targetable positions. --- OpenRA.Game/Actor.cs | 8 +++--- OpenRA.Mods.Common/Projectiles/Bullet.cs | 9 ++++--- OpenRA.Mods.Common/Warheads/DamageWarhead.cs | 10 +++++-- .../Warheads/SpreadDamageWarhead.cs | 26 +++++++++++++----- .../Warheads/TargetDamageWarhead.cs | 27 +++++++++++++------ OpenRA.Mods.Common/WorldExtensions.cs | 10 ++++--- 6 files changed, 61 insertions(+), 29 deletions(-) diff --git a/OpenRA.Game/Actor.cs b/OpenRA.Game/Actor.cs index ec766061e2..0f307bd16e 100644 --- a/OpenRA.Game/Actor.cs +++ b/OpenRA.Game/Actor.cs @@ -68,6 +68,7 @@ namespace OpenRA public IEffectiveOwner EffectiveOwner { get; } public IOccupySpace OccupiesSpace { get; } public ITargetable[] Targetables { get; } + public IEnumerable EnabledTargetablePositions { get; private set; } public bool IsIdle => CurrentActivity == null; public bool IsDead => Disposed || (health != null && health.IsDead); @@ -114,7 +115,6 @@ namespace OpenRA readonly IDefaultVisibility defaultVisibility; readonly INotifyBecomingIdle[] becomingIdles; readonly INotifyIdle[] tickIdles; - readonly IEnumerable enabledTargetablePositions; readonly IEnumerable enabledTargetableWorldPositions; bool created; @@ -192,8 +192,8 @@ namespace OpenRA tickIdles = tickIdlesList.ToArray(); Targetables = targetablesList.ToArray(); var targetablePositions = targetablePositionsList.ToArray(); - enabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled); - enabledTargetableWorldPositions = enabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this)); + EnabledTargetablePositions = targetablePositions.Where(Exts.IsTraitEnabled); + enabledTargetableWorldPositions = EnabledTargetablePositions.SelectMany(tp => tp.TargetablePositions(this)); SyncHashes = syncHashesList.ToArray(); } } @@ -530,7 +530,7 @@ namespace OpenRA public IEnumerable GetTargetablePositions() { - if (enabledTargetablePositions.Any()) + if (EnabledTargetablePositions.Any()) return enabledTargetableWorldPositions; return new[] { CenterPosition }; diff --git a/OpenRA.Mods.Common/Projectiles/Bullet.cs b/OpenRA.Mods.Common/Projectiles/Bullet.cs index 07bd528b9d..2abb050521 100644 --- a/OpenRA.Mods.Common/Projectiles/Bullet.cs +++ b/OpenRA.Mods.Common/Projectiles/Bullet.cs @@ -11,7 +11,6 @@ using System; using System.Collections.Generic; -using System.Linq; using OpenRA.GameRules; using OpenRA.Graphics; using OpenRA.Mods.Common.Effects; @@ -336,9 +335,11 @@ namespace OpenRA.Mods.Common.Projectiles continue; // If the impact position is within any actor's HitShape, we have a direct hit - var activeShapes = victim.TraitsImplementing().Where(Exts.IsTraitEnabled); - if (activeShapes.Any(i => i.DistanceFromEdge(victim, pos).Length <= 0)) - return true; + // PERF: Avoid using TraitsImplementing that needs to find the actor in the trait dictionary. + foreach (var targetPos in victim.EnabledTargetablePositions) + if (targetPos is HitShape h) + if (h.DistanceFromEdge(victim, pos).Length <= 0) + return true; } return false; diff --git a/OpenRA.Mods.Common/Warheads/DamageWarhead.cs b/OpenRA.Mods.Common/Warheads/DamageWarhead.cs index cc494640d6..5b20e95359 100644 --- a/OpenRA.Mods.Common/Warheads/DamageWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/DamageWarhead.cs @@ -50,8 +50,14 @@ namespace OpenRA.Mods.Common.Warheads if (!IsValidAgainst(victim, firedBy)) return; - var closestActiveShape = victim.TraitsImplementing().Where(Exts.IsTraitEnabled) - .MinByOrDefault(t => t.DistanceFromEdge(victim, victim.CenterPosition)); + // PERF: Avoid using TraitsImplementing that needs to find the actor in the trait dictionary. + var closestActiveShape = (HitShape)victim.EnabledTargetablePositions.MinByOrDefault(t => + { + if (t is HitShape h) + return h.DistanceFromEdge(victim, victim.CenterPosition); + else + return WDist.MaxValue; + }); // Cannot be damaged without an active HitShape if (closestActiveShape == null) diff --git a/OpenRA.Mods.Common/Warheads/SpreadDamageWarhead.cs b/OpenRA.Mods.Common/Warheads/SpreadDamageWarhead.cs index 52382dd565..1bf58cf8f4 100644 --- a/OpenRA.Mods.Common/Warheads/SpreadDamageWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/SpreadDamageWarhead.cs @@ -63,20 +63,32 @@ namespace OpenRA.Mods.Common.Warheads if (!IsValidAgainst(victim, firedBy)) continue; - var closestActiveShape = victim.TraitsImplementing() - .Where(Exts.IsTraitEnabled) - .Select(s => (HitShape: s, Distance: s.DistanceFromEdge(victim, pos))) - .MinByOrDefault(s => s.Distance); + HitShape closestActiveShape = null; + var closestDistance = int.MaxValue; + + // PERF: Avoid using TraitsImplementing that needs to find the actor in the trait dictionary. + foreach (var targetPos in victim.EnabledTargetablePositions) + { + if (targetPos is HitShape h) + { + var distance = h.DistanceFromEdge(victim, pos).Length; + if (distance < closestDistance) + { + closestDistance = distance; + closestActiveShape = h; + } + } + } // Cannot be damaged without an active HitShape. - if (closestActiveShape.HitShape == null) + if (closestActiveShape == null) continue; var falloffDistance = 0; switch (DamageCalculationType) { case DamageCalculationType.HitShape: - falloffDistance = closestActiveShape.Distance.Length; + falloffDistance = closestDistance; break; case DamageCalculationType.ClosestTargetablePosition: falloffDistance = victim.GetTargetablePositions().Select(x => (x - pos).Length).Min(); @@ -108,7 +120,7 @@ namespace OpenRA.Mods.Common.Warheads ImpactOrientation = impactOrientation, }; - InflictDamage(victim, firedBy, closestActiveShape.HitShape, updatedWarheadArgs); + InflictDamage(victim, firedBy, closestActiveShape, updatedWarheadArgs); } } diff --git a/OpenRA.Mods.Common/Warheads/TargetDamageWarhead.cs b/OpenRA.Mods.Common/Warheads/TargetDamageWarhead.cs index 43f3f83059..0a3ff598b9 100644 --- a/OpenRA.Mods.Common/Warheads/TargetDamageWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/TargetDamageWarhead.cs @@ -9,7 +9,6 @@ */ #endregion -using System.Linq; using OpenRA.GameRules; using OpenRA.Mods.Common.Traits; using OpenRA.Traits; @@ -36,20 +35,32 @@ namespace OpenRA.Mods.Common.Warheads if (!IsValidAgainst(victim, firedBy)) continue; - var closestActiveShape = victim.TraitsImplementing() - .Where(Exts.IsTraitEnabled) - .Select(s => (HitShape: s, Distance: s.DistanceFromEdge(victim, pos))) - .MinByOrDefault(s => s.Distance); + HitShape closestActiveShape = null; + var closestDistance = int.MaxValue; + + // PERF: Avoid using TraitsImplementing that needs to find the actor in the trait dictionary. + foreach (var targetPos in victim.EnabledTargetablePositions) + { + if (targetPos is HitShape hitshape) + { + var distance = hitshape.DistanceFromEdge(victim, pos).Length; + if (distance < closestDistance) + { + closestDistance = distance; + closestActiveShape = hitshape; + } + } + } // Cannot be damaged without an active HitShape. - if (closestActiveShape.HitShape == null) + if (closestActiveShape == null) continue; // Cannot be damaged if HitShape is outside Spread. - if (closestActiveShape.Distance > Spread) + if (closestDistance > Spread.Length) continue; - InflictDamage(victim, firedBy, closestActiveShape.HitShape, args); + InflictDamage(victim, firedBy, closestActiveShape, args); } } } diff --git a/OpenRA.Mods.Common/WorldExtensions.cs b/OpenRA.Mods.Common/WorldExtensions.cs index c6621d444a..b032fca83b 100644 --- a/OpenRA.Mods.Common/WorldExtensions.cs +++ b/OpenRA.Mods.Common/WorldExtensions.cs @@ -9,8 +9,8 @@ */ #endregion +using System; using System.Collections.Generic; -using System.Linq; using OpenRA.Mods.Common.Traits; namespace OpenRA.Mods.Common @@ -49,9 +49,11 @@ namespace OpenRA.Mods.Common foreach (var currActor in actorsInSquare) { var actorWidth = 0; - var shapes = currActor.TraitsImplementing().Where(Exts.IsTraitEnabled); - if (shapes.Any()) - actorWidth = shapes.Max(h => h.Info.Type.OuterRadius.Length); + + // PERF: Avoid using TraitsImplementing that needs to find the actor in the trait dictionary. + foreach (var targetPos in currActor.EnabledTargetablePositions) + if (targetPos is HitShape hitshape) + actorWidth = Math.Max(actorWidth, hitshape.Info.Type.OuterRadius.Length); var projection = lineStart.MinimumPointLineProjection(lineEnd, currActor.CenterPosition); var distance = (currActor.CenterPosition - projection).HorizontalLength;