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 CellLayer<byte> CustomTerrain;
[FieldLoader.Ignore] CellLayer<short> cachedTerrainIndexes;
[FieldLoader.Ignore] bool initializedCellProjection;
[FieldLoader.Ignore] CellLayer<PPos[]> cellProjection;
@@ -292,6 +293,8 @@ namespace OpenRA
{
var ret = new CellLayer<TerrainTile>(tileShape, size);
ret.Clear(tileRef);
if (MaximumTerrainHeight > 0)
ret.CellEntryChanged += UpdateProjection;
return ret;
});
@@ -299,6 +302,8 @@ namespace OpenRA
{
var ret = new CellLayer<byte>(tileShape, size);
ret.Clear(0);
if (MaximumTerrainHeight > 0)
ret.CellEntryChanged += UpdateProjection;
return ret;
});
@@ -762,6 +767,9 @@ namespace OpenRA
bool ContainsAllProjectedCellsCovering(MPos uv)
{
if (MaximumTerrainHeight == 0)
return Contains((PPos)uv);
foreach (var puv in ProjectedCellsCovering(uv))
if (!Contains(puv))
return false;
@@ -947,9 +955,32 @@ namespace OpenRA
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 custom = CustomTerrain[uv];
return custom != byte.MaxValue ? custom : cachedTileSet.Value.GetTerrainIndex(MapTiles.Value[uv]);
var terrainIndex = cachedTerrainIndexes[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)

View File

@@ -74,7 +74,7 @@ namespace OpenRA
public readonly bool PickAny;
public readonly string Category;
TerrainTileInfo[] tileInfo;
readonly TerrainTileInfo[] tileInfo;
public TerrainTemplateInfo(ushort id, string[] images, int2 size, byte[] tiles)
{
@@ -177,7 +177,7 @@ namespace OpenRA
public readonly bool IgnoreTileSpriteOffsets;
[FieldLoader.Ignore]
public readonly Dictionary<ushort, TerrainTemplateInfo> Templates = new Dictionary<ushort, TerrainTemplateInfo>();
public readonly IReadOnlyDictionary<ushort, TerrainTemplateInfo> Templates;
[FieldLoader.Ignore]
public readonly TerrainTypeInfo[] TerrainInfo;
@@ -217,7 +217,7 @@ namespace OpenRA
// Templates
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)

View File

@@ -9,6 +9,7 @@
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
@@ -186,22 +187,51 @@ namespace OpenRA.Traits
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)
{
if (!influence.Contains(a))
yield break;
for (var i = influence[a]; i != null; i = i.Next)
if (!i.Actor.Disposed)
yield return i.Actor;
var uv = a.ToMPos(map);
if (!influence.Contains(uv))
return Enumerable.Empty<Actor>();
return new UnitsAtEnumerable(influence[uv]);
}
public IEnumerable<Actor> GetUnitsAt(CPos a, SubCell sub)
{
if (!influence.Contains(a))
var uv = a.ToMPos(map);
if (!influence.Contains(uv))
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))
yield return i.Actor;
}
@@ -243,20 +273,22 @@ namespace OpenRA.Traits
// NOTE: always includes transients with influence
public bool AnyUnitsAt(CPos a)
{
if (!influence.Contains(a))
var uv = a.ToMPos(map);
if (!influence.Contains(uv))
return false;
return influence[a] != null;
return influence[uv] != null;
}
// NOTE: can not check aircraft
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;
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)
{
@@ -275,11 +307,12 @@ namespace OpenRA.Traits
// NOTE: can not check aircraft
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;
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))
return true;
@@ -290,10 +323,11 @@ namespace OpenRA.Traits
{
foreach (var c in ios.OccupiedCells())
{
if (!influence.Contains(c.First))
var uv = c.First.ToMPos(map);
if (!influence.Contains(uv))
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;
if (cellTriggerInfluence.TryGetValue(c.First, out triggers))
@@ -306,12 +340,13 @@ namespace OpenRA.Traits
{
foreach (var c in ios.OccupiedCells())
{
if (!influence.Contains(c.First))
var uv = c.First.ToMPos(map);
if (!influence.Contains(uv))
continue;
var temp = influence[c.First];
var temp = influence[uv];
RemoveInfluenceInner(ref temp, self);
influence[c.First] = temp;
influence[uv] = temp;
List<CellTrigger> triggers;
if (cellTriggerInfluence.TryGetValue(c.First, out triggers))

View File

@@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Pathfinder
/// <summary>
/// Gets all the Connections for a given node in the graph
/// </summary>
IEnumerable<GraphConnection> GetConnections(CPos position);
List<GraphConnection> GetConnections(CPos position);
/// <summary>
/// Retrieves an object given a node in the graph
@@ -47,13 +47,11 @@ namespace OpenRA.Mods.Common.Pathfinder
public struct GraphConnection
{
public readonly CPos Source;
public readonly CPos Destination;
public readonly int Cost;
public GraphConnection(CPos source, CPos destination, int cost)
public GraphConnection(CPos destination, int cost)
{
Source = source;
Destination = destination;
Cost = cost;
}
@@ -71,6 +69,7 @@ namespace OpenRA.Mods.Common.Pathfinder
readonly CellConditions checkConditions;
readonly MobileInfo mobileInfo;
readonly MobileInfo.WorldMovementInfo worldMovementInfo;
CellLayer<CellInfo> cellInfo;
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;
World = world;
this.mobileInfo = mobileInfo;
worldMovementInfo = mobileInfo.GetWorldMovementInfo(world);
Actor = actor;
LaneBias = 1;
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) },
};
public IEnumerable<GraphConnection> GetConnections(CPos position)
public List<GraphConnection> GetConnections(CPos position)
{
var previousPos = cellInfo[position].PreviousPos;
@@ -108,14 +108,14 @@ namespace OpenRA.Mods.Common.Pathfinder
var dy = position.Y - previousPos.Y;
var index = dy * 3 + dx + 4;
var validNeighbors = new LinkedList<GraphConnection>();
var directions = DirectedNeighbors[index];
var validNeighbors = new List<GraphConnection>(directions.Length);
for (var i = 0; i < directions.Length; i++)
{
var neighbor = position + directions[i];
var movementCost = GetCostToNode(neighbor, directions[i]);
if (movementCost != Constants.InvalidNode)
validNeighbors.AddLast(new GraphConnection(position, neighbor, movementCost));
validNeighbors.Add(new GraphConnection(neighbor, movementCost));
}
return validNeighbors;
@@ -123,17 +123,9 @@ namespace OpenRA.Mods.Common.Pathfinder
int GetCostToNode(CPos destNode, CVec direction)
{
int movementCost;
if (mobileInfo.CanEnterCell(
World,
Actor,
destNode,
out movementCost,
IgnoredActor,
checkConditions) && !(CustomBlock != null && CustomBlock(destNode)))
{
var movementCost = mobileInfo.MovementCostToEnterCell(worldMovementInfo, Actor, destNode, IgnoredActor, checkConditions);
if (movementCost != int.MaxValue && !(CustomBlock != null && CustomBlock(destNode)))
return CalculateCellCost(destNode, direction, movementCost);
}
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, int> TilesetMovementClass;
@@ -136,14 +147,19 @@ namespace OpenRA.Mods.Common.Traits
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;
var index = world.Map.GetTerrainIndex(cell);
var index = map.GetTerrainIndex(cell);
if (index == byte.MaxValue)
return int.MaxValue;
return TilesetTerrainInfo[world.TileSet][index].Cost;
return terrainInfos[index].Cost;
}
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.
var crushables = otherActor.TraitsImplementing<ICrushable>();
if (!crushables.Any())
return true;
var lacksCrushability = true;
foreach (var crushable in crushables)
{
lacksCrushability = false;
if (!crushable.CrushableBy(Crushes, self.Owner))
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.
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 false;
return new WorldMovementInfo(world, this);
}
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(
@@ -283,7 +310,7 @@ namespace OpenRA.Mods.Common.Traits
internal int TicksBeforePathing = 0;
readonly Actor self;
readonly Lazy<ISpeedModifier[]> speedModifiers;
readonly Lazy<IEnumerable<int>> speedModifiers;
public readonly MobileInfo Info;
public bool IsMoving { get; set; }
@@ -324,7 +351,7 @@ namespace OpenRA.Mods.Common.Traits
self = init.Self;
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;
if (init.Contains<SubCellInit>())
@@ -581,7 +608,7 @@ namespace OpenRA.Mods.Common.Traits
if (terrainSpeed == 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);
}