perf: optimize task(s) stealing
[poolifier.git] / src / priority-queue.ts
1 // Copyright Jerome Benoit. 2024. All Rights Reserved.
2
3 /**
4 * Priority queue node.
5 *
6 * @typeParam T - Type of priority queue node data.
7 * @internal
8 */
9 export interface PriorityQueueNode<T> {
10 data: T
11 priority: number
12 }
13
14 /**
15 * Priority queue.
16 *
17 * @typeParam T - Type of priority queue data.
18 * @internal
19 */
20 export class PriorityQueue<T> {
21 private nodeArray!: Array<PriorityQueueNode<T>>
22 /** Prioritized bucket size. */
23 private readonly k: number
24 /** The size of the priority queue. */
25 public size!: number
26 /** The maximum size of the priority queue. */
27 public maxSize!: number
28
29 /**
30 * The number of filled prioritized buckets.
31 */
32 public get buckets (): number {
33 return this.k === Infinity ? 1 : Math.trunc(this.nodeArray.length / this.k)
34 }
35
36 /**
37 * Constructs a priority queue.
38 *
39 * @param k - Prioritized bucket size. @defaultValue Infinity
40 */
41 public constructor (k = Infinity) {
42 if (k !== Infinity && !Number.isSafeInteger(k)) {
43 throw new TypeError('k must be an integer')
44 }
45 if (k < 1) {
46 throw new RangeError('k must be greater than or equal to 1')
47 }
48 this.k = k
49 this.clear()
50 }
51
52 /**
53 * Enqueue data into the priority queue.
54 *
55 * @param data - Data to enqueue.
56 * @param priority - Priority of the data. Lower values have higher priority.
57 * @returns The new size of the priority queue.
58 */
59 public enqueue (data: T, priority?: number): number {
60 priority = priority ?? 0
61 const startIndex = this.k === Infinity ? 0 : this.buckets * this.k
62 let inserted = false
63 for (let index = startIndex; index < this.nodeArray.length; index++) {
64 if (this.nodeArray[index].priority > priority) {
65 this.nodeArray.splice(index, 0, { data, priority })
66 inserted = true
67 break
68 }
69 }
70 if (!inserted) {
71 this.nodeArray.push({ data, priority })
72 }
73 return this.incrementSize()
74 }
75
76 /**
77 * Dequeue data from the priority queue.
78 *
79 * @param bucket - The prioritized bucket to dequeue from. @defaultValue 0
80 * @returns The dequeued data or `undefined` if the priority queue is empty.
81 */
82 public dequeue (bucket = 0): T | undefined {
83 if (this.k !== Infinity && bucket > 0) {
84 while (bucket > 0) {
85 const index = bucket * this.k
86 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
87 if (this.nodeArray[index] != null) {
88 --this.size
89 return this.nodeArray.splice(index, 1)[0].data
90 }
91 --bucket
92 }
93 }
94 if (this.size > 0) {
95 --this.size
96 }
97 return this.nodeArray.shift()?.data
98 }
99
100 /**
101 * Peeks at the first data.
102 * @returns The first data or `undefined` if the priority queue is empty.
103 */
104 public peekFirst (): T | undefined {
105 return this.nodeArray[0]?.data
106 }
107
108 /**
109 * Peeks at the last data.
110 * @returns The last data or `undefined` if the priority queue is empty.
111 */
112 public peekLast (): T | undefined {
113 return this.nodeArray[this.nodeArray.length - 1]?.data
114 }
115
116 /**
117 * Clears the priority queue.
118 */
119 public clear (): void {
120 this.nodeArray = []
121 this.size = 0
122 this.maxSize = 0
123 }
124
125 /**
126 * Returns an iterator for the priority queue.
127 *
128 * @returns An iterator for the priority queue.
129 * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
130 */
131 [Symbol.iterator] (): Iterator<T> {
132 let i = 0
133 return {
134 next: () => {
135 if (i >= this.nodeArray.length) {
136 return {
137 value: undefined,
138 done: true
139 }
140 }
141 const value = this.nodeArray[i].data
142 i++
143 return {
144 value,
145 done: false
146 }
147 }
148 }
149 }
150
151 /**
152 * Increments the size of the priority queue.
153 *
154 * @returns The new size of the priority queue.
155 */
156 private incrementSize (): number {
157 ++this.size
158 if (this.size > this.maxSize) {
159 this.maxSize = this.size
160 }
161 return this.size
162 }
163 }