Skip to content

Commit

Permalink
Allow custom cost to exclude source locations in path searches.
Browse files Browse the repository at this point in the history
During the refactoring to introduce HierarchicalPathFinder, custom costs were no longer applied to source locations. The logic being that if we are already in the source location, then there should be no cost added to it to get there - we are already there!

Path searches support the ability to go from multiple sources to a single target, but the reverse is not supported. Some callers that require a search from a single source to one of multiple targets perform their search in reverse, swapping the source and targets so they can run the search, then reversing the path they are given so things are the correct way around again. For callers that also use a custom cost like the harvester code that do this in order to find free refineries, they might want the custom cost to be applied to the source location. The harvester code uses this to filter out overloaded refineries. It wants to search from a harvester to multiple refineries, and thus reverses this in order to perform the path search. Without the custom cost being applied to the "source" locations, this filtering logic never gets applied.

To fix this, we now apply the custom cost to source locations. If the custom cost provides an invalid path, then the source location is excluded entirely. Although this seems unintuitive on its own, this allows searches done "in reverse" to work again.
  • Loading branch information
RoosterDragon committed Aug 12, 2022
1 parent 8e85ef8 commit a03a8b3
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 15 deletions.
18 changes: 13 additions & 5 deletions OpenRA.Mods.Common/Pathfinder/PathSearch.cs
Expand Up @@ -48,7 +48,7 @@ static CellInfoLayerPool LayerPoolForWorld(World world)

foreach (var sl in froms)
if (world.Map.Contains(sl))
search.AddInitialCell(sl);
search.AddInitialCell(sl, customCost);

return search;
}
Expand All @@ -75,7 +75,7 @@ static CellInfoLayerPool LayerPoolForWorld(World world)

foreach (var sl in froms)
if (world.Map.Contains(sl))
search.AddInitialCell(sl);
search.AddInitialCell(sl, customCost);

return search;
}
Expand All @@ -87,7 +87,7 @@ static CellInfoLayerPool LayerPoolForWorld(World world)
var graph = new SparsePathGraph(edges, estimatedSearchSize);
var search = new PathSearch(graph, DefaultCostEstimator(locomotor, target), 100, loc => loc == target, recorder);

search.AddInitialCell(from);
search.AddInitialCell(from, null);

return search;
}
Expand Down Expand Up @@ -162,10 +162,18 @@ static CellInfoLayerPool LayerPoolForWorld(World world)
openQueue = new PriorityQueue<GraphConnection>(GraphConnection.ConnectionCostComparer);
}

void AddInitialCell(CPos location)
void AddInitialCell(CPos location, Func<CPos, int> customCost)
{
var initialCost = 0;
if (customCost != null)
{
initialCost = customCost(location);
if (initialCost == PathGraph.PathCostForInvalidPath)
return;
}

var estimatedCost = heuristic(location) * heuristicWeightPercentage / 100;
Graph[location] = new CellInfo(CellStatus.Open, 0, estimatedCost, location);
Graph[location] = new CellInfo(CellStatus.Open, initialCost, initialCost + estimatedCost, location);
var connection = new GraphConnection(location, estimatedCost);
openQueue.Add(connection);
}
Expand Down
19 changes: 10 additions & 9 deletions OpenRA.Mods.Common/Traits/Harvester.cs
Expand Up @@ -183,35 +183,36 @@ bool IsAcceptableProcType(Actor proc)
public Actor ClosestProc(Actor self, Actor ignore)
{
// Find all refineries and their occupancy count:
// Exclude refineries with too many harvesters clogging the delivery location.
var refineries = self.World.ActorsWithTrait<IAcceptResources>()
.Where(r => r.Actor != ignore && r.Actor.Owner == self.Owner && IsAcceptableProcType(r.Actor))
.Select(r => new
{
Location = r.Actor.Location + r.Trait.DeliveryOffset,
Actor = r.Actor,
Occupancy = self.World.ActorsHavingTrait<Harvester>(h => h.LinkedProc == r.Actor).Count()
}).ToLookup(r => r.Location);
})
.Where(r => r.Occupancy < Info.MaxUnloadQueue)
.ToDictionary(r => r.Location);

if (refineries.Count == 0)
return null;

// Start a search from each refinery's delivery location:
var path = mobile.PathFinder.FindPathToTargetCell(
self, refineries.Select(r => r.Key), self.Location, BlockedByActor.None,
location =>
{
if (!refineries.Contains(location))
if (!refineries.ContainsKey(location))
return 0;
var occupancy = refineries[location].First().Occupancy;
// Too many harvesters clogs up the refinery's delivery location:
if (occupancy >= Info.MaxUnloadQueue)
return PathGraph.PathCostForInvalidPath;
// Prefer refineries with less occupancy (multiplier is to offset distance cost):
var occupancy = refineries[location].Occupancy;
return occupancy * Info.UnloadQueueCostModifier;
});

if (path.Count > 0)
return refineries[path.Last()].First().Actor;
return refineries[path.Last()].Actor;

return null;
}
Expand Down
4 changes: 3 additions & 1 deletion OpenRA.Mods.Common/Traits/World/PathFinder.cs
Expand Up @@ -102,7 +102,9 @@ public void WorldLoaded(World w, WorldRenderer wr)
// For adjacent cells on the same layer, we can return the path without invoking a full search.
if (source.Layer == target.Layer && (source - target).LengthSquared < 3)
{
if (!world.Map.Contains(source))
// If the source cell is inaccessible, there is no path.
if (!world.Map.Contains(source) ||
(customCost != null && customCost(source) == PathGraph.PathCostForInvalidPath))
return NoPath;
return new List<CPos>(2) { target, source };
}
Expand Down

0 comments on commit a03a8b3

Please sign in to comment.