From 1584018dcd21c23e62599a0ccaae3364292e7832 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Mon, 23 Mar 2015 22:27:02 +0000 Subject: [PATCH] Batch shroud cell changes. By maintaining a set of changed cells we can avoid repeating work for cells that change multiple times before being rendered. The shroud renderer and radar widget now delay their work until they must render, and thus process each changed cell only once. This avoids significant repetition that was causing major slowdown when many actors were in the world. --- OpenRA.Game/Traits/World/Shroud.cs | 76 +++++++++++-------- .../Traits/World/ShroudRenderer.cs | 41 ++++++---- OpenRA.Mods.Common/Widgets/RadarWidget.cs | 25 +++++- 3 files changed, 95 insertions(+), 47 deletions(-) diff --git a/OpenRA.Game/Traits/World/Shroud.cs b/OpenRA.Game/Traits/World/Shroud.cs index 5898b26e23..efc1bd498b 100644 --- a/OpenRA.Game/Traits/World/Shroud.cs +++ b/OpenRA.Game/Traits/World/Shroud.cs @@ -24,6 +24,8 @@ namespace OpenRA.Traits { [Sync] public bool Disabled; + public event Action> CellsChanged; + readonly Actor self; readonly Map map; @@ -31,23 +33,6 @@ namespace OpenRA.Traits readonly CellLayer generatedShroudCount; readonly CellLayer explored; - public event Action CellEntryChanged - { - add - { - visibleCount.CellEntryChanged += value; - generatedShroudCount.CellEntryChanged += value; - explored.CellEntryChanged += value; - } - - remove - { - visibleCount.CellEntryChanged -= value; - generatedShroudCount.CellEntryChanged -= value; - explored.CellEntryChanged -= value; - } - } - readonly Lazy fogVisibilities; // Cache of visibility that was added, so no matter what crazy trait code does, it @@ -88,8 +73,11 @@ namespace OpenRA.Traits slowVisibleTest = IsVisible; } - void Invalidate() + void Invalidate(IEnumerable changed) { + if (CellsChanged != null) + CellsChanged(changed); + var oldHash = Hash; Hash = Sync.HashPlayer(self.Owner) + self.World.WorldTick * 3; @@ -148,7 +136,7 @@ namespace OpenRA.Traits throw new InvalidOperationException("Attempting to add duplicate actor visibility"); visibility[a] = visible; - Invalidate(); + Invalidate(visible); } void RemoveVisibility(Actor a) @@ -161,7 +149,7 @@ namespace OpenRA.Traits visibleCount[c]--; visibility.Remove(a); - Invalidate(); + Invalidate(visible); } void UpdateVisibility(Actor a, ref CPos[] visible) @@ -190,7 +178,7 @@ namespace OpenRA.Traits throw new InvalidOperationException("Attempting to add duplicate shroud generation"); generation[a] = shrouded; - Invalidate(); + Invalidate(shrouded); } void RemoveShroudGeneration(Actor a) @@ -203,7 +191,7 @@ namespace OpenRA.Traits generatedShroudCount[c]--; generation.Remove(a); - Invalidate(); + Invalidate(shrouded); } void UpdateShroudGeneration(Actor a, ref CPos[] shrouded) @@ -241,10 +229,17 @@ namespace OpenRA.Traits public void Explore(World world, CPos center, WRange range) { + var changed = new List(); foreach (var c in FindVisibleTiles(world, center, range)) - explored[c] = true; + { + if (!explored[c]) + { + explored[c] = true; + changed.Add(c); + } + } - Invalidate(); + Invalidate(changed); } public void Explore(Shroud s) @@ -252,27 +247,48 @@ namespace OpenRA.Traits if (map.Bounds != s.map.Bounds) throw new ArgumentException("The map bounds of these shrouds do not match.", "s"); + var changed = new List(); foreach (var uv in map.Cells.MapCoords) - if (s.explored[uv]) + { + if (!explored[uv] && s.explored[uv]) + { explored[uv] = true; + changed.Add(uv.ToCPos(map)); + } + } - Invalidate(); + Invalidate(changed); } public void ExploreAll(World world) { + var changed = new List(); foreach (var uv in map.Cells.MapCoords) - explored[uv] = true; + { + if (!explored[uv]) + { + explored[uv] = true; + changed.Add(uv.ToCPos(map)); + } + } - Invalidate(); + Invalidate(changed); } public void ResetExploration() { + var changed = new List(); foreach (var uv in map.Cells.MapCoords) - explored[uv] = visibleCount[uv] > 0; + { + var visible = visibleCount[uv] > 0; + if (explored[uv] != visible) + { + explored[uv] = visible; + changed.Add(uv.ToCPos(map)); + } + } - Invalidate(); + Invalidate(changed); } public bool IsExplored(CPos cell) diff --git a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs index 7f31a923b8..b4cd58db1c 100644 --- a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs @@ -9,6 +9,7 @@ #endregion using System; +using System.Collections.Generic; using OpenRA.Graphics; using OpenRA.Traits; @@ -85,6 +86,8 @@ namespace OpenRA.Mods.Common.Traits readonly CellLayer tileInfos; readonly CellLayer shroudDirty; + readonly HashSet cellsDirty; + readonly HashSet cellsAndNeighborsDirty; readonly Vertex[] fogVertices, shroudVertices; readonly Sprite[] fogSprites, shroudSprites; @@ -113,6 +116,8 @@ namespace OpenRA.Mods.Common.Traits tileInfos = new CellLayer(map); shroudDirty = new CellLayer(map); + cellsDirty = new HashSet(); + cellsAndNeighborsDirty = new HashSet(); var verticesLength = map.MapSize.X * map.MapSize.Y * 4; fogVertices = new Vertex[verticesLength]; shroudVertices = new Vertex[verticesLength]; @@ -209,20 +214,38 @@ namespace OpenRA.Mods.Common.Traits if (currentShroud != newShroud) { if (currentShroud != null) - currentShroud.CellEntryChanged -= MarkCellAndNeighborsDirty; + currentShroud.CellsChanged -= MarkCellsDirty; if (newShroud != null) { shroudDirty.Clear(true); - newShroud.CellEntryChanged += MarkCellAndNeighborsDirty; + newShroud.CellsChanged += MarkCellsDirty; } + cellsDirty.Clear(); + cellsAndNeighborsDirty.Clear(); + currentShroud = newShroud; } if (currentShroud != null) { mapBorderShroudIsCached = false; + + // We need to mark newly dirtied areas of the shroud. + // Expand the dirty area to cover the neighboring cells, since shroud is affected by neighboring cells. + foreach (var cell in cellsDirty) + { + cellsAndNeighborsDirty.Add(cell); + foreach (var direction in CVec.Directions) + cellsAndNeighborsDirty.Add(cell + direction); + } + + foreach (var cell in cellsAndNeighborsDirty) + shroudDirty[cell] = true; + + cellsDirty.Clear(); + cellsAndNeighborsDirty.Clear(); } else if (!mapBorderShroudIsCached) { @@ -231,9 +254,9 @@ namespace OpenRA.Mods.Common.Traits } } - void MarkCellAndNeighborsDirty(CPos cell) + void MarkCellsDirty(IEnumerable cellsChanged) { - // Mark this cell and its 8 neighbors as being out of date. + // Mark changed cells as being out of date. // We don't want to do anything more than this for several performance reasons: // - If the cells remain off-screen for a long time, they may change several times before we next view // them, so calculating their new vertices is wasted effort since we may recalculate them again before we @@ -242,15 +265,7 @@ namespace OpenRA.Mods.Common.Traits // leaves a trail of fog filling in behind). If we recalculated a cell and its neighbors when the first // cell in a group changed, many cells would be recalculated again when the second cell, right next to the // first, is updated. In fact we might do on the order of 3x the work we needed to! - shroudDirty[cell + new CVec(-1, -1)] = true; - shroudDirty[cell + new CVec(0, -1)] = true; - shroudDirty[cell + new CVec(1, -1)] = true; - shroudDirty[cell + new CVec(-1, 0)] = true; - shroudDirty[cell] = true; - shroudDirty[cell + new CVec(1, 0)] = true; - shroudDirty[cell + new CVec(-1, 1)] = true; - shroudDirty[cell + new CVec(0, 1)] = true; - shroudDirty[cell + new CVec(1, 1)] = true; + cellsDirty.UnionWith(cellsChanged); } void CacheMapBorderShroud() diff --git a/OpenRA.Mods.Common/Widgets/RadarWidget.cs b/OpenRA.Mods.Common/Widgets/RadarWidget.cs index 7c9964d46c..b10eee9c9b 100644 --- a/OpenRA.Mods.Common/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.Common/Widgets/RadarWidget.cs @@ -9,6 +9,7 @@ #endregion using System; +using System.Collections.Generic; using System.Drawing; using System.Linq; using OpenRA.Graphics; @@ -33,6 +34,8 @@ namespace OpenRA.Mods.Common.Widgets readonly WorldRenderer worldRenderer; readonly RadarPings radarPings; + readonly HashSet dirtyShroudCells = new HashSet(); + float radarMinimapHeight; int frame; bool hasRadar; @@ -130,6 +133,11 @@ namespace OpenRA.Mods.Common.Widgets } } + void MarkShroudDirty(IEnumerable cellsChanged) + { + dirtyShroudCells.UnionWith(cellsChanged); + } + public override string GetCursor(int2 pos) { if (world == null || !hasRadar) @@ -194,6 +202,15 @@ namespace OpenRA.Mods.Common.Widgets if (world == null) return; + if (renderShroud != null) + { + foreach (var cell in dirtyShroudCells) + UpdateShroudCell(cell); + dirtyShroudCells.Clear(); + } + + radarSheet.CommitData(); + var o = new float2(mapRect.Location.X, mapRect.Location.Y + world.Map.Bounds.Height * previewScale * (1 - radarMinimapHeight) / 2); var s = new float2(mapRect.Size.Width, mapRect.Size.Height * radarMinimapHeight); @@ -255,7 +272,7 @@ namespace OpenRA.Mods.Common.Widgets if (newRenderShroud != renderShroud) { if (renderShroud != null) - renderShroud.CellEntryChanged -= UpdateShroudCell; + renderShroud.CellsChanged -= MarkShroudDirty; if (newRenderShroud != null) { @@ -264,9 +281,11 @@ namespace OpenRA.Mods.Common.Widgets OpenRA.Graphics.Util.FastCopyIntoSprite(shroudSprite, bitmap); // Update the notification binding - newRenderShroud.CellEntryChanged += UpdateShroudCell; + newRenderShroud.CellsChanged += MarkShroudDirty; } + dirtyShroudCells.Clear(); + renderShroud = newRenderShroud; } @@ -299,8 +318,6 @@ namespace OpenRA.Mods.Common.Widgets } } } - - radarSheet.CommitData(); } var targetFrame = enabled ? AnimationLength : 0;