diff --git a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts index 6a99edbf1..05602d577 100644 --- a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts +++ b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts @@ -46,6 +46,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest aggregatedHistory$: Observable; statsSubscription: Subscription; + aggregatedHistorySubscription: Subscription; + fragmentSubscription: Subscription; isLoading = true; formatNumber = formatNumber; timespan = ''; @@ -79,8 +81,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest } this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); - - this.route.fragment.subscribe((fragment) => { + + this.fragmentSubscription = this.route.fragment.subscribe((fragment) => { if (['24h', '3d', '1w', '1m', '3m', 'all'].indexOf(fragment) > -1) { this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false }); } @@ -113,7 +115,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest share(), ); - this.aggregatedHistory$.subscribe(); + this.aggregatedHistorySubscription = this.aggregatedHistory$.subscribe(); } ngOnChanges(changes: SimpleChanges): void { @@ -335,8 +337,8 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest } ngOnDestroy(): void { - if (this.statsSubscription) { - this.statsSubscription.unsubscribe(); - } + this.aggregatedHistorySubscription?.unsubscribe(); + this.fragmentSubscription?.unsubscribe(); + this.statsSubscription?.unsubscribe(); } } diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index d59e38c13..419a51995 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -172,13 +172,19 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On ngOnDestroy(): void { if (this.animationFrameRequest) { cancelAnimationFrame(this.animationFrameRequest); - clearTimeout(this.animationHeartBeat); } + clearTimeout(this.animationHeartBeat); if (this.canvas) { this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost); this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored); - this.themeChangedSubscription?.unsubscribe(); } + if (this.scene) { + this.scene.destroy(); + } + this.vertexArray.destroy(); + this.vertexArray = null; + this.themeChangedSubscription?.unsubscribe(); + this.searchSubscription?.unsubscribe(); } clear(direction): void { @@ -447,7 +453,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On } this.applyQueuedUpdates(); // skip re-render if there's no change to the scene - if (this.scene && this.gl) { + if (this.scene && this.gl && this.vertexArray) { /* SET UP SHADER UNIFORMS */ // screen dimensions this.gl.uniform2f(this.gl.getUniformLocation(this.shaderProgram, 'screenSize'), this.displayWidth, this.displayHeight); @@ -489,9 +495,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On if (this.running && this.scene && now <= (this.scene.animateUntil + 500)) { this.doRun(); } else { - if (this.animationHeartBeat) { - clearTimeout(this.animationHeartBeat); - } + clearTimeout(this.animationHeartBeat); this.animationHeartBeat = window.setTimeout(() => { this.start(); }, 1000); diff --git a/frontend/src/app/components/block-overview-graph/fast-vertex-array.ts b/frontend/src/app/components/block-overview-graph/fast-vertex-array.ts index 42439ef8d..8f9978d13 100644 --- a/frontend/src/app/components/block-overview-graph/fast-vertex-array.ts +++ b/frontend/src/app/components/block-overview-graph/fast-vertex-array.ts @@ -19,6 +19,7 @@ export class FastVertexArray { freeSlots: number[]; lastSlot: number; dirty = false; + destroyed = false; constructor(length, stride) { this.length = length; @@ -32,6 +33,9 @@ export class FastVertexArray { } insert(sprite: TxSprite): number { + if (this.destroyed) { + return; + } this.count++; let position; @@ -45,11 +49,14 @@ export class FastVertexArray { } } this.sprites[position] = sprite; - return position; this.dirty = true; + return position; } remove(index: number): void { + if (this.destroyed) { + return; + } this.count--; this.clearData(index); this.freeSlots.push(index); @@ -61,20 +68,26 @@ export class FastVertexArray { } setData(index: number, dataChunk: number[]): void { + if (this.destroyed) { + return; + } this.data.set(dataChunk, (index * this.stride)); this.dirty = true; } - clearData(index: number): void { + private clearData(index: number): void { this.data.fill(0, (index * this.stride), ((index + 1) * this.stride)); this.dirty = true; } getData(index: number): Float32Array { + if (this.destroyed) { + return; + } return this.data.subarray(index, this.stride); } - expand(): void { + private expand(): void { this.length *= 2; const newData = new Float32Array(this.length * this.stride); newData.set(this.data); @@ -82,7 +95,7 @@ export class FastVertexArray { this.dirty = true; } - compact(): void { + private 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) { @@ -110,4 +123,13 @@ export class FastVertexArray { getVertexData(): Float32Array { return this.data; } + + destroy(): void { + this.data = null; + this.sprites = null; + this.freeSlots = null; + this.lastSlot = 0; + this.dirty = false; + this.destroyed = true; + } } diff --git a/frontend/src/app/components/block-view/block-view.component.ts b/frontend/src/app/components/block-view/block-view.component.ts index b5d5256ee..19a18383e 100644 --- a/frontend/src/app/components/block-view/block-view.component.ts +++ b/frontend/src/app/components/block-view/block-view.component.ts @@ -116,7 +116,7 @@ export class BlockViewComponent implements OnInit, OnDestroy { this.isLoadingBlock = false; this.isLoadingOverview = true; }), - shareReplay(1) + shareReplay({ bufferSize: 1, refCount: true }) ); this.overviewSubscription = block$.pipe( @@ -176,5 +176,8 @@ export class BlockViewComponent implements OnInit, OnDestroy { if (this.queryParamsSubscription) { this.queryParamsSubscription.unsubscribe(); } + if (this.blockGraph) { + this.blockGraph.destroy(); + } } } diff --git a/frontend/src/app/components/block/block-preview.component.ts b/frontend/src/app/components/block/block-preview.component.ts index b2fc3fb6f..42a47f3c4 100644 --- a/frontend/src/app/components/block/block-preview.component.ts +++ b/frontend/src/app/components/block/block-preview.component.ts @@ -117,7 +117,7 @@ export class BlockPreviewComponent implements OnInit, OnDestroy { this.openGraphService.waitOver('block-data-' + this.rawId); }), throttleTime(50, asyncScheduler, { leading: true, trailing: true }), - shareReplay(1) + shareReplay({ bufferSize: 1, refCount: true }) ); this.overviewSubscription = block$.pipe( diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index dab3c00fa..ddcf023ed 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -1,8 +1,8 @@ import { Component, OnInit, OnDestroy, ViewChildren, QueryList, ChangeDetectorRef } from '@angular/core'; import { Location } from '@angular/common'; -import { ActivatedRoute, ParamMap, Router } from '@angular/router'; +import { ActivatedRoute, ParamMap, Params, Router } from '@angular/router'; import { ElectrsApiService } from '@app/services/electrs-api.service'; -import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter } from 'rxjs/operators'; +import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, filter, take } from 'rxjs/operators'; import { Observable, of, Subscription, asyncScheduler, EMPTY, combineLatest, forkJoin } from 'rxjs'; import { StateService } from '@app/services/state.service'; import { SeoService } from '@app/services/seo.service'; @@ -68,6 +68,7 @@ export class BlockComponent implements OnInit, OnDestroy { paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5; numUnexpected: number = 0; mode: 'projected' | 'actual' = 'projected'; + currentQueryParams: Params; overviewSubscription: Subscription; accelerationsSubscription: Subscription; @@ -80,8 +81,8 @@ export class BlockComponent implements OnInit, OnDestroy { timeLtr: boolean; childChangeSubscription: Subscription; auditPrefSubscription: Subscription; + isAuditEnabledSubscription: Subscription; oobSubscription: Subscription; - priceSubscription: Subscription; blockConversion: Price; @@ -118,7 +119,7 @@ export class BlockComponent implements OnInit, OnDestroy { this.setAuditAvailable(this.auditSupported); if (this.auditSupported) { - this.isAuditEnabledFromParam().subscribe(auditParam => { + this.isAuditEnabledSubscription = this.isAuditEnabledFromParam().subscribe(auditParam => { if (this.auditParamEnabled) { this.auditModeEnabled = auditParam; } else { @@ -281,7 +282,7 @@ export class BlockComponent implements OnInit, OnDestroy { } }), throttleTime(300, asyncScheduler, { leading: true, trailing: true }), - shareReplay(1) + shareReplay({ bufferSize: 1, refCount: true }) ); this.overviewSubscription = this.block$.pipe( @@ -363,6 +364,7 @@ export class BlockComponent implements OnInit, OnDestroy { .subscribe((network) => this.network = network); this.queryParamsSubscription = this.route.queryParams.subscribe((params) => { + this.currentQueryParams = params; if (params.showDetails === 'true') { this.showDetails = true; } else { @@ -414,6 +416,7 @@ export class BlockComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.stateService.markBlock$.next({}); this.overviewSubscription?.unsubscribe(); + this.accelerationsSubscription?.unsubscribe(); this.keyNavigationSubscription?.unsubscribe(); this.blocksSubscription?.unsubscribe(); this.cacheBlocksSubscription?.unsubscribe(); @@ -421,8 +424,16 @@ export class BlockComponent implements OnInit, OnDestroy { this.queryParamsSubscription?.unsubscribe(); this.timeLtrSubscription?.unsubscribe(); this.childChangeSubscription?.unsubscribe(); - this.priceSubscription?.unsubscribe(); + this.auditPrefSubscription?.unsubscribe(); + this.isAuditEnabledSubscription?.unsubscribe(); this.oobSubscription?.unsubscribe(); + this.priceSubscription?.unsubscribe(); + this.blockGraphProjected.forEach(graph => { + graph.destroy(); + }); + this.blockGraphActual.forEach(graph => { + graph.destroy(); + }); } // TODO - Refactor this.fees/this.reward for liquid because it is not @@ -733,19 +744,18 @@ export class BlockComponent implements OnInit, OnDestroy { toggleAuditMode(): void { this.stateService.hideAudit.next(this.auditModeEnabled); - this.route.queryParams.subscribe(params => { - const queryParams = { ...params }; - delete queryParams['audit']; + const queryParams = { ...this.currentQueryParams }; + delete queryParams['audit']; - let newUrl = this.router.url.split('?')[0]; - const queryString = new URLSearchParams(queryParams).toString(); - if (queryString) { - newUrl += '?' + queryString; - } - - this.location.replaceState(newUrl); - }); + let newUrl = this.router.url.split('?')[0]; + const queryString = new URLSearchParams(queryParams).toString(); + if (queryString) { + newUrl += '?' + queryString; + } + this.location.replaceState(newUrl); + // avoid duplicate subscriptions + this.auditPrefSubscription?.unsubscribe(); this.auditPrefSubscription = this.stateService.hideAudit.subscribe((hide) => { this.auditModeEnabled = !hide; this.showAudit = this.auditAvailable && this.auditModeEnabled; @@ -762,7 +772,7 @@ export class BlockComponent implements OnInit, OnDestroy { return this.route.queryParams.pipe( map(params => { this.auditParamEnabled = 'audit' in params; - + return this.auditParamEnabled ? !(params['audit'] === 'false') : true; }) ); diff --git a/frontend/src/app/components/eight-blocks/eight-blocks.component.ts b/frontend/src/app/components/eight-blocks/eight-blocks.component.ts index 8ca8437ac..0e0861382 100644 --- a/frontend/src/app/components/eight-blocks/eight-blocks.component.ts +++ b/frontend/src/app/components/eight-blocks/eight-blocks.component.ts @@ -162,6 +162,9 @@ export class EightBlocksComponent implements OnInit, OnDestroy { this.cacheBlocksSubscription?.unsubscribe(); this.networkChangedSubscription?.unsubscribe(); this.queryParamsSubscription?.unsubscribe(); + this.blockGraphs.forEach(graph => { + graph.destroy(); + }); } shiftTestBlocks(): void { 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 fca8b279c..a46be2733 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 @@ -120,6 +120,7 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang } ngOnDestroy(): void { + this.blockGraph?.destroy(); this.blockSub.unsubscribe(); this.timeLtrSubscription.unsubscribe(); this.websocketService.stopTrackMempoolBlock();