diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 04448696c4c81f..839ce2c7b0408f 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -501,6 +501,7 @@ deno_core::extension!(deno_node, "internal/normalize_encoding.ts", "internal/options.ts", "internal/primordials.mjs", + "internal/priority_queue.ts", "internal/process/per_thread.mjs", "internal/process/report.ts", "internal/process/warning.ts", diff --git a/ext/node/polyfills/01_require.js b/ext/node/polyfills/01_require.js index dfb02782bf58cf..97764c1298c0e9 100644 --- a/ext/node/polyfills/01_require.js +++ b/ext/node/polyfills/01_require.js @@ -132,6 +132,7 @@ import internalFsUtils from "ext:deno_node/internal/fs/utils.mjs"; import internalHttp from "ext:deno_node/internal/http.ts"; import internalHttp2Core from "ext:deno_node/internal/http2/core.ts"; import internalHttp2Util from "ext:deno_node/internal/http2/util.ts"; +import internalPriorityQueue from "ext:deno_node/internal/priority_queue.ts"; import internalReadlineUtils from "ext:deno_node/internal/readline/utils.mjs"; import internalStreamsAddAbortSignal from "ext:deno_node/internal/streams/add-abort-signal.js"; import internalStreamsLazyTransform from "ext:deno_node/internal/streams/lazy_transform.js"; @@ -239,6 +240,7 @@ function setupBuiltinModules() { "internal/http": internalHttp, "internal/http2/core": internalHttp2Core, "internal/http2/util": internalHttp2Util, + "internal/priority_queue": internalPriorityQueue, "internal/readline/utils": internalReadlineUtils, "internal/streams/add-abort-signal": internalStreamsAddAbortSignal, "internal/streams/lazy_transform": internalStreamsLazyTransform, diff --git a/ext/node/polyfills/internal/priority_queue.ts b/ext/node/polyfills/internal/priority_queue.ts new file mode 100644 index 00000000000000..e4a1d89c2d613c --- /dev/null +++ b/ext/node/polyfills/internal/priority_queue.ts @@ -0,0 +1,136 @@ +// Copyright 2018-2026 the Deno authors. MIT license. +// Copyright Joyent, Inc. and other Node contributors. + +// The PriorityQueue is a basic implementation of a binary heap that accepts +// a custom sorting function via its constructor. This function is passed +// the two nodes to compare, similar to the native Array#sort. Crucially +// this enables priority queues that are based on a comparison of more than +// just a single criteria. + +// deno-lint-ignore-file no-explicit-any + +type Comparator = (a: T, b: T) => number; +type SetPosition = (node: T, pos: number) => void; + +export class PriorityQueue { + #compare: Comparator = (a: any, b: any) => a - b; + #heap: (T | undefined)[] = [undefined, undefined]; + #setPosition?: SetPosition; + #size = 0; + + constructor(comparator?: Comparator, setPosition?: SetPosition) { + if (comparator !== undefined) { + this.#compare = comparator; + } + if (setPosition !== undefined) { + this.#setPosition = setPosition; + } + } + + insert(value: T): void { + const heap = this.#heap; + const pos = ++this.#size; + heap[pos] = value; + + this.percolateUp(pos); + } + + peek(): T | undefined { + return this.#heap[1]; + } + + peekBottom(): T | undefined { + return this.#heap[this.#size]; + } + + percolateDown(pos: number): void { + const compare = this.#compare; + const setPosition = this.#setPosition; + const hasSetPosition = setPosition !== undefined; + const heap = this.#heap; + const size = this.#size; + const hsize = size >> 1; + const item = heap[pos] as T; + + while (pos <= hsize) { + let child = pos << 1; + const nextChild = child + 1; + let childItem = heap[child] as T; + + if (nextChild <= size && compare(heap[nextChild] as T, childItem) < 0) { + child = nextChild; + childItem = heap[nextChild] as T; + } + + if (compare(item, childItem) <= 0) break; + + if (hasSetPosition) { + setPosition(childItem, pos); + } + + heap[pos] = childItem; + pos = child; + } + + heap[pos] = item; + if (hasSetPosition) { + setPosition(item, pos); + } + } + + percolateUp(pos: number): void { + const heap = this.#heap; + const compare = this.#compare; + const setPosition = this.#setPosition; + const hasSetPosition = setPosition !== undefined; + const item = heap[pos] as T; + + while (pos > 1) { + const parent = pos >> 1; + const parentItem = heap[parent] as T; + if (compare(parentItem, item) <= 0) { + break; + } + heap[pos] = parentItem; + if (hasSetPosition) { + setPosition(parentItem, pos); + } + pos = parent; + } + + heap[pos] = item; + if (hasSetPosition) { + setPosition(item, pos); + } + } + + removeAt(pos: number): void { + const heap = this.#heap; + let size = this.#size; + heap[pos] = heap[size]; + heap[size] = undefined; + size = --this.#size; + + if (size > 0 && pos <= size) { + if (pos > 1 && this.#compare(heap[pos >> 1] as T, heap[pos] as T) > 0) { + this.percolateUp(pos); + } else { + this.percolateDown(pos); + } + } + } + + shift(): T | undefined { + const heap = this.#heap; + const value = heap[1]; + if (value === undefined) { + return undefined; + } + + this.removeAt(1); + + return value; + } +} + +export default PriorityQueue; diff --git a/tests/node_compat/config.jsonc b/tests/node_compat/config.jsonc index be8d242b787157..60156b157032f0 100644 --- a/tests/node_compat/config.jsonc +++ b/tests/node_compat/config.jsonc @@ -2503,6 +2503,7 @@ "ignore": true, "reason": "Tests Node.js internal modules (require('internal/...')) which are not exposed in Deno" }, + "parallel/test-priority-queue.js": {}, "parallel/test-process-abort.js": {}, "parallel/test-process-argv-0.js": {}, "parallel/test-process-available-memory.js": {},