diff --git a/backend/src/Model/Message.ts b/backend/src/Model/Message.ts index a97a67d..14a5f66 100644 --- a/backend/src/Model/Message.ts +++ b/backend/src/Model/Message.ts @@ -1,3 +1,5 @@ export interface Message { value?: any | undefined + length: number + received: Date } diff --git a/backend/src/Model/RingBuffer.ts b/backend/src/Model/RingBuffer.ts new file mode 100644 index 0000000..7891f4c --- /dev/null +++ b/backend/src/Model/RingBuffer.ts @@ -0,0 +1,60 @@ +interface Lengthwise { + length: number +} + +export class RingBuffer { + private capacity: number + private maxItems: number + private usage: number = 0 + private items: (any)[] = [] + private start: number = 0 + private end: number = 0 + + constructor(capacity: number, maxItems = Infinity) { + this.capacity = capacity + this.maxItems = maxItems + } + + public toArray() { + return this.items.slice(this.start, this.end) + } + + public add(item: T) { + const size = item.length + this.enforceCapacityConstraints(size) + this.usage += size + this.items[this.end] = item + this.end += 1 + } + + private enforceCapacityConstraints(addedItemSize: number) { + const remainingSize = this.capacity - (this.usage + addedItemSize) + if (remainingSize < 0) { + this.freeSomeSpace(Math.abs(remainingSize)) + } + while ((this.end - this.start) >= this.maxItems) { + this.dropFirst() + } + } + + private freeSpace(): number { + return this.capacity - this.usage + } + + private isEmpty(): boolean { + return this.usage === 0 + } + + private freeSomeSpace(requiredSpace: number) { + while (this.freeSpace() < requiredSpace && !this.isEmpty()) { + this.dropFirst() + } + } + + private dropFirst() { + const freedSpace = this.items[this.start].length + this.usage -= freedSpace + delete this.items[this.start] + this.start += 1 + } +} diff --git a/backend/src/Model/TreeNode.ts b/backend/src/Model/TreeNode.ts index d167e8b..767347d 100644 --- a/backend/src/Model/TreeNode.ts +++ b/backend/src/Model/TreeNode.ts @@ -1,9 +1,10 @@ -import { Edge, Message } from './' +import { Edge, Message, RingBuffer } from './' import { EventDispatcher } from '../../../events' export class TreeNode { public sourceEdge?: Edge public message?: Message + public messageHistory = new RingBuffer(3000, 100) public edges: {[s: string]: Edge} = {} public collapsed = false public messages: number = 0 @@ -21,7 +22,7 @@ export class TreeNode { sourceEdge.target = this } - this.setMessage(message) + message && this.setMessage(message) this.onMerge.subscribe(() => { this.cachedLeafes = undefined this.cachedLeafMessageCount = undefined @@ -35,8 +36,9 @@ export class TreeNode { }) } - public setMessage(value: any) { - this.message = value + public setMessage(message: Message) { + this.messageHistory.add(message) + this.message = message this.messages += 1 } diff --git a/backend/src/Model/TreeNodeFactory.ts b/backend/src/Model/TreeNodeFactory.ts index 8d36bdf..91f3481 100644 --- a/backend/src/Model/TreeNodeFactory.ts +++ b/backend/src/Model/TreeNodeFactory.ts @@ -1,7 +1,11 @@ -import { Edge, Message, Tree, TreeNode } from './' +import { Edge, Tree, TreeNode } from './' + +interface HasLength { + length: number +} export abstract class TreeNodeFactory { - public static fromEdgesAndValue(edgeNames: string[], value: any): TreeNode { + public static fromEdgesAndValue(edgeNames: string[], value: T): TreeNode { let currentNode: TreeNode = new Tree() for (const edgeName of edgeNames) { const edge = new Edge(edgeName) @@ -11,7 +15,11 @@ export abstract class TreeNodeFactory { currentNode = newNode } - currentNode.setMessage({ value }) + currentNode.setMessage({ + value, + length: value.length, + received: new Date(), + }) return currentNode } } diff --git a/backend/src/Model/index.ts b/backend/src/Model/index.ts index 14977e0..a6bfe16 100644 --- a/backend/src/Model/index.ts +++ b/backend/src/Model/index.ts @@ -4,3 +4,4 @@ export { Message } from './Message' export { TreeNodeFactory } from './TreeNodeFactory' export { Tree } from './Tree' export { Hashable } from './Hashable' +export * from './RingBuffer' diff --git a/backend/src/Model/spec/RingBuffer.spec.ts b/backend/src/Model/spec/RingBuffer.spec.ts new file mode 100644 index 0000000..df4eb65 --- /dev/null +++ b/backend/src/Model/spec/RingBuffer.spec.ts @@ -0,0 +1,46 @@ +import { RingBuffer } from '../' +import { expect } from 'chai' +import 'mocha' + +describe('RingBuffer', () => { + it('should add a new value', () => { + const b = new RingBuffer(30) + b.add('hello') + expect(b.toArray()[0]).to.eq('hello') + }) + + it('should add a new value after the first', () => { + const b = new RingBuffer(30) + b.add('hello') + b.add('world') + expect(b.toArray()[1]).to.eq('world') + }) + + it('should remove old values if buffer size is reached', () => { + const b = new RingBuffer(6) + b.add('hello') + b.add('world') + expect(b.toArray()[0]).to.eq('world') + }) + + it('items bigger then the buffer should be stored anyway', () => { + const b = new RingBuffer(3) + b.add('hello') + expect(b.toArray()[0]).to.eq('hello') + b.add('world') + expect(b.toArray()[0]).to.eq('world') + }) + + it('max item count should be respected', () => { + const b = new RingBuffer(100, 3) + b.add('a') + b.add('b') + b.add('c') + b.add('d') + b.add('e') + expect(b.toArray().length).to.eq(3) + expect(b.toArray()[0]).to.eq('c') + expect(b.toArray()[1]).to.eq('d') + expect(b.toArray()[2]).to.eq('e') + }) +})