UNPKG

deadem

Version:

JavaScript (Node.js & Browsers) parser for Deadlock (Valve Source 2 Engine) demo/replay files

223 lines (176 loc) 5.53 kB
import { Buffer } from 'node:buffer'; import Assert from '#core/Assert.js'; import BinaryHeap from '#core/BinaryHeap.js'; import FieldPathOperation from '#data/enums/FieldPathOperation.js'; import HuffmanTreePriority from './HuffmanTreePriority.js'; const HUFFMAN_TREE_DEPTH = 17; const MAX_CODE = (1 << HUFFMAN_TREE_DEPTH) - 1; const OPERATIONS = FieldPathOperation.getAll(); class HuffmanTree { /** * @public * @constructor * @param {HuffmanTreePriority} priority * @param {FieldPathOperation=} operation * @param {HuffmanTree=} leftChild * @param {HuffmanTree=} rightChild */ constructor(priority, operation, leftChild, rightChild) { Assert.isTrue(priority instanceof HuffmanTreePriority); Assert.isTrue(!operation || operation instanceof FieldPathOperation); Assert.isTrue(!leftChild || leftChild instanceof HuffmanTree); Assert.isTrue(!rightChild || rightChild instanceof HuffmanTree); this._priority = priority; this._operation = operation || null; this._leftChild = leftChild || null; this._rightChild = rightChild || null; } /** * The depth of the HuffmanTree. * * @static * @returns {number} */ static get DEPTH() { return HUFFMAN_TREE_DEPTH; } /** * @static * @param {number} code - The code. * @returns {{bitsUsed: number, operation: FieldPathOperation}} */ static getOperationByCode(code) { Assert.isTrue(code < MAX_CODE); const bitsUsed = PRECALCULATED_TABLE.bits[code]; const operationIndex = PRECALCULATED_TABLE.operations[code]; return { bitsUsed, operation: OPERATIONS[operationIndex] }; } /** * @public * @returns {HuffmanTreePriority} */ get priority() { return this._priority; } /** * @public * @returns {FieldPathOperation|null} */ get operation() { return this._operation; } /** * @public * @returns {HuffmanTree|null} */ get leftChild() { return this._leftChild; } /** * @public * @returns {HuffmanTree|null} */ get rightChild() { return this._rightChild; } /** * @public * @returns {boolean} */ getIsLeaf() { return this._leftChild === null && this._rightChild === null; } } const TREE = { codeTable: new Map(), depth: 0, root: build() }; let PRECALCULATED_TABLE; (() => { dfs(TREE.root); if (TREE.depth !== HUFFMAN_TREE_DEPTH) { throw new Error(`Unexpected HuffmanTree depth [ ${TREE.depth} ]`); } PRECALCULATED_TABLE = { bits: Buffer.allocUnsafe(MAX_CODE), operations: Buffer.allocUnsafe(MAX_CODE) }; for (let code = 0; code < MAX_CODE; code++) { const { bits, node } = getNodeByCode(code); PRECALCULATED_TABLE.bits[code] = bits; PRECALCULATED_TABLE.operations[code] = node.operation.sequence; } })(); /** * Builds a {@link HuffmanTree} * * @returns {HuffmanTree} - The root. */ function build() { const weigh = operation => Math.max(operation.weight, 1); const heap = new BinaryHeap(huffmanTree => huffmanTree.priority, (priorityA, priorityB) => priorityA.compare(priorityB)); OPERATIONS.forEach((operation) => { const weight = weigh(operation); const huffmanTreePriority = new HuffmanTreePriority(weight, operation.sequence); const huffmanTree = new HuffmanTree(huffmanTreePriority, operation); heap.insert(huffmanTree); }); let sequence = heap.length; while (heap.length > 1) { const childA = heap.extract(); const childB = heap.extract(); const huffmanTreePriority = new HuffmanTreePriority(childA.priority.weight + childB.priority.weight, sequence); const huffmanTree = new HuffmanTree(huffmanTreePriority, null, childA, childB); sequence += 1; heap.insert(huffmanTree); } return heap.extract(); } /** * Traverses a {@link HuffmanTree} according to the binary path, * mapping the path to the corresponding node in the TREE.codeTable. * Updates the maximum depth of the tree. * * @param {HuffmanTree} node * @param {String} path * @param {number} depth */ function dfs(node, path = '', depth = 0) { if (TREE.depth < depth) { TREE.depth = depth; } if (node.leftChild) { dfs(node.leftChild, path + '0', depth + 1); } if (node.rightChild) { dfs(node.rightChild, path + '1', depth + 1); } if (node.getIsLeaf()) { const code = parseInt(path, 2); TREE.codeTable.set(code, node); } } /** * Given a code, searches for the shortest prefix that matches a key in the TREE.codeTable. * * @param {number} code - A decimal number whose binary form (or form of its prefix) determines the traversal path. * @returns {{bits: number, node: HuffmanTree}} */ function getNodeByCode(code) { let bits = 0; let prefix = 0; for (let i = 0; i < TREE.depth; i++) { const iBit = (code >> i) & 1; prefix = prefix << 1; if (iBit) { prefix |= iBit; } bits += 1; if (TREE.codeTable.has(prefix)) { return { bits, node: TREE.codeTable.get(prefix) }; } } throw new Error(`Unable to find a node for code [ ${code} ]. This should never happen. Verify the depth of the tree`); } export default HuffmanTree;