#define DECREASE_KEY
using System.Collections.Generic;
namespace Pathfinding {
///
/// Stores temporary node data for a single pathfinding request.
/// Every node has one PathNode per thread used.
/// It stores e.g G score, H score and other temporary variables needed
/// for path calculation, but which are not part of the graph structure.
///
/// See: Pathfinding.PathHandler
/// See: https://en.wikipedia.org/wiki/A*_search_algorithm
///
public class PathNode {
/// Reference to the actual graph node
public GraphNode node;
/// Parent node in the search tree
public PathNode parent;
/// The path request (in this thread, if multithreading is used) which last used this node
public ushort pathID;
#if DECREASE_KEY
///
/// Index of the node in the binary heap.
/// The open list in the A* algorithm is backed by a binary heap.
/// To support fast 'decrease key' operations, the index of the node
/// is saved here.
///
public ushort heapIndex = BinaryHeap.NotInHeap;
#endif
/// Bitpacked variable which stores several fields
private uint flags;
/// Cost uses the first 28 bits
private const uint CostMask = (1U << 28) - 1U;
/// Flag 1 is at bit 28
private const int Flag1Offset = 28;
private const uint Flag1Mask = (uint)(1 << Flag1Offset);
/// Flag 2 is at bit 29
private const int Flag2Offset = 29;
private const uint Flag2Mask = (uint)(1 << Flag2Offset);
public uint cost {
get {
return flags & CostMask;
}
set {
flags = (flags & ~CostMask) | value;
}
}
///
/// Use as temporary flag during pathfinding.
/// Pathfinders (only) can use this during pathfinding to mark
/// nodes. When done, this flag should be reverted to its default state (false) to
/// avoid messing up other pathfinding requests.
///
public bool flag1 {
get {
return (flags & Flag1Mask) != 0;
}
set {
flags = (flags & ~Flag1Mask) | (value ? Flag1Mask : 0U);
}
}
///
/// Use as temporary flag during pathfinding.
/// Pathfinders (only) can use this during pathfinding to mark
/// nodes. When done, this flag should be reverted to its default state (false) to
/// avoid messing up other pathfinding requests.
///
public bool flag2 {
get {
return (flags & Flag2Mask) != 0;
}
set {
flags = (flags & ~Flag2Mask) | (value ? Flag2Mask : 0U);
}
}
/// Backing field for the G score
private uint g;
/// Backing field for the H score
private uint h;
/// G score, cost to get to this node
public uint G { get { return g; } set { g = value; } }
/// H score, estimated cost to get to to the target
public uint H { get { return h; } set { h = value; } }
/// F score. H score + G score
public uint F { get { return g+h; } }
public void UpdateG (Path path) {
#if ASTAR_NO_TRAVERSAL_COST
g = parent.g + cost;
#else
g = parent.g + cost + path.GetTraversalCost(node);
#endif
}
}
/// Handles thread specific path data.
public class PathHandler {
///
/// Current PathID.
/// See:
///
private ushort pathID;
public readonly int threadID;
public readonly int totalThreadCount;
///
/// Binary heap to keep track of nodes on the "Open list".
/// See: https://en.wikipedia.org/wiki/A*_search_algorithm
///
public readonly BinaryHeap heap = new BinaryHeap(128);
/// ID for the path currently being calculated or last path that was calculated
public ushort PathID { get { return pathID; } }
/// Array of all PathNodes
public PathNode[] nodes = new PathNode[0];
///
/// StringBuilder that paths can use to build debug strings.
/// Better for performance and memory usage to use a single StringBuilder instead of each path creating its own
///
public readonly System.Text.StringBuilder DebugStringBuilder = new System.Text.StringBuilder();
public PathHandler (int threadID, int totalThreadCount) {
this.threadID = threadID;
this.totalThreadCount = totalThreadCount;
}
public void InitializeForPath (Path p) {
pathID = p.pathID;
heap.Clear();
}
/// Internal method to clean up node data
public void DestroyNode (GraphNode node) {
PathNode pn = GetPathNode(node);
// Clean up references to help the GC
pn.node = null;
pn.parent = null;
// This is not required for pathfinding, but not clearing it may confuse gizmo drawing for a fraction of a second.
// Especially when 'Show Search Tree' is enabled
pn.pathID = 0;
pn.G = 0;
pn.H = 0;
}
/// Internal method to initialize node data
public void InitializeNode (GraphNode node) {
//Get the index of the node
int ind = node.NodeIndex;
if (ind >= nodes.Length) {
// Grow by a factor of 2
PathNode[] newNodes = new PathNode[System.Math.Max(128, nodes.Length*2)];
nodes.CopyTo(newNodes, 0);
// Initialize all PathNode instances at once
// It is important that we do this here and don't for example leave the entries as NULL and initialize
// them lazily. By allocating them all at once we are much more likely to allocate the PathNodes close
// to each other in memory (most systems use some kind of bumb-allocator) and this improves cache locality
// and reduces false sharing (which would happen if we allocated PathNodes for the different threads close
// to each other). This has been profiled to give around a 4% difference in overall pathfinding performance.
for (int i = nodes.Length; i < newNodes.Length; i++) newNodes[i] = new PathNode();
nodes = newNodes;
}
nodes[ind].node = node;
}
public PathNode GetPathNode (int nodeIndex) {
return nodes[nodeIndex];
}
///
/// Returns the PathNode corresponding to the specified node.
/// The PathNode is specific to this PathHandler since multiple PathHandlers
/// are used at the same time if multithreading is enabled.
///
public PathNode GetPathNode (GraphNode node) {
return nodes[node.NodeIndex];
}
///
/// Set all nodes' pathIDs to 0.
/// See: Pathfinding.PathNode.pathID
///
public void ClearPathIDs () {
for (int i = 0; i < nodes.Length; i++) {
if (nodes[i] != null) nodes[i].pathID = 0;
}
}
}
}