@jawis/shared-algs
Version:
Data structures for building concurrent programs.
352 lines (351 loc) • 11.8 kB
JavaScript
"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;