fastds
Version:
Fast, Zero-Dependency, TypeScript-based data structures for high-performance applications.
526 lines (524 loc) • 16.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "RingBuffer", {
enumerable: true,
get: function() {
return RingBuffer;
}
});
const DEFAULT_CAPACITY = 8;
class RingBuffer {
#buffer;
#head = 0;
#tail = 0;
#mask;
#length = 0;
static from(values) {
const n = values.length;
const ring = new RingBuffer(n + 1);
for(let i = 0; i < n; i++){
ring.#buffer[i] = values[i];
}
ring.#head = 0;
ring.#tail = n;
ring.#length = n;
return ring;
}
[Symbol.toStringTag] = 'RingBuffer';
constructor(capacity = DEFAULT_CAPACITY){
const size = Math.max(1 << 32 - Math.clz32(capacity - 1), DEFAULT_CAPACITY);
this.#buffer = new Array(size);
this.#mask = size - 1;
}
get capacity() {
return this.#mask + 1;
}
get length() {
return this.#length;
}
isEmpty() {
return this.#tail === this.#head;
}
isWrapped() {
return this.#head > this.#tail;
}
getHeadOffset(index) {
return this.#head + index & this.#mask;
}
getTailOffset(index) {
return this.#tail + index & this.#mask;
}
resize(capacity) {
const buffer = this.#buffer;
const bufferLength = buffer.length;
if (bufferLength > capacity && bufferLength >> 1 < capacity) {
return false;
}
const size = Math.max(1 << 32 - Math.clz32(capacity - 1), DEFAULT_CAPACITY);
const length = this.#length;
if (size < length) {
return false;
}
const head = this.#head;
const prevMask = this.#mask;
const prevTail = this.#tail;
const nextMask = size - 1;
const nextTail = head + length & nextMask;
const wrapIndex = size > bufferLength ? prevTail < head ? bufferLength - head : length : nextTail < head ? size - head : length;
for(let i = length - 1; i >= wrapIndex; i--){
const read = head + i & prevMask;
const write = head + i & nextMask;
buffer[write] = buffer[read];
if (read !== write) {
buffer[read] = undefined;
}
}
this.#buffer.length = size;
this.#tail = nextTail;
this.#mask = nextMask;
return true;
}
grow(capacity = this.#mask + 1) {
const buffer = this.#buffer;
const bufferLength = buffer.length;
if (bufferLength >= capacity + 1) {
return;
}
const size = 1 << 32 - Math.clz32(capacity);
this.#buffer.length = size;
const oldTail = this.#tail;
if (oldTail < this.#head) {
for(let i = 0; i < oldTail; i++){
buffer[bufferLength + i] = buffer[i];
}
this.#tail = bufferLength + oldTail;
}
this.#mask = size - 1;
}
allocate(index, count) {
const prevLength = this.#length;
if (index < 0 || count <= 0) {
return false;
}
index = Math.min(index, prevLength);
const buffer = this.#buffer;
const head = this.#head;
const tail = this.#tail;
const prevMask = this.#mask;
const nextLength = count + prevLength;
const wrapIndex = buffer.length - head;
const isWrapped = head > tail;
if (nextLength >= buffer.length) {
const size = 1 << 32 - Math.clz32(nextLength);
buffer.length = size;
this.#mask = size - 1;
}
const mask = this.#mask;
const isResized = prevMask !== mask;
const leftMoveCount = index;
const rightMoveCount = prevLength - index;
if (leftMoveCount < rightMoveCount) {
if (isResized && isWrapped) {
for(let i = 0; i < tail; i++){
const read = i;
const write = head + wrapIndex + i & mask;
buffer[write] = buffer[read];
buffer[read] = undefined;
}
}
const writeBase = head - count & mask;
for(let i = 0; i < leftMoveCount; i++){
const read = head + i & mask;
const write = writeBase + i & mask;
buffer[write] = buffer[read];
buffer[read] = undefined;
}
this.#head = writeBase;
} else {
if (isResized && isWrapped) {
for(let i = 0; i < tail; i++){
const read = i;
const write = head + wrapIndex + i & mask;
buffer[write] = buffer[read];
buffer[read] = undefined;
}
}
for(let i = rightMoveCount - 1; i >= 0; i--){
const read = head + index + i & mask;
const write = head + index + count + i & mask;
buffer[write] = buffer[read];
buffer[read] = undefined;
}
this.#tail = head + nextLength & mask;
}
this.#length = nextLength;
return true;
}
deallocate(index, count) {
const prevLength = this.#length;
if (index < 0 || index >= prevLength || count <= 0) {
return false;
}
const actualCount = Math.min(count, prevLength - index);
const nextLength = prevLength - actualCount;
const buffer = this.#buffer;
const head = this.#head;
const tail = this.#tail;
const mask = this.#mask;
if (index === 0) {
for(let i = 0; i < actualCount; i++){
buffer[head + i & mask] = undefined;
}
this.#head = head + actualCount & mask;
this.#length = nextLength;
return true;
}
if (index + actualCount === prevLength) {
for(let i = 0; i < actualCount; i++){
buffer[tail - i - 1 & mask] = undefined;
}
this.#tail = tail - actualCount & mask;
this.#length = nextLength;
return true;
}
const leftMoveCount = index;
const rightMoveCount = prevLength - index - actualCount;
if (leftMoveCount < rightMoveCount) {
for(let i = leftMoveCount - 1; i >= 0; i--){
const read = head + i & mask;
const write = head + i + actualCount & mask;
buffer[write] = buffer[read];
}
for(let i = 0; i < actualCount; i++){
buffer[head + i & mask] = undefined;
}
this.#head = head + actualCount & mask;
} else {
for(let i = 0; i < rightMoveCount; i++){
const read = head + index + actualCount + i & mask;
const write = head + index + i & mask;
buffer[write] = buffer[read];
}
this.#tail = tail - actualCount & mask;
for(let i = 0; i < actualCount; i++){
buffer[tail + i & mask] = undefined;
}
}
this.#length = nextLength;
return true;
}
indexOf(value, index = 0) {
const length = this.#length;
const buffer = this.#buffer;
const head = this.#head;
const tail = this.#tail;
if (head < tail) {
const offset = head + index;
const result = buffer.indexOf(value, offset);
if (result !== -1 && result < tail) {
return result - head;
}
return -1;
}
const capacity = buffer.length;
const firstSegmentLength = capacity - head;
if (index < firstSegmentLength) {
const result = buffer.indexOf(value, head + index);
if (result !== -1) {
return result - head;
}
index = firstSegmentLength;
}
if (index < length) {
const result = buffer.indexOf(value, 0);
if (result !== -1 && result < tail) {
return firstSegmentLength + result;
}
}
return -1;
}
unwrap() {
if (this.isEmpty()) {
return false;
}
const length = this.#length;
const buffer = this.#buffer;
const prevHead = this.#head;
const prevTail = this.#tail;
if (prevHead > prevTail) {
const bufferLength = buffer.length;
buffer.length = bufferLength + prevTail;
for(let i = 0; i < prevTail; i++){
buffer[bufferLength + i] = buffer[i];
buffer[i] = undefined;
}
}
for(let i = 0; i < length; i++){
buffer[i] = buffer[prevHead + i];
}
buffer.length = this.#mask + 1;
this.#head = 0;
this.#tail = length;
this.#length = length;
return true;
}
compact(filter) {
if (this.isEmpty()) {
return false;
}
const length = this.#length;
const buffer = this.#buffer;
const head = this.#head;
const mask = this.#mask;
let bufferLength = buffer.length;
let write = 0;
for(let read = 0; read < length; read++){
const readOffset = head + read & mask;
const value = buffer[readOffset];
if (filter(value, read)) {
if (read !== write) {
const writeOffset = head + write & mask;
buffer[writeOffset] = value;
}
write++;
}
}
if (write === length) {
return false;
}
if (write < bufferLength / 2) {
const size = 1 << 32 - Math.clz32(write - 1);
buffer.length = size;
bufferLength = size;
}
for(let i = write; i < bufferLength; i++){
buffer[i] = undefined;
}
this.#head = 0;
this.#tail = write;
this.#length = write;
return true;
}
set(index, values, insert = false) {
const length = this.#length;
if (index < 0 || index > length) {
return false;
}
const count = values.length;
if (insert) {
this.allocate(index, count);
} else {
const extra = Math.max(index + values.length - length, 0);
if (extra > 0) {
this.allocate(length, extra);
}
}
const buffer = this.#buffer;
const mask = this.#mask;
const baseWrite = this.#head + index;
for(let i = 0; i < count; i++){
buffer[baseWrite + i & mask] = values[i];
}
return true;
}
setOne(index, value, insert = false) {
const length = this.#length;
if (index < 0 || index > length) {
return false;
}
if (insert) {
this.allocate(index, 1);
} else {
const extra = Math.max(index + 1 - length, 0);
if (extra > 0) {
this.allocate(length, extra);
}
}
const buffer = this.#buffer;
const mask = this.#mask;
buffer[this.#head + index & mask] = value;
return true;
}
slice(start = 0, end = this.#length) {
const length = this.#length;
const buffer = this.#buffer;
const head = this.#head;
const tail = this.#tail;
const mask = this.#mask;
const actualStart = start < 0 ? Math.max(length + start, 0) : Math.min(start, length);
const actualEnd = end < 0 ? Math.max(length + end, 0) : Math.min(end, length);
if (head <= tail) {
return this.#buffer.slice(head + actualStart & mask, head + actualEnd & mask);
}
const size = Math.max(actualEnd - actualStart, 0);
const result = new Array(size);
for(let i = 0; i < size; i++){
result[i] = buffer[head + actualStart + i & mask];
}
return result;
}
removeOne(index) {
const length = this.#length;
if (index < 0 || index >= length) {
return -1;
}
const buffer = this.#buffer;
const mask = this.#mask;
const head = this.#head;
const leftMoveCount = index;
const rightMoveCount = length - index;
if (leftMoveCount < rightMoveCount) {
for(let i = index; i > 0; i--){
buffer[head + i & mask] = buffer[head + i - 1 & mask];
}
buffer[head] = undefined;
this.#head = head + 1 & mask;
} else {
for(let i = index; i < length - 1; i++){
buffer[head + i & mask] = buffer[head + i + 1 & mask];
}
const tail = head + length - 1 & mask;
buffer[tail] = undefined;
this.#tail = tail;
}
this.#length = length - 1;
return index;
}
removeFirst(value, index = 0) {
const foundIndex = this.indexOf(value, index);
if (foundIndex === -1) {
return -1;
}
return this.removeOne(foundIndex);
}
remove(index, count) {
const result = this.slice(index, index + count);
this.deallocate(index, count);
return result;
}
push(value) {
const nextTail = this.#tail + 1 & this.#mask;
if (nextTail === this.#head) {
this.grow(this.#mask + 2);
this.#buffer[this.#tail] = value;
this.#tail = this.#tail + 1 & this.#mask;
} else {
this.#buffer[this.#tail] = value;
this.#tail = nextTail;
}
this.#length++;
return this;
}
unshift(value) {
const newHead = this.#head - 1 & this.#mask;
if (newHead === this.#tail) {
this.grow(this.#mask + 2);
this.#head = this.#head - 1 & this.#mask;
} else {
this.#head = newHead;
}
this.#buffer[this.#head] = value;
this.#length++;
return this;
}
shift() {
if (this.#head === this.#tail) {
return undefined;
}
const value = this.#buffer[this.#head];
this.#buffer[this.#head] = undefined;
this.#head = this.#head + 1 & this.#mask;
this.#length--;
return value;
}
pop() {
if (this.#head === this.#tail) {
return undefined;
}
this.#tail = this.#tail - 1 & this.#mask;
const value = this.#buffer[this.#tail];
this.#buffer[this.#tail] = undefined;
this.#length--;
return value;
}
peekAt(index) {
if (index < 0 || index >= this.#length) {
return undefined;
}
const offset = this.getHeadOffset(index);
return this.#buffer[offset];
}
peekFirst() {
return this.#buffer[this.#head];
}
peekLast() {
const offset = this.getTailOffset(-1);
return this.#buffer[offset];
}
has(value) {
return this.indexOf(value) !== -1;
}
clear() {
this.#buffer.length = 0;
this.#buffer.length = DEFAULT_CAPACITY;
this.#head = 0;
this.#tail = 0;
this.#length = 0;
this.#mask = DEFAULT_CAPACITY - 1;
return this;
}
toArray() {
return this.slice();
}
drain() {
return Iterator.from({
[Symbol.iterator]: ()=>{
return {
next: ()=>{
if (this.#length === 0) {
return {
done: true,
value: undefined
};
}
const value = this.shift();
return {
done: false,
value
};
}
};
}
});
}
iter() {
return Iterator.from(this[Symbol.iterator]());
}
[Symbol.iterator]() {
const buffer = this.#buffer;
const mask = this.#mask;
const length = this.#length;
let count = 0;
let idx = this.#head;
return {
next: ()=>{
if (count >= length) {
return {
done: true,
value: undefined
};
}
const value = buffer[idx];
idx = idx + 1 & mask;
count++;
return {
done: false,
value
};
}
};
}
}
//# sourceMappingURL=ring-buffer.cjs.map