From 49b76baf3fbbebb0d4a5ecadf5b346328d825acb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 26 Nov 2016 16:57:15 -0800 Subject: [PATCH] Add CircularList and basic tests Part of #361 --- src/utils/CircularList.test.ts | 81 +++++++++++++++++++++++++++++ src/utils/CircularList.ts | 95 ++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 src/utils/CircularList.test.ts create mode 100644 src/utils/CircularList.ts diff --git a/src/utils/CircularList.test.ts b/src/utils/CircularList.test.ts new file mode 100644 index 00000000..4a89aa3c --- /dev/null +++ b/src/utils/CircularList.test.ts @@ -0,0 +1,81 @@ +import { assert } from 'chai'; +import { CircularList } from './CircularList'; + +describe('CircularList', () => { + describe('push', () => { + it('should push values onto the array', () => { + const list = new CircularList(5); + list.push('1'); + list.push('2'); + list.push('3'); + list.push('4'); + list.push('5'); + assert.equal(list.get(0), '1'); + assert.equal(list.get(1), '2'); + assert.equal(list.get(2), '3'); + assert.equal(list.get(3), '4'); + assert.equal(list.get(4), '5'); + }); + + it('should push old values from the start out of the array when max length is reached', () => { + const list = new CircularList(2); + list.push('1'); + list.push('2'); + assert.equal(list.get(0), '1'); + assert.equal(list.get(1), '2'); + list.push('3'); + assert.equal(list.get(0), '2'); + assert.equal(list.get(1), '3'); + list.push('4'); + assert.equal(list.get(0), '3'); + assert.equal(list.get(1), '4'); + }); + }); + + describe('maxLength', () => { + it('should increase the size of the list', () => { + const list = new CircularList(2); + list.push('1'); + list.push('2'); + assert.equal(list.get(0), '1'); + assert.equal(list.get(1), '2'); + list.maxLength = 4; + list.push('3'); + list.push('4'); + assert.equal(list.get(0), '1'); + assert.equal(list.get(1), '2'); + assert.equal(list.get(2), '3'); + assert.equal(list.get(3), '4'); + list.push('wrapped'); + assert.equal(list.get(0), '2'); + assert.equal(list.get(1), '3'); + assert.equal(list.get(2), '4'); + assert.equal(list.get(3), 'wrapped'); + }); + + it('should return the maximum length of the list', () => { + const list = new CircularList(2); + assert.equal(list.maxLength, 2); + list.push('1'); + list.push('2'); + assert.equal(list.maxLength, 2); + list.push('3'); + assert.equal(list.maxLength, 2); + list.maxLength = 4; + assert.equal(list.maxLength, 4); + }); + }); + + describe('length', () => { + it('should return the current length of the list, capped at the maximum length', () => { + const list = new CircularList(2); + assert.equal(list.length, 0); + list.push('1'); + assert.equal(list.length, 1); + list.push('2'); + assert.equal(list.length, 2); + list.push('3'); + assert.equal(list.length, 2); + }); + }); +}); diff --git a/src/utils/CircularList.ts b/src/utils/CircularList.ts new file mode 100644 index 00000000..51da0c6d --- /dev/null +++ b/src/utils/CircularList.ts @@ -0,0 +1,95 @@ +/** + * xterm.js: xterm, in the browser + * Copyright (c) 2016, SourceLair Private Company (MIT License) + */ + +/** + * Represents a circular list; a list with a maximum size that wraps around when push is called, + * overriding values at the start of the list. + */ +export class CircularList { + private _array: T[]; + private _startIndex: number; + private _length: number; + + constructor(maxLength: number) { + this._array = new Array(maxLength); + this._startIndex = 0; + this._length = 0; + } + + public get maxLength(): number { + return this._array.length; + } + + public set maxLength(newMaxLength: number) { + // Reconstruct array, starting at index 0. Only transfer values from the + // indexes 0 to length. + let newArray = new Array(newMaxLength); + for (let i = 0; i < Math.min(newMaxLength, this.length); i++) { + newArray[i] = this._array[this._getCyclicIndex(i)]; + } + this._array = newArray; + this._startIndex = 0; + } + + public get length(): number { + return this._length; + } + + public set length(newLength: number) { + // TODO: Is this auto fill is needed or can it be + if (newLength > this._length) { + for (let i = this._length; i < newLength; i++) { + this._array[i] = undefined; + } + } + this._length = newLength; + } + + public get forEach(): (callbackfn: (value: T, index: number, array: T[]) => void) => void { + return this._array.forEach; + } + + /** + * Gets the value at an index. + * + * Note that for performance reasons there is no bounds checking here, the index reference is + * circular so this should always return a value and never throw. + * @param index The index of the value to get. + * @return The value corresponding to the index. + */ + public get(index: number): T { + return this._array[this._getCyclicIndex(index)]; + } + + /** + * Sets the value at an index. + * + * Note that for performance reasons there is no bounds checking here, the index reference is + * circular so this should always return a value and never throw. + * @param index The index to set. + * @param value The value to set. + */ + public set(index: number, value: T): void { + this._array[this._getCyclicIndex(index)] = value; + } + + /** + * Pushes a new value onto the list, wrapping around to the start of the array, overriding index 0 + * if the maximum length is reached. + * @param value The value to push onto the list. + */ + public push(value: T): void { + this._array[this._getCyclicIndex(this._length)] = value; + if (this._length === this.maxLength) { + this._startIndex++; + } else { + this._length++; + } + } + + private _getCyclicIndex(index: number): number { + return (this._startIndex + index) % this.maxLength; + } +}