Change GetCustomMovementLayers to expose an array, not a dictionary.

As there are few custom movement layers, using an array is good for improving lookup speed. Additionally, we can simplify some code by reserving index 0 of the array for the ground layer. Code that needs to maintain a state for the ground layer and every custom movement layer can now maintain a flat array of state using index 0 for the ground layer, and the the ICustomMovementLayer.Index for the custom movement layer. This removes a lot of ternary statements checking for the ground layer special case.
This commit is contained in:
RoosterDragon
2021-10-08 21:11:14 +01:00
committed by Paul Chote
parent 3310f14dea
commit dd9d600ef9
4 changed files with 105 additions and 86 deletions

View File

@@ -88,22 +88,23 @@ namespace OpenRA.Mods.Common.Pathfinder
readonly Locomotor locomotor;
readonly CellInfoLayerPool.PooledCellInfoLayer pooledLayer;
readonly bool checkTerrainHeight;
CellLayer<CellInfo> groundInfo;
readonly Dictionary<byte, (ICustomMovementLayer Layer, CellLayer<CellInfo> Info)> customLayerInfo =
new Dictionary<byte, (ICustomMovementLayer, CellLayer<CellInfo>)>();
readonly CellLayer<CellInfo>[] cellInfoForLayer;
public PathGraph(CellInfoLayerPool layerPool, Locomotor locomotor, Actor actor, World world, BlockedByActor check)
{
pooledLayer = layerPool.Get();
groundInfo = pooledLayer.GetLayer();
var locomotorInfo = locomotor.Info;
this.locomotor = locomotor;
// As we support a search over the whole map area,
// use the pool to grab the CellInfos we need to track the graph state.
// This allows us to avoid the cost of allocating large arrays constantly.
// PERF: Avoid LINQ
foreach (var cml in world.GetCustomMovementLayers().Values)
if (cml.EnabledForLocomotor(locomotorInfo))
customLayerInfo[cml.Index] = (cml, pooledLayer.GetLayer());
var cmls = world.GetCustomMovementLayers();
pooledLayer = layerPool.Get();
cellInfoForLayer = new CellLayer<CellInfo>[cmls.Length];
cellInfoForLayer[0] = pooledLayer.GetLayer();
foreach (var cml in cmls)
if (cml != null && cml.EnabledForLocomotor(locomotor.Info))
cellInfoForLayer[cml.Index] = pooledLayer.GetLayer();
World = world;
Actor = actor;
@@ -132,8 +133,8 @@ namespace OpenRA.Mods.Common.Pathfinder
public List<GraphConnection> GetConnections(CPos position)
{
var posLayer = position.Layer;
var info = posLayer == 0 ? groundInfo : customLayerInfo[posLayer].Info;
var layer = position.Layer;
var info = cellInfoForLayer[layer];
var previousPos = info[position].PreviousPos;
var dx = position.X - previousPos.X;
@@ -141,7 +142,7 @@ namespace OpenRA.Mods.Common.Pathfinder
var index = dy * 3 + dx + 4;
var directions = DirectedNeighbors[index];
var validNeighbors = new List<GraphConnection>(directions.Length + (posLayer == 0 ? customLayerInfo.Count : 1));
var validNeighbors = new List<GraphConnection>(directions.Length + (layer == 0 ? cellInfoForLayer.Length : 1));
for (var i = 0; i < directions.Length; i++)
{
var dir = directions[i];
@@ -153,12 +154,16 @@ namespace OpenRA.Mods.Common.Pathfinder
validNeighbors.Add(new GraphConnection(neighbor, pathCost));
}
if (posLayer == 0)
var cmls = World.GetCustomMovementLayers();
if (layer == 0)
{
foreach (var cli in customLayerInfo.Values)
foreach (var cml in cmls)
{
var layerPosition = new CPos(position.X, position.Y, cli.Layer.Index);
var entryCost = cli.Layer.EntryMovementCost(locomotor.Info, layerPosition);
if (cml == null || !cml.EnabledForLocomotor(locomotor.Info))
continue;
var layerPosition = new CPos(position.X, position.Y, cml.Index);
var entryCost = cml.EntryMovementCost(locomotor.Info, layerPosition);
if (entryCost != MovementCostForUnreachableCell)
validNeighbors.Add(new GraphConnection(layerPosition, entryCost));
}
@@ -166,7 +171,7 @@ namespace OpenRA.Mods.Common.Pathfinder
else
{
var layerPosition = new CPos(position.X, position.Y, 0);
var exitCost = customLayerInfo[posLayer].Layer.ExitMovementCost(locomotor.Info, layerPosition);
var exitCost = cmls[layer].ExitMovementCost(locomotor.Info, layerPosition);
if (exitCost != MovementCostForUnreachableCell)
validNeighbors.Add(new GraphConnection(layerPosition, exitCost));
}
@@ -226,14 +231,12 @@ namespace OpenRA.Mods.Common.Pathfinder
public CellInfo this[CPos pos]
{
get => (pos.Layer == 0 ? groundInfo : customLayerInfo[pos.Layer].Info)[pos];
set => (pos.Layer == 0 ? groundInfo : customLayerInfo[pos.Layer].Info)[pos] = value;
get => cellInfoForLayer[pos.Layer][pos];
set => cellInfoForLayer[pos.Layer][pos] = value;
}
public void Dispose()
{
groundInfo = null;
customLayerInfo.Clear();
pooledLayer.Dispose();
}
}

View File

@@ -428,10 +428,8 @@ namespace OpenRA.Mods.Common.Traits
if (ToCell.Layer == 0)
return true;
if (self.World.GetCustomMovementLayers().TryGetValue(ToCell.Layer, out var layer))
return layer.InteractsWithDefaultLayer;
return true;
var layer = self.World.GetCustomMovementLayers()[ToCell.Layer];
return layer == null || layer.InteractsWithDefaultLayer;
}
#endregion
@@ -862,9 +860,7 @@ namespace OpenRA.Mods.Common.Traits
return;
}
var cml = self.World.WorldActor.TraitsImplementing<ICustomMovementLayer>()
.First(l => l.Index == self.Location.Layer);
var cml = self.World.GetCustomMovementLayers()[self.Location.Layer];
if (!cml.ReturnToGroundLayerOnIdle)
return;

