snowflakify
Version:
The most complete Snowflake ID generator in TypeScript
105 lines • 4.31 kB
JavaScript
"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