UNPKG

snowflakify

Version:

The most complete Snowflake ID generator in TypeScript

141 lines (140 loc) 4.2 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