Optimize block visualization rendering

This commit is contained in:
Mononaut
2022-06-14 23:08:18 +00:00
parent 7f4c6352ba
commit 300f5375c8
5 changed files with 108 additions and 54 deletions

View File

@@ -1,4 +1,4 @@
import { Component, ElementRef, ViewChild, HostListener, Input, Output, EventEmitter, NgZone, AfterViewInit } from '@angular/core';
import { Component, ElementRef, ViewChild, HostListener, Input, Output, EventEmitter, NgZone, AfterViewInit, OnDestroy } from '@angular/core';
import { MempoolBlockDelta, TransactionStripped } from 'src/app/interfaces/websocket.interface';
import { WebsocketService } from 'src/app/services/websocket.service';
import { FastVertexArray } from './fast-vertex-array';
@@ -11,7 +11,7 @@ import TxView from './tx-view';
templateUrl: './block-overview-graph.component.html',
styleUrls: ['./block-overview-graph.component.scss'],
})
export class BlockOverviewGraphComponent implements AfterViewInit {
export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy {
@Input() isLoading: boolean;
@Input() resolution: number;
@Input() blockLimit: number;
@@ -24,6 +24,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit {
gl: WebGLRenderingContext;
animationFrameRequest: number;
animationHeartBeat: number;
displayWidth: number;
displayHeight: number;
cssWidth: number;
@@ -50,34 +51,46 @@ export class BlockOverviewGraphComponent implements AfterViewInit {
this.resizeCanvas();
}
ngOnDestroy(): void {
if (this.animationFrameRequest) {
cancelAnimationFrame(this.animationFrameRequest);
clearTimeout(this.animationHeartBeat);
}
}
clear(direction): void {
this.exit(direction);
this.hoverTx = null;
this.selectedTx = null;
this.txPreviewEvent.emit(null);
this.start();
}
enter(transactions: TransactionStripped[], direction: string): void {
if (this.scene) {
this.scene.enter(transactions, direction);
this.start();
}
}
exit(direction: string): void {
if (this.scene) {
this.scene.exit(direction);
this.start();
}
}
replace(transactions: TransactionStripped[], direction: string, sort: boolean = true): void {
if (this.scene) {
this.scene.replace(transactions || [], direction, sort);
this.start();
}
}
update(add: TransactionStripped[], remove: string[], direction: string = 'left', resetLayout: boolean = false): void {
if (this.scene) {
this.scene.update(add, remove, direction, resetLayout);
this.start();
}
}
@@ -140,6 +153,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit {
}
if (this.scene) {
this.scene.resize({ width: this.displayWidth, height: this.displayHeight });
this.start();
} else {
this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution,
blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray });
@@ -182,44 +196,64 @@ export class BlockOverviewGraphComponent implements AfterViewInit {
start(): void {
this.running = true;
this.ngZone.runOutsideAngular(() => this.run());
this.ngZone.runOutsideAngular(() => this.doRun());
}
doRun(): void {
if (this.animationFrameRequest) {
cancelAnimationFrame(this.animationFrameRequest);
}
this.animationFrameRequest = requestAnimationFrame(() => this.run());
}
run(now?: DOMHighResTimeStamp): void {
if (!now) {
now = performance.now();
}
// skip re-render if there's no change to the scene
if (this.scene) {
/* SET UP SHADER UNIFORMS */
// screen dimensions
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);
/* SET UP SHADER UNIFORMS */
// screen dimensions
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);
if (this.vertexArray.dirty) {
/* SET UP SHADER ATTRIBUTES */
Object.keys(attribs).forEach((key, i) => {
this.gl.vertexAttribPointer(attribs[key].pointer,
attribs[key].count, // number of primitives in this attribute
this.gl[attribs[key].type], // type of primitive in this attribute (e.g. gl.FLOAT)
false, // never normalised
stride, // distance between values of the same attribute
attribs[key].offset); // offset of the first value
});
/* SET UP SHADER ATTRIBUTES */
Object.keys(attribs).forEach((key, i) => {
this.gl.vertexAttribPointer(attribs[key].pointer,
attribs[key].count, // number of primitives in this attribute
this.gl[attribs[key].type], // type of primitive in this attribute (e.g. gl.FLOAT)
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);
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.vertexArray.dirty = false;
} else {
const pointArray = this.vertexArray.getVertexData();
if (pointArray.length) {
this.gl.drawArrays(this.gl.TRIANGLES, 0, pointArray.length / TxSprite.vertexSize);
}
}
}
/* LOOP */
if (this.running) {
if (this.animationFrameRequest) {
cancelAnimationFrame(this.animationFrameRequest);
this.animationFrameRequest = null;
if (this.running && this.scene && now <= (this.scene.animateUntil + 500)) {
this.doRun();
} else {
if (this.animationHeartBeat) {
clearTimeout(this.animationHeartBeat);
}
this.animationFrameRequest = requestAnimationFrame(() => this.run());
this.animationHeartBeat = window.setTimeout(() => {
this.start();
}, 1000);
}
}
@@ -246,11 +280,15 @@ export class BlockOverviewGraphComponent implements AfterViewInit {
const currentPreview = this.selectedTx || this.hoverTx;
if (selected !== currentPreview) {
if (currentPreview) {
currentPreview.setHover(false);
if (currentPreview && this.scene) {
this.scene.setHover(currentPreview, false);
this.start();
}
if (selected) {
selected.setHover(true);
if (selected && this.scene) {
this.scene.setHover(selected, true);
this.start();
}
this.txPreviewEvent.emit({
txid: selected.txid,
fee: selected.fee,