UNPKG

state-trooper

Version:
192 lines (159 loc) • 4.29 kB
"use strict"; // TODO: Consider EmptyError & FullError to avoid redundant bound // checks, to improve performance (may need benchmarks) function acopy(src, src_start, dst, dst_start, length) { var count = 0; while (true) { if (count >= length) { break; } dst[dst_start + count] = src[src_start + count]; count ++; } } var EMPTY = { toString: function() { return "[object EMPTY]"; } }; var RingBuffer = function(head, tail, length, array) { this.length = length; this.array = array; this.head = head; this.tail = tail; }; // Internal method, callers must do bound check RingBuffer.prototype._unshift = function(item) { var array = this.array; var head = this.head; array[head] = item; this.head = (head + 1) % array.length; this.length ++; }; RingBuffer.prototype._resize = function() { var array = this.array; var new_length = 2 * array.length; var new_array = new Array(new_length); var head = this.head; var tail = this.tail; var length = this.length; if (tail < head) { acopy(array, tail, new_array, 0, length); this.tail = 0; this.head = length; this.array = new_array; } else if (tail > head) { acopy(array, tail, new_array, 0, array.length - tail); acopy(array, 0, new_array, array.length - tail, head); this.tail = 0; this.head = length; this.array = new_array; } else if (tail === head) { this.tail = 0; this.head = 0; this.array = new_array; } }; RingBuffer.prototype.unbounded_unshift = function(item) { if (this.length + 1 === this.array.length) { this._resize(); } this._unshift(item); }; RingBuffer.prototype.pop = function() { if (this.length === 0) { return EMPTY; } var array = this.array; var tail = this.tail; var item = array[tail]; array[tail] = null; this.tail = (tail + 1) % array.length; this.length --; return item; }; RingBuffer.prototype.cleanup = function(predicate) { var length = this.length; for (var i = 0; i < length; i++) { var item = this.pop(); if (predicate(item)) { this._unshift(item); } } }; var FixedBuffer = function(buf, n) { this.buf = buf; this.n = n; }; FixedBuffer.prototype.is_full = function() { return this.buf.length >= this.n; }; FixedBuffer.prototype.remove = function() { return this.buf.pop(); }; FixedBuffer.prototype.add = function(item) { // Note that even though the underlying buffer may grow, "n" is // fixed so after overflowing the buffer is still considered full. this.buf.unbounded_unshift(item); }; FixedBuffer.prototype.count = function() { return this.buf.length; }; var DroppingBuffer = function(buf, n) { this.buf = buf; this.n = n; }; DroppingBuffer.prototype.is_full = function() { return false; }; DroppingBuffer.prototype.remove = function() { return this.buf.pop(); }; DroppingBuffer.prototype.add = function(item) { if (this.buf.length < this.n) { this.buf._unshift(item); } }; DroppingBuffer.prototype.count = function() { return this.buf.length; }; var SlidingBuffer = function(buf, n) { this.buf = buf; this.n = n; }; SlidingBuffer.prototype.is_full = function() { return false; }; SlidingBuffer.prototype.remove = function() { return this.buf.pop(); }; SlidingBuffer.prototype.add = function(item) { if (this.buf.length === this.n) { this.buf.pop(); } this.buf._unshift(item); }; SlidingBuffer.prototype.count = function() { return this.buf.length; }; var ring = exports.ring = function ring_buffer(n) { return new RingBuffer(0, 0, 0, new Array(n)); }; /** * Returns a buffer that is considered "full" when it reaches size n, * but still accepts additional items, effectively allow overflowing. * The overflowing behavior is useful for supporting "expanding" * transducers, where we want to check if a buffer is full before * running the transduced step function, while still allowing a * transduced step to expand into multiple "essence" steps. */ exports.fixed = function fixed_buffer(n) { return new FixedBuffer(ring(n), n); }; exports.dropping = function dropping_buffer(n) { return new DroppingBuffer(ring(n), n); }; exports.sliding = function sliding_buffer(n) { return new SlidingBuffer(ring(n), n); }; exports.EMPTY = EMPTY;