Teach HierarchicalPathFinder about Immovable actors.

By tracking updates on the ActorMap the HierarchicalPathFinder can be aware of actors moving around the map. We track a subset of immovable actors that always block. These actors can be treated as impassable obstacles just like terrain. When a path needs to be found the abstract path will guide the search around this subset of immovable actors just like it can guide the search around impassable terrain. For path searches that were previously imperformant because some immovable actors created a bottleneck that needed to be routed around, these will now be performant instead. Path searches with bottlenecks created by items such as trees, walls and buildings should see a performance improvement. Bottlenecks created by other units will not benefit.

We now maintain two sets of HPFs. One is aware of immovable actors and will be used for path searches that request BlockedByActor.Immovable, BlockedByActor.Stationary and BlockedByActor.All to guide that around the immovable obstacles. The other is aware of terrain only and will be used for searches that request BlockedByActor.None, or if an ignoreActor is provided. A new UI dropdown when using the `/hpf` command will allow switching between the visuals of the two sets.
This commit is contained in:
RoosterDragon
2022-08-07 17:21:00 +01:00
committed by Matthias Mailänder
parent 7e7d94ca89
commit 2d45e67bca
11 changed files with 286 additions and 68 deletions

View File

@@ -403,6 +403,18 @@ namespace OpenRA.Mods.Common.Traits
return AnyActorsAt(uv, layer, sub, withCondition);
}
public IEnumerable<Actor> AllActors()
{
foreach (var layer in influence)
{
if (layer == null)
continue;
foreach (var node in layer)
for (var i = node; i != null; i = i.Next)
yield return i.Actor;
}
}
public void AddInfluence(Actor self, IOccupySpace ios)
{
foreach (var c in ios.OccupiedCells())

View File

@@ -48,6 +48,11 @@ namespace OpenRA.Mods.Common.Traits
/// </summary>
public Locomotor Locomotor { get; set; }
/// <summary>
/// The blocking check selected in the UI which the overlay will display.
/// </summary>
public BlockedByActor Check { get; set; } = BlockedByActor.Immovable;
public HierarchicalPathFinderOverlay(HierarchicalPathFinderOverlayInfo info)
{
this.info = info;
@@ -88,7 +93,7 @@ namespace OpenRA.Mods.Common.Traits
: new[] { Locomotor };
foreach (var locomotor in locomotors)
{
var (abstractGraph, abstractDomains) = pathFinder.GetOverlayDataForLocomotor(locomotor);
var (abstractGraph, abstractDomains) = pathFinder.GetOverlayDataForLocomotor(locomotor, Check);
// Locomotor doesn't allow movement, nothing to display.
if (abstractGraph == null || abstractDomains == null)

View File

@@ -311,6 +311,9 @@ namespace OpenRA.Mods.Common.Traits
return world.ActorMap.FreeSubCell(cell, preferredSubCell);
}
/// <remarks>This logic is replicated in <see cref="HierarchicalPathFinder.ActorIsBlocking"/> and
/// <see cref="HierarchicalPathFinder.ActorCellIsBlocking"/>. If this method is updated please update those as
/// well.</remarks>
bool IsBlockedBy(Actor actor, Actor otherActor, Actor ignoreActor, CPos cell, BlockedByActor check, CellFlag cellFlag)
{
if (otherActor == ignoreActor)
@@ -450,6 +453,9 @@ namespace OpenRA.Mods.Common.Traits
}
}
/// <remarks>This logic is replicated in <see cref="HierarchicalPathFinder.ActorIsBlocking"/> and
/// <see cref="HierarchicalPathFinder.ActorCellIsBlocking"/>. If this method is updated please update those as
/// well.</remarks>
void UpdateCellBlocking(CPos cell)
{
using (new PerfSample("locomotor_cache"))

View File

@@ -19,8 +19,8 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[TraitLocation(SystemActors.World)]
[Desc("Calculates routes for mobile units with locomotors based on the A* search algorithm.", " Attach this to the world actor.")]
public class PathFinderInfo : TraitInfo, Requires<LocomotorInfo>
[Desc("Calculates routes for mobile actors with locomotors based on the A* search algorithm.", " Attach this to the world actor.")]
public class PathFinderInfo : TraitInfo, Requires<LocomotorInfo>, Requires<ActorMapInfo>
{
public override object Create(ActorInitializer init)
{
@@ -40,7 +40,8 @@ namespace OpenRA.Mods.Common.Traits
readonly World world;
PathFinderOverlay pathFinderOverlay;
Dictionary<Locomotor, HierarchicalPathFinder> hierarchicalPathFindersByLocomotor;
Dictionary<Locomotor, HierarchicalPathFinder> hierarchicalPathFindersBlockedByNoneByLocomotor;
Dictionary<Locomotor, HierarchicalPathFinder> hierarchicalPathFindersBlockedByImmovableByLocomotor;
public PathFinder(Actor self)
{
@@ -49,9 +50,10 @@ namespace OpenRA.Mods.Common.Traits
public (
IReadOnlyDictionary<CPos, List<GraphConnection>> AbstractGraph,
IReadOnlyDictionary<CPos, uint> AbstractDomains) GetOverlayDataForLocomotor(Locomotor locomotor)
IReadOnlyDictionary<CPos, uint> AbstractDomains) GetOverlayDataForLocomotor(
Locomotor locomotor, BlockedByActor check)
{
return hierarchicalPathFindersByLocomotor[locomotor].GetOverlayData();
return GetHierarchicalPathFinder(locomotor, check, null).GetOverlayData();
}
public void WorldLoaded(World w, WorldRenderer wr)
@@ -59,9 +61,13 @@ namespace OpenRA.Mods.Common.Traits
pathFinderOverlay = world.WorldActor.TraitOrDefault<PathFinderOverlay>();
// Requires<LocomotorInfo> ensures all Locomotors have been initialized.
hierarchicalPathFindersByLocomotor = w.WorldActor.TraitsImplementing<Locomotor>().ToDictionary(
var locomotors = w.WorldActor.TraitsImplementing<Locomotor>().ToList();
hierarchicalPathFindersBlockedByNoneByLocomotor = locomotors.ToDictionary(
locomotor => locomotor,
locomotor => new HierarchicalPathFinder(world, locomotor));
locomotor => new HierarchicalPathFinder(world, locomotor, w.ActorMap, BlockedByActor.None));
hierarchicalPathFindersBlockedByImmovableByLocomotor = locomotors.ToDictionary(
locomotor => locomotor,
locomotor => new HierarchicalPathFinder(world, locomotor, w.ActorMap, BlockedByActor.Immovable));
}
/// <summary>
@@ -110,15 +116,25 @@ namespace OpenRA.Mods.Common.Traits
}
// Use a hierarchical path search, which performs a guided bidirectional search.
return hierarchicalPathFindersByLocomotor[locomotor].FindPath(
return GetHierarchicalPathFinder(locomotor, check, ignoreActor).FindPath(
self, source, target, check, DefaultHeuristicWeightPercentage, customCost, ignoreActor, laneBias, pathFinderOverlay);
}
// Use a hierarchical path search, which performs a guided unidirectional search.
return hierarchicalPathFindersByLocomotor[locomotor].FindPath(
return GetHierarchicalPathFinder(locomotor, check, ignoreActor).FindPath(
self, sourcesList, target, check, DefaultHeuristicWeightPercentage, customCost, ignoreActor, laneBias, pathFinderOverlay);
}
HierarchicalPathFinder GetHierarchicalPathFinder(Locomotor locomotor, BlockedByActor check, Actor ignoreActor)
{
// If there is an actor to ignore, we cannot use an HPF that accounts for any blocking actors.
// One of the blocking actors might be the one we need to ignore!
var hpfs = check == BlockedByActor.None || ignoreActor != null
? hierarchicalPathFindersBlockedByNoneByLocomotor
: hierarchicalPathFindersBlockedByImmovableByLocomotor;
return hpfs[locomotor];
}
/// <summary>
/// Calculates a path for the actor from multiple possible sources, whilst searching for an acceptable target.
/// Returned path is *reversed* and given target to source.
@@ -149,7 +165,7 @@ namespace OpenRA.Mods.Common.Traits
/// </summary>
public bool PathExistsForLocomotor(Locomotor locomotor, CPos source, CPos target)
{
return hierarchicalPathFindersByLocomotor[locomotor].PathExists(source, target);
return hierarchicalPathFindersBlockedByNoneByLocomotor[locomotor].PathExists(source, target);
}
static Locomotor GetActorLocomotor(Actor self)