// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace SixLabors.PolygonClipper { /// /// Represents a stable priority queue that maintains the order of items with the same priority. /// /// The type of elements in the priority queue. /// The type of comparer used to determine the priority of the elements. [DebuggerDisplay("Count = {Count}")] internal sealed class StablePriorityQueue where TComparer : IComparer { private const int Log2Arity = 2; private const int DefaultCapacity = 16; private readonly List heap; /// /// Initializes a new instance of the class with a specified comparer. /// /// The comparer to determine the priority of the elements. public StablePriorityQueue(TComparer comparer) : this(comparer, DefaultCapacity) { } /// /// Initializes a new instance of the class with a specified comparer. /// /// The comparer to determine the priority of the elements. /// The initial capacity of the priority queue. public StablePriorityQueue(TComparer comparer, int capacity) { this.Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); this.heap = new List(capacity > 0 ? capacity : DefaultCapacity); } /// /// Initializes a new instance of the class /// with a specified comparer and an initial collection of unordered elements. /// The heap property is established in linear time. /// /// The comparer to determine the priority of the elements. /// /// The initial collection of elements to heapify. /// Note: The collection is modified to establish the heap property. /// public StablePriorityQueue(TComparer comparer, List items) { this.Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer)); this.heap = items ?? throw new ArgumentNullException(nameof(items)); this.Heapify(this.heap); } /// /// Gets the number of elements in the priority queue. /// public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => this.heap.Count; } /// /// Gets the comparer used to determine the priority of the elements. /// public TComparer Comparer { get; } /// /// Adds an item to the priority queue, maintaining the heap property. /// /// The item to add. public void Enqueue(T item) { List data = this.heap; data.Add(item); this.Up((uint)data.Count - 1, data); } /// /// Removes and returns the item with the highest priority (lowest value) from the priority queue. /// /// The item with the highest priority. /// Thrown if the priority queue is empty. public T Dequeue() { List data = this.heap; int count = data.Count; ThrowIfEmpty(count); ref T dRef = ref MemoryMarshal.GetReference(CollectionsMarshal.AsSpan(data)); int maxIndex = count - 1; T top = Unsafe.Add(ref dRef, 0u); T bottom = Unsafe.Add(ref dRef, (uint)maxIndex); data.RemoveAt(maxIndex); if (--count > 0) { Unsafe.Add(ref dRef, 0u) = bottom; this.Down(0u, data); } return top; } /// /// Returns the item with the highest priority (lowest value) without removing it. /// /// The item with the highest priority. /// Thrown if the priority queue is empty. public T Peek() { ThrowIfEmpty(this.Count); return this.heap[0]; } /// /// Restores the min-heap property by moving the item at the specified index upward /// through the heap until it is in the correct position. This is called after insertion. /// /// The index of the newly added item to sift upward. /// The heap to operate on. private void Up(uint index, List heap) { ref T dRef = ref MemoryMarshal.GetReference(CollectionsMarshal.AsSpan(heap)); T item = Unsafe.Add(ref dRef, index); TComparer comparer = this.Comparer; while (index > 0) { uint parent = (index - 1u) >> Log2Arity; T current = Unsafe.Add(ref dRef, parent); if (comparer.Compare(item, current) >= 0) { break; } Unsafe.Add(ref dRef, index) = current; index = parent; } Unsafe.Add(ref dRef, index) = item; } /// /// Restores the min-heap property by moving the item at the specified index downward /// through the heap until it is in the correct position. This is called after removal of the root. /// /// The index of the item to sift downward (typically the root). /// The heap to operate on. private void Down(uint index, List heap) { Span data = CollectionsMarshal.AsSpan(heap); ref T dRef = ref MemoryMarshal.GetReference(data); uint length = (uint)data.Length; T item = Unsafe.Add(ref dRef, index); TComparer comparer = this.Comparer; while ((index << Log2Arity) + 1u < length) { uint firstChild = (index << Log2Arity) + 1u; uint bestChild = firstChild; uint maxChild = Math.Min(firstChild + (1u << Log2Arity), length); for (uint i = firstChild + 1u; i < maxChild; i++) { if (comparer.Compare(Unsafe.Add(ref dRef, i), Unsafe.Add(ref dRef, bestChild)) < 0) { bestChild = i; } } if (comparer.Compare(Unsafe.Add(ref dRef, bestChild), item) >= 0) { break; } Unsafe.Add(ref dRef, index) = Unsafe.Add(ref dRef, bestChild); index = bestChild; } Unsafe.Add(ref dRef, index) = item; } /// /// Heapifies the given list to establish the min-heap property. /// /// The list to heapify. private void Heapify(List heap) { int count = heap.Count; if (count <= 1) { return; } int lastParent = (count - 2) >> Log2Arity; for (int i = lastParent; i >= 0; i--) { this.Down((uint)i, heap); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowIfEmpty(int count) { if (count == 0) { throw new InvalidOperationException("Queue is empty."); } } } }