Skip to content

Instantly share code, notes, and snippets.

@bb010g
Created October 16, 2020 04:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bb010g/e6cc1cc2493e4d410339d1620ffd0126 to your computer and use it in GitHub Desktop.
Save bb010g/e6cc1cc2493e4d410339d1620ffd0126 to your computer and use it in GitHub Desktop.
TypeScript power-of-two ring buffer (circular buffer)
// Copyright 2020 Dusk <me@bb010g.com>
// SPDX-License-Identifier: 0BSD
class RingBuffer<T> {
buffer: T[];
readIx: number;
writeIx: number;
ixMask: number;
canGrow: boolean;
canDrop: boolean;
constructor(capacity: number, canGrow: boolean, canDrop: boolean) {
capacity = capacity + 1;
if (!Number.isInteger(capacity) || capacity > 0x8000_0000) {
throw "ring buffer capacity must be a non-negative integer <= (2**31 - 1)";
} else if (capacity !== 0 && (capacity & (capacity - 1)) !== 0) {
throw "ring buffer capacity must be a (power of 2) - 1";
}
this.buffer = new Array(capacity);
this.readIx = 0, this.writeIx = 0;
this.ixMask = (capacity - 1) | 0;
this.canGrow = canGrow, this.canDrop = canDrop;
}
get capacity(): number { return this.buffer.length - 1; }
isEmpty() { return this.writeIx === this.readIx; }
isFull() { return ((this.writeIx + 1) & this.ixMask) === this.readIx; }
get length(): number { return (this.writeIx - this.readIx) & this.ixMask; }
grow(): number | undefined {
const buffer = this.buffer;
if (!this.canGrow || buffer.length === 0x8000_0000) { return undefined; }
let oldCapacity = buffer.length;
let capacity = oldCapacity << 1;
buffer.length = capacity;
this.ixMask = this.ixMask = (capacity - 1) | 0;
const writeIx = this.writeIx;
if (writeIx < this.readIx) {
// capacity has been doubled, so tmpWriteIx can't wrap around to 0 while copying old elements
for (let tmpReadIx = 0, tmpWriteIx = oldCapacity; tmpReadIx !== writeIx; ++tmpReadIx, ++tmpWriteIx) {
buffer[tmpWriteIx] = buffer[tmpReadIx];
delete buffer[tmpReadIx];
}
this.writeIx = writeIx + oldCapacity;
}
return capacity;
}
replace(element: T): T | undefined {
let writeIx = this.writeIx;
if (writeIx === this.readIx) { return undefined; }
return this.buffer[writeIx] = element;
}
push(element: T): T | undefined {
let buffer = this.buffer, readIx = this.readIx, writeIx = this.writeIx, ixMask = this.ixMask;
if (((writeIx + 1) & ixMask) === readIx) {
if (this.grow() !== undefined) {
writeIx = this.writeIx, ixMask = this.ixMask;
} else if (this.canDrop) {
delete buffer[readIx];
this.readIx = (readIx + 1) & ixMask;
} else { return undefined; }
}
buffer[writeIx] = element;
this.writeIx = (writeIx + 1) & ixMask;
return element;
}
peek(): T | undefined { return this.buffer[this.readIx]; }
pop(): T | undefined { let element = this.peek(); this.drop(); return element; }
drop(): boolean {
let readIx = this.readIx;
if (this.writeIx === readIx) { return false; }
delete this.buffer[readIx];
this.readIx = (readIx + 1) & this.ixMask;
return true;
}
}
(function () {
let buf = new RingBuffer(3, false, true);
let log = function (...args: any[]): void {
return console.log(...args, buf.buffer, buf.readIx, buf.writeIx);
}
console.log('isEmpty', buf.isEmpty(), 'isFull', buf.isFull());
log('push', buf.push(0));
console.log('isEmpty', buf.isEmpty(), 'isFull', buf.isFull());
log('push', buf.push(1));
log('push', buf.push(2));
console.log('isEmpty', buf.isEmpty(), 'isFull', buf.isFull());
log('push', buf.push(3));
console.log('isEmpty', buf.isEmpty(), 'isFull', buf.isFull());
log('pop', buf.pop());
console.log('isEmpty', buf.isEmpty(), 'isFull', buf.isFull());
log('pop', buf.pop());
console.log('isEmpty', buf.isEmpty(), 'isFull', buf.isFull());
log('pop', buf.pop());
log('pop', buf.pop());
log('pop', buf.pop());
log('push', buf.push('a'));
log('push', buf.push('b'));
log('pop', buf.pop());
log('push', buf.push('c'));
log('length', buf.length);
log('pop', buf.pop());
log('push', buf.push('d'));
console.log('isEmpty', buf.isEmpty(), 'isFull', buf.isFull());
log('push', buf.push('e'));
console.log('isEmpty', buf.isEmpty(), 'isFull', buf.isFull());
log('pop', buf.pop());
console.log('isEmpty', buf.isEmpty(), 'isFull', buf.isFull());
log('pop', buf.pop());
console.log('isEmpty', buf.isEmpty(), 'isFull', buf.isFull());
log('pop', buf.pop());
log('pop', buf.pop());
log('pop', buf.pop());
console.log('isEmpty', buf.isEmpty(), 'isFull', buf.isFull());
log('push', buf.push('f'));
console.log('isEmpty', buf.isEmpty(), 'isFull', buf.isFull());
log('pop', buf.pop());
console.log('isEmpty', buf.isEmpty(), 'isFull', buf.isFull());
log('pop', buf.pop());
console.log('isEmpty', buf.isEmpty(), 'isFull', buf.isFull());
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment