Merge pull request #9140 from RoosterDragon/safe-pathfinder-perf
Improve pathfinder performance
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user