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;