From f0a2ddf57b75b4e4751488c9ea21cb28868c8700 Mon Sep 17 00:00:00 2001 From: softsimon Date: Thu, 2 Jun 2022 01:29:03 +0400 Subject: [PATCH] Minor refactor for projected block transactions --- backend/src/api/mempool-blocks.ts | 45 ++-- backend/src/api/websocket-handler.ts | 13 +- .../mempool-block-overview/block-scene.ts | 1 - .../fast-vertex-array.ts | 110 ++++---- .../mempool-block-overview.component.ts | 243 +++++++++--------- .../mempool-block-overview/sprite-types.ts | 19 +- .../mempool-block-overview/tx-sprite.ts | 172 +++++++------ .../mempool-block-overview/tx-view.ts | 110 ++++---- frontend/src/app/services/state.service.ts | 2 +- .../src/app/services/websocket.service.ts | 12 +- 10 files changed, 379 insertions(+), 348 deletions(-) diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index d202c3a44..5eb5aa9c8 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -27,7 +27,7 @@ class MempoolBlocks { } public getMempoolBlockDeltas(): MempoolBlockDelta[] { - return this.mempoolBlockDeltas + return this.mempoolBlockDeltas; } public updateMempoolBlocks(memPool: { [txid: string]: TransactionExtended }): void { @@ -72,11 +72,12 @@ class MempoolBlocks { logger.debug('Mempool blocks calculated in ' + time / 1000 + ' seconds'); const { blocks, deltas } = this.calculateMempoolBlocks(memPoolArray, this.mempoolBlocks); - this.mempoolBlocks = blocks - this.mempoolBlockDeltas = deltas + this.mempoolBlocks = blocks; + this.mempoolBlockDeltas = deltas; } - private calculateMempoolBlocks(transactionsSorted: TransactionExtended[], prevBlocks: MempoolBlockWithTransactions[]): { blocks: MempoolBlockWithTransactions[], deltas: MempoolBlockDelta[] } { + private calculateMempoolBlocks(transactionsSorted: TransactionExtended[], prevBlocks: MempoolBlockWithTransactions[]): + { blocks: MempoolBlockWithTransactions[], deltas: MempoolBlockDelta[] } { const mempoolBlocks: MempoolBlockWithTransactions[] = []; const mempoolBlockDeltas: MempoolBlockDelta[] = []; let blockWeight = 0; @@ -100,37 +101,41 @@ class MempoolBlocks { } // Calculate change from previous block states for (let i = 0; i < Math.max(mempoolBlocks.length, prevBlocks.length); i++) { - let added: TransactionStripped[] = [] - let removed: string[] = [] + let added: TransactionStripped[] = []; + let removed: string[] = []; if (mempoolBlocks[i] && !prevBlocks[i]) { - added = mempoolBlocks[i].transactions + added = mempoolBlocks[i].transactions; } else if (!mempoolBlocks[i] && prevBlocks[i]) { - removed = prevBlocks[i].transactions.map(tx => tx.txid) + removed = prevBlocks[i].transactions.map(tx => tx.txid); } else if (mempoolBlocks[i] && prevBlocks[i]) { - const prevIds = {} - const newIds = {} + const prevIds = {}; + const newIds = {}; prevBlocks[i].transactions.forEach(tx => { - prevIds[tx.txid] = true - }) + prevIds[tx.txid] = true; + }); mempoolBlocks[i].transactions.forEach(tx => { - newIds[tx.txid] = true - }) + newIds[tx.txid] = true; + }); prevBlocks[i].transactions.forEach(tx => { - if (!newIds[tx.txid]) removed.push(tx.txid) - }) + if (!newIds[tx.txid]) { + removed.push(tx.txid); + } + }); mempoolBlocks[i].transactions.forEach(tx => { - if (!prevIds[tx.txid]) added.push(tx) - }) + if (!prevIds[tx.txid]) { + added.push(tx); + } + }); } mempoolBlockDeltas.push({ added, removed - }) + }); } return { blocks: mempoolBlocks, deltas: mempoolBlockDeltas - } + }; } private dataToMempoolBlocks(transactions: TransactionExtended[], diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 341b09e7f..c5c2999b2 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -1,6 +1,7 @@ import logger from '../logger'; import * as WebSocket from 'ws'; -import { BlockExtended, TransactionExtended, WebsocketResponse, MempoolBlock, MempoolBlockDelta, OptimizedStatistic, ILoadingIndicators, IConversionRates } from '../mempool.interfaces'; +import { BlockExtended, TransactionExtended, WebsocketResponse, MempoolBlock, MempoolBlockDelta, + OptimizedStatistic, ILoadingIndicators, IConversionRates } from '../mempool.interfaces'; import blocks from './blocks'; import memPool from './mempool'; import backendInfo from './backend-info'; @@ -110,15 +111,15 @@ class WebsocketHandler { } } - if (parsedMessage && parsedMessage['track-mempool-block'] != null) { + if (parsedMessage && parsedMessage['track-mempool-block'] !== undefined) { if (Number.isInteger(parsedMessage['track-mempool-block']) && parsedMessage['track-mempool-block'] >= 0) { const index = parsedMessage['track-mempool-block']; client['track-mempool-block'] = index; const mBlocksWithTransactions = mempoolBlocks.getMempoolBlocksWithTransactions(); if (mBlocksWithTransactions[index]) { - response['projected-mempool-block'] = { + response['projected-block-transactions'] = { index: index, - block: mBlocksWithTransactions[index], + blockTransactions: mBlocksWithTransactions[index].transactions }; } } else { @@ -389,7 +390,7 @@ class WebsocketHandler { if (client['track-mempool-block'] >= 0) { const index = client['track-mempool-block']; if (mBlockDeltas[index]) { - response['projected-mempool-block'] = { + response['projected-block-transactions'] = { index: index, delta: mBlockDeltas[index], }; @@ -526,7 +527,7 @@ class WebsocketHandler { if (client['track-mempool-block'] >= 0) { const index = client['track-mempool-block']; if (mBlockDeltas && mBlockDeltas[index]) { - response['projected-mempool-block'] = { + response['projected-block-transactions'] = { index: index, delta: mBlockDeltas[index], }; diff --git a/frontend/src/app/components/mempool-block-overview/block-scene.ts b/frontend/src/app/components/mempool-block-overview/block-scene.ts index 5e781e429..0974056d8 100644 --- a/frontend/src/app/components/mempool-block-overview/block-scene.ts +++ b/frontend/src/app/components/mempool-block-overview/block-scene.ts @@ -1,5 +1,4 @@ import { FastVertexArray } from './fast-vertex-array' -import TxSprite from './tx-sprite' import TxView from './tx-view' import { TransactionStripped } from 'src/app/interfaces/websocket.interface'; import { Position, Square } from './sprite-types' diff --git a/frontend/src/app/components/mempool-block-overview/fast-vertex-array.ts b/frontend/src/app/components/mempool-block-overview/fast-vertex-array.ts index 6bd025fdd..0581fb391 100644 --- a/frontend/src/app/components/mempool-block-overview/fast-vertex-array.ts +++ b/frontend/src/app/components/mempool-block-overview/fast-vertex-array.ts @@ -8,7 +8,7 @@ or compacting into a smaller Float32Array when there's space to do so. */ -import TxSprite from './tx-sprite' +import TxSprite from './tx-sprite'; export class FastVertexArray { length: number; @@ -19,85 +19,87 @@ export class FastVertexArray { freeSlots: number[]; lastSlot: number; - constructor (length, stride) { - this.length = length - this.count = 0 - this.stride = stride - this.sprites = [] - this.data = new Float32Array(this.length * this.stride) - this.freeSlots = [] - this.lastSlot = 0 + constructor(length, stride) { + this.length = length; + this.count = 0; + this.stride = stride; + this.sprites = []; + this.data = new Float32Array(this.length * this.stride); + this.freeSlots = []; + this.lastSlot = 0; } - insert (sprite: TxSprite): number { - this.count++ + insert(sprite: TxSprite): number { + this.count++; - let position + let position; if (this.freeSlots.length) { - position = this.freeSlots.shift() + position = this.freeSlots.shift(); } else { - position = this.lastSlot - this.lastSlot++ + position = this.lastSlot; + this.lastSlot++; if (this.lastSlot > this.length) { - this.expand() + this.expand(); } } - this.sprites[position] = sprite - return position + this.sprites[position] = sprite; + return position; } - remove (index: number): void { - this.count-- - this.clearData(index) - this.freeSlots.push(index) - this.sprites[index] = null - if (this.length > 2048 && this.count < (this.length * 0.4)) this.compact() + remove(index: number): void { + this.count--; + this.clearData(index); + this.freeSlots.push(index); + this.sprites[index] = null; + if (this.length > 2048 && this.count < (this.length * 0.4)) { + this.compact(); + } } - setData (index: number, dataChunk: number[]): void { - this.data.set(dataChunk, (index * this.stride)) + setData(index: number, dataChunk: number[]): void { + this.data.set(dataChunk, (index * this.stride)); } - clearData (index: number): void { - this.data.fill(0, (index * this.stride), ((index+1) * this.stride)) + clearData(index: number): void { + this.data.fill(0, (index * this.stride), ((index + 1) * this.stride)); } - getData (index: number): Float32Array { - return this.data.subarray(index, this.stride) + getData(index: number): Float32Array { + return this.data.subarray(index, this.stride); } - expand (): void { - this.length *= 2 - const newData = new Float32Array(this.length * this.stride) - newData.set(this.data) - this.data = newData + expand(): void { + this.length *= 2; + const newData = new Float32Array(this.length * this.stride); + newData.set(this.data); + this.data = newData; } - compact (): void { + compact(): void { // New array length is the smallest power of 2 larger than the sprite count (but no smaller than 512) - const newLength = Math.max(512, Math.pow(2, Math.ceil(Math.log2(this.count)))) - if (newLength != this.length) { - this.length = newLength - this.data = new Float32Array(this.length * this.stride) - let sprite - const newSprites = [] - let i = 0 - for (var index in this.sprites) { - sprite = this.sprites[index] + const newLength = Math.max(512, Math.pow(2, Math.ceil(Math.log2(this.count)))); + if (newLength !== this.length) { + this.length = newLength; + this.data = new Float32Array(this.length * this.stride); + let sprite; + const newSprites = []; + let i = 0; + for (const index in this.sprites) { + sprite = this.sprites[index]; if (sprite) { - newSprites.push(sprite) - sprite.moveVertexPointer(i) - sprite.compile() - i++ + newSprites.push(sprite); + sprite.moveVertexPointer(i); + sprite.compile(); + i++; } } - this.sprites = newSprites - this.freeSlots = [] - this.lastSlot = i + this.sprites = newSprites; + this.freeSlots = []; + this.lastSlot = i; } } - getVertexData (): Float32Array { - return this.data + getVertexData(): Float32Array { + return this.data; } } diff --git a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts index e75a78896..d017f92a9 100644 --- a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts +++ b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts @@ -1,7 +1,8 @@ -import { Component, ElementRef, ViewChild, HostListener, Input, Output, EventEmitter, OnInit, OnDestroy, OnChanges, ChangeDetectionStrategy, NgZone } from '@angular/core'; +import { Component, ElementRef, ViewChild, HostListener, Input, Output, EventEmitter, OnInit, + OnDestroy, OnChanges, ChangeDetectionStrategy, NgZone, AfterViewInit } from '@angular/core'; import { StateService } from 'src/app/services/state.service'; import { MempoolBlockWithTransactions, MempoolBlockDelta, TransactionStripped } from 'src/app/interfaces/websocket.interface'; -import { Observable, Subscription, BehaviorSubject } from 'rxjs'; +import { Subscription, BehaviorSubject } from 'rxjs'; import { WebsocketService } from 'src/app/services/websocket.service'; import { FastVertexArray } from './fast-vertex-array'; import BlockScene from './block-scene'; @@ -14,9 +15,9 @@ import TxView from './tx-view'; styleUrls: ['./mempool-block-overview.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChanges { +export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit { @Input() index: number; - @Output() txPreviewEvent = new EventEmitter() + @Output() txPreviewEvent = new EventEmitter(); @ViewChild('blockCanvas') canvas: ElementRef; @@ -41,33 +42,33 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang constructor( public stateService: StateService, private websocketService: WebsocketService, - readonly _ngZone: NgZone, + readonly ngZone: NgZone, ) { - this.vertexArray = new FastVertexArray(512, TxSprite.dataSize) + this.vertexArray = new FastVertexArray(512, TxSprite.dataSize); } ngOnInit(): void { - this.blockSub = this.stateService.mempoolBlock$.subscribe((block) => { - this.replaceBlock(block) - }) + this.blockSub = this.stateService.mempoolBlockTransactions$.subscribe((transactionsStripped) => { + this.replaceBlock(transactionsStripped); + }); this.deltaSub = this.stateService.mempoolBlockDelta$.subscribe((delta) => { - this.updateBlock(delta) - }) + this.updateBlock(delta); + }); } ngAfterViewInit(): void { - this.canvas.nativeElement.addEventListener("webglcontextlost", this.handleContextLost, false) - this.canvas.nativeElement.addEventListener("webglcontextrestored", this.handleContextRestored, false) - this.gl = this.canvas.nativeElement.getContext('webgl') - this.initCanvas() + this.canvas.nativeElement.addEventListener('webglcontextlost', this.handleContextLost, false); + this.canvas.nativeElement.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.gl = this.canvas.nativeElement.getContext('webgl'); + this.initCanvas(); - this.resizeCanvas() + this.resizeCanvas(); } ngOnChanges(changes): void { if (changes.index) { - this.clearBlock(changes.index.currentValue > changes.index.previousValue ? 'right' : 'left') - this.isLoading$.next(true) + this.clearBlock(changes.index.currentValue > changes.index.previousValue ? 'right' : 'left'); + this.isLoading$.next(true); this.websocketService.startTrackMempoolBlock(changes.index.currentValue); } } @@ -80,53 +81,56 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang clearBlock(direction): void { if (this.scene) { - this.scene.exit(direction) + this.scene.exit(direction); } - this.hoverTx = null - this.selectedTx = null - this.txPreviewEvent.emit(null) + this.hoverTx = null; + this.selectedTx = null; + this.txPreviewEvent.emit(null); } - replaceBlock(block: MempoolBlockWithTransactions): void { + replaceBlock(transactionsStripped: TransactionStripped[]): void { if (!this.scene) { - this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: 75, blockLimit: this.stateService.blockVSize, vertexArray: this.vertexArray }) + this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: 75, + blockLimit: this.stateService.blockVSize, vertexArray: this.vertexArray }); } - const blockMined = (this.stateService.latestBlockHeight > this.lastBlockHeight) - if (this.blockIndex != this.index) { - const direction = (this.blockIndex == null || this.index < this.blockIndex) ? 'left' : 'right' - this.scene.enter(block.transactions, direction) + const blockMined = (this.stateService.latestBlockHeight > this.lastBlockHeight); + if (this.blockIndex !== this.index) { + const direction = (this.blockIndex == null || this.index < this.blockIndex) ? 'left' : 'right'; + this.scene.enter(transactionsStripped, direction); } else { - this.scene.replace(block.transactions, blockMined ? 'right' : 'left') + this.scene.replace(transactionsStripped, blockMined ? 'right' : 'left'); } - this.lastBlockHeight = this.stateService.latestBlockHeight - this.blockIndex = this.index - this.isLoading$.next(false) + this.lastBlockHeight = this.stateService.latestBlockHeight; + this.blockIndex = this.index; + this.isLoading$.next(false); } updateBlock(delta: MempoolBlockDelta): void { if (!this.scene) { - this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: 75, blockLimit: this.stateService.blockVSize, vertexArray: this.vertexArray }) + this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: 75, + blockLimit: this.stateService.blockVSize, vertexArray: this.vertexArray }); } - const blockMined = (this.stateService.latestBlockHeight > this.lastBlockHeight) + const blockMined = (this.stateService.latestBlockHeight > this.lastBlockHeight); - if (this.blockIndex != this.index) { - const direction = (this.blockIndex == null || this.index < this.blockIndex) ? 'left' : 'right' - this.scene.exit(direction) - this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: 75, blockLimit: this.stateService.blockVSize, vertexArray: this.vertexArray }) - this.scene.enter(delta.added, direction) + if (this.blockIndex !== this.index) { + const direction = (this.blockIndex == null || this.index < this.blockIndex) ? 'left' : 'right'; + this.scene.exit(direction); + this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: 75, + blockLimit: this.stateService.blockVSize, vertexArray: this.vertexArray }); + this.scene.enter(delta.added, direction); } else { - this.scene.update(delta.added, delta.removed, blockMined ? 'right' : 'left', blockMined) + this.scene.update(delta.added, delta.removed, blockMined ? 'right' : 'left', blockMined); } - this.lastBlockHeight = this.stateService.latestBlockHeight - this.blockIndex = this.index - this.isLoading$.next(false) + this.lastBlockHeight = this.stateService.latestBlockHeight; + this.blockIndex = this.index; + this.isLoading$.next(false); } - initCanvas (): void { - this.gl.clearColor(0.0, 0.0, 0.0, 0.0) - this.gl.clear(this.gl.COLOR_BUFFER_BIT) + initCanvas(): void { + this.gl.clearColor(0.0, 0.0, 0.0, 0.0); + this.gl.clear(this.gl.COLOR_BUFFER_BIT); const shaderSet = [ { @@ -137,97 +141,101 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang type: this.gl.FRAGMENT_SHADER, src: fragShaderSrc } - ] + ]; - this.shaderProgram = this.buildShaderProgram(shaderSet) + this.shaderProgram = this.buildShaderProgram(shaderSet); - this.gl.useProgram(this.shaderProgram) + this.gl.useProgram(this.shaderProgram); // Set up alpha blending this.gl.enable(this.gl.BLEND); this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA); - const glBuffer = this.gl.createBuffer() - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, glBuffer) + const glBuffer = this.gl.createBuffer(); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, glBuffer); /* SET UP SHADER ATTRIBUTES */ Object.keys(attribs).forEach((key, i) => { - attribs[key].pointer = this.gl.getAttribLocation(this.shaderProgram, key) + attribs[key].pointer = this.gl.getAttribLocation(this.shaderProgram, key); this.gl.enableVertexAttribArray(attribs[key].pointer); - }) + }); - this.start() + this.start(); } handleContextLost(event): void { - event.preventDefault() - cancelAnimationFrame(this.animationFrameRequest) - this.animationFrameRequest = null - this.running = false + event.preventDefault(); + cancelAnimationFrame(this.animationFrameRequest); + this.animationFrameRequest = null; + this.running = false; } handleContextRestored(event): void { - this.initCanvas() + this.initCanvas(); } @HostListener('window:resize', ['$event']) resizeCanvas(): void { - this.displayWidth = this.canvas.nativeElement.parentElement.clientWidth - this.displayHeight = this.canvas.nativeElement.parentElement.clientHeight - this.canvas.nativeElement.width = this.displayWidth - this.canvas.nativeElement.height = this.displayHeight - if (this.gl) this.gl.viewport(0, 0, this.canvas.nativeElement.width, this.canvas.nativeElement.height) - if (this.scene) this.scene.resize({ width: this.displayWidth, height: this.displayHeight }) + this.displayWidth = this.canvas.nativeElement.parentElement.clientWidth; + this.displayHeight = this.canvas.nativeElement.parentElement.clientHeight; + this.canvas.nativeElement.width = this.displayWidth; + this.canvas.nativeElement.height = this.displayHeight; + if (this.gl) { + this.gl.viewport(0, 0, this.canvas.nativeElement.width, this.canvas.nativeElement.height); + } + if (this.scene) { + this.scene.resize({ width: this.displayWidth, height: this.displayHeight }); + } } compileShader(src, type): WebGLShader { - let shader = this.gl.createShader(type) + const shader = this.gl.createShader(type); - this.gl.shaderSource(shader, src) - this.gl.compileShader(shader) + this.gl.shaderSource(shader, src); + this.gl.compileShader(shader); if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { - console.log(`Error compiling ${type === this.gl.VERTEX_SHADER ? "vertex" : "fragment"} shader:`) - console.log(this.gl.getShaderInfoLog(shader)) + console.log(`Error compiling ${type === this.gl.VERTEX_SHADER ? 'vertex' : 'fragment'} shader:`); + console.log(this.gl.getShaderInfoLog(shader)); } - return shader + return shader; } buildShaderProgram(shaderInfo): WebGLProgram { - let program = this.gl.createProgram() + const program = this.gl.createProgram(); shaderInfo.forEach((desc) => { - let shader = this.compileShader(desc.src, desc.type) + const shader = this.compileShader(desc.src, desc.type); if (shader) { - this.gl.attachShader(program, shader) + this.gl.attachShader(program, shader); } - }) + }); - this.gl.linkProgram(program) + this.gl.linkProgram(program); if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) { - console.log("Error linking shader program:") - console.log(this.gl.getProgramInfoLog(program)) + console.log('Error linking shader program:'); + console.log(this.gl.getProgramInfoLog(program)); } - return program + return program; } start(): void { - this.running = true - this._ngZone.runOutsideAngular(() => this.run()) + this.running = true; + this.ngZone.runOutsideAngular(() => this.run()); } - run (now?: DOMHighResTimeStamp): void { + run(now?: DOMHighResTimeStamp): void { if (!now) { - now = performance.now() + now = performance.now(); } /* SET UP SHADER UNIFORMS */ // screen dimensions - this.gl.uniform2f(this.gl.getUniformLocation(this.shaderProgram, 'screenSize'), this.displayWidth, this.displayHeight) + this.gl.uniform2f(this.gl.getUniformLocation(this.shaderProgram, 'screenSize'), this.displayWidth, this.displayHeight); // frame timestamp - this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, 'now'), now) + this.gl.uniform1f(this.gl.getUniformLocation(this.shaderProgram, 'now'), now); /* SET UP SHADER ATTRIBUTES */ Object.keys(attribs).forEach((key, i) => { @@ -237,20 +245,20 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang false, // never normalised stride, // distance between values of the same attribute attribs[key].offset); // offset of the first value - }) + }); - const pointArray = this.vertexArray.getVertexData() + const pointArray = this.vertexArray.getVertexData(); if (pointArray.length) { - this.gl.bufferData(this.gl.ARRAY_BUFFER, pointArray, this.gl.DYNAMIC_DRAW) - this.gl.drawArrays(this.gl.TRIANGLES, 0, pointArray.length / TxSprite.vertexSize) + this.gl.bufferData(this.gl.ARRAY_BUFFER, pointArray, this.gl.DYNAMIC_DRAW); + this.gl.drawArrays(this.gl.TRIANGLES, 0, pointArray.length / TxSprite.vertexSize); } /* LOOP */ if (this.running) { if (this.animationFrameRequest) { - cancelAnimationFrame(this.animationFrameRequest) - this.animationFrameRequest = null + cancelAnimationFrame(this.animationFrameRequest); + this.animationFrameRequest = null; } this.animationFrameRequest = requestAnimationFrame(() => this.run()); } @@ -258,49 +266,54 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang @HostListener('click', ['$event']) onClick(event) { - this.setPreviewTx(event.offsetX, event.offsetY, true) + this.setPreviewTx(event.offsetX, event.offsetY, true); } @HostListener('pointermove', ['$event']) onPointerMove(event) { - this.setPreviewTx(event.offsetX, event.offsetY, false) + this.setPreviewTx(event.offsetX, event.offsetY, false); } @HostListener('pointerleave', ['$event']) onPointerLeave(event) { - this.setPreviewTx(-1, -1, false) + this.setPreviewTx(-1, -1, false); } setPreviewTx(x: number, y: number, clicked: boolean = false) { if (this.scene && (!this.selectedTx || clicked)) { - const selected = this.scene.getTxAt({ x, y }) - const currentPreview = this.selectedTx || this.hoverTx + const selected = this.scene.getTxAt({ x, y }); + const currentPreview = this.selectedTx || this.hoverTx; if (selected !== currentPreview) { - if (currentPreview) currentPreview.setHover(false) + if (currentPreview) { + currentPreview.setHover(false); + } if (selected) { - selected.setHover(true) + selected.setHover(true); this.txPreviewEvent.emit({ txid: selected.txid, fee: selected.fee, vsize: selected.vsize, value: selected.value - }) - if (clicked) this.selectedTx = selected - else this.hoverTx = selected + }); + if (clicked) { + this.selectedTx = selected; + } else { + this.hoverTx = selected; + } } else { if (clicked) { - this.selectedTx = null + this.selectedTx = null; } - this.hoverTx = null - this.txPreviewEvent.emit(null) + this.hoverTx = null; + this.txPreviewEvent.emit(null); } } else if (clicked) { if (selected === this.selectedTx) { - this.hoverTx = this.selectedTx - this.selectedTx = null + this.hoverTx = this.selectedTx; + this.selectedTx = null; } else { - this.selectedTx = selected + this.selectedTx = selected; } } } @@ -317,16 +330,16 @@ const attribs = { colG: { type: 'FLOAT', count: 4, pointer: null, offset: 0 }, colB: { type: 'FLOAT', count: 4, pointer: null, offset: 0 }, colA: { type: 'FLOAT', count: 4, pointer: null, offset: 0 } -} +}; // Calculate the number of bytes per vertex based on specified attributes const stride = Object.values(attribs).reduce((total, attrib) => { - return total + (attrib.count * 4) -}, 0) + return total + (attrib.count * 4); +}, 0); // Calculate vertex attribute offsets for (let i = 0, offset = 0; i < Object.keys(attribs).length; i++) { - let attrib = Object.values(attribs)[i] - attrib.offset = offset - offset += (attrib.count * 4) + const attrib = Object.values(attribs)[i]; + attrib.offset = offset; + offset += (attrib.count * 4); } const vertShaderSrc = ` @@ -376,7 +389,7 @@ void main() { vColor = vec4(red, green, blue, alpha); } -` +`; const fragShaderSrc = ` varying lowp vec4 vColor; @@ -386,4 +399,4 @@ void main() { // premultiply alpha gl_FragColor.rgb *= gl_FragColor.a; } -` +`; diff --git a/frontend/src/app/components/mempool-block-overview/sprite-types.ts b/frontend/src/app/components/mempool-block-overview/sprite-types.ts index ef0778582..45bf86e3c 100644 --- a/frontend/src/app/components/mempool-block-overview/sprite-types.ts +++ b/frontend/src/app/components/mempool-block-overview/sprite-types.ts @@ -1,18 +1,18 @@ export type Position = { x: number, y: number, -} +}; export type Square = Position & { s?: number -} +}; export type Color = { r: number, g: number, b: number, a: number, -} +}; export type InterpolatedAttribute = { a: number, @@ -20,9 +20,9 @@ export type InterpolatedAttribute = { t: number, v: number, d: number -} +}; -export type Update = Position & { s: number } & Color +export type Update = Position & { s: number } & Color; export type Attributes = { x: InterpolatedAttribute, @@ -32,7 +32,7 @@ export type Attributes = { g: InterpolatedAttribute, b: InterpolatedAttribute, a: InterpolatedAttribute -} +}; export type OptionalAttributes = { x?: InterpolatedAttribute, @@ -42,7 +42,8 @@ export type OptionalAttributes = { g?: InterpolatedAttribute, b?: InterpolatedAttribute, a?: InterpolatedAttribute -} +}; + export type SpriteUpdateParams = { x?: number, y?: number, @@ -56,7 +57,7 @@ export type SpriteUpdateParams = { minDuration?: number, adjust?: boolean, temp?: boolean -} +}; export type ViewUpdateParams = { display: { @@ -70,4 +71,4 @@ export type ViewUpdateParams = { jitter?: number, state?: string, adjust?: boolean -} +}; diff --git a/frontend/src/app/components/mempool-block-overview/tx-sprite.ts b/frontend/src/app/components/mempool-block-overview/tx-sprite.ts index 3145a8ea2..75c1577fc 100644 --- a/frontend/src/app/components/mempool-block-overview/tx-sprite.ts +++ b/frontend/src/app/components/mempool-block-overview/tx-sprite.ts @@ -1,10 +1,14 @@ -import { FastVertexArray } from './fast-vertex-array' -import { InterpolatedAttribute, Attributes, OptionalAttributes, SpriteUpdateParams, Update } from './sprite-types' +import { FastVertexArray } from './fast-vertex-array'; +import { InterpolatedAttribute, Attributes, OptionalAttributes, SpriteUpdateParams, Update } from './sprite-types'; -const attribKeys = ['a', 'b', 't', 'v'] -const updateKeys = ['x', 'y', 's', 'r', 'g', 'b', 'a'] +const attribKeys = ['a', 'b', 't', 'v']; +const updateKeys = ['x', 'y', 's', 'r', 'g', 'b', 'a']; export default class TxSprite { + static vertexSize = 30; + static vertexCount = 6; + static dataSize: number = (30 * 6); + vertexArray: FastVertexArray; vertexPointer: number; vertexData: number[]; @@ -12,17 +16,14 @@ export default class TxSprite { attributes: Attributes; tempAttributes: OptionalAttributes; - static vertexSize: number = 30; - static vertexCount: number = 6; - static dataSize: number = (30*6); constructor(params: SpriteUpdateParams, vertexArray: FastVertexArray) { - const offsetTime = params.start - this.vertexArray = vertexArray - this.vertexData = Array(VI.length).fill(0) + const offsetTime = params.start; + this.vertexArray = vertexArray; + this.vertexData = Array(VI.length).fill(0); this.updateMap = { x: 0, y: 0, s: 0, r: 0, g: 0, b: 0, a: 0 - } + }; this.attributes = { x: { a: params.x, b: params.x, t: offsetTime, v: 0, d: 0 }, @@ -32,35 +33,36 @@ export default class TxSprite { g: { a: params.g, b: params.g, t: offsetTime, v: 0, d: 0 }, b: { a: params.b, b: params.b, t: offsetTime, v: 0, d: 0 }, a: { a: params.a, b: params.a, t: offsetTime, v: 0, d: 0 }, - } + }; // Used to temporarily modify the sprite, so that the base view can be resumed later - this.tempAttributes = null + this.tempAttributes = null; - this.vertexPointer = this.vertexArray.insert(this) + this.vertexPointer = this.vertexArray.insert(this); - this.compile() + this.compile(); } - private interpolateAttributes (updateMap: Update, attributes: OptionalAttributes, offsetTime: DOMHighResTimeStamp, v: number, duration: number, minDuration: number, adjust: boolean): void { + private interpolateAttributes(updateMap: Update, attributes: OptionalAttributes, offsetTime: DOMHighResTimeStamp, v: number, + duration: number, minDuration: number, adjust: boolean): void { for (const key of Object.keys(updateMap)) { // for each non-null attribute: if (updateMap[key] != null) { // calculate current interpolated value, and set as 'from' - interpolateAttributeStart(attributes[key], offsetTime) + interpolateAttributeStart(attributes[key], offsetTime); // update start time - attributes[key].t = offsetTime + attributes[key].t = offsetTime; - if (!adjust || (duration && attributes[key].d == 0)) { - attributes[key].v = v - attributes[key].d = duration + if (!adjust || (duration && attributes[key].d === 0)) { + attributes[key].v = v; + attributes[key].d = duration; } else if (minDuration > attributes[key].d) { // enforce minimum transition duration - attributes[key].v = 1 / minDuration - attributes[key].d = minDuration + attributes[key].v = 1 / minDuration; + attributes[key].d = minDuration; } // set 'to' to target value - attributes[key].b = updateMap[key] + attributes[key].b = updateMap[key]; } } } @@ -75,137 +77,139 @@ export default class TxSprite { minDuration: minimum remaining transition duration when adjust = true temp: if true, this update is only temporary (can be reversed with 'resume') */ - update (params: SpriteUpdateParams): void { - const offsetTime = params.start || performance.now() - const v = params.duration > 0 ? (1 / params.duration) : 0 + update(params: SpriteUpdateParams): void { + const offsetTime = params.start || performance.now(); + const v = params.duration > 0 ? (1 / params.duration) : 0; updateKeys.forEach(key => { - this.updateMap[key] = params[key] - }) + this.updateMap[key] = params[key]; + }); - const isModified = !!this.tempAttributes + const isModified = !!this.tempAttributes; if (!params.temp) { - this.interpolateAttributes(this.updateMap, this.attributes, offsetTime, v, params.duration, params.minDuration, params.adjust) + this.interpolateAttributes(this.updateMap, this.attributes, offsetTime, v, params.duration, params.minDuration, params.adjust); } else { if (!isModified) { // set up tempAttributes - this.tempAttributes = {} + this.tempAttributes = {}; for (const key of Object.keys(this.updateMap)) { if (this.updateMap[key] != null) { - this.tempAttributes[key] = { ...this.attributes[key] } + this.tempAttributes[key] = { ...this.attributes[key] }; } } } - this.interpolateAttributes(this.updateMap, this.tempAttributes, offsetTime, v, params.duration, params.minDuration, params.adjust) + this.interpolateAttributes(this.updateMap, this.tempAttributes, offsetTime, v, params.duration, params.minDuration, params.adjust); } - this.compile() + this.compile(); } // Transition back from modified state back to base attributes - resume (duration: number, start : DOMHighResTimeStamp = performance.now()): void { + resume(duration: number, start: DOMHighResTimeStamp = performance.now()): void { // If not in modified state, there's nothing to do - if (!this.tempAttributes) return + if (!this.tempAttributes) { + return; + } - const offsetTime = start - const v = duration > 0 ? (1 / duration) : 0 + const offsetTime = start; + const v = duration > 0 ? (1 / duration) : 0; for (const key of Object.keys(this.tempAttributes)) { // If this base attribute is static (fixed or post-transition), transition smoothly back - if (this.attributes[key].v == 0 || (this.attributes[key].t + this.attributes[key].d) <= start) { + if (this.attributes[key].v === 0 || (this.attributes[key].t + this.attributes[key].d) <= start) { // calculate current interpolated value, and set as 'from' - interpolateAttributeStart(this.tempAttributes[key], offsetTime) - this.attributes[key].a = this.tempAttributes[key].a - this.attributes[key].t = offsetTime - this.attributes[key].v = v - this.attributes[key].d = duration + interpolateAttributeStart(this.tempAttributes[key], offsetTime); + this.attributes[key].a = this.tempAttributes[key].a; + this.attributes[key].t = offsetTime; + this.attributes[key].v = v; + this.attributes[key].d = duration; } } - this.tempAttributes = null + this.tempAttributes = null; - this.compile() + this.compile(); } // Write current state into the graphics vertex array for rendering - compile (): void { - let attributes = this.attributes + compile(): void { + let attributes = this.attributes; if (this.tempAttributes) { attributes = { ...this.attributes, ...this.tempAttributes - } + }; } - const size = attributes.s + const size = attributes.s; // update vertex data in place // ugly, but avoids overhead of allocating large temporary arrays - const vertexStride = VI.length + 2 + const vertexStride = VI.length + 2; for (let vertex = 0; vertex < 6; vertex++) { - this.vertexData[vertex * vertexStride] = vertexOffsetFactors[vertex][0] - this.vertexData[(vertex * vertexStride) + 1] = vertexOffsetFactors[vertex][1] + this.vertexData[vertex * vertexStride] = vertexOffsetFactors[vertex][0]; + this.vertexData[(vertex * vertexStride) + 1] = vertexOffsetFactors[vertex][1]; for (let step = 0; step < VI.length; step++) { // components of each field in the vertex array are defined by an entry in VI: // VI[i].a is the attribute, VI[i].f is the inner field, VI[i].offA and VI[i].offB are offset factors - this.vertexData[(vertex * vertexStride) + step + 2] = attributes[VI[step].a][VI[step].f] + this.vertexData[(vertex * vertexStride) + step + 2] = attributes[VI[step].a][VI[step].f]; } } - this.vertexArray.setData(this.vertexPointer, this.vertexData) + this.vertexArray.setData(this.vertexPointer, this.vertexData); } - moveVertexPointer (index: number): void { - this.vertexPointer = index + moveVertexPointer(index: number): void { + this.vertexPointer = index; } - destroy (): void { - this.vertexArray.remove(this.vertexPointer) - this.vertexPointer = null + destroy(): void { + this.vertexArray.remove(this.vertexPointer); + this.vertexPointer = null; } } // expects 0 <= x <= 1 function smootherstep(x: number): number { - let ix = 1 - x; - x = x * x - return x / (x + ix * ix) + const ix = 1 - x; + x = x * x; + return x / (x + ix * ix); } function interpolateAttributeStart(attribute: InterpolatedAttribute, start: DOMHighResTimeStamp): void { - if (attribute.v == 0 || (attribute.t + attribute.d) <= start) { + if (attribute.v === 0 || (attribute.t + attribute.d) <= start) { // transition finished, next transition starts from current end state // (clamp to 1) - attribute.a = attribute.b - attribute.v = 0 - attribute.d = 0 + attribute.a = attribute.b; + attribute.v = 0; + attribute.d = 0; } else if (attribute.t > start) { // transition not started // (clamp to 0) } else { // transition in progress // (interpolate) - let progress = (start - attribute.t) - let delta = smootherstep(progress / attribute.d) - attribute.a = attribute.a + (delta * (attribute.b - attribute.a)) - attribute.d = attribute.d - progress - attribute.v = 1 / attribute.d + const progress = (start - attribute.t); + const delta = smootherstep(progress / attribute.d); + attribute.a = attribute.a + (delta * (attribute.b - attribute.a)); + attribute.d = attribute.d - progress; + attribute.v = 1 / attribute.d; } } const vertexOffsetFactors = [ - [0,0], - [1,1], - [1,0], - [0,0], - [1,1], - [0,1] -] + [0, 0], + [1, 1], + [1, 0], + [0, 0], + [1, 1], + [0, 1] +]; -const VI = [] +const VI = []; updateKeys.forEach((attribute, aIndex) => { attribKeys.forEach(field => { VI.push({ a: attribute, f: field - }) - }) -}) + }); + }); +}); diff --git a/frontend/src/app/components/mempool-block-overview/tx-view.ts b/frontend/src/app/components/mempool-block-overview/tx-view.ts index 464b8987e..cb98bef72 100644 --- a/frontend/src/app/components/mempool-block-overview/tx-view.ts +++ b/frontend/src/app/components/mempool-block-overview/tx-view.ts @@ -1,14 +1,14 @@ -import TxSprite from './tx-sprite' -import { FastVertexArray } from './fast-vertex-array' +import TxSprite from './tx-sprite'; +import { FastVertexArray } from './fast-vertex-array'; import { TransactionStripped } from 'src/app/interfaces/websocket.interface'; -import { SpriteUpdateParams, Square, Color, ViewUpdateParams } from './sprite-types' +import { SpriteUpdateParams, Square, Color, ViewUpdateParams } from './sprite-types'; import { feeLevels, mempoolFeeColors } from 'src/app/app.constants'; -const hoverTransitionTime = 300 -const defaultHoverColor = hexToColor('1bd8f4') +const hoverTransitionTime = 300; +const defaultHoverColor = hexToColor('1bd8f4'); // convert from this class's update format to TxSprite's update format -function toSpriteUpdate(params : ViewUpdateParams): SpriteUpdateParams { +function toSpriteUpdate(params: ViewUpdateParams): SpriteUpdateParams { return { start: (params.start || performance.now()) + (params.delay || 0), duration: params.duration, @@ -16,7 +16,7 @@ function toSpriteUpdate(params : ViewUpdateParams): SpriteUpdateParams { ...params.display.position, ...params.display.color, adjust: params.adjust - } + }; } export default class TxView implements TransactionStripped { @@ -33,41 +33,43 @@ export default class TxView implements TransactionStripped { hoverColor: Color | void; screenPosition: Square; - gridPosition : Square | void; + gridPosition: Square | void; dirty: boolean; - constructor (tx: TransactionStripped, vertexArray: FastVertexArray) { - this.txid = tx.txid - this.fee = tx.fee - this.vsize = tx.vsize - this.value = tx.value - this.feerate = tx.fee / tx.vsize - this.initialised = false - this.vertexArray = vertexArray + constructor(tx: TransactionStripped, vertexArray: FastVertexArray) { + this.txid = tx.txid; + this.fee = tx.fee; + this.vsize = tx.vsize; + this.value = tx.value; + this.feerate = tx.fee / tx.vsize; + this.initialised = false; + this.vertexArray = vertexArray; - this.hover = false + this.hover = false; - this.screenPosition = { x: 0, y: 0, s: 0 } + this.screenPosition = { x: 0, y: 0, s: 0 }; - this.dirty = true + this.dirty = true; } - destroy (): void { + destroy(): void { if (this.sprite) { - this.sprite.destroy() - this.sprite = null - this.initialised = false + this.sprite.destroy(); + this.sprite = null; + this.initialised = false; } } - applyGridPosition (position: Square): void { - if (!this.gridPosition) this.gridPosition = { x: 0, y: 0, s: 0 } - if (this.gridPosition.x != position.x || this.gridPosition.y != position.y || this.gridPosition.s != position.s) { - this.gridPosition.x = position.x - this.gridPosition.y = position.y - this.gridPosition.s = position.s - this.dirty = true + applyGridPosition(position: Square): void { + if (!this.gridPosition) { + this.gridPosition = { x: 0, y: 0, s: 0 }; + } + if (this.gridPosition.x !== position.x || this.gridPosition.y !== position.y || this.gridPosition.s !== position.s) { + this.gridPosition.x = position.x; + this.gridPosition.y = position.y; + this.gridPosition.s = position.s; + this.dirty = true; } } @@ -81,15 +83,17 @@ export default class TxView implements TransactionStripped { jitter: if set, adds a random amount to the delay, adjust: if true, modify an in-progress transition instead of replacing it */ - update (params: ViewUpdateParams): void { - if (params.jitter) params.delay += (Math.random() * params.jitter) + update(params: ViewUpdateParams): void { + if (params.jitter) { + params.delay += (Math.random() * params.jitter); + } if (!this.initialised || !this.sprite) { - this.initialised = true + this.initialised = true; this.sprite = new TxSprite( toSpriteUpdate(params), this.vertexArray - ) + ); // apply any pending hover event if (this.hover) { this.sprite.update({ @@ -97,48 +101,50 @@ export default class TxView implements TransactionStripped { duration: hoverTransitionTime, adjust: false, temp: true - }) + }); } } else { this.sprite.update( toSpriteUpdate(params) - ) + ); } - this.dirty = false + this.dirty = false; } // Temporarily override the tx color - setHover (hoverOn: boolean, color: Color | void = defaultHoverColor): void { + setHover(hoverOn: boolean, color: Color | void = defaultHoverColor): void { if (hoverOn) { - this.hover = true - this.hoverColor = color + this.hover = true; + this.hoverColor = color; this.sprite.update({ ...this.hoverColor, duration: hoverTransitionTime, adjust: false, temp: true - }) + }); } else { - this.hover = false - this.hoverColor = null - if (this.sprite) this.sprite.resume(hoverTransitionTime) + this.hover = false; + this.hoverColor = null; + if (this.sprite) { + this.sprite.resume(hoverTransitionTime); + } } - this.dirty = false + this.dirty = false; } - getColor (): Color { + getColor(): Color { let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => (this.feerate || 1) >= feeLvl); feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex; - return hexToColor(mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]) + return hexToColor(mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]); } } -function hexToColor (hex: string): Color { +function hexToColor(hex: string): Color { return { - r: parseInt(hex.slice(0,2), 16) / 255, - g: parseInt(hex.slice(2,4), 16) / 255, - b: parseInt(hex.slice(4,6), 16) / 255, + r: parseInt(hex.slice(0, 2), 16) / 255, + g: parseInt(hex.slice(2, 4), 16) / 255, + b: parseInt(hex.slice(4, 6), 16) / 255, a: 1 - } + }; } diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 0397b53ee..73c0e905d 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -80,7 +80,7 @@ export class StateService { bsqPrice$ = new ReplaySubject(1); mempoolInfo$ = new ReplaySubject(1); mempoolBlocks$ = new ReplaySubject(1); - mempoolBlock$ = new Subject(); + mempoolBlockTransactions$ = new Subject(); mempoolBlockDelta$ = new Subject(); txReplaced$ = new Subject(); utxoSpent$ = new Subject(); diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index 9507ea9d5..7cb279a08 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -309,12 +309,12 @@ export class WebsocketService { }); } - if (response['projected-mempool-block']) { - if (response['projected-mempool-block'].index == this.trackingMempoolBlock) { - if (response['projected-mempool-block'].block) { - this.stateService.mempoolBlock$.next(response['projected-mempool-block'].block); - } else if (response['projected-mempool-block'].delta) { - this.stateService.mempoolBlockDelta$.next(response['projected-mempool-block'].delta); + if (response['projected-block-transactions']) { + if (response['projected-block-transactions'].index == this.trackingMempoolBlock) { + if (response['projected-block-transactions'].blockTransactions) { + this.stateService.mempoolBlockTransactions$.next(response['projected-block-transactions'].blockTransactions); + } else if (response['projected-block-transactions'].delta) { + this.stateService.mempoolBlockDelta$.next(response['projected-block-transactions'].delta); } } }