Merge pull request #9140 from RoosterDragon/safe-pathfinder-perf

Improve pathfinder performance
This commit is contained in:
reaperrr
2015-09-01 13:02:29 +02:00
5 changed files with 138 additions and 53 deletions

View File

@@ -246,6 +246,7 @@ namespace OpenRA
[FieldLoader.Ignore] public Lazy<CellLayer<byte>> MapHeight; [FieldLoader.Ignore] public Lazy<CellLayer<byte>> MapHeight;
[FieldLoader.Ignore] public CellLayer<byte> CustomTerrain; [FieldLoader.Ignore] public CellLayer<byte> CustomTerrain;
[FieldLoader.Ignore] CellLayer<short> cachedTerrainIndexes;
[FieldLoader.Ignore] bool initializedCellProjection; [FieldLoader.Ignore] bool initializedCellProjection;
[FieldLoader.Ignore] CellLayer<PPos[]> cellProjection; [FieldLoader.Ignore] CellLayer<PPos[]> cellProjection;
@@ -292,6 +293,8 @@ namespace OpenRA
{ {
var ret = new CellLayer<TerrainTile>(tileShape, size); var ret = new CellLayer<TerrainTile>(tileShape, size);
ret.Clear(tileRef); ret.Clear(tileRef);
if (MaximumTerrainHeight > 0)
ret.CellEntryChanged += UpdateProjection;
return ret; return ret;
}); });
@@ -299,6 +302,8 @@ namespace OpenRA
{ {
var ret = new CellLayer<byte>(tileShape, size); var ret = new CellLayer<byte>(tileShape, size);
ret.Clear(0); ret.Clear(0);
if (MaximumTerrainHeight > 0)
ret.CellEntryChanged += UpdateProjection;
return ret; return ret;
}); });
@@ -762,6 +767,9 @@ namespace OpenRA
bool ContainsAllProjectedCellsCovering(MPos uv) bool ContainsAllProjectedCellsCovering(MPos uv)
{ {
if (MaximumTerrainHeight == 0)
return Contains((PPos)uv);
foreach (var puv in ProjectedCellsCovering(uv)) foreach (var puv in ProjectedCellsCovering(uv))
if (!Contains(puv)) if (!Contains(puv))
return false; return false;
@@ -947,9 +955,32 @@ namespace OpenRA
public byte GetTerrainIndex(CPos cell) public byte GetTerrainIndex(CPos cell)
{ {
const short InvalidCachedTerrainIndex = -1;
// Lazily initialize a cache for terrain indexes.
if (cachedTerrainIndexes == null)
{
cachedTerrainIndexes = new CellLayer<short>(this);
cachedTerrainIndexes.Clear(InvalidCachedTerrainIndex);
// Invalidate the entry for a cell if anything could cause the terrain index to change.
Action<CPos> invalidateTerrainIndex = c => cachedTerrainIndexes[c] = InvalidCachedTerrainIndex;
CustomTerrain.CellEntryChanged += invalidateTerrainIndex;
MapTiles.Value.CellEntryChanged += invalidateTerrainIndex;
}
var uv = cell.ToMPos(this); var uv = cell.ToMPos(this);
var custom = CustomTerrain[uv]; var terrainIndex = cachedTerrainIndexes[uv];
return custom != byte.MaxValue ? custom : cachedTileSet.Value.GetTerrainIndex(MapTiles.Value[uv]);
// Cache terrain indexes per cell on demand.
if (terrainIndex == InvalidCachedTerrainIndex)
{
var custom = CustomTerrain[uv];
terrainIndex = cachedTerrainIndexes[uv] =
custom != byte.MaxValue ? custom : cachedTileSet.Value.GetTerrainIndex(MapTiles.Value[uv]);
}
return (byte)terrainIndex;
} }
public TerrainTypeInfo GetTerrainInfo(CPos cell) public TerrainTypeInfo GetTerrainInfo(CPos cell)

View File

@@ -74,7 +74,7 @@ namespace OpenRA
public readonly bool PickAny; public readonly bool PickAny;
public readonly string Category; public readonly string Category;
TerrainTileInfo[] tileInfo; readonly TerrainTileInfo[] tileInfo;
public TerrainTemplateInfo(ushort id, string[] images, int2 size, byte[] tiles) public TerrainTemplateInfo(ushort id, string[] images, int2 size, byte[] tiles)
{ {
@@ -177,7 +177,7 @@ namespace OpenRA
public readonly bool IgnoreTileSpriteOffsets; public readonly bool IgnoreTileSpriteOffsets;
[FieldLoader.Ignore] [FieldLoader.Ignore]
public readonly Dictionary<ushort, TerrainTemplateInfo> Templates = new Dictionary<ushort, TerrainTemplateInfo>(); public readonly IReadOnlyDictionary<ushort, TerrainTemplateInfo> Templates;
[FieldLoader.Ignore] [FieldLoader.Ignore]
public readonly TerrainTypeInfo[] TerrainInfo; public readonly TerrainTypeInfo[] TerrainInfo;
@@ -217,7 +217,7 @@ namespace OpenRA
// Templates // Templates
Templates = yaml["Templates"].ToDictionary().Values Templates = yaml["Templates"].ToDictionary().Values
.Select(y => new TerrainTemplateInfo(this, y)).ToDictionary(t => t.Id); .Select(y => new TerrainTemplateInfo(this, y)).ToDictionary(t => t.Id).AsReadOnly();
} }
public TileSet(string name, string id, string palette, TerrainTypeInfo[] terrainInfo) public TileSet(string name, string id, string palette, TerrainTypeInfo[] terrainInfo)

View File

@@ -9,6 +9,7 @@
#endregion #endregion
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
@@ -186,22 +187,51 @@ namespace OpenRA.Traits
actorShouldBeRemoved = removeActorPosition.Contains; actorShouldBeRemoved = removeActorPosition.Contains;
} }
sealed class UnitsAtEnumerator : IEnumerator<Actor>
{
InfluenceNode node;
public UnitsAtEnumerator(InfluenceNode node) { this.node = node; }
public void Reset() { throw new NotSupportedException(); }
public Actor Current { get; private set; }
object IEnumerator.Current { get { return Current; } }
public void Dispose() { }
public bool MoveNext()
{
while (node != null)
{
Current = node.Actor;
node = node.Next;
if (!Current.Disposed)
return true;
}
return false;
}
}
sealed class UnitsAtEnumerable : IEnumerable<Actor>
{
readonly InfluenceNode node;
public UnitsAtEnumerable(InfluenceNode node) { this.node = node; }
public IEnumerator<Actor> GetEnumerator() { return new UnitsAtEnumerator(node); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
public IEnumerable<Actor> GetUnitsAt(CPos a) public IEnumerable<Actor> GetUnitsAt(CPos a)
{ {
if (!influence.Contains(a)) var uv = a.ToMPos(map);
yield break; if (!influence.Contains(uv))
return Enumerable.Empty<Actor>();
for (var i = influence[a]; i != null; i = i.Next) return new UnitsAtEnumerable(influence[uv]);
if (!i.Actor.Disposed)
yield return i.Actor;
} }
public IEnumerable<Actor> GetUnitsAt(CPos a, SubCell sub) public IEnumerable<Actor> GetUnitsAt(CPos a, SubCell sub)
{ {
if (!influence.Contains(a)) var uv = a.ToMPos(map);
if (!influence.Contains(uv))
yield break; yield break;
for (var i = influence[a]; i != null; i = i.Next) for (var i = influence[uv]; i != null; i = i.Next)
if (!i.Actor.Disposed && (i.SubCell == sub || i.SubCell == SubCell.FullCell)) if (!i.Actor.Disposed && (i.SubCell == sub || i.SubCell == SubCell.FullCell))
yield return i.Actor; yield return i.Actor;
} }
@@ -243,20 +273,22 @@ namespace OpenRA.Traits
// NOTE: always includes transients with influence // NOTE: always includes transients with influence
public bool AnyUnitsAt(CPos a) public bool AnyUnitsAt(CPos a)
{ {
if (!influence.Contains(a)) var uv = a.ToMPos(map);
if (!influence.Contains(uv))
return false; return false;
return influence[a] != null; return influence[uv] != null;
} }
// NOTE: can not check aircraft // NOTE: can not check aircraft
public bool AnyUnitsAt(CPos a, SubCell sub, bool checkTransient = true) public bool AnyUnitsAt(CPos a, SubCell sub, bool checkTransient = true)
{ {
if (!influence.Contains(a)) var uv = a.ToMPos(map);
if (!influence.Contains(uv))
return false; return false;
var always = sub == SubCell.FullCell || sub == SubCell.Any; var always = sub == SubCell.FullCell || sub == SubCell.Any;
for (var i = influence[a]; i != null; i = i.Next) for (var i = influence[uv]; i != null; i = i.Next)
{ {
if (always || i.SubCell == sub || i.SubCell == SubCell.FullCell) if (always || i.SubCell == sub || i.SubCell == SubCell.FullCell)
{ {
@@ -275,11 +307,12 @@ namespace OpenRA.Traits
// NOTE: can not check aircraft // NOTE: can not check aircraft
public bool AnyUnitsAt(CPos a, SubCell sub, Func<Actor, bool> withCondition) public bool AnyUnitsAt(CPos a, SubCell sub, Func<Actor, bool> withCondition)
{ {
if (!influence.Contains(a)) var uv = a.ToMPos(map);
if (!influence.Contains(uv))
return false; return false;
var always = sub == SubCell.FullCell || sub == SubCell.Any; var always = sub == SubCell.FullCell || sub == SubCell.Any;
for (var i = influence[a]; i != null; i = i.Next) for (var i = influence[uv]; i != null; i = i.Next)
if ((always || i.SubCell == sub || i.SubCell == SubCell.FullCell) && !i.Actor.Disposed && withCondition(i.Actor)) if ((always || i.SubCell == sub || i.SubCell == SubCell.FullCell) && !i.Actor.Disposed && withCondition(i.Actor))
return true; return true;
@@ -290,10 +323,11 @@ namespace OpenRA.Traits
{ {
foreach (var c in ios.OccupiedCells()) foreach (var c in ios.OccupiedCells())
{ {
if (!influence.Contains(c.First)) var uv = c.First.ToMPos(map);
if (!influence.Contains(uv))
continue; continue;
influence[c.First] = new InfluenceNode { Next = influence[c.First], SubCell = c.Second, Actor = self }; influence[uv] = new InfluenceNode { Next = influence[uv], SubCell = c.Second, Actor = self };
List<CellTrigger> triggers; List<CellTrigger> triggers;
if (cellTriggerInfluence.TryGetValue(c.First, out triggers)) if (cellTriggerInfluence.TryGetValue(c.First, out triggers))
@@ -306,12 +340,13 @@ namespace OpenRA.Traits
{ {
foreach (var c in ios.OccupiedCells()) foreach (var c in ios.OccupiedCells())
{ {
if (!influence.Contains(c.First)) var uv = c.First.ToMPos(map);
if (!influence.Contains(uv))
continue; continue;
var temp = influence[c.First]; var temp = influence[uv];
RemoveInfluenceInner(ref temp, self); RemoveInfluenceInner(ref temp, self);
influence[c.First] = temp; influence[uv] = temp;
List<CellTrigger> triggers; List<CellTrigger> triggers;
if (cellTriggerInfluence.TryGetValue(c.First, out triggers)) if (cellTriggerInfluence.TryGetValue(c.First, out triggers))

View File

@@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Pathfinder
/// <summary> /// <summary>
/// Gets all the Connections for a given node in the graph /// Gets all the Connections for a given node in the graph
/// </summary> /// </summary>
IEnumerable<GraphConnection> GetConnections(CPos position); List<GraphConnection> GetConnections(CPos position);
/// <summary> /// <summary>
/// Retrieves an object given a node in the graph /// Retrieves an object given a node in the graph
@@ -47,13 +47,11 @@ namespace OpenRA.Mods.Common.Pathfinder
public struct GraphConnection public struct GraphConnection
{ {
public readonly CPos Source;
public readonly CPos Destination; public readonly CPos Destination;
public readonly int Cost; public readonly int Cost;
public GraphConnection(CPos source, CPos destination, int cost) public GraphConnection(CPos destination, int cost)
{ {
Source = source;
Destination = destination; Destination = destination;
Cost = cost; Cost = cost;
} }
@@ -71,6 +69,7 @@ namespace OpenRA.Mods.Common.Pathfinder
readonly CellConditions checkConditions; readonly CellConditions checkConditions;
readonly MobileInfo mobileInfo; readonly MobileInfo mobileInfo;
readonly MobileInfo.WorldMovementInfo worldMovementInfo;
CellLayer<CellInfo> cellInfo; CellLayer<CellInfo> cellInfo;
public PathGraph(CellLayer<CellInfo> cellInfo, MobileInfo mobileInfo, Actor actor, World world, bool checkForBlocked) public PathGraph(CellLayer<CellInfo> cellInfo, MobileInfo mobileInfo, Actor actor, World world, bool checkForBlocked)
@@ -78,6 +77,7 @@ namespace OpenRA.Mods.Common.Pathfinder
this.cellInfo = cellInfo; this.cellInfo = cellInfo;
World = world; World = world;
this.mobileInfo = mobileInfo; this.mobileInfo = mobileInfo;
worldMovementInfo = mobileInfo.GetWorldMovementInfo(world);
Actor = actor; Actor = actor;
LaneBias = 1; LaneBias = 1;
checkConditions = checkForBlocked ? CellConditions.TransientActors : CellConditions.None; checkConditions = checkForBlocked ? CellConditions.TransientActors : CellConditions.None;
@@ -100,7 +100,7 @@ namespace OpenRA.Mods.Common.Pathfinder
new[] { new CVec(1, -1), new CVec(1, 0), new CVec(-1, 1), new CVec(0, 1), new CVec(1, 1) }, new[] { new CVec(1, -1), new CVec(1, 0), new CVec(-1, 1), new CVec(0, 1), new CVec(1, 1) },
}; };
public IEnumerable<GraphConnection> GetConnections(CPos position) public List<GraphConnection> GetConnections(CPos position)
{ {
var previousPos = cellInfo[position].PreviousPos; var previousPos = cellInfo[position].PreviousPos;
@@ -108,14 +108,14 @@ namespace OpenRA.Mods.Common.Pathfinder
var dy = position.Y - previousPos.Y; var dy = position.Y - previousPos.Y;
var index = dy * 3 + dx + 4; var index = dy * 3 + dx + 4;
var validNeighbors = new LinkedList<GraphConnection>();
var directions = DirectedNeighbors[index]; var directions = DirectedNeighbors[index];
var validNeighbors = new List<GraphConnection>(directions.Length);
for (var i = 0; i < directions.Length; i++) for (var i = 0; i < directions.Length; i++)
{ {
var neighbor = position + directions[i]; var neighbor = position + directions[i];
var movementCost = GetCostToNode(neighbor, directions[i]); var movementCost = GetCostToNode(neighbor, directions[i]);
if (movementCost != Constants.InvalidNode) if (movementCost != Constants.InvalidNode)
validNeighbors.AddLast(new GraphConnection(position, neighbor, movementCost)); validNeighbors.Add(new GraphConnection(neighbor, movementCost));
} }
return validNeighbors; return validNeighbors;
@@ -123,17 +123,9 @@ namespace OpenRA.Mods.Common.Pathfinder
int GetCostToNode(CPos destNode, CVec direction) int GetCostToNode(CPos destNode, CVec direction)
{ {
int movementCost; var movementCost = mobileInfo.MovementCostToEnterCell(worldMovementInfo, Actor, destNode, IgnoredActor, checkConditions);
if (mobileInfo.CanEnterCell( if (movementCost != int.MaxValue && !(CustomBlock != null && CustomBlock(destNode)))
World,
Actor,
destNode,
out movementCost,
IgnoredActor,
checkConditions) && !(CustomBlock != null && CustomBlock(destNode)))
{
return CalculateCellCost(destNode, direction, movementCost); return CalculateCellCost(destNode, direction, movementCost);
}
return Constants.InvalidNode; return Constants.InvalidNode;
} }

View File

@@ -125,6 +125,17 @@ namespace OpenRA.Mods.Common.Traits
} }
} }
public struct WorldMovementInfo
{
internal readonly World World;
internal readonly TerrainInfo[] TerrainInfos;
internal WorldMovementInfo(World world, MobileInfo info)
{
World = world;
TerrainInfos = info.TilesetTerrainInfo[world.TileSet];
}
}
public readonly Cache<TileSet, TerrainInfo[]> TilesetTerrainInfo; public readonly Cache<TileSet, TerrainInfo[]> TilesetTerrainInfo;
public readonly Cache<TileSet, int> TilesetMovementClass; public readonly Cache<TileSet, int> TilesetMovementClass;
@@ -136,14 +147,19 @@ namespace OpenRA.Mods.Common.Traits
public int MovementCostForCell(World world, CPos cell) public int MovementCostForCell(World world, CPos cell)
{ {
if (!world.Map.Contains(cell)) return MovementCostForCell(world.Map, TilesetTerrainInfo[world.TileSet], cell);
}
int MovementCostForCell(Map map, TerrainInfo[] terrainInfos, CPos cell)
{
if (!map.Contains(cell))
return int.MaxValue; return int.MaxValue;
var index = world.Map.GetTerrainIndex(cell); var index = map.GetTerrainIndex(cell);
if (index == byte.MaxValue) if (index == byte.MaxValue)
return int.MaxValue; return int.MaxValue;
return TilesetTerrainInfo[world.TileSet][index].Cost; return terrainInfos[index].Cost;
} }
public int CalculateTilesetMovementClass(TileSet tileset) public int CalculateTilesetMovementClass(TileSet tileset)
@@ -226,22 +242,33 @@ namespace OpenRA.Mods.Common.Traits
// If the other actor in our way cannot be crushed, we are blocked. // If the other actor in our way cannot be crushed, we are blocked.
var crushables = otherActor.TraitsImplementing<ICrushable>(); var crushables = otherActor.TraitsImplementing<ICrushable>();
if (!crushables.Any()) var lacksCrushability = true;
return true;
foreach (var crushable in crushables) foreach (var crushable in crushables)
{
lacksCrushability = false;
if (!crushable.CrushableBy(Crushes, self.Owner)) if (!crushable.CrushableBy(Crushes, self.Owner))
return true; return true;
}
// If there are no crushable traits at all, this means the other actor cannot be crushed - we are blocked.
if (lacksCrushability)
return true;
// We are not blocked by the other actor. // We are not blocked by the other actor.
return false; return false;
} }
public bool CanEnterCell(World world, Actor self, CPos cell, out int movementCost, Actor ignoreActor = null, CellConditions check = CellConditions.All) public WorldMovementInfo GetWorldMovementInfo(World world)
{ {
if ((movementCost = MovementCostForCell(world, cell)) == int.MaxValue) return new WorldMovementInfo(world, this);
return false; }
return CanMoveFreelyInto(world, self, cell, ignoreActor, check); public int MovementCostToEnterCell(WorldMovementInfo worldMovementInfo, Actor self, CPos cell, Actor ignoreActor = null, CellConditions check = CellConditions.All)
{
var cost = MovementCostForCell(worldMovementInfo.World.Map, worldMovementInfo.TerrainInfos, cell);
if (cost == int.MaxValue || !CanMoveFreelyInto(worldMovementInfo.World, self, cell, ignoreActor, check))
return int.MaxValue;
return cost;
} }
public SubCell GetAvailableSubCell( public SubCell GetAvailableSubCell(
@@ -283,7 +310,7 @@ namespace OpenRA.Mods.Common.Traits
internal int TicksBeforePathing = 0; internal int TicksBeforePathing = 0;
readonly Actor self; readonly Actor self;
readonly Lazy<ISpeedModifier[]> speedModifiers; readonly Lazy<IEnumerable<int>> speedModifiers;
public readonly MobileInfo Info; public readonly MobileInfo Info;
public bool IsMoving { get; set; } public bool IsMoving { get; set; }
@@ -324,7 +351,7 @@ namespace OpenRA.Mods.Common.Traits
self = init.Self; self = init.Self;
Info = info; Info = info;
speedModifiers = Exts.Lazy(() => self.TraitsImplementing<ISpeedModifier>().ToArray()); speedModifiers = Exts.Lazy(() => self.TraitsImplementing<ISpeedModifier>().ToArray().Select(x => x.GetSpeedModifier()));
ToSubCell = FromSubCell = info.SharesCell ? init.World.Map.DefaultSubCell : SubCell.FullCell; ToSubCell = FromSubCell = info.SharesCell ? init.World.Map.DefaultSubCell : SubCell.FullCell;
if (init.Contains<SubCellInit>()) if (init.Contains<SubCellInit>())
@@ -581,7 +608,7 @@ namespace OpenRA.Mods.Common.Traits
if (terrainSpeed == 0) if (terrainSpeed == 0)
return 0; return 0;
var modifiers = speedModifiers.Value.Select(x => x.GetSpeedModifier()).Append(terrainSpeed); var modifiers = speedModifiers.Value.Append(terrainSpeed);
return Util.ApplyPercentageModifiers(Info.Speed, modifiers); return Util.ApplyPercentageModifiers(Info.Speed, modifiers);
} }