summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/Collections/BinaryHeap.cs
diff options
context:
space:
mode:
authorchai <215380520@qq.com>2024-05-23 10:08:29 +0800
committerchai <215380520@qq.com>2024-05-23 10:08:29 +0800
commit8722a9920c1f6119bf6e769cba270e63097f8e25 (patch)
tree2eaf9865de7fb1404546de4a4296553d8f68cc3b /Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/Collections/BinaryHeap.cs
parent3ba4020b69e5971bb0df7ee08b31d10ea4d01937 (diff)
+ astar project
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/Collections/BinaryHeap.cs')
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/Collections/BinaryHeap.cs359
1 files changed, 359 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/Collections/BinaryHeap.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/Collections/BinaryHeap.cs
new file mode 100644
index 0000000..d76a67d
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Core/Collections/BinaryHeap.cs
@@ -0,0 +1,359 @@
+// #define VALIDATE_BINARY_HEAP
+#pragma warning disable 162
+#pragma warning disable 429
+using Unity.Mathematics;
+using Unity.Collections;
+using Unity.Burst;
+using Unity.Burst.CompilerServices;
+
+namespace Pathfinding {
+ using Pathfinding.Util;
+
+ /// <summary>
+ /// Binary heap implementation.
+ /// Binary heaps are really fast for ordering nodes in a way that
+ /// makes it possible to get the node with the lowest F score.
+ /// Also known as a priority queue.
+ ///
+ /// This has actually been rewritten as a 4-ary heap
+ /// for performance, but it's the same principle.
+ ///
+ /// See: http://en.wikipedia.org/wiki/Binary_heap
+ /// See: https://en.wikipedia.org/wiki/D-ary_heap
+ /// </summary>
+ [BurstCompile]
+ public struct BinaryHeap {
+ /// <summary>Number of items in the tree</summary>
+ public int numberOfItems;
+
+ /// <summary>The tree will grow by at least this factor every time it is expanded</summary>
+ public const float GrowthFactor = 2;
+
+ /// <summary>
+ /// Number of children of each node in the tree.
+ /// Different values have been tested and 4 has been empirically found to perform the best.
+ /// See: https://en.wikipedia.org/wiki/D-ary_heap
+ /// </summary>
+ const int D = 4;
+
+ /// <summary>
+ /// Sort nodes by G score if there is a tie when comparing the F score.
+ /// Disabling this will improve pathfinding performance with around 2.5%
+ /// but may break ties between paths that have the same length in a less
+ /// desirable manner (only relevant for grid graphs).
+ /// </summary>
+ const bool SortGScores = true;
+
+ public const ushort NotInHeap = 0xFFFF;
+
+ /// <summary>Internal backing array for the heap</summary>
+ private UnsafeSpan<HeapNode> heap;
+
+ /// <summary>True if the heap does not contain any elements</summary>
+ public bool isEmpty => numberOfItems <= 0;
+
+ /// <summary>Item in the heap</summary>
+ private struct HeapNode {
+ public uint pathNodeIndex;
+ /// <summary>Bitpacked F and G scores</summary>
+ public ulong sortKey;
+
+ public HeapNode (uint pathNodeIndex, uint g, uint f) {
+ this.pathNodeIndex = pathNodeIndex;
+ this.sortKey = ((ulong)f << 32) | (ulong)g;
+ }
+
+ public uint F {
+ get => (uint)(sortKey >> 32);
+ set => sortKey = (sortKey & 0xFFFFFFFFUL) | ((ulong)value << 32);
+ }
+
+ public uint G => (uint)sortKey;
+ }
+
+ /// <summary>
+ /// Rounds up v so that it has remainder 1 when divided by D.
+ /// I.e it is of the form n*D + 1 where n is any non-negative integer.
+ /// </summary>
+ static int RoundUpToNextMultipleMod1 (int v) {
+ // I have a feeling there is a nicer way to do this
+ return v + (4 - ((v-1) % D)) % D;
+ }
+
+ /// <summary>Create a new heap with the specified initial capacity</summary>
+ public BinaryHeap (int capacity) {
+ // Make sure the size has remainder 1 when divided by D
+ // This allows us to always guarantee that indices used in the Remove method
+ // will never throw out of bounds exceptions
+ capacity = RoundUpToNextMultipleMod1(capacity);
+
+ heap = new UnsafeSpan<HeapNode>(Unity.Collections.Allocator.Persistent, capacity);
+ numberOfItems = 0;
+ }
+
+ public void Dispose () {
+ unsafe {
+ AllocatorManager.Free<HeapNode>(Allocator.Persistent, heap.ptr, heap.Length);
+ }
+ }
+
+ /// <summary>Removes all elements from the heap</summary>
+ public void Clear (UnsafeSpan<PathNode> pathNodes) {
+ // Clear all heap indices, and references to the heap nodes
+ for (int i = 0; i < numberOfItems; i++) {
+ pathNodes[heap[i].pathNodeIndex].heapIndex = NotInHeap;
+ }
+
+ numberOfItems = 0;
+ }
+
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ public uint GetPathNodeIndex(int heapIndex) => heap[heapIndex].pathNodeIndex;
+
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ public uint GetG(int heapIndex) => heap[heapIndex].G;
+
+ [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
+ public uint GetF(int heapIndex) => heap[heapIndex].F;
+
+ public void SetH (int heapIndex, uint h) {
+ heap[heapIndex].F = heap[heapIndex].G + h;
+ }
+
+ /// <summary>Expands to a larger backing array when the current one is too small</summary>
+ static void Expand (ref UnsafeSpan<HeapNode> heap) {
+ // 65533 == 1 mod 4 and slightly smaller than 1<<16 = 65536
+ int newSize = math.max(heap.Length+4, math.min(65533, (int)math.round(heap.Length*GrowthFactor)));
+
+ // Make sure the size has remainder 1 when divided by D
+ // This allows us to always guarantee that indices used in the Remove method
+ // will never throw out of bounds exceptions
+ newSize = RoundUpToNextMultipleMod1(newSize);
+
+ // Check if the heap is really large
+ // Also note that heaps larger than this are not supported
+ // since PathNode.heapIndex is a ushort and can only store
+ // values up to 65535 (NotInHeap = 65535 is reserved however)
+ if (newSize > (1<<16) - 2) {
+ throw new System.Exception("Binary Heap Size really large (>65534). A heap size this large is probably the cause of pathfinding running in an infinite loop. ");
+ }
+
+ var newHeap = new UnsafeSpan<HeapNode>(Unity.Collections.Allocator.Persistent, newSize);
+ newHeap.CopyFrom(heap);
+ unsafe {
+ AllocatorManager.Free<HeapNode>(Allocator.Persistent, heap.ptr, heap.Length);
+ }
+#if ASTARDEBUG
+ UnityEngine.Debug.Log("Resizing binary heap to "+newSize);
+#endif
+ heap = newHeap;
+ }
+
+ /// <summary>Adds a node to the heap</summary>
+ public void Add (UnsafeSpan<PathNode> nodes, uint pathNodeIndex, uint g, uint f) {
+ Add(ref this, ref nodes, pathNodeIndex, g, f);
+ }
+
+ [BurstCompile]
+ static void Add (ref BinaryHeap binaryHeap, ref UnsafeSpan<PathNode> nodes, uint pathNodeIndex, uint g, uint f) {
+ ref var numberOfItems = ref binaryHeap.numberOfItems;
+ ref var heap = ref binaryHeap.heap;
+
+ // Check if node is already in the heap
+ ref var node = ref nodes[pathNodeIndex];
+ if (node.heapIndex != NotInHeap) {
+ var activeNode = new HeapNode(pathNodeIndex, g, f);
+ UnityEngine.Assertions.Assert.AreEqual(activeNode.pathNodeIndex, pathNodeIndex);
+ DecreaseKey(heap, nodes, activeNode, node.heapIndex);
+ Validate(ref nodes, ref binaryHeap);
+ } else {
+ if (numberOfItems == heap.Length) {
+ Expand(ref heap);
+ }
+ Validate(ref nodes, ref binaryHeap);
+
+ DecreaseKey(heap, nodes, new HeapNode(pathNodeIndex, g, f), (ushort)numberOfItems);
+ numberOfItems++;
+ Validate(ref nodes, ref binaryHeap);
+ }
+ }
+
+ static void DecreaseKey (UnsafeSpan<HeapNode> heap, UnsafeSpan<PathNode> nodes, HeapNode node, ushort index) {
+ // This is where 'obj' is in the binary heap logically speaking
+ // (for performance reasons we don't actually store it there until
+ // we know the final index, that's just a waste of CPU cycles)
+ uint bubbleIndex = index;
+
+ while (bubbleIndex != 0) {
+ // Parent node of the bubble node
+ uint parentIndex = (bubbleIndex-1) / D;
+
+ Hint.Assume(parentIndex < heap.length);
+ Hint.Assume(bubbleIndex < heap.length);
+ if (node.sortKey < heap[parentIndex].sortKey) {
+ // Swap the bubble node and parent node
+ // (we don't really need to store the bubble node until we know the final index though
+ // so we do that after the loop instead)
+ heap[bubbleIndex] = heap[parentIndex];
+ nodes[heap[bubbleIndex].pathNodeIndex].heapIndex = (ushort)bubbleIndex;
+ bubbleIndex = parentIndex;
+ } else {
+ break;
+ }
+ }
+
+ Hint.Assume(bubbleIndex < heap.length);
+ heap[bubbleIndex] = node;
+ nodes[node.pathNodeIndex].heapIndex = (ushort)bubbleIndex;
+ }
+
+ /// <summary>Returns the node with the lowest F score from the heap</summary>
+ public uint Remove (UnsafeSpan<PathNode> nodes, out uint g, out uint f) {
+ return Remove(ref nodes, ref this, out g, out f);
+ }
+
+ [BurstCompile]
+ static uint Remove (ref UnsafeSpan<PathNode> nodes, ref BinaryHeap binaryHeap, [NoAlias] out uint removedG, [NoAlias] out uint removedF) {
+ ref var numberOfItems = ref binaryHeap.numberOfItems;
+ var heap = binaryHeap.heap;
+
+ if (numberOfItems == 0) {
+ throw new System.InvalidOperationException("Removing item from empty heap");
+ }
+
+ // This is the smallest item in the heap.
+ // Mark it as removed from the heap.
+ Hint.Assume(0UL < heap.length);
+ uint returnIndex = heap[0].pathNodeIndex;
+ nodes[returnIndex].heapIndex = NotInHeap;
+ removedG = heap[0].G;
+ removedF = heap[0].F;
+
+ numberOfItems--;
+ if (numberOfItems == 0) {
+ return returnIndex;
+ }
+
+ // Last item in the heap array
+ Hint.Assume((uint)numberOfItems < heap.length);
+ var swapItem = heap[numberOfItems];
+ uint swapIndex = 0;
+ ulong comparisonKey = swapItem.sortKey;
+
+ // Trickle upwards
+ while (true) {
+ var parent = swapIndex;
+ uint pd = parent * D + 1;
+
+ // If this holds, then the indices used
+ // below are guaranteed to not throw an index out of bounds
+ // exception since we choose the size of the array in that way
+ if (pd < numberOfItems) {
+ // Find the child node with the smallest F score, or if equal, the smallest G score.
+ // The sorting key is the tuple (F,G).
+ // However, to be able to easily get the smallest index, we steal the lowest 2 bits of G
+ // and use it for the child index (0..3) instead.
+ // This means that tie-breaking will not be perfect, but in all practical cases it will
+ // yield exactly the same result since G scores typically differ by more than 4 anyway.
+ Hint.Assume(pd+0 < heap.length);
+ ulong l0 = (heap[pd+0].sortKey & ~0x3UL) | 0;
+ Hint.Assume(pd+1 < heap.length);
+ ulong l1 = (heap[pd+1].sortKey & ~0x3UL) | 1;
+ Hint.Assume(pd+2 < heap.length);
+ ulong l2 = (heap[pd+2].sortKey & ~0x3UL) | 2;
+ Hint.Assume(pd+3 < heap.length);
+ ulong l3 = (heap[pd+3].sortKey & ~0x3UL) | 3;
+
+ ulong smallest = l0;
+ // Not all children may exist, so we need to check that the index is valid
+ if (pd+1 < numberOfItems) smallest = math.min(smallest, l1);
+ if (pd+2 < numberOfItems) smallest = math.min(smallest, l2);
+ if (pd+3 < numberOfItems) smallest = math.min(smallest, l3);
+
+ if (smallest < comparisonKey) {
+ swapIndex = pd + (uint)(smallest & 0x3UL);
+
+ // One if the parent's children are smaller or equal, swap them
+ // (actually we are just pretenting we swapped them, we hold the swapItem
+ // in local variable and only assign it once we know the final index)
+ Hint.Assume(parent < heap.length);
+ Hint.Assume(swapIndex < heap.length);
+ heap[parent] = heap[swapIndex];
+ Hint.Assume(swapIndex < heap.length);
+ nodes[heap[swapIndex].pathNodeIndex].heapIndex = (ushort)parent;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ // Assign element to the final position
+ Hint.Assume(swapIndex < heap.length);
+ heap[swapIndex] = swapItem;
+ nodes[swapItem.pathNodeIndex].heapIndex = (ushort)swapIndex;
+
+ // For debugging
+ Validate(ref nodes, ref binaryHeap);
+
+ return returnIndex;
+ }
+
+ [System.Diagnostics.Conditional("VALIDATE_BINARY_HEAP")]
+ static void Validate (ref UnsafeSpan<PathNode> nodes, ref BinaryHeap binaryHeap) {
+ for (int i = 1; i < binaryHeap.numberOfItems; i++) {
+ int parentIndex = (i-1)/D;
+ if (binaryHeap.heap[parentIndex].F > binaryHeap.heap[i].F) {
+ throw new System.Exception("Invalid state at " + i + ":" + parentIndex + " ( " + binaryHeap.heap[parentIndex].F + " > " + binaryHeap.heap[i].F + " ) ");
+ }
+
+ if (binaryHeap.heap[parentIndex].sortKey > binaryHeap.heap[i].sortKey) {
+ throw new System.Exception("Invalid state at " + i + ":" + parentIndex + " ( " + binaryHeap.heap[parentIndex].F + " > " + binaryHeap.heap[i].F + " ) ");
+ }
+
+ if (nodes[binaryHeap.heap[i].pathNodeIndex].heapIndex != i) {
+ throw new System.Exception("Invalid heap index");
+ }
+ }
+ }
+
+ /// <summary>
+ /// Rebuilds the heap by trickeling down all items.
+ /// Usually called after the hTarget on a path has been changed
+ /// </summary>
+ public void Rebuild (UnsafeSpan<PathNode> nodes) {
+#if ASTARDEBUG
+ int changes = 0;
+#endif
+
+ for (int i = 2; i < numberOfItems; i++) {
+ int bubbleIndex = i;
+ var node = heap[i];
+ uint nodeF = node.F;
+ while (bubbleIndex != 1) {
+ int parentIndex = bubbleIndex / D;
+
+ if (nodeF < heap[parentIndex].F) {
+ heap[bubbleIndex] = heap[parentIndex];
+ nodes[heap[bubbleIndex].pathNodeIndex].heapIndex = (ushort)bubbleIndex;
+
+ heap[parentIndex] = node;
+ nodes[heap[parentIndex].pathNodeIndex].heapIndex = (ushort)parentIndex;
+
+ bubbleIndex = parentIndex;
+#if ASTARDEBUG
+ changes++;
+#endif
+ } else {
+ break;
+ }
+ }
+ }
+
+#if ASTARDEBUG
+ UnityEngine.Debug.Log("+++ Rebuilt Heap - "+changes+" changes +++");
+#endif
+ }
+ }
+}