View File

@@ -171,9 +171,10 @@ namespace OpenRA.Mods.Common.Traits
readonly Dictionary<int, ProximityTrigger> proximityTriggers = new Dictionary<int, ProximityTrigger>();
int nextTriggerId;
readonly CellLayer<InfluenceNode> influence;
readonly Dictionary<int, CellLayer<InfluenceNode>> customInfluence = new Dictionary<int, CellLayer<InfluenceNode>>();
public readonly Dictionary<int, ICustomMovementLayer> CustomMovementLayers = new Dictionary<int, ICustomMovementLayer>();
CellLayer<InfluenceNode>[] influence;
// Index 0 is kept null as layer 0 is used for the ground layer.
public ICustomMovementLayer[] CustomMovementLayers = new ICustomMovementLayer[] { null };
public event Action<CPos> CellUpdated;
readonly Bin[] bins;
readonly int rows, cols;
@@ -191,7 +192,7 @@ namespace OpenRA.Mods.Common.Traits
{
this.info = info;
map = world.Map;
influence = new CellLayer<InfluenceNode>(world.Map);
influence = new[] { new CellLayer<InfluenceNode>(world.Map) };
cols = CellCoordToBinIndex(world.Map.MapSize.X) + 1;
rows = CellCoordToBinIndex(world.Map.MapSize.Y) + 1;
@@ -210,10 +211,18 @@ namespace OpenRA.Mods.Common.Traits
void INotifyCreated.Created(Actor self)
{
foreach (var cml in self.TraitsImplementing<ICustomMovementLayer>())
var cmls = self.TraitsImplementing<ICustomMovementLayer>().ToList();
if (cmls.Count == 0)
return;
var length = cmls.Max(cml => cml.Index) + 1;
Array.Resize(ref CustomMovementLayers, length);
Array.Resize(ref influence, length);
foreach (var cml in cmls)
{
CustomMovementLayers[cml.Index] = cml;
customInfluence.Add(cml.Index, new CellLayer<InfluenceNode>(self.World.Map));
influence[cml.Index] = new CellLayer<InfluenceNode>(self.World.Map);
}
}
@@ -259,21 +268,21 @@ namespace OpenRA.Mods.Common.Traits
{
// PERF: Custom enumerator for efficiency - using `yield` is slower.
var uv = a.ToMPos(map);
if (!influence.Contains(uv))
var layer = influence[a.Layer];
if (!layer.Contains(uv))
return Enumerable.Empty<Actor>();
var layer = a.Layer == 0 ? influence : customInfluence[a.Layer];
return new ActorsAtEnumerable(layer[uv]);
}
public IEnumerable<Actor> GetActorsAt(CPos a, SubCell sub)
{
var uv = a.ToMPos(map);
if (!influence.Contains(uv))
var layer = influence[a.Layer];
if (!layer.Contains(uv))
yield break;
var always = sub == SubCell.FullCell || sub == SubCell.Any;
var layer = a.Layer == 0 ? influence : customInfluence[a.Layer];
for (var i = layer[uv]; i != null; i = i.Next)
if (!i.Actor.Disposed && (i.SubCell == sub || i.SubCell == SubCell.FullCell || always))
yield return i.Actor;
@@ -287,17 +296,18 @@ namespace OpenRA.Mods.Common.Traits
public SubCell FreeSubCell(CPos cell, SubCell preferredSubCell = SubCell.Any, bool checkTransient = true)
{
var uv = cell.ToMPos(map);
if (!influence.Contains(uv))
var layer = influence[cell.Layer];
if (!layer.Contains(uv))
return preferredSubCell != SubCell.Any ? preferredSubCell : SubCell.First;
if (preferredSubCell != SubCell.Any && !AnyActorsAt(uv, cell, preferredSubCell, checkTransient))
if (preferredSubCell != SubCell.Any && !AnyActorsAt(uv, cell, layer, preferredSubCell, checkTransient))
return preferredSubCell;
if (!AnyActorsAt(uv, cell.Layer))
if (!AnyActorsAt(uv, layer))
return map.Grid.DefaultSubCell;
for (var i = (int)SubCell.First; i < map.Grid.SubCellOffsets.Length; i++)
if (i != (int)preferredSubCell && !AnyActorsAt(uv, cell, (SubCell)i, checkTransient))
if (i != (int)preferredSubCell && !AnyActorsAt(uv, cell, layer, (SubCell)i, checkTransient))
return (SubCell)i;
return SubCell.Invalid;
@@ -306,26 +316,26 @@ namespace OpenRA.Mods.Common.Traits
public SubCell FreeSubCell(CPos cell, SubCell preferredSubCell, Func<Actor, bool> checkIfBlocker)
{
var uv = cell.ToMPos(map);
if (!influence.Contains(uv))
var layer = influence[cell.Layer];
if (!layer.Contains(uv))
return preferredSubCell != SubCell.Any ? preferredSubCell : SubCell.First;
if (preferredSubCell != SubCell.Any && !AnyActorsAt(uv, cell, preferredSubCell, checkIfBlocker))
if (preferredSubCell != SubCell.Any && !AnyActorsAt(uv, layer, preferredSubCell, checkIfBlocker))
return preferredSubCell;
if (!AnyActorsAt(uv, cell.Layer))
if (!AnyActorsAt(uv, layer))
return map.Grid.DefaultSubCell;
for (var i = (byte)SubCell.First; i < map.Grid.SubCellOffsets.Length; i++)
if (i != (byte)preferredSubCell && !AnyActorsAt(uv, cell, (SubCell)i, checkIfBlocker))
if (i != (byte)preferredSubCell && !AnyActorsAt(uv, layer, (SubCell)i, checkIfBlocker))
return (SubCell)i;
return SubCell.Invalid;
}
// NOTE: pos required to be in map bounds
bool AnyActorsAt(MPos uv, int layerIndex)
bool AnyActorsAt(MPos uv, CellLayer<InfluenceNode> layer)
{
var layer = layerIndex == 0 ? influence : customInfluence[layerIndex];
return layer[uv] != null;
}
@@ -333,17 +343,17 @@ namespace OpenRA.Mods.Common.Traits
public bool AnyActorsAt(CPos a)
{
var uv = a.ToMPos(map);
if (!influence.Contains(uv))
var layer = influence[a.Layer];
if (!layer.Contains(uv))
return false;
return AnyActorsAt(uv, a.Layer);
return AnyActorsAt(uv, layer);
}
// NOTE: pos required to be in map bounds
bool AnyActorsAt(MPos uv, CPos a, SubCell sub, bool checkTransient)
bool AnyActorsAt(MPos uv, CPos a, CellLayer<InfluenceNode> layer, SubCell sub, bool checkTransient)
{
var always = sub == SubCell.FullCell || sub == SubCell.Any;
var layer = a.Layer == 0 ? influence : customInfluence[a.Layer];
for (var i = layer[uv]; i != null; i = i.Next)
{
if (always || i.SubCell == sub || i.SubCell == SubCell.FullCell)
@@ -364,17 +374,17 @@ namespace OpenRA.Mods.Common.Traits
public bool AnyActorsAt(CPos a, SubCell sub, bool checkTransient = true)
{
var uv = a.ToMPos(map);
if (!influence.Contains(uv))
var layer = influence[a.Layer];
if (!layer.Contains(uv))
return false;
return AnyActorsAt(uv, a, sub, checkTransient);
return AnyActorsAt(uv, a, layer, sub, checkTransient);
}
// NOTE: can not check aircraft
bool AnyActorsAt(MPos uv, CPos a, SubCell sub, Func<Actor, bool> withCondition)
bool AnyActorsAt(MPos uv, CellLayer<InfluenceNode> layer, SubCell sub, Func<Actor, bool> withCondition)
{
var always = sub == SubCell.FullCell || sub == SubCell.Any;
var layer = a.Layer == 0 ? influence : customInfluence[a.Layer];
for (var i = layer[uv]; i != null; i = i.Next)
if ((always || i.SubCell == sub || i.SubCell == SubCell.FullCell) && !i.Actor.Disposed && withCondition(i.Actor))
return true;
@@ -386,10 +396,11 @@ namespace OpenRA.Mods.Common.Traits
public bool AnyActorsAt(CPos a, SubCell sub, Func<Actor, bool> withCondition)
{
var uv = a.ToMPos(map);
if (!influence.Contains(uv))
var layer = influence[a.Layer];
if (!layer.Contains(uv))
return false;
return AnyActorsAt(uv, a, sub, withCondition);
return AnyActorsAt(uv, layer, sub, withCondition);
}
public void AddInfluence(Actor self, IOccupySpace ios)
@@ -397,10 +408,10 @@ namespace OpenRA.Mods.Common.Traits
foreach (var c in ios.OccupiedCells())
{
var uv = c.Cell.ToMPos(map);
if (!influence.Contains(uv))
var layer = influence[c.Cell.Layer];
if (!layer.Contains(uv))
continue;
var layer = c.Cell.Layer == 0 ? influence : customInfluence[c.Cell.Layer];
layer[uv] = new InfluenceNode { Next = layer[uv], SubCell = c.SubCell, Actor = self };
if (cellTriggerInfluence.TryGetValue(c.Cell, out var triggers))
@@ -416,10 +427,10 @@ namespace OpenRA.Mods.Common.Traits
foreach (var c in ios.OccupiedCells())
{
var uv = c.Cell.ToMPos(map);
if (!influence.Contains(uv))
var layer = influence[c.Cell.Layer];
if (!layer.Contains(uv))
continue;
var layer = c.Cell.Layer == 0 ? influence : customInfluence[c.Cell.Layer];
var temp = layer[uv];
RemoveInfluenceInner(ref temp, self);
layer[uv] = temp;
@@ -496,9 +507,10 @@ namespace OpenRA.Mods.Common.Traits
var t = new CellTrigger(cells, onEntry, onExit);
cellTriggers.Add(id, t);
var layer = influence[0];
foreach (var c in cells)
{
if (!influence.Contains(c))
if (!layer.Contains(c))
continue;
if (!cellTriggerInfluence.ContainsKey(c))
@@ -649,7 +661,15 @@ namespace OpenRA.Mods.Common.Traits
public static class ActorMapWorldExts
{
public static Dictionary<int, ICustomMovementLayer> GetCustomMovementLayers(this World world)
/// <summary>
/// Returns an array of custom movement layers.
/// The <see cref="ICustomMovementLayer.Index"/> of a layer is used to index into this array.
/// This array may contain null entries for layers which are not present in the world.
/// This array is guaranteed to have a length of at least one. Index 0 is always null.
/// Index 0 is kept null as layer 0 is used for the ground layer, consumers can combine
/// the ground layer and custom layers into a single array for easy indexing.
/// </summary>
public static ICustomMovementLayer[] GetCustomMovementLayers(this World world)
{
return ((ActorMap)world.ActorMap).CustomMovementLayers;
}

View File

@@ -144,18 +144,16 @@ namespace OpenRA.Mods.Common.Traits
public readonly LocomotorInfo Info;
public readonly uint MovementClass;
CellLayer<short> cellsCost;
CellLayer<CellCache> blockingCache;
readonly Dictionary<byte, CellLayer<short>> customLayerCellsCost = new Dictionary<byte, CellLayer<short>>();
readonly Dictionary<byte, CellLayer<CellCache>> customLayerBlockingCache = new Dictionary<byte, CellLayer<CellCache>>();
readonly LocomotorInfo.TerrainInfo[] terrainInfos;
readonly World world;
readonly HashSet<CPos> dirtyCells = new HashSet<CPos>();
readonly bool sharesCell;
CellLayer<short>[] cellsCost;
CellLayer<CellCache>[] blockingCache;
IActorMap actorMap;
bool sharesCell;
public Locomotor(Actor self, LocomotorInfo info)
{
@@ -177,7 +175,7 @@ namespace OpenRA.Mods.Common.Traits
if (!world.Map.Contains(cell))
return PathGraph.MovementCostForUnreachableCell;
return cell.Layer == 0 ? cellsCost[cell] : customLayerCellsCost[cell.Layer][cell];
return cellsCost[cell.Layer][cell];
}
public int MovementSpeedForCell(CPos cell)
@@ -193,7 +191,7 @@ namespace OpenRA.Mods.Common.Traits
if (!world.Map.Contains(destNode))
return PathGraph.MovementCostForUnreachableCell;
var cellCost = destNode.Layer == 0 ? cellsCost[destNode] : customLayerCellsCost[destNode.Layer][destNode];
var cellCost = cellsCost[destNode.Layer][destNode];
if (cellCost == PathGraph.MovementCostForUnreachableCell ||
!CanMoveFreelyInto(actor, destNode, check, ignoreActor))
@@ -359,8 +357,8 @@ namespace OpenRA.Mods.Common.Traits
actorMap = w.ActorMap;
actorMap.CellUpdated += CellUpdated;
blockingCache = new CellLayer<CellCache>(map);
cellsCost = new CellLayer<short>(map);
cellsCost = new[] { new CellLayer<short>(map) };
blockingCache = new[] { new CellLayer<CellCache>(map) };
foreach (var cell in map.AllCells)
UpdateCellCost(cell);
@@ -371,12 +369,17 @@ namespace OpenRA.Mods.Common.Traits
// This section needs to run after WorldLoaded() because we need to be sure that all types of ICustomMovementLayer have been initialized.
w.AddFrameEndTask(_ =>
{
var customMovementLayers = w.WorldActor.TraitsImplementing<ICustomMovementLayer>();
foreach (var cml in customMovementLayers)
var cmls = world.GetCustomMovementLayers();
Array.Resize(ref cellsCost, cmls.Length);
Array.Resize(ref blockingCache, cmls.Length);
foreach (var cml in cmls)
{
if (cml == null)
continue;
var cellLayer = new CellLayer<short>(map);
customLayerCellsCost[cml.Index] = cellLayer;
customLayerBlockingCache[cml.Index] = new CellLayer<CellCache>(map);
cellsCost[cml.Index] = cellLayer;
blockingCache[cml.Index] = new CellLayer<CellCache>(map);
foreach (var cell in map.AllCells)
{
@@ -395,13 +398,10 @@ namespace OpenRA.Mods.Common.Traits
CellCache GetCache(CPos cell)
{
if (dirtyCells.Contains(cell))
{
if (dirtyCells.Remove(cell))
UpdateCellBlocking(cell);
dirtyCells.Remove(cell);
}
var cache = cell.Layer == 0 ? blockingCache : customLayerBlockingCache[cell.Layer];
var cache = blockingCache[cell.Layer];
return cache[cell];
}
@@ -422,7 +422,7 @@ namespace OpenRA.Mods.Common.Traits
if (index != byte.MaxValue)
cost = terrainInfos[index].Cost;
var cache = cell.Layer == 0 ? cellsCost : customLayerCellsCost[cell.Layer];
var cache = cellsCost[cell.Layer];
cache[cell] = cost;
}
@@ -431,7 +431,7 @@ namespace OpenRA.Mods.Common.Traits
{
using (new PerfSample("locomotor_cache"))
{
var cache = cell.Layer == 0 ? blockingCache : customLayerBlockingCache[cell.Layer];
var cache = blockingCache[cell.Layer];
var actors = actorMap.GetActorsAt(cell);
var cellFlag = CellFlag.HasFreeSpace;