Bring PathSearch in line with the current code style.

This commit is contained in:
Paul Chote
2014-05-17 13:45:11 +12:00
parent 2026747f2a
commit 97a61273dd
3 changed files with 148 additions and 132 deletions

View File

@@ -119,7 +119,7 @@ namespace OpenRA.Mods.RA.Activities
var path = pathFinder.FindBidiPath( var path = pathFinder.FindBidiPath(
PathSearch.FromPoints(self.World, mobile.Info, self, searchCells, loc, true), PathSearch.FromPoints(self.World, mobile.Info, self, searchCells, loc, true),
PathSearch.FromPoint(self.World, mobile.Info, self, loc, targetPosition, true).InReverse() PathSearch.FromPoint(self.World, mobile.Info, self, loc, targetPosition, true).Reverse()
); );
inner = mobile.MoveTo(() => path); inner = mobile.MoveTo(() => path);

30
OpenRA.Mods.RA/Move/PathFinder.cs Executable file → Normal file
View File

@@ -68,7 +68,7 @@ namespace OpenRA.Mods.RA.Move
var pb = FindBidiPath( var pb = FindBidiPath(
PathSearch.FromPoint(world, mi, self, target, from, true), PathSearch.FromPoint(world, mi, self, target, from, true),
PathSearch.FromPoint(world, mi, self, from, target, true).InReverse() PathSearch.FromPoint(world, mi, self, from, target, true).Reverse()
); );
CheckSanePath2(pb, from, target); CheckSanePath2(pb, from, target);
@@ -109,7 +109,7 @@ namespace OpenRA.Mods.RA.Move
var path = FindBidiPath( var path = FindBidiPath(
PathSearch.FromPoints(world, mi, self, tilesInRange, src, true), PathSearch.FromPoints(world, mi, self, tilesInRange, src, true),
PathSearch.FromPoint(world, mi, self, src, targetCell, true).InReverse() PathSearch.FromPoint(world, mi, self, src, targetCell, true).Reverse()
); );
return path; return path;
@@ -124,12 +124,12 @@ namespace OpenRA.Mods.RA.Move
{ {
List<CPos> path = null; List<CPos> path = null;
while (!search.queue.Empty) while (!search.Queue.Empty)
{ {
var p = search.Expand(world); var p = search.Expand(world);
if (search.heuristic(p) == 0) if (search.Heuristic(p) == 0)
{ {
path = MakePath(search.cellInfo, p); path = MakePath(search.CellInfo, p);
break; break;
} }
} }
@@ -137,7 +137,7 @@ namespace OpenRA.Mods.RA.Move
var dbg = world.WorldActor.TraitOrDefault<PathfinderDebugOverlay>(); var dbg = world.WorldActor.TraitOrDefault<PathfinderDebugOverlay>();
if (dbg != null) if (dbg != null)
{ {
dbg.AddLayer(search.considered.Select(p => new Pair<CPos, int>(p, search.cellInfo[p.X, p.Y].MinCost)), search.maxCost, search.owner); dbg.AddLayer(search.Considered.Select(p => new Pair<CPos, int>(p, search.CellInfo[p.X, p.Y].MinCost)), search.MaxCost, search.Owner);
} }
if (path != null) if (path != null)
@@ -176,13 +176,13 @@ namespace OpenRA.Mods.RA.Move
{ {
List<CPos> path = null; List<CPos> path = null;
while (!fromSrc.queue.Empty && !fromDest.queue.Empty) while (!fromSrc.Queue.Empty && !fromDest.Queue.Empty)
{ {
/* make some progress on the first search */ /* make some progress on the first search */
var p = fromSrc.Expand(world); var p = fromSrc.Expand(world);
if (fromDest.cellInfo[p.X, p.Y].Seen && if (fromDest.CellInfo[p.X, p.Y].Seen &&
fromDest.cellInfo[p.X, p.Y].MinCost < float.PositiveInfinity) fromDest.CellInfo[p.X, p.Y].MinCost < float.PositiveInfinity)
{ {
path = MakeBidiPath(fromSrc, fromDest, p); path = MakeBidiPath(fromSrc, fromDest, p);
break; break;
@@ -191,8 +191,8 @@ namespace OpenRA.Mods.RA.Move
/* make some progress on the second search */ /* make some progress on the second search */
var q = fromDest.Expand(world); var q = fromDest.Expand(world);
if (fromSrc.cellInfo[q.X, q.Y].Seen && if (fromSrc.CellInfo[q.X, q.Y].Seen &&
fromSrc.cellInfo[q.X, q.Y].MinCost < float.PositiveInfinity) fromSrc.CellInfo[q.X, q.Y].MinCost < float.PositiveInfinity)
{ {
path = MakeBidiPath(fromSrc, fromDest, q); path = MakeBidiPath(fromSrc, fromDest, q);
break; break;
@@ -202,8 +202,8 @@ namespace OpenRA.Mods.RA.Move
var dbg = world.WorldActor.TraitOrDefault<PathfinderDebugOverlay>(); var dbg = world.WorldActor.TraitOrDefault<PathfinderDebugOverlay>();
if (dbg != null) if (dbg != null)
{ {
dbg.AddLayer(fromSrc.considered.Select(p => new Pair<CPos, int>(p, fromSrc.cellInfo[p.X, p.Y].MinCost)), fromSrc.maxCost, fromSrc.owner); dbg.AddLayer(fromSrc.Considered.Select(p => new Pair<CPos, int>(p, fromSrc.CellInfo[p.X, p.Y].MinCost)), fromSrc.MaxCost, fromSrc.Owner);
dbg.AddLayer(fromDest.considered.Select(p => new Pair<CPos, int>(p, fromDest.cellInfo[p.X, p.Y].MinCost)), fromDest.maxCost, fromDest.owner); dbg.AddLayer(fromDest.Considered.Select(p => new Pair<CPos, int>(p, fromDest.CellInfo[p.X, p.Y].MinCost)), fromDest.MaxCost, fromDest.Owner);
} }
if (path != null) if (path != null)
@@ -216,8 +216,8 @@ namespace OpenRA.Mods.RA.Move
static List<CPos> MakeBidiPath(PathSearch a, PathSearch b, CPos p) static List<CPos> MakeBidiPath(PathSearch a, PathSearch b, CPos p)
{ {
var ca = a.cellInfo; var ca = a.CellInfo;
var cb = b.cellInfo; var cb = b.CellInfo;
var ret = new List<CPos>(); var ret = new List<CPos>();

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information #region Copyright & License Information
/* /*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS) * Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made * This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License * available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information, * as published by the Free Software Foundation. For more information,
@@ -17,38 +17,88 @@ namespace OpenRA.Mods.RA.Move
{ {
public sealed class PathSearch : IDisposable public sealed class PathSearch : IDisposable
{ {
World world; public CellInfo[,] CellInfo;
public CellInfo[,] cellInfo; public PriorityQueue<PathDistance> Queue;
public PriorityQueue<PathDistance> queue; public Func<CPos, int> Heuristic;
public Func<CPos, int> heuristic; public bool CheckForBlocked;
public Actor IgnoreBuilding;
public bool InReverse;
public HashSet<CPos> Considered;
public Player Owner { get { return self.Owner; } }
public int MaxCost;
Actor self;
MobileInfo mobileInfo;
Func<CPos, int> customCost; Func<CPos, int> customCost;
Func<CPos, bool> customBlock; Func<CPos, bool> customBlock;
public bool checkForBlocked;
public Actor ignoreBuilding;
public bool inReverse;
public HashSet<CPos> considered;
public int maxCost;
Pair<CVec, int>[] nextDirections; Pair<CVec, int>[] nextDirections;
MobileInfo mobileInfo; int laneBias = 1;
Actor self;
public Player owner { get { return self.Owner; } }
public PathSearch(World world, MobileInfo mobileInfo, Actor self) public PathSearch(World world, MobileInfo mobileInfo, Actor self)
{ {
this.world = world; this.self = self;
cellInfo = InitCellInfo(); CellInfo = InitCellInfo();
this.mobileInfo = mobileInfo; this.mobileInfo = mobileInfo;
this.self = self; this.self = self;
customCost = null; customCost = null;
queue = new PriorityQueue<PathDistance>(); Queue = new PriorityQueue<PathDistance>();
considered = new HashSet<CPos>(); Considered = new HashSet<CPos>();
maxCost = 0; MaxCost = 0;
nextDirections = CVec.directions.Select(d => new Pair<CVec, int>(d, 0)).ToArray(); nextDirections = CVec.directions.Select(d => new Pair<CVec, int>(d, 0)).ToArray();
} }
public PathSearch InReverse() public static PathSearch Search(World world, MobileInfo mi, Actor self, bool checkForBlocked)
{ {
inReverse = true; var search = new PathSearch(world, mi, self)
{
CheckForBlocked = checkForBlocked
};
return search;
}
public static PathSearch FromPoint(World world, MobileInfo mi, Actor self, CPos from, CPos target, bool checkForBlocked)
{
var search = new PathSearch(world, mi, self)
{
Heuristic = DefaultEstimator(target),
CheckForBlocked = checkForBlocked
};
search.AddInitialCell(from);
return search;
}
public static PathSearch FromPoints(World world, MobileInfo mi, Actor self, IEnumerable<CPos> froms, CPos target, bool checkForBlocked)
{
var search = new PathSearch(world, mi, self)
{
Heuristic = DefaultEstimator(target),
CheckForBlocked = checkForBlocked
};
foreach (var sl in froms)
search.AddInitialCell(sl);
return search;
}
public static Func<CPos, int> DefaultEstimator(CPos destination)
{
return here =>
{
var diag = Math.Min(Math.Abs(here.X - destination.X), Math.Abs(here.Y - destination.Y));
var straight = Math.Abs(here.X - destination.X) + Math.Abs(here.Y - destination.Y);
// HACK: this relies on fp and cell-size assumptions.
var h = (3400 * diag / 24) + 100 * (straight - (2 * diag));
return (int)(h * 1.001);
};
}
public PathSearch Reverse()
{
InReverse = true;
return this; return this;
} }
@@ -60,13 +110,13 @@ namespace OpenRA.Mods.RA.Move
public PathSearch WithIgnoredBuilding(Actor b) public PathSearch WithIgnoredBuilding(Actor b)
{ {
ignoreBuilding = b; IgnoreBuilding = b;
return this; return this;
} }
public PathSearch WithHeuristic(Func<CPos, int> h) public PathSearch WithHeuristic(Func<CPos, int> h)
{ {
heuristic = h; Heuristic = h;
return this; return this;
} }
@@ -78,7 +128,7 @@ namespace OpenRA.Mods.RA.Move
public PathSearch WithoutLaneBias() public PathSearch WithoutLaneBias()
{ {
LaneBias = 0; laneBias = 0;
return this; return this;
} }
@@ -88,18 +138,18 @@ namespace OpenRA.Mods.RA.Move
return this; return this;
} }
int LaneBias = 1;
public CPos Expand(World world) public CPos Expand(World world)
{ {
var p = queue.Pop(); var p = Queue.Pop();
while (cellInfo[p.Location.X, p.Location.Y].Seen) while (CellInfo[p.Location.X, p.Location.Y].Seen)
if (queue.Empty) {
if (Queue.Empty)
return p.Location; return p.Location;
else
p = queue.Pop();
cellInfo[p.Location.X, p.Location.Y].Seen = true; p = Queue.Pop();
}
CellInfo[p.Location.X, p.Location.Y].Seen = true;
var thisCost = mobileInfo.MovementCostForCell(world, p.Location); var thisCost = mobileInfo.MovementCostForCell(world, p.Location);
@@ -114,7 +164,7 @@ namespace OpenRA.Mods.RA.Move
} }
// This current cell is ok; check all immediate directions: // This current cell is ok; check all immediate directions:
considered.Add(p.Location); Considered.Add(p.Location);
for (var i = 0; i < nextDirections.Length; ++i) for (var i = 0; i < nextDirections.Length; ++i)
{ {
@@ -126,7 +176,8 @@ namespace OpenRA.Mods.RA.Move
// Is this direction flat-out unusable or already seen? // Is this direction flat-out unusable or already seen?
if (!world.Map.IsInMap(newHere)) if (!world.Map.IsInMap(newHere))
continue; continue;
if (cellInfo[newHere.X, newHere.Y].Seen)
if (CellInfo[newHere.X, newHere.Y].Seen)
continue; continue;
// Now we may seriously consider this direction using heuristics: // Now we may seriously consider this direction using heuristics:
@@ -135,18 +186,19 @@ namespace OpenRA.Mods.RA.Move
if (costHere == int.MaxValue) if (costHere == int.MaxValue)
continue; continue;
if (!mobileInfo.CanEnterCell(world, self, newHere, ignoreBuilding, checkForBlocked, false)) if (!mobileInfo.CanEnterCell(world, self, newHere, IgnoreBuilding, CheckForBlocked, false))
continue; continue;
if (customBlock != null && customBlock(newHere)) if (customBlock != null && customBlock(newHere))
continue; continue;
var est = heuristic(newHere); var est = Heuristic(newHere);
if (est == int.MaxValue) if (est == int.MaxValue)
continue; continue;
var cellCost = costHere; var cellCost = costHere;
if (d.X * d.Y != 0) cellCost = (cellCost * 34) / 24; if (d.X * d.Y != 0)
cellCost = (cellCost * 34) / 24;
var userCost = 0; var userCost = 0;
if (customCost != null) if (customCost != null)
@@ -156,105 +208,80 @@ namespace OpenRA.Mods.RA.Move
} }
// directional bonuses for smoother flow! // directional bonuses for smoother flow!
if (LaneBias != 0) if (laneBias != 0)
{ {
var ux = (newHere.X + (inReverse ? 1 : 0) & 1); var ux = newHere.X + (InReverse ? 1 : 0) & 1;
var uy = (newHere.Y + (inReverse ? 1 : 0) & 1); var uy = newHere.Y + (InReverse ? 1 : 0) & 1;
if (ux == 0 && d.Y < 0) cellCost += LaneBias; if (ux == 0 && d.Y < 0)
else if (ux == 1 && d.Y > 0) cellCost += LaneBias; cellCost += laneBias;
if (uy == 0 && d.X < 0) cellCost += LaneBias; else if (ux == 1 && d.Y > 0)
else if (uy == 1 && d.X > 0) cellCost += LaneBias; cellCost += laneBias;
if (uy == 0 && d.X < 0)
cellCost += laneBias;
else if (uy == 1 && d.X > 0)
cellCost += laneBias;
} }
var newCost = cellInfo[p.Location.X, p.Location.Y].MinCost + cellCost; var newCost = CellInfo[p.Location.X, p.Location.Y].MinCost + cellCost;
// Cost is even higher; next direction: // Cost is even higher; next direction:
if (newCost > cellInfo[newHere.X, newHere.Y].MinCost) if (newCost > CellInfo[newHere.X, newHere.Y].MinCost)
continue; continue;
cellInfo[newHere.X, newHere.Y].Path = p.Location; CellInfo[newHere.X, newHere.Y].Path = p.Location;
cellInfo[newHere.X, newHere.Y].MinCost = newCost; CellInfo[newHere.X, newHere.Y].MinCost = newCost;
nextDirections[i].Second = newCost + est; nextDirections[i].Second = newCost + est;
queue.Add(new PathDistance(newCost + est, newHere)); Queue.Add(new PathDistance(newCost + est, newHere));
if (newCost > maxCost) maxCost = newCost; if (newCost > MaxCost)
considered.Add(newHere); MaxCost = newCost;
Considered.Add(newHere);
} }
// Sort to prefer the cheaper direction: // Sort to prefer the cheaper direction:
// Array.Sort(nextDirections, (a, b) => a.Second.CompareTo(b.Second)); // Array.Sort(nextDirections, (a, b) => a.Second.CompareTo(b.Second));
return p.Location; return p.Location;
} }
public void AddInitialCell(CPos location) public void AddInitialCell(CPos location)
{ {
if (!world.Map.IsInMap(location)) if (!self.World.Map.IsInMap(location))
return; return;
cellInfo[location.X, location.Y] = new CellInfo(0, location, false); CellInfo[location.X, location.Y] = new CellInfo(0, location, false);
queue.Add(new PathDistance(heuristic(location), location)); Queue.Add(new PathDistance(Heuristic(location), location));
} }
public static PathSearch Search(World world, MobileInfo mi, Actor self, bool checkForBlocked) static readonly Queue<CellInfo[,]> CellInfoPool = new Queue<CellInfo[,]>();
{
var search = new PathSearch(world, mi, self)
{
checkForBlocked = checkForBlocked
};
return search;
}
public static PathSearch FromPoint(World world, MobileInfo mi, Actor self, CPos from, CPos target, bool checkForBlocked)
{
var search = new PathSearch(world, mi, self)
{
heuristic = DefaultEstimator(target),
checkForBlocked = checkForBlocked
};
search.AddInitialCell(from);
return search;
}
public static PathSearch FromPoints(World world, MobileInfo mi, Actor self, IEnumerable<CPos> froms, CPos target, bool checkForBlocked)
{
var search = new PathSearch(world, mi, self)
{
heuristic = DefaultEstimator(target),
checkForBlocked = checkForBlocked
};
foreach (var sl in froms)
search.AddInitialCell(sl);
return search;
}
static readonly Queue<CellInfo[,]> cellInfoPool = new Queue<CellInfo[,]>();
static CellInfo[,] GetFromPool() static CellInfo[,] GetFromPool()
{ {
lock (cellInfoPool) lock (CellInfoPool)
return cellInfoPool.Dequeue(); return CellInfoPool.Dequeue();
} }
static void PutBackIntoPool(CellInfo[,] ci) static void PutBackIntoPool(CellInfo[,] ci)
{ {
lock (cellInfoPool) lock (CellInfoPool)
cellInfoPool.Enqueue(ci); CellInfoPool.Enqueue(ci);
} }
CellInfo[,] InitCellInfo() CellInfo[,] InitCellInfo()
{ {
CellInfo[,] result = null; CellInfo[,] result = null;
while (cellInfoPool.Count > 0)
// HACK: Uses a static cache so that double-ended searches (which have two PathSearch instances)
// can implicitly share data. The PathFinder should allocate the CellInfo array and pass it
// explicitly to the things that need to share it.
while (CellInfoPool.Count > 0)
{ {
var cellInfo = GetFromPool(); var cellInfo = GetFromPool();
if (cellInfo.GetUpperBound(0) != world.Map.MapSize.X - 1 || if (cellInfo.GetUpperBound(0) != self.World.Map.MapSize.X - 1 ||
cellInfo.GetUpperBound(1) != world.Map.MapSize.Y - 1) cellInfo.GetUpperBound(1) != self.World.Map.MapSize.Y - 1)
{ {
Log.Write("debug", "Discarding old pooled CellInfo of wrong size."); Log.Write("debug", "Discarding old pooled CellInfo of wrong size.");
continue; continue;
@@ -265,36 +292,25 @@ namespace OpenRA.Mods.RA.Move
} }
if (result == null) if (result == null)
result = new CellInfo[world.Map.MapSize.X, world.Map.MapSize.Y]; result = new CellInfo[self.World.Map.MapSize.X, self.World.Map.MapSize.Y];
for (var x = 0; x < world.Map.MapSize.X; x++) for (var x = 0; x < self.World.Map.MapSize.X; x++)
for (var y = 0; y < world.Map.MapSize.Y; y++) for (var y = 0; y < self.World.Map.MapSize.Y; y++)
result[x, y] = new CellInfo(int.MaxValue, new CPos(x, y), false); result[x, y] = new CellInfo(int.MaxValue, new CPos(x, y), false);
return result; return result;
} }
public static Func<CPos, int> DefaultEstimator(CPos destination)
{
return here =>
{
var diag = Math.Min(Math.Abs(here.X - destination.X), Math.Abs(here.Y - destination.Y));
var straight = (Math.Abs(here.X - destination.X) + Math.Abs(here.Y - destination.Y));
var h = (3400 * diag / 24) + 100 * (straight - (2 * diag));
h = (int)(h * 1.001);
return h;
};
}
bool disposed; bool disposed;
public void Dispose() public void Dispose()
{ {
if (disposed) if (disposed)
return; return;
disposed = true; disposed = true;
PutBackIntoPool(cellInfo); PutBackIntoPool(CellInfo);
cellInfo = null; CellInfo = null;
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }