UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

249 lines (196 loc) • 5.4 kB
import { assert } from "../assert.js"; import { min2 } from "../math/min2.js"; /** * Fixed-sized container structure, where ends are connected into a circle/ring * This is a FIFO structure (first in, first out) * @see https://en.wikipedia.org/wiki/Circular_buffer * @template V */ export class RingBuffer { /** * @param {number} size */ constructor(size) { assert.isNumber(size, 'size'); assert.greaterThan(size, 0, `size`); assert.isNonNegativeInteger(size, 'size'); /** * Capacity of the buffer, number of slots * @type {number} */ this.size = size; /** * * @type {number} */ this.head = 0; /** * * @type {number} */ this.tail = 0; /** * Number of used slots. Starts at 0, goes up to {@link #size} * @type {number} */ this.count = 0; /** * * @type {V[]} */ this.data = new Array(size); } /** * * @param {number} new_size */ resize(new_size) { assert.isNonNegativeInteger(new_size, 'new_size'); assert.greaterThan(new_size, 0, `new_size`); if (new_size === this.size) { // already the right size return; } /** * * @type {V[]} */ const array = new Array(new_size); this.data = array; this.size = new_size; this.count = min2(new_size, this.count); this.clear(); // TODO implement a way to keep the old data, need to figure out the correct adjustments to head and tail } /** * * @return {V} */ getHead() { return this.data[this.head - 1]; } /** * * @param {number} offset * @returns {V} */ getFromHead(offset) { assert.isNonNegativeInteger(offset, 'offset'); let i = this.head - (offset + 1); while (i < 0) { i += this.size; } return this.data[i]; } clear() { this.head = 0 this.tail = 0; this.count = 0; } /** * * @param {V} element */ push(element) { const head = this.head; this.data[head] = element; const size = this.size; const newHead = (head + 1) % size; this.head = newHead; if (this.count === size) { //update tail this.tail = (this.tail + 1) % size; } else { this.count++; } if (newHead < head) { //wrap around just happened } } /** * Remove element from the tail * @returns {V|undefined} */ shift() { if (this.count === 0) { //nothing to remove return undefined; } const element = this.data[this.tail]; this.count--; this.tail = (this.tail + 1) % this.size; return element; } /** * * @param {number} index */ removeElementByIndex(index) { if (index >= this.count) { return; } //shift elements back const size = this.size; const tail = this.tail; const count = this.count; const data = this.data; for (let i = index; i < count; i++) { const p = tail + i; const j = (p) % size; const k = (p + 1) % size; data[j] = data[k]; } // Clear the last element (important for GC if holding objects) data[(tail + count - 1) % size] = undefined; // update head and handle underflow this.head = (this.head - 1 + size) % size; this.count--; } /** * Removed first element that fulfills the criteria * @param {function(V):boolean} condition * @param {*} [thisArg] * @returns {V|undefined} */ removeIf(condition, thisArg) { assert.isFunction(condition, 'condition'); const count = this.count; const size = this.size; const tail = this.tail; const data = this.data; for (let i = 0; i < count; i++) { const index = (tail + i) % size; const el = data[index]; if (condition.call(thisArg, el)) { this.removeElementByIndex(i); return el; } } } /** * * @param {function(V)} visitor * @param {*} [thisArg] */ forEach(visitor, thisArg) { assert.isFunction(visitor, 'visitor'); const count = this.count; const size = this.size; const tail = this.tail; const data = this.data; for (let i = 0; i < count; i++) { const index = (tail + i) % size; const el = data[index]; visitor.call(thisArg, el); } } /** * * @param {V} value * @returns {boolean} */ contains(value) { // TODO do a bounds check return this.data.indexOf(value) !== -1; } }