Files
oh-my-pi/packages/utils/src/ring.ts
T
can1357 93e28a8029 refactor: consolidated timing instrumentation to centralized logger methods
- Migrated timing instrumentation from custom utils/timings module to centralized logger.time() and logger.timeAsync() methods across coding-agent and natives packages.
- Removed PI_DEBUG_STARTUP environment variable and debugStartup() function, consolidating debug output to logger.debug() for conditional logging.
- Replaced custom TextDecoder buffer decoding with native toString('utf-8') method in natives package for simplified string conversion.
- Added RingBuffer<T> circular buffer implementation to pi-utils with comprehensive test coverage for efficient queue operations.
- Enhanced logger interface with time() and timeAsync() methods providing unified operation timing and performance tracking across packages.
2026-02-21 22:42:18 +01:00

170 lines
4.6 KiB
TypeScript

/**
* A fixed-capacity circular buffer that supports efficient push/pop/shift/unshift operations.
* When the buffer is full, adding new items overwrites the oldest items (FIFO behavior).
*
* @template T The type of elements stored in the buffer.
*/
export class RingBuffer<T> {
#buf: (T | undefined)[];
#head = 0;
#size = 0;
/**
* Creates a new ring buffer with the specified capacity.
*
* @param capacity - The maximum number of elements the buffer can hold. Must be positive.
*/
constructor(public readonly capacity: number) {
this.#buf = new Array(capacity);
}
/**
* The number of elements currently in the buffer.
*/
get length(): number {
return this.#size;
}
/**
* Whether the buffer is at full capacity.
*/
get isFull(): boolean {
return this.#size === this.capacity;
}
/**
* Whether the buffer is empty (contains no elements).
*/
get isEmpty(): boolean {
return this.#size === 0;
}
/**
* Adds an item to the end of the buffer.
* If the buffer is full, the oldest item is overwritten and returned.
*
* @param item - The item to add.
* @returns The overwritten item if the buffer was full, otherwise `undefined`.
*/
push(item: T): T | undefined {
const idx = (this.#head + this.#size) % this.capacity;
const overwritten = this.#size === this.capacity ? this.#buf[idx] : undefined;
this.#buf[idx] = item;
if (this.#size === this.capacity) {
this.#head = (this.#head + 1) % this.capacity;
} else {
this.#size++;
}
return overwritten;
}
/**
* Removes and returns the first (oldest) item from the buffer.
*
* @returns The removed item, or `undefined` if the buffer is empty.
*/
shift(): T | undefined {
if (this.#size === 0) return undefined;
const item = this.#buf[this.#head];
this.#buf[this.#head] = undefined;
this.#head = (this.#head + 1) % this.capacity;
this.#size--;
return item;
}
/**
* Removes and returns the last (newest) item from the buffer.
*
* @returns The removed item, or `undefined` if the buffer is empty.
*/
pop(): T | undefined {
if (this.#size === 0) return undefined;
const idx = (this.#head + this.#size - 1) % this.capacity;
const item = this.#buf[idx];
this.#buf[idx] = undefined;
this.#size--;
return item;
}
/**
* Adds an item to the beginning of the buffer.
* If the buffer is full, the newest item is overwritten and returned.
*
* @param item - The item to add.
* @returns The overwritten item if the buffer was full, otherwise `undefined`.
*/
unshift(item: T): T | undefined {
this.#head = (this.#head - 1 + this.capacity) % this.capacity;
const overwritten = this.#size === this.capacity ? this.#buf[this.#head] : undefined;
this.#buf[this.#head] = item;
if (this.#size < this.capacity) this.#size++;
return overwritten;
}
/**
* Returns the element at the specified index without removing it.
* Supports negative indices (e.g., `-1` for the last element).
*
* @param index - The zero-based index, or negative index from the end.
* @returns The element at the index, or `undefined` if the index is out of bounds.
*/
at(index: number): T | undefined {
if (index < 0) index += this.#size;
if (index < 0 || index >= this.#size) return undefined;
return this.#buf[(this.#head + index) % this.capacity];
}
/**
* Returns the first (oldest) element without removing it.
*
* @returns The first element, or `undefined` if the buffer is empty.
*/
peek(): T | undefined {
return this.at(0);
}
/**
* Returns the last (newest) element without removing it.
*
* @returns The last element, or `undefined` if the buffer is empty.
*/
peekBack(): T | undefined {
return this.at(this.#size - 1);
}
/**
* Removes all elements from the buffer, resetting it to an empty state.
*/
clear(): void {
this.#buf.fill(undefined, 0, this.capacity);
this.#head = 0;
this.#size = 0;
}
/**
* Returns an iterator that yields elements in logical order (oldest to newest).
* Allows the buffer to be used with `for...of` loops and spread syntax.
*
* @yields Elements in FIFO order.
*/
*[Symbol.iterator](): Iterator<T> {
for (let i = 0; i < this.#size; i++) {
yield this.#buf[(this.#head + i) % this.capacity] as T;
}
}
/**
* Creates a new array containing all elements in logical order (oldest to newest).
*
* @returns A new array with all buffer elements.
*/
toArray(): T[] {
if (this.#head + this.#size <= this.capacity) {
return this.#buf.slice(this.#head, this.#head + this.#size) as T[];
}
const tail = this.#buf.slice(this.#head, this.capacity);
const head = this.#buf.slice(0, (this.#head + this.#size) % this.capacity);
return tail.concat(head) as T[];
}
}