diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07e6e47 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules diff --git a/README.md b/README.md index f596b60..4df9875 100644 --- a/README.md +++ b/README.md @@ -107,3 +107,13 @@ unlimited, then maxItems will be zero. #### .ringSize The size of each circular buffer. The queue will grow / shrink by this many items at a time. + +### Events +#### ready +The queue has one or more items stored in it. + +#### full +The queue is full. + +#### empty +The queue is empty. diff --git a/package.json b/package.json index 6587c8f..2b745b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "spique", - "version": "1.0.8", + "version": "2.0.0", "description": "A spiral deque - high performance and dynamic queue size", "main": "spique.js", "scripts": { @@ -25,5 +25,8 @@ "bugs": { "url": "https://github.com/erayd/spique/issues" }, - "homepage": "https://github.com/erayd/spique#readme" + "homepage": "https://github.com/erayd/spique#readme", + "dependencies": { + "events": "^3.0.0" + } } diff --git a/ringbuffer.js b/ringbuffer.js new file mode 100644 index 0000000..46f7278 --- /dev/null +++ b/ringbuffer.js @@ -0,0 +1,113 @@ +/* ISC License + * + * Copyright (c) 2016-2020, Erayd LTD + * + * Permission to use, copy, modify, and/or distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright notice + * and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, + * OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, + * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +"use strict"; +const events = require("events"); + +module.exports = class Ringbuffer extends events.EventEmitter { + constructor(size) { + super(); + var head = 0; + var items = 0; + var buffer = new Array(size); + + // check whether the buffer is empty + this.isEmpty = function() { + return !items; + } + + // get the number of free slots + this.available = function() { + return size - items; + } + + // push item onto the end of the buffer + this.push = function(value) { + if(items < size) { + var pos = (head + items++) % size; + buffer[pos] = value; + if (items === 1) + this.emit("ready", this); + if (items === size) + this.emit("full", this); + } + else + throw new Error('Buffer is full'); + }; + + // push item onto the start of the buffer + this.unshift = function(value) { + if(items < size) { + var pos = head ? --head : (head = size - 1); + buffer[pos] = value; + items++; + if (items === 1) + this.emit("ready", this); + if (items === size) + this.emit("full", this); + } + else + throw new Error('Buffer is full'); + }; + + // pop an item off the end of the buffer + this.pop = function() { + if(this.isEmpty()) + return undefined; + var pos = (head + --items) % size; + var value = buffer[pos]; + buffer[pos] = undefined; + if (this.isEmpty()) + this.emit("empty", this); + return value; + }; + + // pop an item off the start of the buffer + this.shift = function() { + if(this.isEmpty()) + return undefined; + var value = buffer[head]; + buffer[head] = undefined; + if(++head == size) + head = 0; + items--; + if (this.isEmpty()) + this.emit("empty", this); + return value; + }; + + // peek at the end of the buffer + this.peek = function() { + if(this.isEmpty()) + return undefined; + return buffer[(head + (items - 1)) % size]; + }; + + // peek at the start of the buffer + this.peekStart = function() { + if(this.isEmpty()) + return undefined; + return buffer[head]; + }; + + // get the number of items in the buffer + Object.defineProperty(this, 'length', {get: function() { + return items; + }}); + } +} + diff --git a/spique.js b/spique.js index 005a7d2..6e154fe 100644 --- a/spique.js +++ b/spique.js @@ -1,6 +1,7 @@ /* ISC License * - * Copyright (c) 2016, Erayd LTD + * Copyright (c) 2016-2020, Erayd LTD + * * Permission to use, copy, modify, and/or distribute this software for any purpose * with or without fee is hereby granted, provided that the above copyright notice * and this permission notice appear in all copies. @@ -14,219 +15,160 @@ */ "use strict"; -module.exports = Spique; -module.exports.RingBuffer = RingBuffer; - -function Spique(maxItems, ringSize) { - var defaultRingSize = 1000; - ringSize = ringSize ? parseInt(ringSize) : defaultRingSize; - if(!maxItems || !(typeof maxItems === 'number')) - maxItems = Math.floor(Number.MAX_VALUE); - - var firstRing = allocateRing(); - var lastRing = firstRing; - var spareRing = undefined; - var rings = 1; - var items = 0; - - // allocate a new ring, or return the spare if available - function allocateRing() { - var newRing = spareRing; - if(newRing !== undefined) - spareRing = undefined; - else - newRing = new RingBuffer(ringSize); - return newRing; - } - - // check whether the buffer is empty - this.isEmpty = function() { - return firstRing == lastRing && firstRing.isEmpty(); - }; - - // push item(s) onto the end of the buffer - this.enqueue = this.push = function push(value) { - if(items >= maxItems) - return new Error('Buffer is full'); - // add another ring if necessary - if(!lastRing.available()) { - var newRing = allocateRing(); - lastRing.nextRing = newRing; - newRing.prevRing = lastRing; - lastRing = newRing; - rings++; +const events = require("events"); +const RingBuffer = require("./ringbuffer.js"); + +module.exports = class Spique extends events.EventEmitter { + constructor(maxItems, ringSize) { + super(); + var defaultRingSize = 1000; + ringSize = ringSize ? parseInt(ringSize) : defaultRingSize; + if(!maxItems || !(typeof maxItems === 'number')) + maxItems = Math.floor(Number.MAX_VALUE); + + var firstRing = allocateRing(); + var lastRing = firstRing; + var spareRing = undefined; + var rings = 1; + var items = 0; + + // allocate a new ring, or return the spare if available + function allocateRing() { + var newRing = spareRing; + if(newRing !== undefined) + spareRing = undefined; + else + newRing = new RingBuffer(ringSize); + return newRing; } - lastRing.push(value); - items++; - // process variadic args - for(var argIndex = 1; argIndex < arguments.length; argIndex++) { - if(push(arguments[argIndex])) - return new Error('Buffer is full'); - } - } + // check whether the buffer is empty + this.isEmpty = function() { + return firstRing == lastRing && firstRing.isEmpty(); + }; + + // push item(s) onto the end of the buffer + this.enqueue = this.push = function push(value) { + if(items >= maxItems) + throw new Error('Buffer is full'); + // add another ring if necessary + if(!lastRing.available()) { + var newRing = allocateRing(); + lastRing.nextRing = newRing; + newRing.prevRing = lastRing; + lastRing = newRing; + rings++; + } + lastRing.push(value); + items++; - // push item(s) onto the start of the buffer - this.unshift = function unshift(value) { - if(items >= maxItems) - return new Error('Buffer is full'); - // add another ring if necessary - if(!firstRing.available()) { - var newRing = allocateRing(); - newRing.nextRing = firstRing; - firstRing.prevRing = newRing; - firstRing = newRing; - rings++; + // fire events + if(items === 1) + this.emit("ready", this); + if(items === maxItems) + this.emit("full", this); + + // process variadic args + for(var argIndex = 1; argIndex < arguments.length; argIndex++) { + if(push(arguments[argIndex])) + throw new Error('Buffer is full'); + } } - firstRing.unshift(value); - items++; - // process variadic args - for(var argIndex = 1; argIndex < arguments.length; argIndex++) { - if(unshift(arguments[argIndex])) - return new Error('Buffer is full'); - } - } + // push item(s) onto the start of the buffer + this.unshift = function unshift(value) { + if(items >= maxItems) + throw new Error('Buffer is full'); + // add another ring if necessary + if(!firstRing.available()) { + var newRing = allocateRing(); + newRing.nextRing = firstRing; + firstRing.prevRing = newRing; + firstRing = newRing; + rings++; + } + firstRing.unshift(value); + items++; - // pop an item off the end of the buffer - this.pop = function() { - var value = lastRing.pop(); - // delete the ring if it's empty and not the last one - if(lastRing.isEmpty() && lastRing.prevRing) { - lastRing = lastRing.prevRing; - spareRing = lastRing.nextRing; - delete lastRing.nextRing; - rings--; + // fire events + if(items === 1) + this.emit("ready", this); + if(items === maxItems) + this.emit("full", this); + + // process variadic args + for(var argIndex = 1; argIndex < arguments.length; argIndex++) { + if(unshift(arguments[argIndex])) + throw new Error('Buffer is full'); + } } - items--; - return value; - }; - - // pop an item off the start of the buffer - this.dequeue = this.shift = function() { - var value = firstRing.shift(); - // delete the ring if it's empty and not the last one - if(firstRing.isEmpty() && firstRing.nextRing) { - firstRing = firstRing.nextRing; - spareRing = firstRing.prevRing; - delete firstRing.prevRing; - rings--; - } - items--; - return value; - }; - - // peek at the end of the buffer - this.last = this.peek = function() { - return lastRing.peek(); - }; - - // peek at the start of the buffer - this.first = this.peekStart = function() { - return firstRing.peekStart(); - }; - - // iterator dequeue - this[Symbol.iterator] = function*() { - while(!this.isEmpty()) - yield this.dequeue(); - }; - - // get the number of items in the buffer - Object.defineProperty(this, 'length', {get: function() { - return items; - }}); - - // get the current capacity - Object.defineProperty(this, 'capacity', {get: function() { - return rings * ringSize; - }}); - - // get the max number of items - Object.defineProperty(this, 'maxItems', {get: function() { - return maxItems | 0; - }}); - - // get the ring size - Object.defineProperty(this, 'ringSize', {get: function() { - return ringSize; - }}); -} - -function RingBuffer(size) { - var head = 0; - var items = 0; - var buffer = new Array(size); - - // check whether the buffer is empty - this.isEmpty = function() { - return !items; - } - // get the number of free slots - this.available = function() { - return size - items; + // pop an item off the end of the buffer + this.pop = function() { + var value = lastRing.pop(); + // delete the ring if it's empty and not the last one + if(lastRing.isEmpty() && lastRing.prevRing) { + lastRing = lastRing.prevRing; + spareRing = lastRing.nextRing; + delete lastRing.nextRing; + rings--; + } + items--; + if (items === 0) + this.emit("empty"); + return value; + }; + + // pop an item off the start of the buffer + this.dequeue = this.shift = function() { + var value = firstRing.shift(); + // delete the ring if it's empty and not the last one + if(firstRing.isEmpty() && firstRing.nextRing) { + firstRing = firstRing.nextRing; + spareRing = firstRing.prevRing; + delete firstRing.prevRing; + rings--; + } + items--; + if (items === 0) + this.emit("empty"); + return value; + }; + + // peek at the end of the buffer + this.last = this.peek = function() { + return lastRing.peek(); + }; + + // peek at the start of the buffer + this.first = this.peekStart = function() { + return firstRing.peekStart(); + }; + + // iterator dequeue + this[Symbol.iterator] = function*() { + while(!this.isEmpty()) + yield this.dequeue(); + }; + + // get the number of items in the buffer + Object.defineProperty(this, 'length', {get: function() { + return items; + }}); + + // get the current capacity + Object.defineProperty(this, 'capacity', {get: function() { + return rings * ringSize; + }}); + + // get the max number of items + Object.defineProperty(this, 'maxItems', {get: function() { + return maxItems | 0; + }}); + + // get the ring size + Object.defineProperty(this, 'ringSize', {get: function() { + return ringSize; + }}); } - - // push item onto the end of the buffer - this.push = function(value) { - if(items < size) { - var pos = (head + items++) % size; - buffer[pos] = value; - } - else - return new Error('Buffer is full'); - }; - - // push item onto the start of the buffer - this.unshift = function(value) { - if(items < size) { - var pos = head ? --head : (head = size - 1); - buffer[pos] = value; - items++; - } - else - return new Error('Buffer is full'); - }; - - // pop an item off the end of the buffer - this.pop = function() { - if(this.isEmpty()) - return undefined; - var pos = (head + --items) % size; - var value = buffer[pos]; - buffer[pos] = undefined; - return value; - }; - - // pop an item off the start of the buffer - this.shift = function() { - if(this.isEmpty()) - return undefined; - var value = buffer[head]; - buffer[head] = undefined; - if(++head == size) - head = 0; - items--; - return value; - }; - - // peek at the end of the buffer - this.peek = function() { - if(this.isEmpty()) - return undefined; - return buffer[(head + (items - 1)) % size]; - }; - - // peek at the start of the buffer - this.peekStart = function() { - if(this.isEmpty()) - return undefined; - return buffer[head]; - }; - - // get the number of items in the buffer - Object.defineProperty(this, 'length', {get: function() { - return items; - }}); } diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..9ae1f88 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +events@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" + integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==