snowflakify
Version:
The most complete Snowflake ID generator in TypeScript
141 lines (140 loc) • 4.2 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