Expose a setting for Weighted A*

Replace Constants.CellCost and Constants.DiagonalCellCost with a dynamically calculated value based on the lowest cost terrain to traverse. Using a fixed value meant the pathfinder heuristics would be incorrect.

In the four default mods, the minimum cost is in fact 100, not 125. This increase would essentially allow the pathfinder to return suboptimal paths up to 25% longer in the worst case, but it would be quicker to do so.

This is exactly what Weighted A* does - overestimate the heuristic by some factor in order to speed up the search by checking fewer routes. This makes the heuristic inadmissible and it may now return suboptimal paths, but their worst case length is bounded by the weight. A weight of 125% will never produce paths more than 25% longer than the shortest, optimal, path.

We set the default weight to 25% to effectively maintain the existing, suboptimal, behaviour due to the choice of the old constant - in future it may prove a useful tuning knob for performance.
This commit is contained in:
RoosterDragon
2019-10-24 20:56:30 +01:00
committed by reaperrr
parent 72eb4e1749
commit 04912ea996
5 changed files with 47 additions and 65 deletions

View File

@@ -11,6 +11,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.Common.Traits;
using OpenRA.Primitives;
namespace OpenRA.Mods.Common.Pathfinder
@@ -39,6 +41,8 @@ namespace OpenRA.Mods.Common.Pathfinder
IPathSearch WithHeuristic(Func<CPos, int> h);
IPathSearch WithHeuristicWeight(int percentage);
IPathSearch WithCustomCost(Func<CPos, int> w);
IPathSearch WithoutLaneBias();
@@ -71,6 +75,7 @@ namespace OpenRA.Mods.Common.Pathfinder
public bool Debug { get; set; }
protected Func<CPos, int> heuristic;
protected Func<CPos, bool> isGoal;
protected int heuristicWeightPercentage;
// This member is used to compute the ID of PathSearch.
// Essentially, it represents a collection of the initial
@@ -79,12 +84,20 @@ namespace OpenRA.Mods.Common.Pathfinder
// a deterministic set of calculations
protected readonly IPriorityQueue<GraphConnection> StartPoints;
private readonly int cellCost, diagonalCellCost;
protected BasePathSearch(IGraph<CellInfo> graph)
{
Graph = graph;
OpenQueue = new PriorityQueue<GraphConnection>(GraphConnection.ConnectionCostComparer);
StartPoints = new PriorityQueue<GraphConnection>(GraphConnection.ConnectionCostComparer);
MaxCost = 0;
heuristicWeightPercentage = 100;
// Determine the minimum possible cost for moving horizontally between cells based on terrain speeds.
// The minimum possible cost diagonally is then Sqrt(2) times more costly.
cellCost = graph.Actor.Trait<Mobile>().Locomotor.Info.TerrainSpeeds.Values.Min(ti => ti.Cost);
diagonalCellCost = cellCost * 141421 / 100000;
}
/// <summary>
@@ -92,7 +105,7 @@ namespace OpenRA.Mods.Common.Pathfinder
/// http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
/// </summary>
/// <returns>A delegate that calculates the estimation for a node</returns>
protected static Func<CPos, int> DefaultEstimator(CPos destination)
protected Func<CPos, int> DefaultEstimator(CPos destination)
{
return here =>
{
@@ -102,7 +115,7 @@ namespace OpenRA.Mods.Common.Pathfinder
// According to the information link, this is the shape of the function.
// We just extract factors to simplify.
// Possible simplification: var h = Constants.CellCost * (straight + (Constants.Sqrt2 - 2) * diag);
return Constants.CellCost * straight + (Constants.DiagonalCellCost - 2 * Constants.CellCost) * diag;
return (cellCost * straight + (diagonalCellCost - 2 * cellCost) * diag) * heuristicWeightPercentage / 100;
};
}
@@ -130,6 +143,12 @@ namespace OpenRA.Mods.Common.Pathfinder
return this;
}
public IPathSearch WithHeuristicWeight(int percentage)
{
heuristicWeightPercentage = percentage;
return this;
}
public IPathSearch WithCustomCost(Func<CPos, int> w)
{
Graph.CustomCost = w;