Spatially partition frozen actor layer.
Track changes in the shroud in a spatial partition in frozen actor layer. This allows us to run the expensive visibility updates only on frozen actors with a footprint in affected partitions, rather than on all frozen actors every time.
This commit is contained in:
@@ -13,13 +13,17 @@ using System.Collections.Generic;
|
|||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Graphics;
|
using OpenRA.Graphics;
|
||||||
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA.Traits
|
namespace OpenRA.Traits
|
||||||
{
|
{
|
||||||
[Desc("Required for FrozenUnderFog to work. Attach this to the player actor.")]
|
[Desc("Required for FrozenUnderFog to work. Attach this to the player actor.")]
|
||||||
public class FrozenActorLayerInfo : ITraitInfo
|
public class FrozenActorLayerInfo : Requires<ShroudInfo>, ITraitInfo
|
||||||
{
|
{
|
||||||
public object Create(ActorInitializer init) { return new FrozenActorLayer(init.Self); }
|
[Desc("Size of partition bins (cells)")]
|
||||||
|
public readonly int BinSize = 10;
|
||||||
|
|
||||||
|
public object Create(ActorInitializer init) { return new FrozenActorLayer(init.Self, this); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FrozenActor
|
public class FrozenActor
|
||||||
@@ -74,13 +78,11 @@ namespace OpenRA.Traits
|
|||||||
|
|
||||||
public void Tick()
|
public void Tick()
|
||||||
{
|
{
|
||||||
UpdateVisibility();
|
|
||||||
|
|
||||||
if (flashTicks > 0)
|
if (flashTicks > 0)
|
||||||
flashTicks--;
|
flashTicks--;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateVisibility()
|
public void UpdateVisibility()
|
||||||
{
|
{
|
||||||
var wasVisible = Visible;
|
var wasVisible = Visible;
|
||||||
Shrouded = true;
|
Shrouded = true;
|
||||||
@@ -146,54 +148,127 @@ namespace OpenRA.Traits
|
|||||||
[Sync] public int VisibilityHash;
|
[Sync] public int VisibilityHash;
|
||||||
[Sync] public int FrozenHash;
|
[Sync] public int FrozenHash;
|
||||||
|
|
||||||
|
readonly int binSize;
|
||||||
readonly World world;
|
readonly World world;
|
||||||
readonly Player owner;
|
readonly Player owner;
|
||||||
readonly Dictionary<uint, FrozenActor> frozen;
|
readonly Dictionary<uint, FrozenActor> frozenActorsById;
|
||||||
|
readonly SpatiallyPartitioned<uint> partitionedFrozenActorIds;
|
||||||
|
readonly bool[] dirtyBins;
|
||||||
|
readonly HashSet<uint> dirtyFrozenActorIds = new HashSet<uint>();
|
||||||
|
|
||||||
public FrozenActorLayer(Actor self)
|
public FrozenActorLayer(Actor self, FrozenActorLayerInfo info)
|
||||||
{
|
{
|
||||||
|
binSize = info.BinSize;
|
||||||
world = self.World;
|
world = self.World;
|
||||||
owner = self.Owner;
|
owner = self.Owner;
|
||||||
frozen = new Dictionary<uint, FrozenActor>();
|
frozenActorsById = new Dictionary<uint, FrozenActor>();
|
||||||
|
|
||||||
|
// PERF: Partition the map into a series of coarse-grained bins and track changes in the shroud against
|
||||||
|
// bin - marking that bin dirty if it changes. This is fairly cheap to track and allows us to perform the
|
||||||
|
// expensive visibility update for frozen actors in these regions.
|
||||||
|
partitionedFrozenActorIds = new SpatiallyPartitioned<uint>(
|
||||||
|
world.Map.MapSize.X, world.Map.MapSize.Y, binSize);
|
||||||
|
var maxX = world.Map.MapSize.X / binSize + 1;
|
||||||
|
var maxY = world.Map.MapSize.Y / binSize + 1;
|
||||||
|
dirtyBins = new bool[maxX * maxY];
|
||||||
|
self.Trait<Shroud>().CellsChanged += cells =>
|
||||||
|
{
|
||||||
|
foreach (var cell in cells)
|
||||||
|
{
|
||||||
|
var x = cell.U / binSize;
|
||||||
|
var y = cell.V / binSize;
|
||||||
|
dirtyBins[y * maxX + x] = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(FrozenActor fa)
|
public void Add(FrozenActor fa)
|
||||||
{
|
{
|
||||||
frozen.Add(fa.ID, fa);
|
frozenActorsById.Add(fa.ID, fa);
|
||||||
world.ScreenMap.Add(owner, fa);
|
world.ScreenMap.Add(owner, fa);
|
||||||
|
partitionedFrozenActorIds.Add(fa.ID, FootprintBounds(fa));
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle FootprintBounds(FrozenActor fa)
|
||||||
|
{
|
||||||
|
var p1 = fa.Footprint[0];
|
||||||
|
var minU = p1.U;
|
||||||
|
var maxU = p1.U;
|
||||||
|
var minV = p1.V;
|
||||||
|
var maxV = p1.V;
|
||||||
|
foreach (var p in fa.Footprint)
|
||||||
|
{
|
||||||
|
if (minU > p.U)
|
||||||
|
minU = p.U;
|
||||||
|
else if (maxU < p.U)
|
||||||
|
maxU = p.U;
|
||||||
|
|
||||||
|
if (minV > p.V)
|
||||||
|
minV = p.V;
|
||||||
|
else if (maxV < p.V)
|
||||||
|
maxV = p.V;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Rectangle.FromLTRB(minU, minV, maxU, maxV);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Tick(Actor self)
|
public void Tick(Actor self)
|
||||||
{
|
{
|
||||||
var remove = new List<uint>();
|
UpdateDirtyFrozenActorsFromDirtyBins();
|
||||||
|
|
||||||
|
var idsToRemove = new List<uint>();
|
||||||
VisibilityHash = 0;
|
VisibilityHash = 0;
|
||||||
FrozenHash = 0;
|
FrozenHash = 0;
|
||||||
|
|
||||||
// TODO: Track shroud updates using Shroud.CellsChanged
|
foreach (var kvp in frozenActorsById)
|
||||||
// and then only tick FrozenActors that might have changed
|
|
||||||
foreach (var kvp in frozen)
|
|
||||||
{
|
{
|
||||||
var hash = (int)kvp.Key;
|
var id = kvp.Key;
|
||||||
|
var hash = (int)id;
|
||||||
FrozenHash += hash;
|
FrozenHash += hash;
|
||||||
|
|
||||||
var frozenActor = kvp.Value;
|
var frozenActor = kvp.Value;
|
||||||
frozenActor.Tick();
|
frozenActor.Tick();
|
||||||
|
if (dirtyFrozenActorIds.Contains(id))
|
||||||
|
frozenActor.UpdateVisibility();
|
||||||
|
|
||||||
if (frozenActor.ShouldBeRemoved(owner))
|
if (frozenActor.ShouldBeRemoved(owner))
|
||||||
remove.Add(kvp.Key);
|
idsToRemove.Add(id);
|
||||||
else if (frozenActor.Visible)
|
else if (frozenActor.Visible)
|
||||||
VisibilityHash += hash;
|
VisibilityHash += hash;
|
||||||
else if (frozenActor.Actor == null)
|
else if (frozenActor.Actor == null)
|
||||||
remove.Add(kvp.Key);
|
idsToRemove.Add(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var actorID in remove)
|
dirtyFrozenActorIds.Clear();
|
||||||
|
|
||||||
|
foreach (var id in idsToRemove)
|
||||||
{
|
{
|
||||||
world.ScreenMap.Remove(owner, frozen[actorID]);
|
partitionedFrozenActorIds.Remove(id);
|
||||||
frozen.Remove(actorID);
|
world.ScreenMap.Remove(owner, frozenActorsById[id]);
|
||||||
|
frozenActorsById.Remove(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UpdateDirtyFrozenActorsFromDirtyBins()
|
||||||
|
{
|
||||||
|
// Check which bins on the map were dirtied due to changes in the shroud and gather the frozen actors whose
|
||||||
|
// footprint overlap with these bins.
|
||||||
|
var maxX = world.Map.MapSize.X / binSize + 1;
|
||||||
|
var maxY = world.Map.MapSize.Y / binSize + 1;
|
||||||
|
for (var y = 0; y < maxY; y++)
|
||||||
|
{
|
||||||
|
for (var x = 0; x < maxX; x++)
|
||||||
|
{
|
||||||
|
if (!dirtyBins[y * maxX + x])
|
||||||
|
continue;
|
||||||
|
var box = new Rectangle(x * binSize, y * binSize, binSize, binSize);
|
||||||
|
dirtyFrozenActorIds.UnionWith(partitionedFrozenActorIds.InBox(box));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Clear(dirtyBins, 0, dirtyBins.Length);
|
||||||
|
}
|
||||||
|
|
||||||
public virtual IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr)
|
public virtual IEnumerable<IRenderable> Render(Actor self, WorldRenderer wr)
|
||||||
{
|
{
|
||||||
return world.ScreenMap.FrozenActorsInBox(owner, wr.Viewport.TopLeft, wr.Viewport.BottomRight)
|
return world.ScreenMap.FrozenActorsInBox(owner, wr.Viewport.TopLeft, wr.Viewport.BottomRight)
|
||||||
@@ -203,11 +278,11 @@ namespace OpenRA.Traits
|
|||||||
|
|
||||||
public FrozenActor FromID(uint id)
|
public FrozenActor FromID(uint id)
|
||||||
{
|
{
|
||||||
FrozenActor ret;
|
FrozenActor fa;
|
||||||
if (!frozen.TryGetValue(id, out ret))
|
if (!frozenActorsById.TryGetValue(id, out fa))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return ret;
|
return fa;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user