From 912a0630e20d973ce87abad0e29db85923311e67 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Sat, 28 Nov 2015 20:55:46 +0000 Subject: [PATCH 1/4] Tweak some Render methods for efficiency. --- OpenRA.Mods.Common/Effects/Contrail.cs | 2 +- OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs | 2 +- OpenRA.Mods.Common/Traits/Render/RenderVoxels.cs | 4 ++-- OpenRA.Mods.Common/Traits/Render/WithParachute.cs | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/OpenRA.Mods.Common/Effects/Contrail.cs b/OpenRA.Mods.Common/Effects/Contrail.cs index 8a9c32b495..04c952c488 100644 --- a/OpenRA.Mods.Common/Effects/Contrail.cs +++ b/OpenRA.Mods.Common/Effects/Contrail.cs @@ -61,7 +61,7 @@ namespace OpenRA.Mods.Common.Effects public IEnumerable Render(Actor self, WorldRenderer wr) { - yield return trail; + return new IRenderable[] { trail }; } } } diff --git a/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs b/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs index 2683c5744c..839fcff764 100644 --- a/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs +++ b/OpenRA.Mods.Common/Traits/Render/RenderNameTag.cs @@ -51,7 +51,7 @@ namespace OpenRA.Mods.Common.Traits var spaceBuffer = (int)(10 / wr.Viewport.Zoom); var effectPos = wr.ProjectedPosition(new int2(pos.X, bounds.Y - spaceBuffer)); - yield return new TextRenderable(font, effectPos, 0, color, name); + return new IRenderable[] { new TextRenderable(font, effectPos, 0, color, name) }; } } } diff --git a/OpenRA.Mods.Common/Traits/Render/RenderVoxels.cs b/OpenRA.Mods.Common/Traits/Render/RenderVoxels.cs index c37640100f..243963c12d 100644 --- a/OpenRA.Mods.Common/Traits/Render/RenderVoxels.cs +++ b/OpenRA.Mods.Common/Traits/Render/RenderVoxels.cs @@ -103,10 +103,10 @@ namespace OpenRA.Mods.Common.Traits initializePalettes = false; } - yield return new VoxelRenderable( + return new IRenderable[] { new VoxelRenderable( components, self.CenterPosition, 0, camera, info.Scale, lightSource, info.LightAmbientColor, info.LightDiffuseColor, - colorPalette, normalsPalette, shadowPalette); + colorPalette, normalsPalette, shadowPalette) }; } public string Image { get { return info.Image ?? self.Info.Name; } } diff --git a/OpenRA.Mods.Common/Traits/Render/WithParachute.cs b/OpenRA.Mods.Common/Traits/Render/WithParachute.cs index 55f0a6ac4c..984722515f 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithParachute.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithParachute.cs @@ -134,20 +134,20 @@ namespace OpenRA.Mods.Common.Traits public IEnumerable Render(Actor self, WorldRenderer wr) { if (info.ShadowImage == null) - yield break; + return Enumerable.Empty(); if (IsTraitDisabled) - yield break; + return Enumerable.Empty(); if (self.IsDead || !self.IsInWorld) - yield break; + return Enumerable.Empty(); if (self.World.FogObscures(self)) - yield break; + return Enumerable.Empty(); shadow.Tick(); var pos = self.CenterPosition - new WVec(0, 0, self.CenterPosition.Z); - yield return new SpriteRenderable(shadow.Image, pos, info.ShadowOffset, info.ShadowZOffset, wr.Palette(info.ShadowPalette), 1, true); + return new IRenderable[] { new SpriteRenderable(shadow.Image, pos, info.ShadowOffset, info.ShadowZOffset, wr.Palette(info.ShadowPalette), 1, true) }; } } } From c0286bb1470b2f836f810893bcc67c4f66f62c07 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Sat, 28 Nov 2015 21:42:06 +0000 Subject: [PATCH 2/4] Remove ActorMap.ActorsInWorld. Prefer the more direct and efficient query on World.Actors instead. --- OpenRA.Game/Traits/World/ActorMap.cs | 5 ----- OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs | 2 +- OpenRA.Mods.Common/Scripting/Properties/PlayerProperties.cs | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/OpenRA.Game/Traits/World/ActorMap.cs b/OpenRA.Game/Traits/World/ActorMap.cs index 42a731d692..2180415721 100644 --- a/OpenRA.Game/Traits/World/ActorMap.cs +++ b/OpenRA.Game/Traits/World/ActorMap.cs @@ -559,10 +559,5 @@ namespace OpenRA.Traits } } } - - public IEnumerable ActorsInWorld() - { - return bins.SelectMany(bin => bin.Actors.Where(actor => actor.IsInWorld)); - } } } diff --git a/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs b/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs index c581301a32..fcac4664c0 100644 --- a/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs +++ b/OpenRA.Game/Widgets/WorldInteractionControllerWidget.cs @@ -291,7 +291,7 @@ namespace OpenRA.Widgets static IEnumerable SelectActorsInWorld(World world, IEnumerable selectionClasses, Player player) { - return SelectActorsByOwnerAndSelectionClass(world.ActorMap.ActorsInWorld(), player, selectionClasses); + return SelectActorsByOwnerAndSelectionClass(world.Actors.Where(a => a.IsInWorld), player, selectionClasses); } static IEnumerable SelectActorsByOwnerAndSelectionClass(IEnumerable actors, Player owner, IEnumerable selectionClasses) diff --git a/OpenRA.Mods.Common/Scripting/Properties/PlayerProperties.cs b/OpenRA.Mods.Common/Scripting/Properties/PlayerProperties.cs index cdaddd042c..2e335c06c9 100644 --- a/OpenRA.Mods.Common/Scripting/Properties/PlayerProperties.cs +++ b/OpenRA.Mods.Common/Scripting/Properties/PlayerProperties.cs @@ -78,7 +78,7 @@ namespace OpenRA.Mods.Common.Scripting if (!Context.World.Map.Rules.Actors.TryGetValue(type, out ai)) throw new LuaException("Unknown actor type '{0}'".F(type)); - result.AddRange(Player.World.ActorMap.ActorsInWorld() + result.AddRange(Player.World.Actors .Where(actor => actor.Owner == Player && !actor.IsDead && actor.IsInWorld && actor.Info.Name == ai.Name)); return result.ToArray(); From aaa82339d10e7609633c24f57123627db4a3b51e Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Sat, 28 Nov 2015 21:42:51 +0000 Subject: [PATCH 3/4] Small cleanup in TraitDictionary. --- OpenRA.Game/TraitDictionary.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/OpenRA.Game/TraitDictionary.cs b/OpenRA.Game/TraitDictionary.cs index b8fa17ef1b..891ac5c055 100644 --- a/OpenRA.Game/TraitDictionary.cs +++ b/OpenRA.Game/TraitDictionary.cs @@ -36,19 +36,14 @@ namespace OpenRA class TraitDictionary { - // construct this delegate once. - static Func doCreateTraitContainer = CreateTraitContainer; - static ITraitContainer CreateTraitContainer(Type t) - { - return (ITraitContainer)typeof(TraitContainer<>).MakeGenericType(t) - .GetConstructor(Type.EmptyTypes).Invoke(null); - } + static readonly Func CreateTraitContainer = t => + (ITraitContainer)typeof(TraitContainer<>).MakeGenericType(t).GetConstructor(Type.EmptyTypes).Invoke(null); - Dictionary traits = new Dictionary(); + readonly Dictionary traits = new Dictionary(); ITraitContainer InnerGet(Type t) { - return traits.GetOrAdd(t, doCreateTraitContainer); + return traits.GetOrAdd(t, CreateTraitContainer); } TraitContainer InnerGet() From b0619a3e25818a29330edd34d4cbbeba13fe3cb2 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Fri, 4 Dec 2015 19:38:20 +0000 Subject: [PATCH 4/4] Added comments in performance sensitive code. --- OpenRA.Game/Actor.cs | 14 ++++++++++++++ OpenRA.Game/Exts.cs | 1 + OpenRA.Game/Graphics/SpriteFont.cs | 1 + OpenRA.Game/Graphics/Viewport.cs | 1 + OpenRA.Game/Map/Map.cs | 2 +- OpenRA.Game/Network/SyncReport.cs | 9 ++++++++- OpenRA.Game/Renderer.cs | 1 + OpenRA.Game/TraitDictionary.cs | 5 +++++ OpenRA.Game/Traits/Player/FrozenActorLayer.cs | 7 +++---- OpenRA.Game/Traits/TraitsInterfaces.cs | 7 ++++++- OpenRA.Game/Traits/Util.cs | 7 ++++++- OpenRA.Game/Traits/World/ActorMap.cs | 4 +++- OpenRA.Game/Widgets/Widget.cs | 1 + OpenRA.Game/Widgets/WidgetUtils.cs | 7 ++++++- OpenRA.Game/WorldUtils.cs | 5 ++++- OpenRA.Mods.Common/Pathfinder/PathSearch.cs | 2 ++ OpenRA.Mods.Common/ShroudExts.cs | 2 ++ OpenRA.Mods.Common/Traits/Mobile.cs | 5 +++++ .../Traits/Modifiers/FrozenUnderFog.cs | 1 + .../Traits/Player/ClassicProductionQueue.cs | 1 + OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs | 2 ++ OpenRA.Mods.Common/Widgets/RadarWidget.cs | 2 ++ 22 files changed, 76 insertions(+), 11 deletions(-) diff --git a/OpenRA.Game/Actor.cs b/OpenRA.Game/Actor.cs index 1cf4b8fcdb..8407a315f1 100644 --- a/OpenRA.Game/Actor.cs +++ b/OpenRA.Game/Actor.cs @@ -97,6 +97,9 @@ namespace OpenRA } } + // PERF: Cache all these traits as soon as the actor is created. This is a fairly cheap one-off cost per + // actor that allows us to provide some fast implementations of commonly used methods that are relied on by + // performance-sensitive parts of the core game engine, such as pathfinding, visibility and rendering. Bounds = DetermineBounds(); VisualBounds = DetermineVisualBounds(); EffectiveOwner = TraitOrDefault(); @@ -149,6 +152,7 @@ namespace OpenRA public IEnumerable Render(WorldRenderer wr) { + // PERF: Avoid LINQ. var renderables = Renderables(wr); foreach (var modifier in renderModifiers) renderables = modifier.ModifyRender(this, wr, renderables); @@ -157,6 +161,13 @@ namespace OpenRA IEnumerable Renderables(WorldRenderer wr) { + // PERF: Avoid LINQ. + // Implementations of Render are permitted to return both an eagerly materialized collection or a lazily + // generated sequence. + // For large amounts of renderables, a lazily generated sequence (e.g. as returned by LINQ, or by using + // `yield`) will avoid the need to allocate a large collection. + // For small amounts of renderables, allocating a small collection can often be faster and require less + // memory than creating the objects needed to represent a sequence. foreach (var render in renders) foreach (var renderable in render.Render(this, wr)) yield return renderable; @@ -206,6 +217,7 @@ namespace OpenRA public override string ToString() { + // PERF: Avoid format strings. var name = Info.Name + " " + ActorID; if (!IsInWorld) name += " (not in world)"; @@ -305,6 +317,7 @@ namespace OpenRA public bool IsDisabled() { + // PERF: Avoid LINQ. foreach (var disable in disables) if (disable.Disabled) return true; @@ -313,6 +326,7 @@ namespace OpenRA public bool CanBeViewedByPlayer(Player player) { + // PERF: Avoid LINQ. foreach (var visibilityModifier in visibilityModifiers) if (!visibilityModifier.IsVisible(this, player)) return false; diff --git a/OpenRA.Game/Exts.cs b/OpenRA.Game/Exts.cs index 6acf22c314..950a088ae6 100644 --- a/OpenRA.Game/Exts.cs +++ b/OpenRA.Game/Exts.cs @@ -113,6 +113,7 @@ namespace OpenRA public static bool HasModifier(this Modifiers k, Modifiers mod) { + // PERF: Enum.HasFlag is slower and requires allocations. return (k & mod) == mod; } diff --git a/OpenRA.Game/Graphics/SpriteFont.cs b/OpenRA.Game/Graphics/SpriteFont.cs index 6cf352dbf5..2d719d0ee6 100644 --- a/OpenRA.Game/Graphics/SpriteFont.cs +++ b/OpenRA.Game/Graphics/SpriteFont.cs @@ -40,6 +40,7 @@ namespace OpenRA.Graphics glyphs = new Cache, GlyphInfo>(CreateGlyph, Pair.EqualityComparer); + // PERF: Cache these delegates for Measure calls. Func characterWidth = character => glyphs[Pair.New(character, Color.White)].Advance; lineWidth = line => line.Sum(characterWidth); diff --git a/OpenRA.Game/Graphics/Viewport.cs b/OpenRA.Game/Graphics/Viewport.cs index 98d6466f03..6052ff3a76 100644 --- a/OpenRA.Game/Graphics/Viewport.cs +++ b/OpenRA.Game/Graphics/Viewport.cs @@ -22,6 +22,7 @@ namespace OpenRA.Graphics { public static bool Includes(this ScrollDirection d, ScrollDirection s) { + // PERF: Enum.HasFlag is slower and requires allocations. return (d & s) == s; } diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 517e62f24b..19bdf5e802 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -1023,7 +1023,7 @@ namespace OpenRA var uv = cell.ToMPos(this); var terrainIndex = cachedTerrainIndexes[uv]; - // Cache terrain indexes per cell on demand. + // PERF: Cache terrain indexes per cell on demand. if (terrainIndex == InvalidCachedTerrainIndex) { var custom = CustomTerrain[uv]; diff --git a/OpenRA.Game/Network/SyncReport.cs b/OpenRA.Game/Network/SyncReport.cs index 4b737adf28..c3a2f0fbd3 100644 --- a/OpenRA.Game/Network/SyncReport.cs +++ b/OpenRA.Game/Network/SyncReport.cs @@ -196,13 +196,20 @@ namespace OpenRA.Network static Func SerializableCopyOfMember(MemberExpression getMember, Type memberType, string name) { + // We need to serialize a copy of the current value so if the sync report is generated, the values can + // be dumped as strings. if (memberType.IsValueType) { - // We can get a copy just by accessing the member since it is a value type. + // PERF: For value types we can avoid the overhead of calling ToString immediately. We can instead + // 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. var boxedCopy = Expression.Convert(getMember, typeof(object)); 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 + // delay, as calling ToString later may produce different results. return MemberToString(getMember, memberType, name); } diff --git a/OpenRA.Game/Renderer.cs b/OpenRA.Game/Renderer.cs index 6e01b39813..b41297e71b 100644 --- a/OpenRA.Game/Renderer.cs +++ b/OpenRA.Game/Renderer.cs @@ -111,6 +111,7 @@ namespace OpenRA public void SetViewportParams(int2 scroll, float zoom) { + // PERF: Calling SetViewportParams on each renderer is slow. Only call it when things change. var resolutionChanged = lastResolution != Resolution; if (resolutionChanged) { diff --git a/OpenRA.Game/TraitDictionary.cs b/OpenRA.Game/TraitDictionary.cs index 891ac5c055..a9201fc085 100644 --- a/OpenRA.Game/TraitDictionary.cs +++ b/OpenRA.Game/TraitDictionary.cs @@ -34,6 +34,9 @@ namespace OpenRA } } + /// + /// Provides efficient ways to query a set of actors by their traits. + /// class TraitDictionary { static readonly Func CreateTraitContainer = t => @@ -161,6 +164,7 @@ namespace OpenRA public IEnumerable GetMultiple(uint actor) { + // PERF: Custom enumerator for efficiency - using `yield` is slower. ++Queries; return new MultipleEnumerable(this, actor); } @@ -197,6 +201,7 @@ namespace OpenRA public IEnumerable> All() { + // PERF: Custom enumerator for efficiency - using `yield` is slower. ++Queries; return new AllEnumerable(this); } diff --git a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs index 1e8a611385..03d80b0277 100644 --- a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs +++ b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs @@ -86,10 +86,9 @@ namespace OpenRA.Traits { var wasVisible = Visible; Shrouded = true; - - // We are doing the following LINQ manually for performance since this is a hot path. - // Visible = !Footprint.Any(shroud.IsVisible); Visible = true; + + // PERF: Avoid LINQ. foreach (var puv in Footprint) { if (shroud.IsVisible(puv)) @@ -142,7 +141,7 @@ namespace OpenRA.Traits public bool ShouldBeRemoved(Player owner) { - // We use a loop here for performance reasons + // PERF: Avoid LINQ. foreach (var rfa in removeFrozenActors) if (rfa.RemoveActor(actor, owner)) return true; diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index c1fdbeda35..027d4e7c7c 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -50,6 +50,7 @@ namespace OpenRA.Traits { public static bool HasStance(this Stance s, Stance stance) { + // PERF: Enum.HasFlag is slower and requires allocations. return (s & stance) == stance; } } @@ -92,7 +93,11 @@ namespace OpenRA.Traits public static class TargetModifiersExts { - public static bool HasModifier(this TargetModifiers self, TargetModifiers m) { return (self & m) == m; } + public static bool HasModifier(this TargetModifiers self, TargetModifiers m) + { + // PERF: Enum.HasFlag is slower and requires allocations. + return (self & m) == m; + } } public interface IOrderTargeter diff --git a/OpenRA.Game/Traits/Util.cs b/OpenRA.Game/Traits/Util.cs index 3757c69dda..4d4bf7170f 100644 --- a/OpenRA.Game/Traits/Util.cs +++ b/OpenRA.Game/Traits/Util.cs @@ -74,10 +74,15 @@ namespace OpenRA.Traits public static Activity RunActivity(Actor self, Activity act) { + // PERF: If there are no activities we can bail straight away and save ourselves a call to + // Stopwatch.GetTimestamp. if (act == null) return act; - // Note - manual iteration here for performance due to high call volume. + // PERF: This is a hot path and must run with minimal added overhead. + // Calling Stopwatch.GetTimestamp is a bit expensive, so we enumerate manually to allow us to call it only + // once per iteration in the normal case. + // See also: DoTimed var longTickThresholdInStopwatchTicks = PerfTimer.LongTickThresholdInStopwatchTicks; var start = Stopwatch.GetTimestamp(); while (act != null) diff --git a/OpenRA.Game/Traits/World/ActorMap.cs b/OpenRA.Game/Traits/World/ActorMap.cs index 2180415721..7448417cba 100644 --- a/OpenRA.Game/Traits/World/ActorMap.cs +++ b/OpenRA.Game/Traits/World/ActorMap.cs @@ -183,7 +183,7 @@ namespace OpenRA.Traits for (var col = 0; col < cols; col++) bins[row * cols + col] = new Bin(); - // Cache this delegate so it does not have to be allocated repeatedly. + // PERF: Cache this delegate so it does not have to be allocated repeatedly. actorShouldBeRemoved = removeActorPosition.Contains; } @@ -219,6 +219,7 @@ namespace OpenRA.Traits public IEnumerable GetActorsAt(CPos a) { + // PERF: Custom enumerator for efficiency - using `yield` is slower. var uv = a.ToMPos(map); if (!influence.Contains(uv)) return Enumerable.Empty(); @@ -534,6 +535,7 @@ namespace OpenRA.Traits public IEnumerable ActorsInBox(WPos a, WPos b) { + // PERF: Inline BinsInBox here to avoid allocations as this method is called often. var left = Math.Min(a.X, b.X); var top = Math.Min(a.Y, b.Y); var right = Math.Max(a.X, b.X); diff --git a/OpenRA.Game/Widgets/Widget.cs b/OpenRA.Game/Widgets/Widget.cs index c2e178a939..410c11a161 100644 --- a/OpenRA.Game/Widgets/Widget.cs +++ b/OpenRA.Game/Widgets/Widget.cs @@ -261,6 +261,7 @@ namespace OpenRA.Widgets public virtual Rectangle GetEventBounds() { + // PERF: Avoid LINQ. var bounds = EventBounds; foreach (var child in Children) if (child.IsVisible()) diff --git a/OpenRA.Game/Widgets/WidgetUtils.cs b/OpenRA.Game/Widgets/WidgetUtils.cs index fbb5055e52..8743737afa 100644 --- a/OpenRA.Game/Widgets/WidgetUtils.cs +++ b/OpenRA.Game/Widgets/WidgetUtils.cs @@ -79,7 +79,12 @@ namespace OpenRA.Widgets return new[] { (int)ss[0].Size.Y, (int)ss[1].Size.Y, (int)ss[2].Size.X, (int)ss[3].Size.X }; } - static bool HasFlags(this PanelSides a, PanelSides b) { return (a & b) == b; } + static bool HasFlags(this PanelSides a, PanelSides b) + { + // PERF: Enum.HasFlag is slower and requires allocations. + return (a & b) == b; + } + public static Rectangle InflateBy(this Rectangle rect, int l, int t, int r, int b) { return Rectangle.FromLTRB(rect.Left - l, rect.Top - t, diff --git a/OpenRA.Game/WorldUtils.cs b/OpenRA.Game/WorldUtils.cs index 89758ed662..b015fd35ae 100644 --- a/OpenRA.Game/WorldUtils.cs +++ b/OpenRA.Game/WorldUtils.cs @@ -43,7 +43,10 @@ namespace OpenRA public static void DoTimed(this IEnumerable e, Action a, string text) { - // Note - manual enumeration here for performance due to high call volume. + // PERF: This is a hot path and must run with minimal added overhead. + // Calling Stopwatch.GetTimestamp is a bit expensive, so we enumerate manually to allow us to call it only + // once per iteration in the normal case. + // See also: RunActivity var longTickThresholdInStopwatchTicks = PerfTimer.LongTickThresholdInStopwatchTicks; using (var enumerator = e.GetEnumerator()) { diff --git a/OpenRA.Mods.Common/Pathfinder/PathSearch.cs b/OpenRA.Mods.Common/Pathfinder/PathSearch.cs index b7a6cd50ff..c6009d48d6 100644 --- a/OpenRA.Mods.Common/Pathfinder/PathSearch.cs +++ b/OpenRA.Mods.Common/Pathfinder/PathSearch.cs @@ -19,6 +19,8 @@ namespace OpenRA.Mods.Common.Pathfinder { public sealed class PathSearch : BasePathSearch { + // PERF: Maintain a pool of layers used for paths searches for each world. These searches are performed often + // so we wish to avoid the high cost of initializing a new search space every time by reusing the old ones. static readonly ConditionalWeakTable LayerPoolTable = new ConditionalWeakTable(); static readonly ConditionalWeakTable.CreateValueCallback CreateLayerPool = world => new CellInfoLayerPool(world.Map); diff --git a/OpenRA.Mods.Common/ShroudExts.cs b/OpenRA.Mods.Common/ShroudExts.cs index 8f418a8caa..3ceef15482 100644 --- a/OpenRA.Mods.Common/ShroudExts.cs +++ b/OpenRA.Mods.Common/ShroudExts.cs @@ -20,6 +20,7 @@ namespace OpenRA.Mods.Common { public static bool AnyExplored(this Shroud shroud, OccupiedCells cells) { + // PERF: Avoid LINQ. foreach (var cell in cells) if (shroud.IsExplored(cell.First)) return true; @@ -29,6 +30,7 @@ namespace OpenRA.Mods.Common public static bool AnyVisible(this Shroud shroud, OccupiedCells cells) { + // PERF: Avoid LINQ. foreach (var cell in cells) if (shroud.IsVisible(cell.First)) return true; diff --git a/OpenRA.Mods.Common/Traits/Mobile.cs b/OpenRA.Mods.Common/Traits/Mobile.cs index 345d558889..1d1a27184b 100644 --- a/OpenRA.Mods.Common/Traits/Mobile.cs +++ b/OpenRA.Mods.Common/Traits/Mobile.cs @@ -33,6 +33,7 @@ namespace OpenRA.Mods.Common.Traits { public static bool HasCellCondition(this CellConditions c, CellConditions cellCondition) { + // PERF: Enum.HasFlag is slower and requires allocations. return (c & cellCondition) == cellCondition; } } @@ -132,6 +133,8 @@ namespace OpenRA.Mods.Common.Traits internal readonly TerrainInfo[] TerrainInfos; internal WorldMovementInfo(World world, MobileInfo info) { + // PERF: This struct allows us to cache the terrain info for the tileset used by the world. + // This allows us to speed up some performance-sensitive pathfinding calculations. World = world; TerrainInfos = info.TilesetTerrainInfo[world.TileSet]; } @@ -217,6 +220,7 @@ namespace OpenRA.Mods.Common.Traits if (SharesCell && world.ActorMap.HasFreeSubCell(cell)) return true; + // PERF: Avoid LINQ. foreach (var otherActor in world.ActorMap.GetActorsAt(cell)) if (IsBlockedBy(self, otherActor, ignoreActor, check)) return false; @@ -242,6 +246,7 @@ namespace OpenRA.Mods.Common.Traits return true; // If the other actor in our way cannot be crushed, we are blocked. + // PERF: Avoid LINQ. var crushables = otherActor.TraitsImplementing(); var lacksCrushability = true; foreach (var crushable in crushables) diff --git a/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs b/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs index d1bb72ee2d..74e42340ce 100644 --- a/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs +++ b/OpenRA.Mods.Common/Traits/Modifiers/FrozenUnderFog.cs @@ -114,6 +114,7 @@ namespace OpenRA.Mods.Common.Traits } else { + // PERF: Minimize lookup cost by combining all state into one, and using an array rather than a dictionary. var state = stateByPlayerIndex[i]; frozenActor = state.FrozenActor; isVisible = !frozenActor.Visible; diff --git a/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs b/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs index ee70c06025..7d91af2158 100644 --- a/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs +++ b/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs @@ -48,6 +48,7 @@ namespace OpenRA.Mods.Common.Traits public override void Tick(Actor self) { + // PERF: Avoid LINQ. isActive = false; foreach (var x in self.World.ActorsWithTrait()) { diff --git a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs index 01cc2224ff..c2486cf033 100644 --- a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs @@ -226,6 +226,8 @@ namespace OpenRA.Mods.Common.Traits void DirtyCells(IEnumerable cells) { + // PERF: Many cells in the shroud change every tick. We only track the changes here and defer the real work + // we need to do until we render. This allows us to avoid wasted work. cellsDirty.UnionWith(cells); } diff --git a/OpenRA.Mods.Common/Widgets/RadarWidget.cs b/OpenRA.Mods.Common/Widgets/RadarWidget.cs index c273c9094c..7564f4c99d 100644 --- a/OpenRA.Mods.Common/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.Common/Widgets/RadarWidget.cs @@ -208,6 +208,8 @@ namespace OpenRA.Mods.Common.Widgets void MarkShroudDirty(IEnumerable projectedCellsChanged) { + // PERF: Many cells in the shroud change every tick. We only track the changes here and defer the real work + // we need to do until we render. This allows us to avoid wasted work. dirtyShroudCells.UnionWith(projectedCellsChanged); }