Files
OpenRA/OpenRA.Mods.Common/Pathfinder/PathSearch.cs
RoosterDragon 19760b04bd Allow the default value of a CellInfo to be an Unvisited location.
In CellInfoLayerPool, instead of having to store a layer with the default values, we know we can just clear the pooled layer in order to reset it. This saves on memory, and also makes resetting marginally faster.

In PathSearch, we need to amend a check to ensure a cell info is not Unvisited before we check on its cost.
2021-10-23 15:43:47 +02:00

154 lines
5.5 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2021 The OpenRA Developers (see AUTHORS)
* 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
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Pathfinder
{
public sealed class PathSearch : BasePathSearch
{
// PERF: Maintain a pool of layers used for paths searches for each world. These searches are performed often
// so we wish to avoid the high cost of initializing a new search space every time by reusing the old ones.
static readonly ConditionalWeakTable<World, CellInfoLayerPool> LayerPoolTable = new ConditionalWeakTable<World, CellInfoLayerPool>();
static readonly ConditionalWeakTable<World, CellInfoLayerPool>.CreateValueCallback CreateLayerPool = world => new CellInfoLayerPool(world.Map);
static CellInfoLayerPool LayerPoolForWorld(World world)
{
return LayerPoolTable.GetValue(world, CreateLayerPool);
}
public override IEnumerable<(CPos, int)> Considered => considered;
LinkedList<(CPos, int)> considered;
#region Constructors
private PathSearch(IGraph<CellInfo> graph)
: base(graph)
{
considered = new LinkedList<(CPos, int)>();
}
public static IPathSearch Search(World world, Locomotor locomotor, Actor self, BlockedByActor check, Func<CPos, bool> goalCondition)
{
var graph = new PathGraph(LayerPoolForWorld(world), locomotor, self, world, check);
var search = new PathSearch(graph);
search.isGoal = goalCondition;
search.heuristic = loc => 0;
return search;
}
public static IPathSearch FromPoint(World world, Locomotor locomotor, Actor self, CPos @from, CPos target, BlockedByActor check)
{
return FromPoints(world, locomotor, self, new[] { from }, target, check);
}
public static IPathSearch FromPoints(World world, Locomotor locomotor, Actor self, IEnumerable<CPos> froms, CPos target, BlockedByActor check)
{
var graph = new PathGraph(LayerPoolForWorld(world), locomotor, self, world, check);
var search = new PathSearch(graph);
search.heuristic = search.DefaultEstimator(target);
// The search will aim for the shortest path by default, a weight of 100%.
// We can allow the search to find paths that aren't optimal by changing the weight.
// We provide a weight that limits the worst case length of the path,
// e.g. a weight of 110% will find a path no more than 10% longer than the shortest possible.
// The benefit of allowing the search to return suboptimal paths is faster computation time.
// The search can skip some areas of the search space, meaning it has less work to do.
// We allow paths up to 25% longer than the shortest, optimal path, to improve pathfinding time.
search.heuristicWeightPercentage = 125;
search.isGoal = loc =>
{
var locInfo = search.Graph[loc];
return locInfo.EstimatedTotal - locInfo.CostSoFar == 0;
};
foreach (var sl in froms)
if (world.Map.Contains(sl))
search.AddInitialCell(sl);
return search;
}
protected override void AddInitialCell(CPos location)
{
var cost = heuristic(location);
Graph[location] = new CellInfo(0, cost, location, CellStatus.Open);
var connection = new GraphConnection(location, cost);
OpenQueue.Add(connection);
StartPoints.Add(connection);
considered.AddLast((location, 0));
}
#endregion
/// <summary>
/// This function analyzes the neighbors of the most promising node in the Pathfinding graph
/// using the A* algorithm (A-star) and returns that node
/// </summary>
/// <returns>The most promising node of the iteration</returns>
public override CPos Expand()
{
var currentMinNode = OpenQueue.Pop().Destination;
var currentCell = Graph[currentMinNode];
Graph[currentMinNode] = new CellInfo(currentCell.CostSoFar, currentCell.EstimatedTotal, currentCell.PreviousPos, CellStatus.Closed);
if (Graph.CustomCost != null && Graph.CustomCost(currentMinNode) == PathGraph.PathCostForInvalidPath)
return currentMinNode;
foreach (var connection in Graph.GetConnections(currentMinNode))
{
// Calculate the cost up to that point
var gCost = currentCell.CostSoFar + connection.Cost;
var neighborCPos = connection.Destination;
var neighborCell = Graph[neighborCPos];
// Cost is even higher; next direction:
if (neighborCell.Status == CellStatus.Closed ||
(neighborCell.Status == CellStatus.Open && gCost >= neighborCell.CostSoFar))
continue;
// Now we may seriously consider this direction using heuristics. If the cell has
// already been processed, we can reuse the result (just the difference between the
// estimated total and the cost so far
int hCost;
if (neighborCell.Status == CellStatus.Open)
hCost = neighborCell.EstimatedTotal - neighborCell.CostSoFar;
else
hCost = heuristic(neighborCPos);
var estimatedCost = gCost + hCost;
Graph[neighborCPos] = new CellInfo(gCost, estimatedCost, currentMinNode, CellStatus.Open);
if (neighborCell.Status != CellStatus.Open)
OpenQueue.Add(new GraphConnection(neighborCPos, estimatedCost));
if (Debug)
{
if (gCost > MaxCost)
MaxCost = gCost;
considered.AddLast((neighborCPos, gCost));
}
}
return currentMinNode;
}
}
}