diff --git a/OpenRA.Game/Network/SyncReport.cs b/OpenRA.Game/Network/SyncReport.cs index 48cdecc2ad..e1f45c9d55 100644 --- a/OpenRA.Game/Network/SyncReport.cs +++ b/OpenRA.Game/Network/SyncReport.cs @@ -166,8 +166,10 @@ namespace OpenRA.Network struct TypeInfo { - static ParameterExpression syncParam = Expression.Parameter(typeof(ISync), "sync"); - static ConstantExpression nullString = Expression.Constant(null, typeof(string)); + static readonly ParameterExpression SyncParam = Expression.Parameter(typeof(ISync), "sync"); + static readonly ConstantExpression NullString = Expression.Constant(null, typeof(string)); + static readonly ConstantExpression TrueString = Expression.Constant(bool.TrueString, typeof(string)); + static readonly ConstantExpression FalseString = Expression.Constant(bool.FalseString, typeof(string)); public readonly Func[] SerializableCopyOfMemberFunctions; public readonly string[] Names; @@ -184,7 +186,7 @@ namespace OpenRA.Network "Properties using the Sync attribute must be readable and must not use index parameters.\n" + "Invalid Property: " + prop.DeclaringType.FullName + "." + prop.Name); - var sync = Expression.Convert(syncParam, type); + var sync = Expression.Convert(SyncParam, type); SerializableCopyOfMemberFunctions = fields .Select(fi => SerializableCopyOfMember(Expression.Field(sync, fi), fi.FieldType, fi.Name)) .Concat(properties.Select(pi => SerializableCopyOfMember(Expression.Property(sync, pi), pi.PropertyType, pi.Name))) @@ -203,8 +205,16 @@ namespace OpenRA.Network // just box a copy of the current value into an object. This is faster than calling ToString. We // can call ToString later when we generate the report. Most of the time, the sync report is never // generated so we successfully avoid the overhead to calling ToString. + if (memberType == typeof(bool)) + { + // PERF: If the member is a Boolean, we can also avoid the allocation caused by boxing it. + // Instead, we can just return the resulting strings directly. + var getBoolString = Expression.Condition(getMember, TrueString, FalseString); + return Expression.Lambda>(getBoolString, name, new[] { SyncParam }).Compile(); + } + var boxedCopy = Expression.Convert(getMember, typeof(object)); - return Expression.Lambda>(boxedCopy, name, new[] { syncParam }).Compile(); + return Expression.Lambda>(boxedCopy, name, new[] { SyncParam }).Compile(); } // For reference types, we have to call ToString right away to get a snapshot of the value. We cannot @@ -231,10 +241,10 @@ namespace OpenRA.Network var member = Expression.Block(new[] { memberVariable }, assignMemberVariable); getString = Expression.Call(member, toString); var nullMember = Expression.Constant(null, memberType); - getString = Expression.Condition(Expression.Equal(member, nullMember), nullString, getString); + getString = Expression.Condition(Expression.Equal(member, nullMember), NullString, getString); } - return Expression.Lambda>(getString, name, new[] { syncParam }).Compile(); + return Expression.Lambda>(getString, name, new[] { SyncParam }).Compile(); } } } diff --git a/OpenRA.Mods.Common/Traits/AutoTarget.cs b/OpenRA.Mods.Common/Traits/AutoTarget.cs index a51d95554f..f112c31479 100644 --- a/OpenRA.Mods.Common/Traits/AutoTarget.cs +++ b/OpenRA.Mods.Common/Traits/AutoTarget.cs @@ -52,7 +52,7 @@ namespace OpenRA.Mods.Common.Traits public class AutoTarget : ConditionalTrait, INotifyIdle, INotifyDamage, ITick, IResolveOrder, ISync { - readonly AttackBase[] attackBases; + readonly IEnumerable activeAttackBases; readonly AttackFollow[] attackFollows; [Sync] int nextScanTime = 0; @@ -67,7 +67,7 @@ namespace OpenRA.Mods.Common.Traits : base(info) { var self = init.Self; - attackBases = self.TraitsImplementing().ToArray(); + activeAttackBases = self.TraitsImplementing().ToArray().Where(Exts.IsTraitEnabled); if (init.Contains()) Stance = init.Get(); @@ -105,7 +105,8 @@ namespace OpenRA.Mods.Common.Traits } // not a lot we can do about things we can't hurt... although maybe we should automatically run away? - if (attackBases.All(a => a.IsTraitDisabled || !a.HasAnyValidWeapons(Target.FromActor(attacker)))) + var attackerAsTarget = Target.FromActor(attacker); + if (!activeAttackBases.Any(a => a.HasAnyValidWeapons(attackerAsTarget))) return; // don't retaliate against own units force-firing on us. It's usually not what the player wanted. @@ -117,8 +118,9 @@ namespace OpenRA.Mods.Common.Traits return; Aggressor = attacker; - var allowMove = Info.AllowMovement && Stance != UnitStance.Defend; - if (attackFollows.All(a => a.IsTraitDisabled || !a.IsReachableTarget(a.Target, allowMove))) + + bool allowMove; + if (ShouldAttack(out allowMove)) Attack(self, Aggressor, allowMove); } @@ -130,11 +132,23 @@ namespace OpenRA.Mods.Common.Traits if (Stance < UnitStance.Defend || !Info.TargetWhenIdle) return; - var allowMove = Info.AllowMovement && Stance != UnitStance.Defend; - if (attackFollows.All(a => a.IsTraitDisabled || !a.IsReachableTarget(a.Target, allowMove))) + bool allowMove; + if (ShouldAttack(out allowMove)) ScanAndAttack(self, allowMove); } + bool ShouldAttack(out bool allowMove) + { + allowMove = Info.AllowMovement && Stance != UnitStance.Defend; + + // PERF: Avoid LINQ. + foreach (var attackFollow in attackFollows) + if (!attackFollow.IsTraitDisabled && attackFollow.IsReachableTarget(attackFollow.Target, allowMove)) + return false; + + return true; + } + public void Tick(Actor self) { if (IsTraitDisabled) @@ -146,8 +160,7 @@ namespace OpenRA.Mods.Common.Traits public Actor ScanForTarget(Actor self, bool allowMove) { - var activeAttackBases = attackBases.Where(Exts.IsTraitEnabled); - if (activeAttackBases.Any() && nextScanTime <= 0) + if (nextScanTime <= 0 && activeAttackBases.Any()) { nextScanTime = self.World.SharedRandom.Next(Info.MinimumScanTimeInterval, Info.MaximumScanTimeInterval); @@ -160,8 +173,6 @@ namespace OpenRA.Mods.Common.Traits var range = Info.ScanRadius > 0 ? WDist.FromCells(Info.ScanRadius) : ab.GetMaximumRange(); return ChooseTarget(self, ab, attackStances, range, allowMove); } - - continue; } } @@ -181,7 +192,6 @@ namespace OpenRA.Mods.Common.Traits var target = Target.FromActor(targetActor); self.SetTargetLine(target, Color.Red, false); - var activeAttackBases = attackBases.Where(Exts.IsTraitEnabled); foreach (var ab in activeAttackBases) ab.AttackTarget(target, false, allowMove); } diff --git a/OpenRA.Mods.Common/Traits/PaletteEffects/RotationPaletteEffect.cs b/OpenRA.Mods.Common/Traits/PaletteEffects/RotationPaletteEffect.cs index 21ba9a0419..258f89f358 100644 --- a/OpenRA.Mods.Common/Traits/PaletteEffects/RotationPaletteEffect.cs +++ b/OpenRA.Mods.Common/Traits/PaletteEffects/RotationPaletteEffect.cs @@ -92,8 +92,8 @@ namespace OpenRA.Mods.Common.Traits foreach (var kvp in palettes) { - if ((info.Palettes.Count > 0 && !info.Palettes.Any(kvp.Key.StartsWith)) - || (info.ExcludePalettes.Count > 0 && info.ExcludePalettes.Any(kvp.Key.StartsWith))) + if ((info.Palettes.Count > 0 && !AnyPaletteNameStartsWith(info.Palettes, kvp.Key)) + || (info.ExcludePalettes.Count > 0 && AnyPaletteNameStartsWith(info.ExcludePalettes, kvp.Key))) continue; var palette = kvp.Value; @@ -105,5 +105,15 @@ namespace OpenRA.Mods.Common.Traits palette[info.RotationBase + i] = rotationBuffer[i]; } } + + static bool AnyPaletteNameStartsWith(HashSet names, string prefix) + { + // PERF: Avoid LINQ. + foreach (var name in names) + if (name.StartsWith(prefix)) + return true; + + return false; + } } } diff --git a/OpenRA.Mods.Common/Traits/Radar/AppearsOnRadar.cs b/OpenRA.Mods.Common/Traits/Radar/AppearsOnRadar.cs index c846c9fcd9..c583986495 100644 --- a/OpenRA.Mods.Common/Traits/Radar/AppearsOnRadar.cs +++ b/OpenRA.Mods.Common/Traits/Radar/AppearsOnRadar.cs @@ -9,6 +9,7 @@ */ #endregion +using System; using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -29,6 +30,9 @@ namespace OpenRA.Mods.Common.Traits.Radar readonly AppearsOnRadarInfo info; IRadarColorModifier modifier; + Color currentColor = Color.Transparent; + Func, Pair> cellToSignature; + public AppearsOnRadar(AppearsOnRadarInfo info) { this.info = info; @@ -48,7 +52,14 @@ namespace OpenRA.Mods.Common.Traits.Radar if (info.UseLocation) return new[] { Pair.New(self.Location, color) }; - return self.OccupiesSpace.OccupiedCells().Select(c => Pair.New(c.First, color)); + // PERF: Cache cellToSignature delegate to avoid allocations as color does not change often. + if (currentColor != color) + { + currentColor = color; + cellToSignature = c => Pair.New(c.First, color); + } + + return self.OccupiesSpace.OccupiedCells().Select(cellToSignature); } } } \ No newline at end of file diff --git a/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs b/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs index 443d58a400..95ae0da122 100644 --- a/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs +++ b/OpenRA.Mods.Common/Traits/Render/SelectionDecorations.cs @@ -41,7 +41,7 @@ namespace OpenRA.Mods.Common.Traits.Render public int[] SelectionBoxBounds { get { return VisualBounds; } } } - public class SelectionDecorations : IRenderAboveShroud, ITick + public class SelectionDecorations : IRenderAboveShroud, INotifyCreated, ITick { // depends on the order of pips in TraitsInterfaces.cs! static readonly string[] PipStrings = { "pip-empty", "pip-green", "pip-yellow", "pip-red", "pip-gray", "pip-blue", "pip-ammo", "pip-ammoempty" }; @@ -49,6 +49,7 @@ namespace OpenRA.Mods.Common.Traits.Render public readonly SelectionDecorationsInfo Info; readonly Animation pipImages; + IPips[] pipSources; public SelectionDecorations(Actor self, SelectionDecorationsInfo info) { @@ -57,6 +58,11 @@ namespace OpenRA.Mods.Common.Traits.Render pipImages = new Animation(self.World, Info.Image); } + void INotifyCreated.Created(Actor self) + { + pipSources = self.TraitsImplementing().ToArray(); + } + IEnumerable ActivityTargetPath(Actor self) { if (!self.IsInWorld || self.IsDead) @@ -76,8 +82,13 @@ namespace OpenRA.Mods.Common.Traits.Render IEnumerable IRenderAboveShroud.RenderAboveShroud(Actor self, WorldRenderer wr) { if (self.World.FogObscures(self)) - yield break; + return Enumerable.Empty(); + return DrawDecorations(self, wr); + } + + IEnumerable DrawDecorations(Actor self, WorldRenderer wr) + { var selected = self.World.Selection.Contains(self); var regularWorld = self.World.Type == WorldType.Regular; var statusBars = Game.Settings.Game.StatusBars; @@ -108,21 +119,25 @@ namespace OpenRA.Mods.Common.Traits.Render if (self.World.LocalPlayer != null && self.World.LocalPlayer.PlayerActor.Trait().PathDebug) yield return new TargetLineRenderable(ActivityTargetPath(self), Color.Green); + foreach (var r in DrawPips(self, wr)) + yield return r; + } + + IEnumerable DrawPips(Actor self, WorldRenderer wr) + { + if (pipSources.Length == 0) + return Enumerable.Empty(); + var b = self.VisualBounds; var pos = wr.ScreenPxPosition(self.CenterPosition); var bl = wr.Viewport.WorldToViewPx(pos + new int2(b.Left, b.Bottom)); var pal = wr.Palette(Info.Palette); - foreach (var r in DrawPips(self, wr, bl, pal)) - yield return r; + return DrawPips(self, bl, pal); } - IEnumerable DrawPips(Actor self, WorldRenderer wr, int2 basePosition, PaletteReference palette) + IEnumerable DrawPips(Actor self, int2 basePosition, PaletteReference palette) { - var pipSources = self.TraitsImplementing(); - if (!pipSources.Any()) - yield break; - pipImages.PlayRepeating(PipStrings[0]); var pipSize = pipImages.Image.Size.XY.ToInt2();