Re-organize all projects (#4166)

This commit is contained in:
DrSmugleaf
2021-06-09 22:19:39 +02:00
committed by GitHub
parent 9f50e4061b
commit ff1a2d97ea
1773 changed files with 5258 additions and 5508 deletions

View File

@@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.CPUJob.JobQueues;
using Content.Shared.AI;
using Robust.Shared.Map;
using Robust.Shared.Utility;
namespace Content.Server.AI.Pathfinding.Pathfinders
{
public class AStarPathfindingJob : Job<Queue<TileRef>>
{
#if DEBUG
public static event Action<SharedAiDebug.AStarRouteDebug>? DebugRoute;
#endif
private readonly PathfindingNode? _startNode;
private PathfindingNode? _endNode;
private readonly PathfindingArgs _pathfindingArgs;
public AStarPathfindingJob(
double maxTime,
PathfindingNode startNode,
PathfindingNode endNode,
PathfindingArgs pathfindingArgs,
CancellationToken cancellationToken) : base(maxTime, cancellationToken)
{
_startNode = startNode;
_endNode = endNode;
_pathfindingArgs = pathfindingArgs;
}
protected override async Task<Queue<TileRef>?> Process()
{
if (_startNode == null ||
_endNode == null ||
Status == JobStatus.Finished)
{
return null;
}
// If we couldn't get a nearby node that's good enough
if (!PathfindingHelpers.TryEndNode(ref _endNode, _pathfindingArgs))
{
return null;
}
var frontier = new PriorityQueue<ValueTuple<float, PathfindingNode>>(new PathfindingComparer());
var costSoFar = new Dictionary<PathfindingNode, float>();
var cameFrom = new Dictionary<PathfindingNode, PathfindingNode>();
PathfindingNode? currentNode = null;
frontier.Add((0.0f, _startNode));
costSoFar[_startNode] = 0.0f;
var routeFound = false;
var count = 0;
while (frontier.Count > 0)
{
// Handle whether we need to pause if we've taken too long
count++;
if (count % 20 == 0 && count > 0)
{
await SuspendIfOutOfTime();
if (_startNode == null || _endNode == null)
{
return null;
}
}
// Actual pathfinding here
(_, currentNode) = frontier.Take();
if (currentNode.Equals(_endNode))
{
routeFound = true;
break;
}
foreach (var nextNode in currentNode.GetNeighbors())
{
// If tile is untraversable it'll be null
var tileCost = PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, nextNode);
if (tileCost == null)
{
continue;
}
// So if we're going NE then that means either N or E needs to be free to actually get there
var direction = PathfindingHelpers.RelativeDirection(nextNode, currentNode);
if (!PathfindingHelpers.DirectionTraversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, currentNode, direction))
{
continue;
}
// f = g + h
// gScore is distance to the start node
// hScore is distance to the end node
var gScore = costSoFar[currentNode] + tileCost.Value;
if (costSoFar.TryGetValue(nextNode, out var nextValue) && gScore >= nextValue)
{
continue;
}
cameFrom[nextNode] = currentNode;
costSoFar[nextNode] = gScore;
// pFactor is tie-breaker where the fscore is otherwise equal.
// See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
// There's other ways to do it but future consideration
// The closer the fScore is to the actual distance then the better the pathfinder will be
// (i.e. somewhere between 1 and infinite)
// Can use hierarchical pathfinder or whatever to improve the heuristic but this is fine for now.
var fScore = gScore + PathfindingHelpers.OctileDistance(_endNode, nextNode) * (1.0f + 1.0f / 1000.0f);
frontier.Add((fScore, nextNode));
}
}
if (!routeFound)
{
return null;
}
DebugTools.AssertNotNull(currentNode);
var route = PathfindingHelpers.ReconstructPath(cameFrom, currentNode!);
if (route.Count == 1)
{
return null;
}
#if DEBUG
// Need to get data into an easier format to send to the relevant clients
if (DebugRoute != null && route.Count > 0)
{
var debugCameFrom = new Dictionary<TileRef, TileRef>(cameFrom.Count);
var debugGScores = new Dictionary<TileRef, float>(costSoFar.Count);
foreach (var (node, parent) in cameFrom)
{
debugCameFrom.Add(node.TileRef, parent.TileRef);
}
foreach (var (node, score) in costSoFar)
{
debugGScores.Add(node.TileRef, score);
}
var debugRoute = new SharedAiDebug.AStarRouteDebug(
_pathfindingArgs.Uid,
route,
debugCameFrom,
debugGScores,
DebugTime);
DebugRoute.Invoke(debugRoute);
}
#endif
return route;
}
}
}

