state-trooper
Version:
Application State Manager
192 lines (159 loc) • 4.29 kB
JavaScript
"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;