// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // namespace MUnique.OpenMU.Pathfinding; using System.Runtime.CompilerServices; /// /// A binary min heap which implements . /// Objects with the lowest index values appear at the top of the heap, and will be retrieved when calling . /// Please note: This class is not thread safe! Push/Pop should not be executed by two different threads at the same time!. /// /// /// This class contains some optimizations which do not make the code nicer. However, /// this data structure is THE bottleneck of the pathfinding algorithm, so optimization is worth it. /// /// The type which should be contained in the heap. public class BinaryMinHeap : IPriorityQueue { private readonly List _innerList = new(); private readonly IComparer _elementComparer; /// Reused variable to reduce stack allocations. private int _i; /// Reused variable to reduce stack allocations. private int _parentIndex; /// Reused variable to reduce stack allocations. private int _left; /// Reused variable to reduce stack allocations. private int _right; /// /// Initializes a new instance of the class. /// public BinaryMinHeap() { this._elementComparer = Comparer.Default; } /// /// Initializes a new instance of the class. /// /// The comparer. public BinaryMinHeap(IComparer comparer) { this._elementComparer = comparer; } /// /// Initializes a new instance of the class. /// /// The comparer. /// The capacity. public BinaryMinHeap(IComparer comparer, int capacity) { this._elementComparer = comparer; this._innerList.Capacity = capacity; } /// public int Count => this._innerList.Count; /// public void Push(T item) { this._i = this._innerList.Count; this._innerList.Add(item); do { if (this._i == 0) { break; } this._parentIndex = unchecked(this._i - 1) >> 1; if (this.OnCompareWithElementOfI(this._parentIndex) < 0) { this.SwitchElementsParentWithI(); this._i = this._parentIndex; } else { break; } } while (true); } /// public T Pop() { if (this.Count == 0) { throw new InvalidOperationException("Heap is empty"); } var result = this._innerList[0]; this._i = 0; this._innerList[0] = this._innerList[^1]; this._innerList.RemoveAt(this._innerList.Count - 1); do { this._parentIndex = this._i; this._left = unchecked((this._i << 1) + 1); this._right = unchecked((this._i << 1) + 2); if (this._innerList.Count > this._left && this.OnCompareWithElementOfI(this._left) > 0) { this._i = this._left; } if (this._innerList.Count > this._right && this.OnCompareWithElementOfI(this._right) > 0) { this._i = this._right; } if (this._i == this._parentIndex) { break; } this.SwitchElementsParentWithI(); } while (true); return result; } /// /// Get the smallest object without removing it. /// /// The smallest object. public T Peek() { if (this._innerList.Count > 0) { return this._innerList[0]; } throw new InvalidOperationException("Heap is empty"); } /// public void Clear() { this._innerList.Clear(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SwitchElementsParentWithI() { T h = this._innerList[this._i]; this._innerList[this._i] = this._innerList[this._parentIndex]; this._innerList[this._parentIndex] = h; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private int OnCompareWithElementOfI(int j) { return this._elementComparer.Compare(this._innerList[this._i], this._innerList[j]); } }