From 9047e98889b77563f004202900ce9d6a2eb326e6 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 19 Mar 2013 06:31:02 +0100 Subject: [PATCH] Add initial ol.structs.Buffer --- src/ol/structs/buffer.js | 137 ++++++++++++++ test/spec/ol/structs/buffer.test.js | 279 ++++++++++++++++++++++++++++ 2 files changed, 416 insertions(+) create mode 100644 src/ol/structs/buffer.js create mode 100644 test/spec/ol/structs/buffer.test.js diff --git a/src/ol/structs/buffer.js b/src/ol/structs/buffer.js new file mode 100644 index 0000000000..1fe99b415c --- /dev/null +++ b/src/ol/structs/buffer.js @@ -0,0 +1,137 @@ +goog.provide('ol.structs.Buffer'); + +goog.require('ol.structs.IntegerSet'); + + + +/** + * @constructor + * @param {Array.=} opt_arr Array. + * @param {number=} opt_used Used. + * @param {boolean=} opt_dirty Dirty. + */ +ol.structs.Buffer = function(opt_arr, opt_used, opt_dirty) { + + /** + * @private + * @type {Array.} + */ + this.arr_ = goog.isDef(opt_arr) ? opt_arr : []; + + /** + * @private + * @type {ol.structs.IntegerSet} + */ + this.dirtySet_ = new ol.structs.IntegerSet(); + + /** + * @private + * @type {ol.structs.IntegerSet} + */ + this.freeSet_ = new ol.structs.IntegerSet(); + + var used = goog.isDef(opt_used) ? opt_used : this.arr_.length; + if (used < this.arr_.length) { + this.freeSet_.addRange(used, this.arr_.length); + } + if (opt_dirty && used !== 0) { + this.dirtySet_.addRange(0, used); + } + +}; + + +/** + * @param {Array.} values Values. + * @return {number} Index. + */ +ol.structs.Buffer.prototype.add = function(values) { + var size = values.length; + goog.asserts.assert(size > 0); + var index = this.freeSet_.findRange(size); + goog.asserts.assert(index != -1); // FIXME + this.freeSet_.removeRange(index, index + size); + var i; + for (i = 0; i < size; ++i) { + this.arr_[index + i] = values[i]; + } + this.dirtySet_.addRange(index, index + size); + return index; +}; + + +/** + * @param {function(this: T, number, number)} f Callback. + * @param {T=} opt_obj The object to be used as the value of 'this' within f. + * @template T + */ +ol.structs.Buffer.prototype.forEachRange = function(f, opt_obj) { + if (this.arr_.length !== 0) { + this.freeSet_.forEachRangeInverted(0, this.arr_.length, f, opt_obj); + } +}; + + +/** + * @return {Array.} Array. + */ +ol.structs.Buffer.prototype.getArray = function() { + return this.arr_; +}; + + +/** + * @return {number} Count. + */ +ol.structs.Buffer.prototype.getCount = function() { + return this.arr_.length - this.freeSet_.getSize(); +}; + + +/** + * @return {ol.structs.IntegerSet} Dirty set. + */ +ol.structs.Buffer.prototype.getDirtySet = function() { + return this.dirtySet_; +}; + + +/** + * @return {ol.structs.IntegerSet} Free set. + */ +ol.structs.Buffer.prototype.getFreeSet = function() { + return this.freeSet_; +}; + + +/** + * @param {number} index Index. + * @param {number} size Size. + */ +ol.structs.Buffer.prototype.remove = function(index, size) { + this.freeSet_.addRange(index, index + size); + this.dirtySet_.removeRange(index, index + size); +}; + + +/** + * @param {number} index Index. + * @param {Array.} values Values. + */ +ol.structs.Buffer.prototype.set = function(index, values) { + var arr = this.arr_; + var n = values.length; + goog.asserts.assert(0 <= index && index + n <= arr.length); + for (i = 0; i < n; ++i) { + arr[index + i] = values[i]; + } + this.dirtySet_.addRange(index, index + n); +}; + + +/** + * Marks the buffer as being clean. + */ +ol.structs.Buffer.prototype.setClean = function() { + this.dirtySet_.clear(); +}; diff --git a/test/spec/ol/structs/buffer.test.js b/test/spec/ol/structs/buffer.test.js new file mode 100644 index 0000000000..b0b8436d8b --- /dev/null +++ b/test/spec/ol/structs/buffer.test.js @@ -0,0 +1,279 @@ +goog.provide('ol.test.structs.Buffer'); + + +describe('ol.structs.Buffer', function() { + + describe('constructor', function() { + + describe('without an argument', function() { + + var b; + beforeEach(function() { + b = new ol.structs.Buffer(); + }); + + it('constructs an empty instance', function() { + expect(b.getArray()).to.be.empty(); + expect(b.getCount()).to.be(0); + }); + + }); + + describe('with a single array argument', function() { + + var b; + beforeEach(function() { + b = new ol.structs.Buffer([0, 1, 2, 3]); + }); + + it('constructs a populated instance', function() { + expect(b.getArray()).to.equalArray([0, 1, 2, 3]); + }); + + }); + + }); + + describe('with an empty instance', function() { + + var b; + beforeEach(function() { + b = new ol.structs.Buffer(); + }); + + describe('forEachRange', function() { + + it('does not call the callback', function() { + var callback = sinon.spy(); + b.forEachRange(callback); + expect(callback).not.to.be.called(); + }); + + }); + + describe('getArray', function() { + + it('returns an empty array', function() { + expect(b.getArray()).to.be.empty(); + }); + + }); + + describe('getCount', function() { + + it('returns 0', function() { + expect(b.getCount()).to.be(0); + }); + + }); + + describe('getDirtySet', function() { + + it('returns an empty set', function() { + expect(b.getDirtySet().isEmpty()).to.be(true); + }); + + }); + + }); + + describe('with an empty instance with spare capacity', function() { + + var b; + beforeEach(function() { + b = new ol.structs.Buffer(new Array(4), 0); + }); + + describe('add', function() { + + it('allows elements to be added', function() { + expect(b.add([0, 1, 2, 3])).to.be(0); + expect(b.getArray()).to.equalArray([0, 1, 2, 3]); + }); + + }); + + describe('forEachRange', function() { + + it('does not call the callback', function() { + var callback = sinon.spy(); + b.forEachRange(callback); + expect(callback).not.to.be.called(); + }); + + }); + + describe('getCount', function() { + + it('returns 0', function() { + expect(b.getCount()).to.be(0); + }); + + }); + + }); + + describe('with an instance with no spare capacity', function() { + + var b; + beforeEach(function() { + b = new ol.structs.Buffer([0, 1, 2, 3]); + }); + + describe('add', function() { + + it('throws an exception', function() { + expect(function() { + b.add([4, 5]); + }).to.throwException(); + }); + + }); + + describe('forEachRange', function() { + + it('calls the callback', function() { + var callback = sinon.spy(); + b.forEachRange(callback); + expect(callback.calledOnce).to.be(true); + expect(callback.args[0]).to.equalArray([0, 4]); + }); + + }); + + describe('getCount', function() { + + it('returns the expected value', function() { + expect(b.getCount()).to.be(4); + }); + + }); + + describe('getDirtySet', function() { + + it('returns an empty set', function() { + expect(b.getDirtySet().isEmpty()).to.be(true); + }); + + }); + + describe('remove', function() { + + it('allows items to be removes', function() { + expect(function() { + b.remove(2, 4); + }).to.not.throwException(); + }); + + }); + + describe('set', function() { + + it('updates the items', function() { + b.set(2, [5, 6]); + expect(b.getArray()).to.equalArray([0, 1, 5, 6]); + }); + + it('marks the set items as dirty', function() { + b.set(2, [5, 6]); + var dirtySet = b.getDirtySet(); + expect(dirtySet.isEmpty()).to.be(false); + expect(dirtySet.getArray()).to.equalArray([2, 4]); + }); + + }); + + }); + + describe('with an instance with spare capacity', function() { + + var b; + beforeEach(function() { + var arr = [0, 1, 2, 3]; + arr.length = 8; + b = new ol.structs.Buffer(arr, 4); + }); + + describe('add', function() { + + it('allows more items to be added', function() { + expect(b.add([4, 5, 6, 7])).to.be(4); + expect(b.getArray()).to.equalArray([0, 1, 2, 3, 4, 5, 6, 7]); + }); + + }); + + describe('forEachRange', function() { + + it('calls the callback with the expected values', function() { + var callback = sinon.spy(); + b.forEachRange(callback); + expect(callback.calledOnce).to.be(true); + expect(callback.args[0]).to.equalArray([0, 4]); + }); + + }); + + describe('getCount', function() { + + it('returns the expected value', function() { + expect(b.getCount()).to.be(4); + }); + + }); + + describe('getDirtySet', function() { + + it('returns an empty set', function() { + expect(b.getDirtySet().isEmpty()).to.be(true); + }); + + }); + + describe('getFreeSet', function() { + + it('returns the expected set', function() { + var freeSet = b.getFreeSet(); + expect(freeSet.isEmpty()).to.be(false); + expect(freeSet.getArray()).to.equalArray([4, 8]); + }); + + }); + + }); + + describe('usage tests', function() { + + it('allows multiple adds and removes', function() { + var b = new ol.structs.Buffer(new Array(8), 0); + expect(b.add([0, 1])).to.be(0); + expect(b.getArray()).to.equalArray([ + 0, 1, + undefined, undefined, + undefined, undefined, + undefined, undefined + ]); + expect(b.getCount()).to.be(2); + expect(b.add([2, 3, 4, 5])).to.be(2); + expect(b.getCount()).to.be(6); + expect(b.add([6, 7])).to.be(6); + expect(b.getCount()).to.be(8); + expect(b.getArray()).to.equalArray([0, 1, 2, 3, 4, 5, 6, 7]); + b.remove(2, 2); + expect(b.getCount()).to.be(6); + expect(b.add([8, 9])).to.be(2); + expect(b.getArray()).to.equalArray([0, 1, 8, 9, 4, 5, 6, 7]); + b.remove(1, 1); + b.remove(4, 4); + expect(b.add([10, 11, 12])).to.be(4); + expect(b.getArray()).to.equalArray([0, 1, 8, 9, 10, 11, 12, 7]); + expect(b.add([13])).to.be(1); + expect(b.getArray()).to.equalArray([0, 13, 8, 9, 10, 11, 12, 7]); + }); + + }); + +}); + + +goog.require('ol.structs.Buffer');