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.
This commit is contained in:
@@ -24,6 +24,8 @@ namespace OpenRA.Traits
|
|||||||
{
|
{
|
||||||
[Sync] public bool Disabled;
|
[Sync] public bool Disabled;
|
||||||
|
|
||||||
|
public event Action<IEnumerable<CPos>> CellsChanged;
|
||||||
|
|
||||||
readonly Actor self;
|
readonly Actor self;
|
||||||
readonly Map map;
|
readonly Map map;
|
||||||
|
|
||||||
@@ -31,23 +33,6 @@ namespace OpenRA.Traits
|
|||||||
readonly CellLayer<short> generatedShroudCount;
|
readonly CellLayer<short> generatedShroudCount;
|
||||||
readonly CellLayer<bool> explored;
|
readonly CellLayer<bool> explored;
|
||||||
|
|
||||||
public event Action<CPos> CellEntryChanged
|
|
||||||
{
|
|
||||||
add
|
|
||||||
{
|
|
||||||
visibleCount.CellEntryChanged += value;
|
|
||||||
generatedShroudCount.CellEntryChanged += value;
|
|
||||||
explored.CellEntryChanged += value;
|
|
||||||
}
|
|
||||||
|
|
||||||
remove
|
|
||||||
{
|
|
||||||
visibleCount.CellEntryChanged -= value;
|
|
||||||
generatedShroudCount.CellEntryChanged -= value;
|
|
||||||
explored.CellEntryChanged -= value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly Lazy<IFogVisibilityModifier[]> fogVisibilities;
|
readonly Lazy<IFogVisibilityModifier[]> fogVisibilities;
|
||||||
|
|
||||||
// Cache of visibility that was added, so no matter what crazy trait code does, it
|
// Cache of visibility that was added, so no matter what crazy trait code does, it
|
||||||
@@ -88,8 +73,11 @@ namespace OpenRA.Traits
|
|||||||
slowVisibleTest = IsVisible;
|
slowVisibleTest = IsVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Invalidate()
|
void Invalidate(IEnumerable<CPos> changed)
|
||||||
{
|
{
|
||||||
|
if (CellsChanged != null)
|
||||||
|
CellsChanged(changed);
|
||||||
|
|
||||||
var oldHash = Hash;
|
var oldHash = Hash;
|
||||||
Hash = Sync.HashPlayer(self.Owner) + self.World.WorldTick * 3;
|
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");
|
throw new InvalidOperationException("Attempting to add duplicate actor visibility");
|
||||||
|
|
||||||
visibility[a] = visible;
|
visibility[a] = visible;
|
||||||
Invalidate();
|
Invalidate(visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RemoveVisibility(Actor a)
|
void RemoveVisibility(Actor a)
|
||||||
@@ -161,7 +149,7 @@ namespace OpenRA.Traits
|
|||||||
visibleCount[c]--;
|
visibleCount[c]--;
|
||||||
|
|
||||||
visibility.Remove(a);
|
visibility.Remove(a);
|
||||||
Invalidate();
|
Invalidate(visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateVisibility(Actor a, ref CPos[] visible)
|
void UpdateVisibility(Actor a, ref CPos[] visible)
|
||||||
@@ -190,7 +178,7 @@ namespace OpenRA.Traits
|
|||||||
throw new InvalidOperationException("Attempting to add duplicate shroud generation");
|
throw new InvalidOperationException("Attempting to add duplicate shroud generation");
|
||||||
|
|
||||||
generation[a] = shrouded;
|
generation[a] = shrouded;
|
||||||
Invalidate();
|
Invalidate(shrouded);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RemoveShroudGeneration(Actor a)
|
void RemoveShroudGeneration(Actor a)
|
||||||
@@ -203,7 +191,7 @@ namespace OpenRA.Traits
|
|||||||
generatedShroudCount[c]--;
|
generatedShroudCount[c]--;
|
||||||
|
|
||||||
generation.Remove(a);
|
generation.Remove(a);
|
||||||
Invalidate();
|
Invalidate(shrouded);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateShroudGeneration(Actor a, ref CPos[] shrouded)
|
void UpdateShroudGeneration(Actor a, ref CPos[] shrouded)
|
||||||
@@ -241,10 +229,17 @@ namespace OpenRA.Traits
|
|||||||
|
|
||||||
public void Explore(World world, CPos center, WRange range)
|
public void Explore(World world, CPos center, WRange range)
|
||||||
{
|
{
|
||||||
|
var changed = new List<CPos>();
|
||||||
foreach (var c in FindVisibleTiles(world, center, range))
|
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)
|
public void Explore(Shroud s)
|
||||||
@@ -252,27 +247,48 @@ namespace OpenRA.Traits
|
|||||||
if (map.Bounds != s.map.Bounds)
|
if (map.Bounds != s.map.Bounds)
|
||||||
throw new ArgumentException("The map bounds of these shrouds do not match.", "s");
|
throw new ArgumentException("The map bounds of these shrouds do not match.", "s");
|
||||||
|
|
||||||
|
var changed = new List<CPos>();
|
||||||
foreach (var uv in map.Cells.MapCoords)
|
foreach (var uv in map.Cells.MapCoords)
|
||||||
if (s.explored[uv])
|
{
|
||||||
|
if (!explored[uv] && s.explored[uv])
|
||||||
|
{
|
||||||
explored[uv] = true;
|
explored[uv] = true;
|
||||||
|
changed.Add(uv.ToCPos(map));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Invalidate();
|
Invalidate(changed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExploreAll(World world)
|
public void ExploreAll(World world)
|
||||||
{
|
{
|
||||||
|
var changed = new List<CPos>();
|
||||||
foreach (var uv in map.Cells.MapCoords)
|
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()
|
public void ResetExploration()
|
||||||
{
|
{
|
||||||
|
var changed = new List<CPos>();
|
||||||
foreach (var uv in map.Cells.MapCoords)
|
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)
|
public bool IsExplored(CPos cell)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
using OpenRA.Traits;
|
using OpenRA.Traits;
|
||||||
|
|
||||||
@@ -85,6 +86,8 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
readonly CellLayer<TileInfo> tileInfos;
|
readonly CellLayer<TileInfo> tileInfos;
|
||||||
readonly CellLayer<bool> shroudDirty;
|
readonly CellLayer<bool> shroudDirty;
|
||||||
|
readonly HashSet<CPos> cellsDirty;
|
||||||
|
readonly HashSet<CPos> cellsAndNeighborsDirty;
|
||||||
|
|
||||||
readonly Vertex[] fogVertices, shroudVertices;
|
readonly Vertex[] fogVertices, shroudVertices;
|
||||||
readonly Sprite[] fogSprites, shroudSprites;
|
readonly Sprite[] fogSprites, shroudSprites;
|
||||||
@@ -113,6 +116,8 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
|
|
||||||
tileInfos = new CellLayer<TileInfo>(map);
|
tileInfos = new CellLayer<TileInfo>(map);
|
||||||
shroudDirty = new CellLayer<bool>(map);
|
shroudDirty = new CellLayer<bool>(map);
|
||||||
|
cellsDirty = new HashSet<CPos>();
|
||||||
|
cellsAndNeighborsDirty = new HashSet<CPos>();
|
||||||
var verticesLength = map.MapSize.X * map.MapSize.Y * 4;
|
var verticesLength = map.MapSize.X * map.MapSize.Y * 4;
|
||||||
fogVertices = new Vertex[verticesLength];
|
fogVertices = new Vertex[verticesLength];
|
||||||
shroudVertices = new Vertex[verticesLength];
|
shroudVertices = new Vertex[verticesLength];
|
||||||
@@ -209,20 +214,38 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
if (currentShroud != newShroud)
|
if (currentShroud != newShroud)
|
||||||
{
|
{
|
||||||
if (currentShroud != null)
|
if (currentShroud != null)
|
||||||
currentShroud.CellEntryChanged -= MarkCellAndNeighborsDirty;
|
currentShroud.CellsChanged -= MarkCellsDirty;
|
||||||
|
|
||||||
if (newShroud != null)
|
if (newShroud != null)
|
||||||
{
|
{
|
||||||
shroudDirty.Clear(true);
|
shroudDirty.Clear(true);
|
||||||
newShroud.CellEntryChanged += MarkCellAndNeighborsDirty;
|
newShroud.CellsChanged += MarkCellsDirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cellsDirty.Clear();
|
||||||
|
cellsAndNeighborsDirty.Clear();
|
||||||
|
|
||||||
currentShroud = newShroud;
|
currentShroud = newShroud;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentShroud != null)
|
if (currentShroud != null)
|
||||||
{
|
{
|
||||||
mapBorderShroudIsCached = false;
|
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)
|
else if (!mapBorderShroudIsCached)
|
||||||
{
|
{
|
||||||
@@ -231,9 +254,9 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MarkCellAndNeighborsDirty(CPos cell)
|
void MarkCellsDirty(IEnumerable<CPos> 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:
|
// 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
|
// - 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
|
// 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
|
// 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
|
// 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!
|
// 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;
|
cellsDirty.UnionWith(cellsChanged);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CacheMapBorderShroud()
|
void CacheMapBorderShroud()
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
@@ -33,6 +34,8 @@ namespace OpenRA.Mods.Common.Widgets
|
|||||||
readonly WorldRenderer worldRenderer;
|
readonly WorldRenderer worldRenderer;
|
||||||
readonly RadarPings radarPings;
|
readonly RadarPings radarPings;
|
||||||
|
|
||||||
|
readonly HashSet<CPos> dirtyShroudCells = new HashSet<CPos>();
|
||||||
|
|
||||||
float radarMinimapHeight;
|
float radarMinimapHeight;
|
||||||
int frame;
|
int frame;
|
||||||
bool hasRadar;
|
bool hasRadar;
|
||||||
@@ -130,6 +133,11 @@ namespace OpenRA.Mods.Common.Widgets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MarkShroudDirty(IEnumerable<CPos> cellsChanged)
|
||||||
|
{
|
||||||
|
dirtyShroudCells.UnionWith(cellsChanged);
|
||||||
|
}
|
||||||
|
|
||||||
public override string GetCursor(int2 pos)
|
public override string GetCursor(int2 pos)
|
||||||
{
|
{
|
||||||
if (world == null || !hasRadar)
|
if (world == null || !hasRadar)
|
||||||
@@ -194,6 +202,15 @@ namespace OpenRA.Mods.Common.Widgets
|
|||||||
if (world == null)
|
if (world == null)
|
||||||
return;
|
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 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);
|
var s = new float2(mapRect.Size.Width, mapRect.Size.Height * radarMinimapHeight);
|
||||||
|
|
||||||
@@ -255,7 +272,7 @@ namespace OpenRA.Mods.Common.Widgets
|
|||||||
if (newRenderShroud != renderShroud)
|
if (newRenderShroud != renderShroud)
|
||||||
{
|
{
|
||||||
if (renderShroud != null)
|
if (renderShroud != null)
|
||||||
renderShroud.CellEntryChanged -= UpdateShroudCell;
|
renderShroud.CellsChanged -= MarkShroudDirty;
|
||||||
|
|
||||||
if (newRenderShroud != null)
|
if (newRenderShroud != null)
|
||||||
{
|
{
|
||||||
@@ -264,9 +281,11 @@ namespace OpenRA.Mods.Common.Widgets
|
|||||||
OpenRA.Graphics.Util.FastCopyIntoSprite(shroudSprite, bitmap);
|
OpenRA.Graphics.Util.FastCopyIntoSprite(shroudSprite, bitmap);
|
||||||
|
|
||||||
// Update the notification binding
|
// Update the notification binding
|
||||||
newRenderShroud.CellEntryChanged += UpdateShroudCell;
|
newRenderShroud.CellsChanged += MarkShroudDirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dirtyShroudCells.Clear();
|
||||||
|
|
||||||
renderShroud = newRenderShroud;
|
renderShroud = newRenderShroud;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,8 +318,6 @@ namespace OpenRA.Mods.Common.Widgets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
radarSheet.CommitData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetFrame = enabled ? AnimationLength : 0;
|
var targetFrame = enabled ? AnimationLength : 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user