View File

@@ -0,0 +1,516 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.CPUJob.JobQueues;
using Content.Shared.AI;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Content.Server.AI.Pathfinding.Pathfinders
{
public class JpsPathfindingJob : Job<Queue<TileRef>>
{
// Some of this is probably fugly due to other structural changes in pathfinding so it could do with optimisation
// Realistically it's probably not getting used given it doesn't support tile costs which can be very useful
#if DEBUG
public static event Action<SharedAiDebug.JpsRouteDebug>? DebugRoute;
#endif
private readonly PathfindingNode? _startNode;
private PathfindingNode? _endNode;
private readonly PathfindingArgs _pathfindingArgs;
public JpsPathfindingJob(double maxTime,
PathfindingNode startNode,
PathfindingNode endNode,
PathfindingArgs pathfindingArgs,
CancellationToken cancellationToken) : base(maxTime, cancellationToken)
{
_startNode = startNode;
_endNode = endNode;
_pathfindingArgs = pathfindingArgs;
}
protected override async Task<Queue<TileRef>?> Process()
{
// VERY similar to A*; main difference is with the neighbor tiles you look for jump nodes instead
if (_startNode == null ||
_endNode == null)
{
return null;
}
// If we couldn't get a nearby node that's good enough
if (!PathfindingHelpers.TryEndNode(ref _endNode, _pathfindingArgs))
{
return null;
}
var openTiles = new PriorityQueue<ValueTuple<float, PathfindingNode>>(new PathfindingComparer());
var gScores = new Dictionary<PathfindingNode, float>();
var cameFrom = new Dictionary<PathfindingNode, PathfindingNode>();
var closedTiles = new HashSet<PathfindingNode>();
#if DEBUG
var jumpNodes = new HashSet<PathfindingNode>();
#endif
PathfindingNode? currentNode = null;
openTiles.Add((0, _startNode));
gScores[_startNode] = 0.0f;
var routeFound = false;
var count = 0;
while (openTiles.Count > 0)
{
count++;
// JPS probably getting a lot fewer nodes than A* is
if (count % 5 == 0 && count > 0)
{
await SuspendIfOutOfTime();
}
(_, currentNode) = openTiles.Take();
if (currentNode.Equals(_endNode))
{
routeFound = true;
break;
}
foreach (var node in currentNode.GetNeighbors())
{
var direction = PathfindingHelpers.RelativeDirection(node, currentNode);
var jumpNode = GetJumpPoint(currentNode, direction, _endNode);
if (jumpNode != null && !closedTiles.Contains(jumpNode))
{
closedTiles.Add(jumpNode);
#if DEBUG
jumpNodes.Add(jumpNode);
#endif
// GetJumpPoint should already check if we can traverse to the node
var tileCost = PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, jumpNode);
if (tileCost == null)
{
throw new InvalidOperationException();
}
var gScore = gScores[currentNode] + tileCost.Value;
if (gScores.TryGetValue(jumpNode, out var nextValue) && gScore >= nextValue)
{
continue;
}
cameFrom[jumpNode] = currentNode;
gScores[jumpNode] = gScore;
// pFactor is tie-breaker where the fscore is otherwise equal.
// See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html#breaking-ties
// There's other ways to do it but future consideration
var fScore = gScores[jumpNode] + PathfindingHelpers.OctileDistance(_endNode, jumpNode) * (1.0f + 1.0f / 1000.0f);
openTiles.Add((fScore, jumpNode));
}
}
}
if (!routeFound)
{
return null;
}
DebugTools.AssertNotNull(currentNode);
var route = PathfindingHelpers.ReconstructJumpPath(cameFrom, currentNode!);
if (route.Count == 1)
{
return null;
}
#if DEBUG
// Need to get data into an easier format to send to the relevant clients
if (DebugRoute != null && route.Count > 0)
{
var debugJumpNodes = new HashSet<TileRef>(jumpNodes.Count);
foreach (var node in jumpNodes)
{
debugJumpNodes.Add(node.TileRef);
}
var debugRoute = new SharedAiDebug.JpsRouteDebug(
_pathfindingArgs.Uid,
route,
debugJumpNodes,
DebugTime);
DebugRoute.Invoke(debugRoute);
}
#endif
return route;
}
private PathfindingNode? GetJumpPoint(PathfindingNode currentNode, Direction direction, PathfindingNode endNode)
{
var count = 0;
while (count < 1000)
{
count++;
PathfindingNode? nextNode = null;
foreach (var node in currentNode.GetNeighbors())
{
if (PathfindingHelpers.RelativeDirection(node, currentNode) == direction)
{
nextNode = node;
break;
}
}
// We'll do opposite DirectionTraversable just because of how the method's setup
// Nodes should be 2-way anyway.
if (nextNode == null ||
PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, nextNode) == null)
{
return null;
}
if (nextNode == endNode)
{
return endNode;
}
// Horizontal and vertical are treated the same i.e.
// They only check in their specific direction
// (So Going North means you check NorthWest and NorthEast to see if we're a jump point)
// Diagonals also check the cardinal directions at the same time at the same time
// See https://harablog.wordpress.com/2011/09/07/jump-point-search/ for original description
switch (direction)
{
case Direction.East:
if (IsCardinalJumpPoint(direction, nextNode))
{
return nextNode;
}
break;
case Direction.NorthEast:
if (IsDiagonalJumpPoint(direction, nextNode))
{
return nextNode;
}
if (GetJumpPoint(nextNode, Direction.North, endNode) != null || GetJumpPoint(nextNode, Direction.East, endNode) != null)
{
return nextNode;
}
break;
case Direction.North:
if (IsCardinalJumpPoint(direction, nextNode))
{
return nextNode;
}
break;
case Direction.NorthWest:
if (IsDiagonalJumpPoint(direction, nextNode))
{
return nextNode;
}
if (GetJumpPoint(nextNode, Direction.North, endNode) != null || GetJumpPoint(nextNode, Direction.West, endNode) != null)
{
return nextNode;
}
break;
case Direction.West:
if (IsCardinalJumpPoint(direction, nextNode))
{
return nextNode;
}
break;
case Direction.SouthWest:
if (IsDiagonalJumpPoint(direction, nextNode))
{
return nextNode;
}
if (GetJumpPoint(nextNode, Direction.South, endNode) != null || GetJumpPoint(nextNode, Direction.West, endNode) != null)
{
return nextNode;
}
break;
case Direction.South:
if (IsCardinalJumpPoint(direction, nextNode))
{
return nextNode;
}
break;
case Direction.SouthEast:
if (IsDiagonalJumpPoint(direction, nextNode))
{
return nextNode;
}
if (GetJumpPoint(nextNode, Direction.South, endNode) != null || GetJumpPoint(nextNode, Direction.East, endNode) != null)
{
return nextNode;
}
break;
default:
throw new ArgumentOutOfRangeException(nameof(direction), direction, null);
}
currentNode = nextNode;
}
Logger.WarningS("pathfinding", "Recursion found in JPS pathfinder");
return null;
}
private bool IsDiagonalJumpPoint(Direction direction, PathfindingNode currentNode)
{
// If we're going diagonally need to check all cardinals.
// I tried just casting direction ints and offsets to make it smaller but brain no worky.
// From NorthEast we check (Closed / Open) S - SE, W - NW
PathfindingNode? openNeighborOne = null;
PathfindingNode? closedNeighborOne = null;
PathfindingNode? openNeighborTwo = null;
PathfindingNode? closedNeighborTwo = null;
switch (direction)
{
case Direction.NorthEast:
foreach (var neighbor in currentNode.GetNeighbors())
{
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
switch (neighborDirection)
{
case Direction.SouthEast:
openNeighborOne = neighbor;
break;
case Direction.South:
closedNeighborOne = neighbor;
break;
case Direction.NorthWest:
openNeighborTwo = neighbor;
break;
case Direction.West:
closedNeighborTwo = neighbor;
break;
}
}
break;
case Direction.SouthEast:
foreach (var neighbor in currentNode.GetNeighbors())
{
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
switch (neighborDirection)
{
case Direction.NorthEast:
openNeighborOne = neighbor;
break;
case Direction.North:
closedNeighborOne = neighbor;
break;
case Direction.SouthWest:
openNeighborTwo = neighbor;
break;
case Direction.West:
closedNeighborTwo = neighbor;
break;
}
}
break;
case Direction.SouthWest:
foreach (var neighbor in currentNode.GetNeighbors())
{
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
switch (neighborDirection)
{
case Direction.NorthWest:
openNeighborOne = neighbor;
break;
case Direction.North:
closedNeighborOne = neighbor;
break;
case Direction.SouthEast:
openNeighborTwo = neighbor;
break;
case Direction.East:
closedNeighborTwo = neighbor;
break;
}
}
break;
case Direction.NorthWest:
foreach (var neighbor in currentNode.GetNeighbors())
{
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
switch (neighborDirection)
{
case Direction.SouthWest:
openNeighborOne = neighbor;
break;
case Direction.South:
closedNeighborOne = neighbor;
break;
case Direction.NorthEast:
openNeighborTwo = neighbor;
break;
case Direction.East:
closedNeighborTwo = neighbor;
break;
}
}
break;
default:
throw new ArgumentOutOfRangeException();
}
if ((closedNeighborOne == null || PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, closedNeighborOne) == null)
&& openNeighborOne != null && PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, openNeighborOne) != null)
{
return true;
}
if ((closedNeighborTwo == null || PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, closedNeighborTwo) == null)
&& openNeighborTwo != null && PathfindingHelpers.GetTileCost(_pathfindingArgs, currentNode, openNeighborTwo) != null)
{
return true;
}
return false;
}
/// <summary>
/// Check to see if the node is a jump point (only works for cardinal directions)
/// </summary>
private bool IsCardinalJumpPoint(Direction direction, PathfindingNode currentNode)
{
PathfindingNode? openNeighborOne = null;
PathfindingNode? closedNeighborOne = null;
PathfindingNode? openNeighborTwo = null;
PathfindingNode? closedNeighborTwo = null;
switch (direction)
{
case Direction.North:
foreach (var neighbor in currentNode.GetNeighbors())
{
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
switch (neighborDirection)
{
case Direction.NorthEast:
openNeighborOne = neighbor;
break;
case Direction.East:
closedNeighborOne = neighbor;
break;
case Direction.NorthWest:
openNeighborTwo = neighbor;
break;
case Direction.West:
closedNeighborTwo = neighbor;
break;
}
}
break;
case Direction.East:
foreach (var neighbor in currentNode.GetNeighbors())
{
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
switch (neighborDirection)
{
case Direction.NorthEast:
openNeighborOne = neighbor;
break;
case Direction.North:
closedNeighborOne = neighbor;
break;
case Direction.SouthEast:
openNeighborTwo = neighbor;
break;
case Direction.South:
closedNeighborTwo = neighbor;
break;
}
}
break;
case Direction.South:
foreach (var neighbor in currentNode.GetNeighbors())
{
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
switch (neighborDirection)
{
case Direction.SouthEast:
openNeighborOne = neighbor;
break;
case Direction.East:
closedNeighborOne = neighbor;
break;
case Direction.SouthWest:
openNeighborTwo = neighbor;
break;
case Direction.West:
closedNeighborTwo = neighbor;
break;
}
}
break;
case Direction.West:
foreach (var neighbor in currentNode.GetNeighbors())
{
var neighborDirection = PathfindingHelpers.RelativeDirection(neighbor, currentNode);
switch (neighborDirection)
{
case Direction.NorthWest:
openNeighborOne = neighbor;
break;
case Direction.North:
closedNeighborOne = neighbor;
break;
case Direction.SouthWest:
openNeighborTwo = neighbor;
break;
case Direction.South:
closedNeighborTwo = neighbor;
break;
}
}
break;
default:
throw new ArgumentOutOfRangeException();
}
if ((closedNeighborOne == null || !PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, closedNeighborOne)) &&
openNeighborOne != null && PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, openNeighborOne))
{
return true;
}
if ((closedNeighborTwo == null || !PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, closedNeighborTwo)) &&
openNeighborTwo != null && PathfindingHelpers.Traversable(_pathfindingArgs.CollisionMask, _pathfindingArgs.Access, openNeighborTwo))
{
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,45 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
namespace Content.Server.AI.Pathfinding.Pathfinders
{
public struct PathfindingArgs
{
public EntityUid Uid { get; }
public ICollection<string> Access { get; }
public int CollisionMask { get; }
public TileRef Start { get; }
public TileRef End { get; }
// How close we need to get to the endpoint to be 'done'
public float Proximity { get; }
// Whether we use cardinal only or not
public bool AllowDiagonals { get; }
// Can we go through walls
public bool NoClip { get; }
// Can we traverse space tiles
public bool AllowSpace { get; }
public PathfindingArgs(
EntityUid entityUid,
ICollection<string> access,
int collisionMask,
TileRef start,
TileRef end,
float proximity = 0.0f,
bool allowDiagonals = true,
bool noClip = false,
bool allowSpace = false)
{
Uid = entityUid;
Access = access;
CollisionMask = collisionMask;
Start = start;
End = end;
Proximity = proximity;
AllowDiagonals = allowDiagonals;
NoClip = noClip;
AllowSpace = allowSpace;
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
namespace Content.Server.AI.Pathfinding.Pathfinders
{
public class PathfindingComparer : IComparer<ValueTuple<float, PathfindingNode>>
{
public int Compare((float, PathfindingNode) x, (float, PathfindingNode) y)
{
return y.Item1.CompareTo(x.Item1);
}
}
}