diff --git a/OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs b/OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs index ca07ddace3..e5ed100ebd 100644 --- a/OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs +++ b/OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs @@ -182,9 +182,9 @@ namespace OpenRA.Mods.Common.Activities var harvPos = self.CenterPosition; // Find any harvestable resources: - List path; - using (var search = PathSearch.ToTargetCellByPredicate( - self.World, mobile.Locomotor, self, new[] { searchFromLoc, self.Location }, + var path = mobile.PathFinder.FindUnitPathToTargetCellByPredicate( + self, + new[] { searchFromLoc, self.Location }, loc => harv.CanHarvestCell(loc) && claimLayer.CanClaimCell(self, loc), @@ -218,8 +218,7 @@ namespace OpenRA.Mods.Common.Activities } return 0; - })) - path = mobile.Pathfinder.FindPath(search); + }); if (path.Count > 0) return path[0]; diff --git a/OpenRA.Mods.Common/Activities/Move/Move.cs b/OpenRA.Mods.Common/Activities/Move/Move.cs index d84574b58f..3fb84e10d0 100644 --- a/OpenRA.Mods.Common/Activities/Move/Move.cs +++ b/OpenRA.Mods.Common/Activities/Move/Move.cs @@ -13,7 +13,6 @@ using System; using System.Collections.Generic; using System.Linq; using OpenRA.Activities; -using OpenRA.Mods.Common.Pathfinder; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; using OpenRA.Traits; @@ -59,9 +58,8 @@ namespace OpenRA.Mods.Common.Activities getPath = check => { - using (var search = PathSearch.ToTargetCell( - self.World, mobile.Locomotor, self, mobile.ToCell, destination, check, laneBias: false)) - return mobile.Pathfinder.FindPath(search); + return mobile.PathFinder.FindUnitPathToTargetCell( + self, new[] { mobile.ToCell }, destination, check, laneBias: false); }; this.destination = destination; @@ -80,7 +78,8 @@ namespace OpenRA.Mods.Common.Activities if (!this.destination.HasValue) return PathFinder.NoPath; - return mobile.Pathfinder.FindUnitPath(mobile.ToCell, this.destination.Value, self, ignoreActor, check); + return mobile.PathFinder.FindUnitPathToTargetCell( + self, new[] { mobile.ToCell }, this.destination.Value, check, ignoreActor: ignoreActor); }; // Note: Will be recalculated from OnFirstRun if evaluateNearestMovableCell is true diff --git a/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs b/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs index 450ce9719b..e4ebe98b2a 100644 --- a/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs +++ b/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs @@ -12,7 +12,6 @@ using System.Collections.Generic; using System.Linq; using OpenRA.Activities; -using OpenRA.Mods.Common.Pathfinder; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; using OpenRA.Traits; @@ -128,9 +127,9 @@ namespace OpenRA.Mods.Common.Activities if (!searchCells.Any()) return PathFinder.NoPath; - using (var fromSrc = PathSearch.ToTargetCell(self.World, Mobile.Locomotor, self, searchCells, loc, check)) - using (var fromDest = PathSearch.ToTargetCell(self.World, Mobile.Locomotor, self, loc, lastVisibleTargetLocation, check, inReverse: true)) - return Mobile.Pathfinder.FindBidiPath(fromSrc, fromDest); + var path = Mobile.PathFinder.FindUnitPathToTargetCell(self, searchCells, loc, check); + path.Reverse(); + return path; } public override IEnumerable GetTargets(Actor self) diff --git a/OpenRA.Mods.Common/Pathfinder/PathGraph.cs b/OpenRA.Mods.Common/Pathfinder/PathGraph.cs index 9b6fda28c9..c6f4aa883e 100644 --- a/OpenRA.Mods.Common/Pathfinder/PathGraph.cs +++ b/OpenRA.Mods.Common/Pathfinder/PathGraph.cs @@ -34,14 +34,14 @@ namespace OpenRA.Mods.Common.Pathfinder readonly BlockedByActor check; readonly Func customCost; readonly Actor ignoreActor; - readonly bool inReverse; readonly bool laneBias; + readonly bool inReverse; readonly bool checkTerrainHeight; readonly CellInfoLayerPool.PooledCellInfoLayer pooledLayer; readonly CellLayer[] cellInfoForLayer; public PathGraph(CellInfoLayerPool layerPool, Locomotor locomotor, Actor actor, World world, BlockedByActor check, - Func customCost, Actor ignoreActor, bool inReverse, bool laneBias) + Func customCost, Actor ignoreActor, bool laneBias, bool inReverse) { customMovementLayers = world.GetCustomMovementLayers(); customMovementLayersEnabledForLocomotor = customMovementLayers.Count(cml => cml != null && cml.EnabledForLocomotor(locomotor.Info)); @@ -51,8 +51,8 @@ namespace OpenRA.Mods.Common.Pathfinder this.check = check; this.customCost = customCost; this.ignoreActor = ignoreActor; - this.inReverse = inReverse; this.laneBias = laneBias; + this.inReverse = inReverse; checkTerrainHeight = world.Map.Grid.MaximumTerrainHeight > 0; // As we support a search over the whole map area, diff --git a/OpenRA.Mods.Common/Pathfinder/PathSearch.cs b/OpenRA.Mods.Common/Pathfinder/PathSearch.cs index fb014cf6a3..95e768e433 100644 --- a/OpenRA.Mods.Common/Pathfinder/PathSearch.cs +++ b/OpenRA.Mods.Common/Pathfinder/PathSearch.cs @@ -26,13 +26,6 @@ namespace OpenRA.Mods.Common.Pathfinder /// public const int DefaultHeuristicWeightPercentage = 125; - /// - /// When searching for paths, use a lane bias to guide units into - /// "lanes" whilst moving, to promote smooth unit flow when groups of - /// units are moving. - /// - public const bool DefaultLaneBias = true; - // 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 LayerPoolTable = new ConditionalWeakTable(); @@ -45,9 +38,11 @@ namespace OpenRA.Mods.Common.Pathfinder public static PathSearch ToTargetCellByPredicate( World world, Locomotor locomotor, Actor self, IEnumerable froms, Func targetPredicate, BlockedByActor check, - Func customCost = null) + Func customCost = null, + Actor ignoreActor = null, + bool laneBias = true) { - var graph = new PathGraph(LayerPoolForWorld(world), locomotor, self, world, check, customCost, null, false, DefaultLaneBias); + var graph = new PathGraph(LayerPoolForWorld(world), locomotor, self, world, check, customCost, ignoreActor, laneBias, false); var search = new PathSearch(graph, loc => 0, DefaultHeuristicWeightPercentage, targetPredicate); foreach (var sl in froms) @@ -57,26 +52,16 @@ namespace OpenRA.Mods.Common.Pathfinder return search; } - public static PathSearch ToTargetCell( - World world, Locomotor locomotor, Actor self, CPos from, CPos target, BlockedByActor check, - Func customCost = null, - Actor ignoreActor = null, - bool inReverse = false, - bool laneBias = DefaultLaneBias) - { - return ToTargetCell(world, locomotor, self, new[] { from }, target, check, customCost, ignoreActor, inReverse, laneBias); - } - public static PathSearch ToTargetCell( World world, Locomotor locomotor, Actor self, IEnumerable froms, CPos target, BlockedByActor check, Func customCost = null, Actor ignoreActor = null, + bool laneBias = true, bool inReverse = false, - bool laneBias = DefaultLaneBias, Func heuristic = null, int heuristicWeightPercentage = DefaultHeuristicWeightPercentage) { - var graph = new PathGraph(LayerPoolForWorld(world), locomotor, self, world, check, customCost, ignoreActor, inReverse, laneBias); + var graph = new PathGraph(LayerPoolForWorld(world), locomotor, self, world, check, customCost, ignoreActor, laneBias, inReverse); heuristic = heuristic ?? DefaultCostEstimator(locomotor, target); var search = new PathSearch(graph, heuristic, heuristicWeightPercentage, loc => loc == target); @@ -168,14 +153,14 @@ namespace OpenRA.Mods.Common.Pathfinder /// Determines if there are more reachable cells and the search can be continued. /// If false, can no longer be called. /// - public bool CanExpand => !openQueue.Empty; + bool CanExpand => !openQueue.Empty; /// /// This function analyzes the neighbors of the most promising node in the pathfinding graph /// using the A* algorithm (A-star) and returns that node /// /// The most promising node of the iteration - public CPos Expand() + CPos Expand() { var currentMinNode = openQueue.Pop().Destination; @@ -215,11 +200,96 @@ namespace OpenRA.Mods.Common.Pathfinder } /// - /// Determines if is the target of the search. + /// Expands the path search until a path is found, and returns that path. + /// Returned path is *reversed* and given target to source. /// - public bool IsTarget(CPos location) + public List FindPath() { - return TargetPredicate(location); + while (CanExpand) + { + var p = Expand(); + if (TargetPredicate(p)) + return MakePath(Graph, p); + } + + return PathFinder.NoPath; + } + + // Build the path from the destination. + // When we find a node that has the same previous position than itself, that node is the source node. + static List MakePath(IPathGraph graph, CPos destination) + { + var ret = new List(); + var currentNode = destination; + + while (graph[currentNode].PreviousNode != currentNode) + { + ret.Add(currentNode); + currentNode = graph[currentNode].PreviousNode; + } + + ret.Add(currentNode); + return ret; + } + + /// + /// Expands both path searches until they intersect, and returns the path. + /// Returned path is from the source of the first search to the source of the second search. + /// + public static List FindBidiPath(PathSearch first, PathSearch second) + { + while (first.CanExpand && second.CanExpand) + { + // make some progress on the first search + var p = first.Expand(); + var pInfo = second.Graph[p]; + if (pInfo.Status == CellStatus.Closed && + pInfo.CostSoFar != PathGraph.PathCostForInvalidPath) + return MakeBidiPath(first, second, p); + + // make some progress on the second search + var q = second.Expand(); + var qInfo = first.Graph[q]; + if (qInfo.Status == CellStatus.Closed && + qInfo.CostSoFar != PathGraph.PathCostForInvalidPath) + return MakeBidiPath(first, second, q); + } + + return PathFinder.NoPath; + } + + // Build the path from the destination of each search. + // When we find a node that has the same previous position than itself, that is the source of that search. + static List MakeBidiPath(PathSearch first, PathSearch second, CPos confluenceNode) + { + var ca = first.Graph; + var cb = second.Graph; + + var ret = new List(); + + var q = confluenceNode; + var previous = ca[q].PreviousNode; + while (previous != q) + { + ret.Add(q); + q = previous; + previous = ca[q].PreviousNode; + } + + ret.Add(q); + + ret.Reverse(); + + q = confluenceNode; + previous = cb[q].PreviousNode; + while (previous != q) + { + q = previous; + previous = cb[q].PreviousNode; + ret.Add(q); + } + + return ret; } public void Dispose() diff --git a/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs index d4f5ac59f2..f5bd8bc9db 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs @@ -13,7 +13,6 @@ using System; using System.Collections.Generic; using System.Linq; using OpenRA.Mods.Common.Activities; -using OpenRA.Mods.Common.Pathfinder; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -44,15 +43,14 @@ namespace OpenRA.Mods.Common.Traits public readonly Actor Actor; public readonly Harvester Harvester; public readonly Parachutable Parachutable; - public readonly Locomotor Locomotor; + public readonly Mobile Mobile; public HarvesterTraitWrapper(Actor actor) { Actor = actor; Harvester = actor.Trait(); Parachutable = actor.TraitOrDefault(); - var mobile = actor.Trait(); - Locomotor = mobile.Locomotor; + Mobile = actor.Trait(); } } @@ -61,7 +59,6 @@ namespace OpenRA.Mods.Common.Traits readonly Func unitCannotBeOrdered; readonly Dictionary harvesters = new Dictionary(); - IPathFinder pathfinder; IResourceLayer resourceLayer; ResourceClaimLayer claimLayer; IBotRequestUnitProduction[] requestUnitProduction; @@ -82,7 +79,6 @@ namespace OpenRA.Mods.Common.Traits protected override void TraitEnabled(Actor self) { - pathfinder = world.WorldActor.Trait(); resourceLayer = world.WorldActor.TraitOrDefault(); claimLayer = world.WorldActor.TraitOrDefault(); @@ -146,14 +142,11 @@ namespace OpenRA.Mods.Common.Traits harv.Harvester.CanHarvestCell(cell) && claimLayer.CanClaimCell(actor, cell); - List path; - using (var search = - PathSearch.ToTargetCellByPredicate( - world, harv.Locomotor, actor, new[] { actor.Location }, isValidResource, BlockedByActor.Stationary, - loc => world.FindActorsInCircle(world.Map.CenterOfCell(loc), Info.HarvesterEnemyAvoidanceRadius) - .Where(u => !u.IsDead && actor.Owner.RelationshipWith(u.Owner) == PlayerRelationship.Enemy) - .Sum(u => Math.Max(WDist.Zero.Length, Info.HarvesterEnemyAvoidanceRadius.Length - (world.Map.CenterOfCell(loc) - u.CenterPosition).Length)))) - path = pathfinder.FindPath(search); + var path = harv.Mobile.PathFinder.FindUnitPathToTargetCellByPredicate( + actor, new[] { actor.Location }, isValidResource, BlockedByActor.Stationary, + loc => world.FindActorsInCircle(world.Map.CenterOfCell(loc), Info.HarvesterEnemyAvoidanceRadius) + .Where(u => !u.IsDead && actor.Owner.RelationshipWith(u.Owner) == PlayerRelationship.Enemy) + .Sum(u => Math.Max(WDist.Zero.Length, Info.HarvesterEnemyAvoidanceRadius.Length - (world.Map.CenterOfCell(loc) - u.CenterPosition).Length))); if (path.Count == 0) return Target.Invalid; diff --git a/OpenRA.Mods.Common/Traits/Harvester.cs b/OpenRA.Mods.Common/Traits/Harvester.cs index f395e2d310..d76eabe86e 100644 --- a/OpenRA.Mods.Common/Traits/Harvester.cs +++ b/OpenRA.Mods.Common/Traits/Harvester.cs @@ -193,10 +193,8 @@ namespace OpenRA.Mods.Common.Traits }).ToLookup(r => r.Location); // Start a search from each refinery's delivery location: - List path; - - using (var search = PathSearch.ToTargetCell( - self.World, mobile.Locomotor, self, refineries.Select(r => r.Key), self.Location, BlockedByActor.None, + var path = mobile.PathFinder.FindUnitPathToTargetCell( + self, refineries.Select(r => r.Key), self.Location, BlockedByActor.None, location => { if (!refineries.Contains(location)) @@ -210,8 +208,7 @@ namespace OpenRA.Mods.Common.Traits // Prefer refineries with less occupancy (multiplier is to offset distance cost): return occupancy * Info.UnloadQueueCostModifier; - })) - path = mobile.Pathfinder.FindPath(search); + }); if (path.Count > 0) return refineries[path.Last()].First().Actor; diff --git a/OpenRA.Mods.Common/Traits/Mobile.cs b/OpenRA.Mods.Common/Traits/Mobile.cs index a2c950e56e..ce0590e0f6 100644 --- a/OpenRA.Mods.Common/Traits/Mobile.cs +++ b/OpenRA.Mods.Common/Traits/Mobile.cs @@ -235,7 +235,7 @@ namespace OpenRA.Mods.Common.Traits public Locomotor Locomotor { get; private set; } - public IPathFinder Pathfinder { get; private set; } + public IPathFinder PathFinder { get; private set; } #region IOccupySpace @@ -302,7 +302,7 @@ namespace OpenRA.Mods.Common.Traits notifyMoving = self.TraitsImplementing().ToArray(); notifyFinishedMoving = self.TraitsImplementing().ToArray(); moveWrappers = self.TraitsImplementing().ToArray(); - Pathfinder = self.World.WorldActor.Trait(); + PathFinder = self.World.WorldActor.Trait(); Locomotor = self.World.WorldActor.TraitsImplementing() .Single(l => l.Info.Name == Info.Locomotor); @@ -825,10 +825,8 @@ namespace OpenRA.Mods.Common.Traits if (CanEnterCell(above)) return above; - List path; - using (var search = PathSearch.ToTargetCellByPredicate( - self.World, Locomotor, self, new[] { self.Location }, loc => loc.Layer == 0 && CanEnterCell(loc), BlockedByActor.All)) - path = Pathfinder.FindPath(search); + var path = PathFinder.FindUnitPathToTargetCellByPredicate( + self, new[] { self.Location }, loc => loc.Layer == 0 && CanEnterCell(loc), BlockedByActor.All); if (path.Count > 0) return path[0]; diff --git a/OpenRA.Mods.Common/Traits/World/PathFinder.cs b/OpenRA.Mods.Common/Traits/World/PathFinder.cs index 54e059614f..435d070ba3 100644 --- a/OpenRA.Mods.Common/Traits/World/PathFinder.cs +++ b/OpenRA.Mods.Common/Traits/World/PathFinder.cs @@ -9,14 +9,16 @@ */ #endregion +using System; using System.Collections.Generic; +using System.Linq; using OpenRA.Mods.Common.Pathfinder; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [TraitLocation(SystemActors.World)] - [Desc("Calculates routes for mobile units based on the A* search algorithm.", " Attach this to the world actor.")] + [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 { public override object Create(ActorInitializer init) @@ -25,34 +27,11 @@ namespace OpenRA.Mods.Common.Traits } } - public interface IPathFinder - { - /// - /// Calculates a path for the actor from source to target. - /// Returned path is *reversed* and given target to source. - /// - List FindUnitPath(CPos source, CPos target, Actor self, Actor ignoreActor, BlockedByActor check); - - /// - /// Expands the path search until a path is found, and returns that path. - /// Returned path is *reversed* and given target to source. - /// - List FindPath(PathSearch search); - - /// - /// Expands both path searches until they intersect, and returns the path. - /// Returned path is from the source of the first search to the source of the second search. - /// - List FindBidiPath(PathSearch fromSrc, PathSearch fromDest); - } - public class PathFinder : IPathFinder { public static readonly List NoPath = new List(0); readonly World world; - DomainIndex domainIndex; - bool cached; public PathFinder(World world) { @@ -60,131 +39,83 @@ namespace OpenRA.Mods.Common.Traits } /// - /// Calculates a path for the actor from source to target. + /// Calculates a path for the actor from multiple possible sources to target. /// Returned path is *reversed* and given target to source. + /// The shortest path between a source and the target is returned. /// - public List FindUnitPath(CPos source, CPos target, Actor self, Actor ignoreActor, BlockedByActor check) + /// + /// Searches that provide a multiple source cells are slower than those than provide only a single source cell, + /// as optimizations are possible for the single source case. Use searches from multiple source cells + /// sparingly. + /// + public List FindUnitPathToTargetCell( + Actor self, IEnumerable sources, CPos target, BlockedByActor check, + Func customCost = null, + Actor ignoreActor = null, + bool laneBias = true) { - // PERF: Because we can be sure that OccupiesSpace is Mobile here, we can save some performance by avoiding querying for the trait. - var locomotor = ((Mobile)self.OccupiesSpace).Locomotor; + var sourcesList = sources.ToList(); + if (sourcesList.Count == 0) + throw new ArgumentException($"{nameof(sources)} must not be empty.", nameof(sources)); - if (!cached) + var locomotor = GetLocomotor(self); + + // If the target cell is inaccessible, bail early. + var inaccessible = + !locomotor.CanMoveFreelyInto(self, target, check, ignoreActor) || + (!(customCost is null) && customCost(target) == PathGraph.PathCostForInvalidPath); + if (inaccessible) + return NoPath; + + // When searching from only one source cell, some optimizations are possible. + if (sourcesList.Count == 1) { - domainIndex = world.WorldActor.TraitOrDefault(); - cached = true; + var source = sourcesList[0]; + + // 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) + return new List(2) { target, source }; + + // With one starting point, we can use a bidirectional search. + using (var fromTarget = PathSearch.ToTargetCell( + world, locomotor, self, new[] { target }, source, check, ignoreActor: ignoreActor)) + using (var fromSource = PathSearch.ToTargetCell( + world, locomotor, self, new[] { source }, target, check, ignoreActor: ignoreActor, inReverse: true)) + return PathSearch.FindBidiPath(fromTarget, fromSource); } - // If a water-land transition is required, bail early - if (domainIndex != null && !domainIndex.IsPassable(source, target, locomotor)) - return NoPath; - - var distance = source - target; - var canMoveFreely = locomotor.CanMoveFreelyInto(self, target, check, null); - if (distance.LengthSquared < 3 && !canMoveFreely) - return NoPath; - - if (source.Layer == target.Layer && distance.LengthSquared < 3 && canMoveFreely) - return new List { target }; - - List pb; - using (var fromSrc = PathSearch.ToTargetCell(world, locomotor, self, target, source, check, ignoreActor: ignoreActor)) - using (var fromDest = PathSearch.ToTargetCell(world, locomotor, self, source, target, check, ignoreActor: ignoreActor, inReverse: true)) - pb = FindBidiPath(fromSrc, fromDest); - - return pb; + // With multiple starting points, we can only use a unidirectional search. + using (var search = PathSearch.ToTargetCell( + world, locomotor, self, sourcesList, target, check, customCost, ignoreActor, laneBias)) + return search.FindPath(); } /// - /// Expands the path search until a path is found, and returns that path. + /// 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. + /// The shortest path between a source and a discovered target is returned. /// - public List FindPath(PathSearch search) + /// + /// Searches with this method are slower than due to the need to search for + /// and discover an acceptable target cell. Use this search sparingly. + /// + public List FindUnitPathToTargetCellByPredicate( + Actor self, IEnumerable sources, Func targetPredicate, BlockedByActor check, + Func customCost = null, + Actor ignoreActor = null, + bool laneBias = true) { - while (search.CanExpand) - { - var p = search.Expand(); - if (search.IsTarget(p)) - return MakePath(search.Graph, p); - } - - return NoPath; + // With no pre-specified target location, we can only use a unidirectional search. + using (var search = PathSearch.ToTargetCellByPredicate( + world, GetLocomotor(self), self, sources, targetPredicate, check, customCost, ignoreActor, laneBias)) + return search.FindPath(); } - // Build the path from the destination. - // When we find a node that has the same previous position than itself, that node is the source node. - static List MakePath(IPathGraph graph, CPos destination) + static Locomotor GetLocomotor(Actor self) { - var ret = new List(); - var currentNode = destination; - - while (graph[currentNode].PreviousNode != currentNode) - { - ret.Add(currentNode); - currentNode = graph[currentNode].PreviousNode; - } - - ret.Add(currentNode); - return ret; - } - - /// - /// Expands both path searches until they intersect, and returns the path. - /// Returned path is from the source of the first search to the source of the second search. - /// - public List FindBidiPath(PathSearch first, PathSearch second) - { - while (first.CanExpand && second.CanExpand) - { - // make some progress on the first search - var p = first.Expand(); - var pInfo = second.Graph[p]; - if (pInfo.Status == CellStatus.Closed && - pInfo.CostSoFar != PathGraph.PathCostForInvalidPath) - return MakeBidiPath(first, second, p); - - // make some progress on the second search - var q = second.Expand(); - var qInfo = first.Graph[q]; - if (qInfo.Status == CellStatus.Closed && - qInfo.CostSoFar != PathGraph.PathCostForInvalidPath) - return MakeBidiPath(first, second, q); - } - - return NoPath; - } - - // Build the path from the destination of each search. - // When we find a node that has the same previous position than itself, that is the source of that search. - static List MakeBidiPath(PathSearch first, PathSearch second, CPos confluenceNode) - { - var ca = first.Graph; - var cb = second.Graph; - - var ret = new List(); - - var q = confluenceNode; - var previous = ca[q].PreviousNode; - while (previous != q) - { - ret.Add(q); - q = previous; - previous = ca[q].PreviousNode; - } - - ret.Add(q); - - ret.Reverse(); - - q = confluenceNode; - previous = cb[q].PreviousNode; - while (previous != q) - { - q = previous; - previous = cb[q].PreviousNode; - ret.Add(q); - } - - return ret; + // PERF: This PathFinder trait requires the use of Mobile, so we can be sure that is in use. + // We can save some performance by avoiding querying for the Locomotor trait and retrieving it from Mobile. + return ((Mobile)self.OccupiesSpace).Locomotor; } } } diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index c5610e60c7..8dc6965c7d 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -791,4 +791,29 @@ namespace OpenRA.Mods.Common.Traits void SetPosition(Actor self, WPos pos); void SetCenterPosition(Actor self, WPos pos); } + + public interface IPathFinder + { + /// + /// Calculates a path for the actor from multiple possible sources to target. + /// Returned path is *reversed* and given target to source. + /// The shortest path between a source and the target is returned. + /// + List FindUnitPathToTargetCell( + Actor self, IEnumerable sources, CPos target, BlockedByActor check, + Func customCost = null, + Actor ignoreActor = null, + bool laneBias = true); + + /// + /// 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. + /// The shortest path between a source and a discovered target is returned. + /// + List FindUnitPathToTargetCellByPredicate( + Actor self, IEnumerable sources, Func targetPredicate, BlockedByActor check, + Func customCost = null, + Actor ignoreActor = null, + bool laneBias = true); + } }