From a0db80cb6a2e90bee6211d92ae8872b75af3b3af Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Fri, 23 May 2014 14:48:11 +0100 Subject: [PATCH] Changes to reduce allocations in the main loop. Targeted some methods that generated allocated a lot of memory in the main game loop: - Actor.Render - Changed LINQ calls into equivalent loops. No allocation for delegates. - Animation.Render - Returned an array rather than a yield expression. The array uses less memory than the complier generated enumerable. - FrozenActor and FrozenUnderFog - Materialize the footprint into an array: The enumerable is not-trivial to evaluate is and evaluated many times inside the Tick function. The memory needed is minimal. Changed LINQ into equivalent loops to prevent delegate allocation. Should result in overall much faster calls. - Widget.GetEventBounds - Changed LINQ calls into equivalent loops. - MobileInfo.CanEnterCell - Changed LINQ calls into equivalent loops. Don't materialize list of blocking actors every time, instead enumerate them and only when they need to be checked. - FrozenUnderFog.TickRender - Generate the renderables lazily and also remove a no-op Select call. --- OpenRA.Game/Actor.cs | 20 +++++++------ OpenRA.Game/Graphics/Animation.cs | 7 +++-- OpenRA.Game/Traits/Player/FrozenActorLayer.cs | 12 ++++++-- OpenRA.Game/Widgets/Widget.cs | 9 +++--- OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs | 23 +++++++++++---- OpenRA.Mods.RA/Move/Mobile.cs | 29 ++++++++++--------- 6 files changed, 63 insertions(+), 37 deletions(-) diff --git a/OpenRA.Game/Actor.cs b/OpenRA.Game/Actor.cs index 034531ad7d..bf9f76206b 100644 --- a/OpenRA.Game/Actor.cs +++ b/OpenRA.Game/Actor.cs @@ -81,9 +81,6 @@ namespace OpenRA health = Exts.Lazy(() => TraitOrDefault()); effectiveOwner = Exts.Lazy(() => TraitOrDefault()); - applyIRender = (x, wr) => x.Render(this, wr); - applyRenderModifier = (m, p, wr) => p.ModifyRender(this, wr, m); - Bounds = Exts.Lazy(() => { var si = Info.Traits.GetOrDefault(); @@ -112,14 +109,19 @@ namespace OpenRA get { return currentActivity == null; } } - // note: these delegates are cached to avoid massive allocation. - Func> applyIRender; - Func, IRenderModifier, WorldRenderer, IEnumerable> applyRenderModifier; public IEnumerable Render(WorldRenderer wr) { - var mods = TraitsImplementing(); - var sprites = TraitsImplementing().SelectMany(x => applyIRender(x, wr)); - return mods.Aggregate(sprites, (m, p) => applyRenderModifier(m, p, wr)); + var renderables = Renderables(wr); + foreach (var modifier in TraitsImplementing()) + renderables = modifier.ModifyRender(this, wr, renderables); + return renderables; + } + + IEnumerable Renderables(WorldRenderer wr) + { + foreach (var render in TraitsImplementing()) + foreach (var renderable in render.Render(this, wr)) + yield return renderable; } public bool IsInWorld { get; internal set; } diff --git a/OpenRA.Game/Graphics/Animation.cs b/OpenRA.Game/Graphics/Animation.cs index 22bdf63967..ff8d3cd446 100644 --- a/OpenRA.Game/Graphics/Animation.cs +++ b/OpenRA.Game/Graphics/Animation.cs @@ -49,13 +49,16 @@ namespace OpenRA.Graphics public IEnumerable Render(WPos pos, WVec offset, int zOffset, PaletteReference palette, float scale) { + var imageRenderable = new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration); + if (CurrentSequence.ShadowStart >= 0) { var shadow = CurrentSequence.GetShadow(CurrentFrame, facingFunc()); - yield return new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true); + var shadowRenderable = new SpriteRenderable(shadow, pos, offset, CurrentSequence.ShadowZOffset + zOffset, palette, scale, true); + return new IRenderable[] { shadowRenderable, imageRenderable }; } - yield return new SpriteRenderable(Image, pos, offset, CurrentSequence.ZOffset + zOffset, palette, scale, IsDecoration); + return new IRenderable[] { imageRenderable }; } public IEnumerable Render(WPos pos, PaletteReference palette) diff --git a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs index 8992e21001..3a6e5bd19e 100755 --- a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs +++ b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs @@ -22,7 +22,7 @@ namespace OpenRA.Traits public class FrozenActor { - public readonly IEnumerable Footprint; + public readonly CPos[] Footprint; public readonly WPos CenterPosition; public readonly Rectangle Bounds; readonly Actor actor; @@ -41,7 +41,7 @@ namespace OpenRA.Traits public FrozenActor(Actor self, IEnumerable footprint) { actor = self; - Footprint = footprint; + Footprint = footprint.ToArray(); CenterPosition = self.CenterPosition; Bounds = self.Bounds.Value; } @@ -54,7 +54,13 @@ namespace OpenRA.Traits int flashTicks; public void Tick(World world, Shroud shroud) { - Visible = !Footprint.Any(c => shroud.IsVisible(c)); + Visible = false; + foreach (var pos in Footprint) + if (shroud.IsVisible(pos)) + { + Visible = true; + break; + } if (flashTicks > 0) flashTicks--; diff --git a/OpenRA.Game/Widgets/Widget.cs b/OpenRA.Game/Widgets/Widget.cs index 2f5ee2c2d1..35a9b558b6 100644 --- a/OpenRA.Game/Widgets/Widget.cs +++ b/OpenRA.Game/Widgets/Widget.cs @@ -237,10 +237,11 @@ namespace OpenRA.Widgets public virtual Rectangle GetEventBounds() { - return Children - .Where(c => c.IsVisible()) - .Select(c => c.GetEventBounds()) - .Aggregate(EventBounds, Rectangle.Union); + var bounds = EventBounds; + foreach (var child in Children) + if (child.IsVisible()) + bounds = Rectangle.Union(bounds, child.GetEventBounds()); + return bounds; } public bool HasMouseFocus { get { return Ui.MouseFocusWidget == this; } } diff --git a/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs b/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs index 3b149dd423..687e7b44d3 100644 --- a/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs +++ b/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs @@ -30,7 +30,7 @@ namespace OpenRA.Mods.RA [Sync] public int VisibilityHash; bool initialized, startsRevealed; - IEnumerable footprint; + readonly CPos[] footprint; Lazy tooltip; Lazy health; @@ -41,7 +41,7 @@ namespace OpenRA.Mods.RA { // Spawned actors (e.g. building husks) shouldn't be revealed startsRevealed = info.StartsRevealed && !init.Contains(); - footprint = FootprintUtils.Tiles(init.self); + footprint = FootprintUtils.Tiles(init.self).ToArray(); tooltip = Exts.Lazy(() => init.self.TraitsImplementing().FirstOrDefault()); tooltip = Exts.Lazy(() => init.self.TraitsImplementing().FirstOrDefault()); health = Exts.Lazy(() => init.self.TraitOrDefault()); @@ -63,8 +63,15 @@ namespace OpenRA.Mods.RA VisibilityHash = 0; foreach (var p in self.World.Players) { - visible[p] = footprint.Any(c => p.Shroud.IsVisible(c)); - if (visible[p]) + var isVisible = false; + foreach (var pos in footprint) + if (p.Shroud.IsVisible(pos)) + { + isVisible = true; + break; + } + visible[p] = isVisible; + if (isVisible) VisibilityHash += p.ClientIndex; } @@ -106,11 +113,15 @@ namespace OpenRA.Mods.RA if (self.Destroyed || !initialized || !visible.Any(v => v.Value)) return; - // Force a copy of the underlying data - var renderables = self.Render(wr).Select(rr => rr).ToArray(); + IRenderable[] renderables = null; foreach (var player in self.World.Players) if (visible[player]) + { + // Lazily generate a copy of the underlying data. + if (renderables == null) + renderables = self.Render(wr).ToArray(); frozen[player].Renderables = renderables; + } } public IEnumerable ModifyRender(Actor self, WorldRenderer wr, IEnumerable r) diff --git a/OpenRA.Mods.RA/Move/Mobile.cs b/OpenRA.Mods.RA/Move/Mobile.cs index 073ae16f42..0ede504b3f 100755 --- a/OpenRA.Mods.RA/Move/Mobile.cs +++ b/OpenRA.Mods.RA/Move/Mobile.cs @@ -121,21 +121,24 @@ namespace OpenRA.Mods.RA.Move if (SharesCell && world.ActorMap.HasFreeSubCell(cell)) return true; - var blockingActors = world.ActorMap.GetUnitsAt(cell) - .Where(x => x != ignoreActor) - // Neutral/enemy units are blockers. Allied units that are moving are not blockers. - .Where(x => blockedByMovers || (self == null || self.Owner.Stances[x.Owner] != Stance.Ally || !IsMovingInMyDirection(self, x))) - .ToList(); - - if (checkTransientActors && blockingActors.Count > 0) + if (checkTransientActors) { - // Non-sharable unit can enter a cell with shareable units only if it can crush all of them - if (self == null || Crushes == null) - return false; + bool canIgnoreMovingAllies = self != null && !blockedByMovers; + bool needsCellExclusively = self == null || Crushes == null; + foreach(var a in world.ActorMap.GetUnitsAt(cell)) + { + if (a == ignoreActor) continue; - if (blockingActors.Any(a => !(a.HasTrait() && - a.TraitsImplementing().Any(b => b.CrushableBy(Crushes, self.Owner))))) - return false; + // Neutral/enemy units are blockers. Allied units that are moving are not blockers. + if (canIgnoreMovingAllies && self.Owner.Stances[a.Owner] == Stance.Ally && IsMovingInMyDirection(self, a)) continue; + + // Non-sharable unit can enter a cell with shareable units only if it can crush all of them. + if (needsCellExclusively) return false; + if (!a.HasTrait()) return false; + foreach (var crushable in a.TraitsImplementing()) + if (!crushable.CrushableBy(Crushes, self.Owner)) + return false; + } } return true;