UNPKG

snowflakify

Version:

The most complete Snowflake ID generator in TypeScript

105 lines 4.31 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const Util_js_1 = require("../Utils/Util.js"); const CircularBufferRefiller_js_1 = __importDefault(require("./CircularBufferRefiller.js")); class CircularBuffer { constructor(bufferSize = 1 << 23, generatorOptions, refillTheshold = 0.5, workerCount = 2) { this.bufferSize = bufferSize; if (typeof bufferSize !== 'number') throw new TypeError('[BUFFER_SIZE_INVALID_TYPE]: Buffer size must be a number.'); if (!(0, Util_js_1.isPowerOfTwo)(bufferSize) || bufferSize < 1) throw new Error('[BUFFER_SIZE_INVALID]: Buffer size must be a positive number and a power of two.'); if (typeof refillTheshold !== 'number') throw new TypeError('[REFILL_THRESHOLD_INVALID_TYPE]: Refill threshold must be a number.'); if (refillTheshold < 0 || refillTheshold > 1) throw new Error('[REFILL_THRESHOLD_INVALID]: Refill threshold must be within 0 and 1'); this.indexBitMask = this.bufferSize - 1; this.thresholdIndex = Math.floor((this.bufferSize - 1) * refillTheshold); this.tailTA = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT)); this.headTA = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT)); this.bufferTA = new BigInt64Array(new SharedArrayBuffer(BigInt64Array.BYTES_PER_ELEMENT * this.bufferSize)); this.tail = -1; this.head = -1; this.semaphoreTA = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT)); Atomics.store(this.semaphoreTA, 0, 1); this.bufferRefiller = new CircularBufferRefiller_js_1.default(this, generatorOptions, workerCount); this.bufferRefiller.refill(); } get size() { return this.bufferSize; } get tail() { return Atomics.load(this.tailTA, 0); } set tail(value) { Atomics.store(this.tailTA, 0, value); } get head() { return Atomics.load(this.headTA, 0); } set head(value) { Atomics.store(this.headTA, 0, value); } get buffer() { return this.bufferTA; } push(value) { this.acquire(); const currTail = this.tail + 1; if (CircularBuffer.distance(currTail, this.head) >= this.bufferSize) { this.release(); throw new Error('[BUFFER_FULL]: Buffer is full, cannot push.'); } this.incrementTail(); this.release(); const nextTailIndex = this.nextIndex(currTail); // Atomics.store(this.buffer, nextTailIndex, value); this.buffer[nextTailIndex] = value; } pop() { const currHead = this.head; const nextHead = Atomics.store(this.headTA, 0, currHead === this.tail ? currHead : currHead + 1); if (nextHead < currHead) throw new Error('[UPEXPECTED_HEAD_INDEX]: Unexpected head index.'); if (nextHead === currHead) throw new Error('[BUFFER_EMPTY]: Buffer is empty, cannot pop.'); if (this.tail - nextHead <= this.thresholdIndex && !this.bufferRefiller.isRefilling()) { this.bufferRefiller.refill(); } const nextHeadIndex = this.nextIndex(nextHead); return Atomics.load(this.buffer, nextHeadIndex); } isFull() { return CircularBuffer.distance(this.tail, this.head) === this.bufferSize - 1; } isEmpty() { return this.head === this.tail; } incrementTail() { return Atomics.add(this.tailTA, 0, 1); } // @ts-ignore incrementHead() { return Atomics.add(this.headTA, 0, 1); } static distance(currTail, currHead) { return (currTail === -1 ? 0 : currTail) - (currHead === -1 ? 0 : currHead); } nextIndex(index) { return index & this.indexBitMask; } acquire() { Atomics.wait(this.semaphoreTA, 0, 0); Atomics.sub(this.semaphoreTA, 0, 1); } release() { Atomics.add(this.semaphoreTA, 0, 1); Atomics.notify(this.semaphoreTA, 0, 1); } } exports.default = CircularBuffer; //# sourceMappingURL=CircularBuffer.js.map