diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index f46fb5b001..ad32fe1c65 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -110,6 +110,34 @@ namespace OpenRA.Traits // HACK: This provides a shim for legacy code until it can be rewritten public interface IDecorationBounds { Rectangle DecorationBounds(Actor self, WorldRenderer wr); } public interface IDecorationBoundsInfo : ITraitInfoInterface { } + public static class DecorationBoundsExtensions + { + public static Rectangle FirstNonEmptyBounds(this IEnumerable decorationBounds, Actor self, WorldRenderer wr) + { + // PERF: Avoid LINQ. + foreach (var decoration in decorationBounds) + { + var bounds = decoration.DecorationBounds(self, wr); + if (!bounds.IsEmpty) + return bounds; + } + + return Rectangle.Empty; + } + + public static Rectangle FirstNonEmptyBounds(this IDecorationBounds[] decorationBounds, Actor self, WorldRenderer wr) + { + // PERF: Avoid LINQ. + foreach (var decoration in decorationBounds) + { + var bounds = decoration.DecorationBounds(self, wr); + if (!bounds.IsEmpty) + return bounds; + } + + return Rectangle.Empty; + } + } public interface IIssueOrder { diff --git a/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs b/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs index 72f4ad9cd8..7349298854 100644 --- a/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs +++ b/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs @@ -163,10 +163,7 @@ namespace OpenRA.Mods.Cnc.Traits { if (unit.CanBeViewedByPlayer(manager.Self.Owner)) { - var bounds = unit.TraitsImplementing() - .Select(b => b.DecorationBounds(unit, wr)) - .FirstOrDefault(b => !b.IsEmpty); - + var bounds = unit.TraitsImplementing().FirstNonEmptyBounds(unit, wr); yield return new SelectionBoxRenderable(unit, bounds, Color.Red); } } @@ -281,10 +278,7 @@ namespace OpenRA.Mods.Cnc.Traits { if (unit.CanBeViewedByPlayer(manager.Self.Owner)) { - var bounds = unit.TraitsImplementing() - .Select(b => b.DecorationBounds(unit, wr)) - .FirstOrDefault(b => !b.IsEmpty); - + var bounds = unit.TraitsImplementing().FirstNonEmptyBounds(unit, wr); yield return new SelectionBoxRenderable(unit, bounds, Color.Red); } } diff --git a/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs b/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs index 4a519385f1..acc58cf584 100644 --- a/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs +++ b/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs @@ -50,7 +50,7 @@ namespace OpenRA.Mods.Common.Traits.Render public IEnumerable Render(Actor self, WorldRenderer wr) { - var bounds = decorationBounds.Select(b => b.DecorationBounds(self, wr)).FirstOrDefault(b => !b.IsEmpty); + var bounds = decorationBounds.FirstNonEmptyBounds(self, wr); var spaceBuffer = (int)(10 / wr.Viewport.Zoom); var effectPos = wr.ProjectedPosition(new int2((bounds.Left + bounds.Right) / 2, bounds.Y - spaceBuffer)); diff --git a/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs b/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs index 80da628de3..14b06bb0b2 100644 --- a/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs +++ b/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs @@ -89,7 +89,7 @@ namespace OpenRA.Mods.Common.Traits.Render var selected = self.World.Selection.Contains(self); var regularWorld = self.World.Type == WorldType.Regular; var statusBars = Game.Settings.Game.StatusBars; - var bounds = decorationBounds.Select(b => b.DecorationBounds(self, wr)).FirstOrDefault(b => !b.IsEmpty); + var bounds = decorationBounds.FirstNonEmptyBounds(self, wr); // Health bars are shown when: // * actor is selected diff --git a/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs index 7fb520e358..a070eabea4 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithDecoration.cs @@ -106,7 +106,7 @@ namespace OpenRA.Mods.Common.Traits.Render if (!ShouldRender(self) || self.World.FogObscures(self)) return Enumerable.Empty(); - var bounds = decorationBounds.Select(b => b.DecorationBounds(self, wr)).FirstOrDefault(b => !b.IsEmpty); + var bounds = decorationBounds.FirstNonEmptyBounds(self, wr); var halfSize = (0.5f * Anim.Image.Size.XY).ToInt2(); var boundsOffset = new int2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom) / 2; diff --git a/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs index 70e2187faf..542beb750b 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithSpriteControlGroupDecoration.cs @@ -72,7 +72,7 @@ namespace OpenRA.Mods.Common.Traits.Render pipImages.PlayFetchIndex(Info.GroupSequence, () => (int)group); - var bounds = decorationBounds.Select(b => b.DecorationBounds(self, wr)).FirstOrDefault(b => !b.IsEmpty); + var bounds = decorationBounds.FirstNonEmptyBounds(self, wr); var boundsOffset = 0.5f * new float2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom); if (Info.ReferencePoint.HasFlag(ReferencePoints.Top)) boundsOffset -= new float2(0, 0.5f * bounds.Height); diff --git a/OpenRA.Mods.Common/Traits/Render/WithTextControlGroupDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithTextControlGroupDecoration.cs index b35a119a62..01842143ea 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithTextControlGroupDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithTextControlGroupDecoration.cs @@ -87,7 +87,7 @@ namespace OpenRA.Mods.Common.Traits.Render if (group == null) yield break; - var bounds = decorationBounds.Select(b => b.DecorationBounds(self, wr)).FirstOrDefault(b => !b.IsEmpty); + var bounds = decorationBounds.FirstNonEmptyBounds(self, wr); var number = group.Value.ToString(); var halfSize = font.Measure(number) / 2; diff --git a/OpenRA.Mods.Common/Traits/Render/WithTextDecoration.cs b/OpenRA.Mods.Common/Traits/Render/WithTextDecoration.cs index 9e42a6e610..b72a4c49b1 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithTextDecoration.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithTextDecoration.cs @@ -105,7 +105,7 @@ namespace OpenRA.Mods.Common.Traits.Render if (!ShouldRender(self) || self.World.FogObscures(self)) return Enumerable.Empty(); - var bounds = decorationBounds.Select(b => b.DecorationBounds(self, wr)).FirstOrDefault(b => !b.IsEmpty); + var bounds = decorationBounds.FirstNonEmptyBounds(self, wr); var halfSize = font.Measure(Info.Text) / 2; var boundsOffset = new int2(bounds.Left + bounds.Right, bounds.Top + bounds.Bottom) / 2; diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/GrantExternalConditionPower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/GrantExternalConditionPower.cs index 1ba5f7d01b..db744be692 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/GrantExternalConditionPower.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/GrantExternalConditionPower.cs @@ -138,9 +138,7 @@ namespace OpenRA.Mods.Common.Traits var xy = wr.Viewport.ViewToWorld(Viewport.LastMousePos); foreach (var unit in power.UnitsInRange(xy)) { - var bounds = unit.TraitsImplementing() - .Select(b => b.DecorationBounds(unit, wr)) - .FirstOrDefault(b => !b.IsEmpty); + var bounds = unit.TraitsImplementing().FirstNonEmptyBounds(unit, wr); yield return new SelectionBoxRenderable(unit, bounds, Color.Red); } } diff --git a/OpenRA.Mods.Common/Widgets/WorldInteractionControllerWidget.cs b/OpenRA.Mods.Common/Widgets/WorldInteractionControllerWidget.cs index dae1963451..5e6fcc4354 100644 --- a/OpenRA.Mods.Common/Widgets/WorldInteractionControllerWidget.cs +++ b/OpenRA.Mods.Common/Widgets/WorldInteractionControllerWidget.cs @@ -51,10 +51,7 @@ namespace OpenRA.Mods.Common.Widgets // TODO: Integrate this with SelectionDecorations to unhardcode the *Renderable if (unit.Info.HasTraitInfo()) { - var bounds = unit.TraitsImplementing() - .Select(b => b.DecorationBounds(unit, worldRenderer)) - .FirstOrDefault(b => !b.IsEmpty); - + var bounds = unit.TraitsImplementing().FirstNonEmptyBounds(unit, worldRenderer); new SelectionBarsRenderable(unit, bounds, true, true).Render(worldRenderer); } }