using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
namespace Pathfinding {
using Pathfinding.Util;
///
/// 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 struct PathNode {
/// The path request (in this thread, if multithreading is used) which last used this node
public ushort pathID;
///
/// 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;
/// Bitpacked variable which stores several fields
private uint flags;
public static readonly PathNode Default = new PathNode { pathID = 0, heapIndex = BinaryHeap.NotInHeap, flags = 0 };
/// Parent index uses the first 26 bits
private const uint ParentIndexMask = (1U << 26) - 1U;
private const int FractionAlongEdgeOffset = 26;
private const uint FractionAlongEdgeMask = ((1U << 30) - 1U) & ~ParentIndexMask;
public const int FractionAlongEdgeQuantization = 1 << (30 - 26);
public static uint ReverseFractionAlongEdge(uint v) => (FractionAlongEdgeQuantization - 1) - v;
public static uint QuantizeFractionAlongEdge (float v) {
v *= FractionAlongEdgeQuantization - 1;
v += 0.5f;
return Unity.Mathematics.math.clamp((uint)v, 0, FractionAlongEdgeQuantization - 1);
}
public static float UnQuantizeFractionAlongEdge (uint v) {
return (float)v * (1.0f / (FractionAlongEdgeQuantization - 1));
}
/// Flag 1 is at bit 30
private const int Flag1Offset = 30;
private const uint Flag1Mask = 1U << Flag1Offset;
/// Flag 2 is at bit 31
private const int Flag2Offset = 31;
private const uint Flag2Mask = 1U << Flag2Offset;
public uint fractionAlongEdge {
get => (flags & FractionAlongEdgeMask) >> FractionAlongEdgeOffset;
set => flags = (flags & ~FractionAlongEdgeMask) | ((value << FractionAlongEdgeOffset) & FractionAlongEdgeMask);
}
public uint parentIndex {
get => flags & ParentIndexMask;
set => flags = (flags & ~ParentIndexMask) | value;
}
///
/// Use as temporary flag during pathfinding.
/// Path types 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 => (flags & Flag1Mask) != 0;
set => flags = (flags & ~Flag1Mask) | (value ? Flag1Mask : 0U);
}
///
/// Use as temporary flag during pathfinding.
/// Path types 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 => (flags & Flag2Mask) != 0;
set => flags = (flags & ~Flag2Mask) | (value ? Flag2Mask : 0U);
}
}
public enum TemporaryNodeType {
Start,
End,
Ignore,
}
public struct TemporaryNode {
public uint associatedNode;
public Int3 position;
public int targetIndex;
public TemporaryNodeType type;
}
/// Handles thread specific path data.
public class PathHandler {
///
/// Current PathID.
/// See:
///
private ushort pathID;
public readonly int threadID;
public readonly int totalThreadCount;
internal readonly GlobalNodeStorage nodeStorage;
public int numTemporaryNodes { get; private set; }
///
/// All path nodes with an index greater or equal to this are temporary nodes that only exist for the duration of a single path.
///
/// This is a copy of NodeStorage.nextNodeIndex. This is used to avoid having to access the NodeStorage while pathfinding as it's an extra indirection.
///
public uint temporaryNodeStartIndex { get; private set; }
UnsafeSpan temporaryNodes;
///
/// Reference to the per-node data for this thread.
///
/// Note: Only guaranteed to point to a valid allocation while the path is being calculated.
///
public UnsafeSpan pathNodes;
#if UNITY_EDITOR
UnsafeSpan debugPathNodes;
#endif
///
/// Binary heap to keep track of nodes on the "Open list".
/// See: https://en.wikipedia.org/wiki/A*_search_algorithm
///
public BinaryHeap heap = new BinaryHeap(128);
/// ID for the path currently being calculated or last path that was calculated
public ushort PathID { get { return pathID; } }
///
/// 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();
internal PathHandler (GlobalNodeStorage nodeStorage, int threadID, int totalThreadCount) {
this.threadID = threadID;
this.totalThreadCount = totalThreadCount;
this.nodeStorage = nodeStorage;
temporaryNodes = new UnsafeSpan(Allocator.Persistent, GlobalNodeStorage.MaxTemporaryNodes);
}
public void InitializeForPath (Path p) {
var lastPathId = pathID;
pathID = p.pathID;
numTemporaryNodes = 0;
temporaryNodeStartIndex = nodeStorage.nextNodeIndex;
// Get the path nodes for this thread (may have been resized since last we calculated a path)
pathNodes = nodeStorage.pathfindingThreadData[threadID].pathNodes;
#if UNITY_EDITOR
var astar = AstarPath.active;
var shouldLog = astar.showGraphs && (astar.debugMode == GraphDebugMode.F || astar.debugMode == GraphDebugMode.H || astar.debugMode == GraphDebugMode.G || astar.showSearchTree);
debugPathNodes = shouldLog ? nodeStorage.pathfindingThreadData[threadID].debugPathNodes : default;
#endif
// Path IDs have overflowed 65K, cleanup is needed to avoid bugs where we think
// we have already visited a node when we haven't.
// Since pathIDs are handed out sequentially, we can check if the new path id
// is smaller than the last one.
if (pathID < lastPathId) {
ClearPathIDs();
}
}
///
/// 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 ref PathNode GetPathNode (GraphNode node, uint variant = 0) {
return ref pathNodes[node.NodeIndex + variant];
}
public bool IsTemporaryNode(uint pathNodeIndex) => pathNodeIndex >= temporaryNodeStartIndex;
public uint AddTemporaryNode (TemporaryNode node) {
if (numTemporaryNodes >= GlobalNodeStorage.MaxTemporaryNodes) {
// It would be nice if we could dynamically re-allocate the temporaryNodes array, and the pathNodes array. But this class allows handing out references to path nodes and temporary nodes,
// and we cannot guarantee that those references will not be used after this function is called (which may lead to memory corruption).
// So instead we just have a hard limit, which can be increased by enabling the ASTAR_MORE_MULTI_TARGET_PATH_TARGETS define.
throw new System.InvalidOperationException("Cannot create more than " + GlobalNodeStorage.MaxTemporaryNodes + " temporary nodes. You can enable ASTAR_MORE_MULTI_TARGET_PATH_TARGETS in the A* Inspector optimizations tab to increase this limit.");
}
var index = temporaryNodeStartIndex + (uint)numTemporaryNodes;
temporaryNodes[numTemporaryNodes] = node;
pathNodes[index] = PathNode.Default;
numTemporaryNodes++;
return index;
}
public GraphNode GetNode(uint nodeIndex) => nodeStorage.GetNode(nodeIndex);
public ref TemporaryNode GetTemporaryNode (uint nodeIndex) {
if (nodeIndex < temporaryNodeStartIndex || nodeIndex >= temporaryNodeStartIndex + numTemporaryNodes)
throw new System.ArgumentOutOfRangeException();
return ref temporaryNodes[(int)(nodeIndex - temporaryNodeStartIndex)];
}
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
public void LogVisitedNode (uint pathNodeIndex, uint h, uint g) {
#if UNITY_EDITOR
if (debugPathNodes.Length > 0 && !IsTemporaryNode(pathNodeIndex)) {
var parent = pathNodes[pathNodeIndex].parentIndex;
debugPathNodes[pathNodeIndex] = new GlobalNodeStorage.DebugPathNode {
h = h,
g = g,
parentIndex = parent >= temporaryNodeStartIndex ? 0 : parent,
pathID = pathID,
fractionAlongEdge = (byte)pathNodes[pathNodeIndex].fractionAlongEdge,
};
}
#endif
}
///
/// Set all nodes' pathIDs to 0.
/// See: Pathfinding.PathNode.pathID
///
public void ClearPathIDs () {
for (int i = 0; i < pathNodes.Length; i++) {
pathNodes[i].pathID = 0;
}
}
public void Dispose () {
heap.Dispose();
temporaryNodes.Free(Allocator.Persistent);
pathNodes = default;
}
}
}