Stream projected block deltas instead of full data
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
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'
|
||||
|
||||
export default class BlockScene {
|
||||
scene: { count: number, offset: { x: number, y: number}};
|
||||
vertexArray: FastVertexArray;
|
||||
txs: { [key: string]: TxView };
|
||||
width: number;
|
||||
height: number;
|
||||
@@ -17,8 +20,8 @@ export default class BlockScene {
|
||||
layout: BlockLayout;
|
||||
dirty: boolean;
|
||||
|
||||
constructor ({ width, height, resolution, blockLimit }: { width: number, height: number, resolution: number, blockLimit: number}) {
|
||||
this.init({ width, height, resolution, blockLimit })
|
||||
constructor ({ width, height, resolution, blockLimit, vertexArray }: { width: number, height: number, resolution: number, blockLimit: number, vertexArray: FastVertexArray }) {
|
||||
this.init({ width, height, resolution, blockLimit, vertexArray })
|
||||
}
|
||||
|
||||
destroy (): void {
|
||||
@@ -37,55 +40,70 @@ export default class BlockScene {
|
||||
}
|
||||
|
||||
// Animate new block entering scene
|
||||
enter (txs: TxView[], direction) {
|
||||
this.replace(txs, [], direction)
|
||||
enter (txs: TransactionStripped[], direction) {
|
||||
this.replace(txs, direction)
|
||||
}
|
||||
|
||||
// Animate block leaving scene
|
||||
exit (direction: string): TxView[] {
|
||||
const removed = []
|
||||
exit (direction: string): void {
|
||||
const startTime = performance.now()
|
||||
Object.values(this.txs).forEach(tx => {
|
||||
this.remove(tx.txid, startTime, direction)
|
||||
removed.push(tx)
|
||||
})
|
||||
return removed
|
||||
}
|
||||
|
||||
// Reset layout and replace with new set of transactions
|
||||
replace (txs: TxView[], remove: TxView[], direction: string = 'left'): void {
|
||||
const startTime = performance.now()
|
||||
this.removeBatch(remove.map(tx => tx.txid), startTime, direction)
|
||||
const removed = this.removeBatch(Object.keys(this.txs), startTime, direction)
|
||||
|
||||
// clean up sprites
|
||||
setTimeout(() => {
|
||||
remove.forEach(tx => {
|
||||
removed.forEach(tx => {
|
||||
tx.destroy()
|
||||
})
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
// Reset layout and replace with new set of transactions
|
||||
replace (txs: TransactionStripped[], direction: string = 'left'): void {
|
||||
const startTime = performance.now()
|
||||
const nextIds = {}
|
||||
const remove = []
|
||||
txs.forEach(tx => {
|
||||
nextIds[tx.txid] = true
|
||||
})
|
||||
Object.keys(this.txs).forEach(txid => {
|
||||
if (!nextIds[txid]) remove.push(txid)
|
||||
})
|
||||
txs.forEach(tx => {
|
||||
if (!this.txs[tx.txid]) this.txs[tx.txid] = new TxView(tx, this.vertexArray)
|
||||
})
|
||||
|
||||
const removed = this.removeBatch(remove, startTime, direction)
|
||||
|
||||
// clean up sprites
|
||||
setTimeout(() => {
|
||||
removed.forEach(tx => {
|
||||
tx.destroy()
|
||||
})
|
||||
}, 1000)
|
||||
|
||||
this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight })
|
||||
|
||||
txs.sort((a,b) => { return b.feerate - a.feerate }).forEach(tx => {
|
||||
this.insert(tx, startTime, direction)
|
||||
Object.values(this.txs).sort((a,b) => { return b.feerate - a.feerate }).forEach(tx => {
|
||||
this.place(tx)
|
||||
})
|
||||
|
||||
this.updateAll(startTime, direction)
|
||||
}
|
||||
|
||||
update (add: TxView[], remove: TxView[], direction: string = 'left'): void {
|
||||
update (add: TransactionStripped[], remove: string[], direction: string = 'left'): void {
|
||||
const startTime = performance.now()
|
||||
this.removeBatch(remove.map(tx => tx.txid), startTime, direction)
|
||||
const removed = this.removeBatch(remove, startTime, direction)
|
||||
|
||||
// clean up sprites
|
||||
setTimeout(() => {
|
||||
remove.forEach(tx => {
|
||||
removed.forEach(tx => {
|
||||
tx.destroy()
|
||||
})
|
||||
}, 1000)
|
||||
|
||||
// try to insert new txs directly
|
||||
const remaining = []
|
||||
add = add.sort((a,b) => { return b.feerate - a.feerate })
|
||||
add.forEach(tx => {
|
||||
add.map(tx => new TxView(tx, this.vertexArray)).sort((a,b) => { return b.feerate - a.feerate }).forEach(tx => {
|
||||
if (!this.tryInsertByFee(tx)) {
|
||||
remaining.push(tx)
|
||||
}
|
||||
@@ -106,7 +124,9 @@ export default class BlockScene {
|
||||
} else return null
|
||||
}
|
||||
|
||||
private init ({ width, height, resolution, blockLimit }: { width: number, height: number, resolution: number, blockLimit: number}): void {
|
||||
private init ({ width, height, resolution, blockLimit, vertexArray }: { width: number, height: number, resolution: number, blockLimit: number, vertexArray: FastVertexArray }): void {
|
||||
this.vertexArray = vertexArray
|
||||
|
||||
this.scene = {
|
||||
count: 0,
|
||||
offset: {
|
||||
@@ -300,11 +320,11 @@ export default class BlockScene {
|
||||
}
|
||||
}
|
||||
|
||||
private removeBatch (ids: string[], startTime: number, direction: string = 'left'): (TxView | void)[] {
|
||||
private removeBatch (ids: string[], startTime: number, direction: string = 'left'): TxView[] {
|
||||
if (!startTime) startTime = performance.now()
|
||||
return ids.map(id => {
|
||||
return this.remove(id, startTime, direction)
|
||||
}).filter(tx => !!tx)
|
||||
}).filter(tx => tx != null) as TxView[]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, ElementRef, ViewChild, HostListener, Input, Output, EventEmitter, OnInit, OnDestroy, OnChanges, ChangeDetectionStrategy, NgZone } from '@angular/core';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
import { MempoolBlockWithTransactions, TransactionStripped } from 'src/app/interfaces/websocket.interface';
|
||||
import { MempoolBlockWithTransactions, MempoolBlockDelta, TransactionStripped } from 'src/app/interfaces/websocket.interface';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||
import { FastVertexArray } from './fast-vertex-array';
|
||||
@@ -29,14 +29,13 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
||||
vertexArray: FastVertexArray;
|
||||
running: boolean;
|
||||
scene: BlockScene;
|
||||
txViews: { [key: string]: TxView };
|
||||
hoverTx: TxView | void;
|
||||
selectedTx: TxView | void;
|
||||
lastBlockHeight: number;
|
||||
blockIndex: number;
|
||||
|
||||
sub: Subscription;
|
||||
mempoolBlock$: Observable<MempoolBlockWithTransactions>;
|
||||
blockSub: Subscription;
|
||||
deltaSub: Subscription;
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
@@ -44,14 +43,14 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
||||
readonly _ngZone: NgZone,
|
||||
) {
|
||||
this.vertexArray = new FastVertexArray(512, TxSprite.dataSize)
|
||||
this.txViews = {}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.websocketService.startTrackMempoolBlock(this.index);
|
||||
this.mempoolBlock$ = this.stateService.mempoolBlock$
|
||||
this.sub = this.mempoolBlock$.subscribe((block) => {
|
||||
this.updateBlock(block)
|
||||
this.blockSub = this.stateService.mempoolBlock$.subscribe((block) => {
|
||||
this.replaceBlock(block)
|
||||
})
|
||||
this.deltaSub = this.stateService.mempoolBlockDelta$.subscribe((delta) => {
|
||||
this.updateBlock(delta)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -66,65 +65,47 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
||||
|
||||
ngOnChanges(changes): void {
|
||||
if (changes.index) {
|
||||
this.clearBlock(changes.index.currentValue)
|
||||
this.websocketService.startTrackMempoolBlock(changes.index.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.sub.unsubscribe();
|
||||
this.blockSub.unsubscribe();
|
||||
this.deltaSub.unsubscribe();
|
||||
this.websocketService.stopTrackMempoolBlock();
|
||||
}
|
||||
|
||||
clearBlock(index: number): void {
|
||||
if (this.scene && index != this.blockIndex) {
|
||||
const direction = (this.blockIndex == null || this.index < this.blockIndex) ? 'left' : 'right'
|
||||
const removed = this.scene.exit(direction)
|
||||
setTimeout(() => {
|
||||
removed.forEach(tx => tx.destroy())
|
||||
}, 1000)
|
||||
this.txViews = {}
|
||||
this.scene = null
|
||||
replaceBlock(block: MempoolBlockWithTransactions): void {
|
||||
if (!this.scene) {
|
||||
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.exit(direction)
|
||||
this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: 75, blockLimit: this.stateService.blockVSize, vertexArray: this.vertexArray })
|
||||
this.scene.enter(block.transactions, direction)
|
||||
} else {
|
||||
this.scene.replace(block.transactions, blockMined ? 'right' : 'left')
|
||||
}
|
||||
|
||||
this.lastBlockHeight = this.stateService.latestBlockHeight
|
||||
this.blockIndex = this.index
|
||||
}
|
||||
|
||||
updateBlock(block: MempoolBlockWithTransactions): void {
|
||||
updateBlock(delta: MempoolBlockDelta): void {
|
||||
if (!this.scene) {
|
||||
this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: 75, blockLimit: this.stateService.blockVSize })
|
||||
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 nextIds = {}
|
||||
let remove = []
|
||||
let add = []
|
||||
block.transactions.forEach(tx => {
|
||||
nextIds[tx.txid] = true
|
||||
})
|
||||
|
||||
// List old transactions to remove
|
||||
Object.keys(this.txViews).forEach(txid => {
|
||||
if (!nextIds[txid]) {
|
||||
remove.push(this.txViews[txid])
|
||||
delete this.txViews[txid]
|
||||
}
|
||||
})
|
||||
|
||||
// List new transactions to add
|
||||
block.transactions.forEach(tx => {
|
||||
if (!this.txViews[tx.txid]) {
|
||||
const txView = new TxView(tx, this.vertexArray)
|
||||
this.txViews[tx.txid] = txView
|
||||
add.push(txView)
|
||||
}
|
||||
})
|
||||
|
||||
if (this.blockIndex != this.index) {
|
||||
const direction = (this.blockIndex == null || this.index < this.blockIndex) ? 'left' : 'right'
|
||||
this.scene.enter(Object.values(this.txViews), direction)
|
||||
} else if (blockMined) {
|
||||
this.scene.replace(Object.values(this.txViews), remove, '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(add, remove, 'left')
|
||||
this.scene.update(delta.added, delta.removed, blockMined ? 'right' : 'left')
|
||||
}
|
||||
|
||||
this.lastBlockHeight = this.stateService.latestBlockHeight
|
||||
|
||||
Reference in New Issue
Block a user