UNPKG

@jawis/shared-algs

Version:

Data structures for building concurrent programs.

352 lines (351 loc) 11.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SharedDoublyLinkedList = void 0; const _jab_1 = require("^jab"); const internal_1 = require("./internal"); const NODE_COUNT_OFFSET = 0; //number of allocated chunks. const HEAD_REF_OFFSET = NODE_COUNT_OFFSET + 1; const META_DATA_LENGTH = HEAD_REF_OFFSET + 1; const META_DATA_BYTES = Uint32Array.BYTES_PER_ELEMENT * META_DATA_LENGTH; /** * * invariant * - The node is allocated iff the node is in the list. * * impl * - Data is on the same page as the header data * * header/meta data * 4 bytes count * 4 bytes headRef * * page layout * x bytes data * 4 bytes ref to prev node * 4 bytes ref to next node * */ class SharedDoublyLinkedList { /** * */ constructor(deps) { this.deps = deps; this.DATA_BYTE_OFFSET = 0; //user's data /** * */ this.pack = () => ({ dataSize: this.deps.dataSize, ref: this.deps.ref, }); /** * */ this.getHead = () => { if (this.headRef === internal_1.UINT32_UNDEFINED) { return; } return this.get(this.headRef); }; /** * */ this.get = (ref) => { const array = this.heap.get(ref, Uint32Array); return this.makeNode({ ref, array }); }; /** * */ this.prevRef = (node) => { const alloc = this.mapToInternal(node); const ref = alloc.array[this.PREVIOUS_OFFSET]; if (ref === internal_1.UINT32_UNDEFINED) { return; } else { return ref; } }; /** * */ this.nextRef = (node) => { const alloc = this.mapToInternal(node); const ref = alloc.array[this.NEXT_OFFSET]; if (ref === internal_1.UINT32_UNDEFINED) { return; } else { return ref; } }; /** * */ this.appendNew = () => { if (this.headRef !== internal_1.UINT32_UNDEFINED) { throw new Error("not impl"); } //only works when empty. const alloc = this._allocate(); this.headRef = alloc.ref; //book keeping this.decl.array[NODE_COUNT_OFFSET]++; // this.deps.verifyAfterOperations && this._invariant(); return this.makeNode(alloc); }; /** * Creates a new node after the given node. */ this.insertNew = (_pos) => { const pos = this.mapToInternal(_pos); if (pos.array[this.NEXT_OFFSET] !== internal_1.UINT32_UNDEFINED) { throw new Error("not impl"); } // only works when appending to the end. const next = this._allocate(); pos.array[this.NEXT_OFFSET] = next.ref; next.array[this.PREVIOUS_OFFSET] = pos.ref; //book keeping this.decl.array[NODE_COUNT_OFFSET]++; this.deps.verifyAfterOperations && this._invariant(); return this.makeNode(next); }; /** * */ this.move = (_node, _pos, before = true) => { const pos = this.mapToInternal(_pos); const node = this.mapToInternal(_node); this._delete_from_list(node); this._insert(pos, node, before); this.deps.verifyAfterOperations && this._invariant(); }; /** * * - Also deallocates the node. * - It's not allowed to delete twice. * * impl * - Make user's object as "deleted", so we can emit warning if the user tries to do further * operations the the object. * */ this.delete = (_node) => { const node = this.mapToInternal(_node); //remove from list this._delete_from_list(node); //remove from heap this.heap.deallocate(node.ref); //ensure the node object user holds is unusable. this.nodeMap.set(_node, "deleted"); //book keeping this.decl.array[NODE_COUNT_OFFSET]--; this.deps.verifyAfterOperations && this._invariant(); }; /** * - Set root node correctly if needed. */ this._insert = (pos, node, before = true) => { const nodeArray = node.array; const posArray = pos.array; if (before) { const prevRef = posArray[this.PREVIOUS_OFFSET]; //forward posArray[this.PREVIOUS_OFFSET] = node.ref; nodeArray[this.NEXT_OFFSET] = pos.ref; //backwards nodeArray[this.PREVIOUS_OFFSET] = prevRef; if (prevRef === internal_1.UINT32_UNDEFINED) { //it's before root this.headRef = node.ref; } else { const prev = this.heap.get(prevRef, Uint32Array); prev[this.NEXT_OFFSET] = node.ref; } } else { const nextRef = posArray[this.NEXT_OFFSET]; //backwards posArray[this.NEXT_OFFSET] = node.ref; nodeArray[this.PREVIOUS_OFFSET] = pos.ref; //forward nodeArray[this.NEXT_OFFSET] = nextRef; //might be undefined if (nextRef !== internal_1.UINT32_UNDEFINED) { const next = this.heap.get(nextRef, Uint32Array); next[this.PREVIOUS_OFFSET] = node.ref; } } this.deps.verifyAfterOperations && this._invariant(); }; /** * Remove the node from the list without deallocating the node. * * impl * - Sets root node correctly if needed. */ this._delete_from_list = (node) => { const prevRef = node.array[this.PREVIOUS_OFFSET]; const nextRef = node.array[this.NEXT_OFFSET]; //update backward link if (prevRef === internal_1.UINT32_UNDEFINED) { //root page if (nextRef === internal_1.UINT32_UNDEFINED) { //it's the only page, so the list is now empty this.headRef = internal_1.UINT32_UNDEFINED; } else { // it has a next page, which must become the new root. this.headRef = nextRef; } } else { //non-root page const prev = this.heap.get(prevRef, Uint32Array); (0, _jab_1.assert)(prev[this.NEXT_OFFSET] === node.ref); //update links on previous page prev[this.NEXT_OFFSET] = nextRef; //might be UINT32_UNDEFINED } //update forward link if (nextRef !== internal_1.UINT32_UNDEFINED) { // it has a next page const next = this.heap.get(nextRef, Uint32Array); (0, _jab_1.assert)(next[this.PREVIOUS_OFFSET] === node.ref); next[this.PREVIOUS_OFFSET] = prevRef; //might be UINT32_UNDEFINED } }; /** * Allocate a new node, but not attached to the list, yet. */ this._allocate = () => { const alloc = this.heap.allocate(Uint32Array); alloc.array[this.PREVIOUS_OFFSET] = internal_1.UINT32_UNDEFINED; alloc.array[this.NEXT_OFFSET] = internal_1.UINT32_UNDEFINED; return alloc; }; /** * */ this.makeNode = (alloc) => { const data = (0, _jab_1.makeTypedArray)(alloc.array, Uint8Array, this.DATA_BYTE_OFFSET, this.deps.dataSize); const node = this.deps.makeNode({ ref: alloc.ref, data, }); this.nodeMap.set(node, alloc); return node; }; /** * */ this.mapToInternal = (node) => { const alloc = this.nodeMap.get(node); if (!alloc) { throw (0, _jab_1.err)("Unknown node", alloc); } if (alloc === "deleted") { throw (0, _jab_1.err)("Node has been deleted.", node); } return alloc; }; /** * todo: it's this implicitly calls: `this.makeNode` */ this._invariant = () => { let actualCount = 0; let prev; for (const node of this) { actualCount++; //prev link if (prev !== undefined) { (0, _jab_1.assert)(this.prevRef(node) === prev.ref); } prev = node; } (0, _jab_1.assertEq)(this.count, actualCount); }; /** * */ this.dispose = () => { (0, _jab_1.assert)(this.count === 0, "Can only dispose when empty."); this.deps.heapFactory.get(META_DATA_BYTES).deallocate(this.decl.ref); }; /** * * - Excludes ref, because it's not important */ this.toString = () => { let res = ""; for (const node of this) { const clone = Object.assign({}, node); delete clone.ref; res += (0, _jab_1.tos)(clone) + "\n"; // prettier-ignore } return "SharedDoublyLinkedList (" + this.count + ")\n" + res; }; (0, _jab_1.assert)(this.deps.dataSize >= 0, undefined, "Data size must be positive: " + this.deps.dataSize); // prettier-ignore this.pageSize = SharedDoublyLinkedList.HEADER_BYTES + this.deps.dataSize; this.heap = this.deps.heapFactory.get(this.pageSize); this.nodeMap = new WeakMap(); //links at the end of the page const pageLength = this.pageSize / Uint32Array.BYTES_PER_ELEMENT; this.NEXT_OFFSET = pageLength - 1; this.PREVIOUS_OFFSET = pageLength - 2; //state if (deps.ref !== undefined) { this.decl = { ref: deps.ref, array: this.deps.heapFactory.get(META_DATA_BYTES).get(deps.ref, Uint32Array), // prettier-ignore }; } else { this.decl = this.deps.heapFactory.get(META_DATA_BYTES).allocate(Uint32Array); // prettier-ignore //initialize this.decl.array[NODE_COUNT_OFFSET] = 0; this.decl.array[HEAD_REF_OFFSET] = internal_1.UINT32_UNDEFINED; } } /** * */ get count() { return this.decl.array[NODE_COUNT_OFFSET]; } /** * */ get headRef() { return this.decl.array[HEAD_REF_OFFSET]; } /** * */ set headRef(ref) { this.decl.array[HEAD_REF_OFFSET] = ref; } /** * */ *[Symbol.iterator]() { let node = this.getHead(); while (node !== undefined) { yield node; //next iteration const next = this.nextRef(node); if (next === undefined) { break; } node = this.get(next); } } } exports.SharedDoublyLinkedList = SharedDoublyLinkedList; SharedDoublyLinkedList.HEADER_BYTES = 8; //next and prev uint32 /** * */ SharedDoublyLinkedList.getDataAvailable = (pageSize) => pageSize - SharedDoublyLinkedList.HEADER_BYTES;