Skip to content

Commit bc1c647

Browse files
committed
refactor: extract reusable functions
1 parent 02fae1e commit bc1c647

10 files changed

Lines changed: 171 additions & 215 deletions

File tree

log-viewer/src/features/timeline/optimised/FlameChart.ts

Lines changed: 23 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,7 @@ import { MeshMarkerRenderer } from './markers/MeshMarkerRenderer.js';
3030
import { MeshRectangleRenderer } from './MeshRectangleRenderer.js';
3131
import { MeshAxisRenderer } from './time-axis/MeshAxisRenderer.js';
3232

33-
import { EventBatchRenderer } from './EventBatchRenderer.js';
34-
import { TimelineMarkerRenderer } from './markers/TimelineMarkerRenderer.js';
3533
import { TextLabelRenderer } from './TextLabelRenderer.js';
36-
import { AxisRenderer } from './time-axis/AxisRenderer.js';
3734

3835
import { cssColorToPixi } from './BucketColorResolver.js';
3936
import { HitDetector } from './interaction/HitDetector.js';
@@ -129,9 +126,9 @@ export class FlameChart<E extends EventNode = EventNode> {
129126
private callbacks: FlameChartCallbacks = {};
130127

131128
private rectangleManager: RectangleCache | null = null;
132-
private batchRenderer: EventBatchRenderer | MeshRectangleRenderer | null = null;
133-
private axisRenderer: AxisRenderer | MeshAxisRenderer | null = null;
134-
private markerRenderer: TimelineMarkerRenderer | MeshMarkerRenderer | null = null;
129+
private batchRenderer: MeshRectangleRenderer | null = null;
130+
private axisRenderer: MeshAxisRenderer | null = null;
131+
private markerRenderer: MeshMarkerRenderer | null = null;
135132
private resizeHandler: TimelineResizeHandler | null = null;
136133

137134
// Search orchestrator (owns search state and rendering)
@@ -287,24 +284,13 @@ export class FlameChart<E extends EventNode = EventNode> {
287284
// Initialize viewport animator for smooth transitions
288285
this.viewportAnimator = new ViewportAnimator();
289286

290-
// Determine renderer type: mesh is default for testing
291-
const useMeshRenderer = options.renderer !== 'sprite';
292-
293287
// Create truncation renderer FIRST (renders behind axis and events)
294288
if (this.markerContainer && this.markers.length > 0) {
295-
if (useMeshRenderer) {
296-
this.markerRenderer = new MeshMarkerRenderer(
297-
this.markerContainer,
298-
this.viewport,
299-
this.markers,
300-
);
301-
} else {
302-
this.markerRenderer = new TimelineMarkerRenderer(
303-
this.markerContainer,
304-
this.viewport,
305-
this.markers,
306-
);
307-
}
289+
this.markerRenderer = new MeshMarkerRenderer(
290+
this.markerContainer,
291+
this.viewport,
292+
this.markers,
293+
);
308294
}
309295

310296
// Create axis renderer SECOND
@@ -316,11 +302,7 @@ export class FlameChart<E extends EventNode = EventNode> {
316302
fontSize: 11,
317303
minLabelSpacing: 120,
318304
};
319-
if (useMeshRenderer) {
320-
this.axisRenderer = new MeshAxisRenderer(this.axisContainer, axisConfig);
321-
} else {
322-
this.axisRenderer = new AxisRenderer(this.axisContainer, axisConfig);
323-
}
305+
this.axisRenderer = new MeshAxisRenderer(this.axisContainer, axisConfig);
324306
this.axisRenderer.setScreenSpaceContainer(this.uiContainer);
325307
// No minimap offset needed - main timeline has its own canvas
326308
}
@@ -342,11 +324,7 @@ export class FlameChart<E extends EventNode = EventNode> {
342324

343325
// Create batch renderer (pure rendering, receives rectangles from RectangleCache)
344326
if (this.worldContainer && this.state) {
345-
if (useMeshRenderer) {
346-
this.batchRenderer = new MeshRectangleRenderer(this.worldContainer, this.state.batches);
347-
} else {
348-
this.batchRenderer = new EventBatchRenderer(this.worldContainer, this.state.batches);
349-
}
327+
this.batchRenderer = new MeshRectangleRenderer(this.worldContainer, this.state.batches);
350328
}
351329

352330
// Create text label renderer (renders method names on rectangles)
@@ -359,17 +337,17 @@ export class FlameChart<E extends EventNode = EventNode> {
359337
this.worldContainer.sortableChildren = true;
360338
}
361339

362-
// For mesh renderers, set stage container for clip-space rendering
363-
if (useMeshRenderer && this.app) {
340+
// Set stage container for clip-space rendering
341+
if (this.app) {
364342
const stage = this.app.stage;
365-
if (this.batchRenderer && 'setStageContainer' in this.batchRenderer) {
366-
(this.batchRenderer as MeshRectangleRenderer).setStageContainer(stage);
343+
if (this.batchRenderer) {
344+
this.batchRenderer.setStageContainer(stage);
367345
}
368-
if (this.markerRenderer && 'setStageContainer' in this.markerRenderer) {
369-
(this.markerRenderer as MeshMarkerRenderer).setStageContainer(stage);
346+
if (this.markerRenderer) {
347+
this.markerRenderer.setStageContainer(stage);
370348
}
371-
if (this.axisRenderer && 'setStageContainer' in this.axisRenderer) {
372-
(this.axisRenderer as MeshAxisRenderer).setStageContainer(stage);
349+
if (this.axisRenderer) {
350+
this.axisRenderer.setStageContainer(stage);
373351
}
374352
// No minimap offset needed - main timeline has its own canvas
375353
}
@@ -414,7 +392,7 @@ export class FlameChart<E extends EventNode = EventNode> {
414392

415393
// Initialize search if enabled via options
416394
if (options.enableSearch) {
417-
this.setupSearch(useMeshRenderer);
395+
this.setupSearch();
418396
}
419397

420398
// Initial render
@@ -545,10 +523,8 @@ export class FlameChart<E extends EventNode = EventNode> {
545523

546524
/**
547525
* Setup search orchestrator for find and navigation functionality.
548-
*
549-
* @param useMeshRenderer - Whether to use mesh-based renderers
550526
*/
551-
private setupSearch(useMeshRenderer: boolean): void {
527+
private setupSearch(): void {
552528
if (
553529
!this.rectangleManager ||
554530
!this.treeNodes ||
@@ -583,13 +559,12 @@ export class FlameChart<E extends EventNode = EventNode> {
583559
this.rectangleManager,
584560
this.state.batches,
585561
this.textLabelRenderer,
586-
useMeshRenderer,
587562
this.viewport,
588563
this.mainTimelineYOffset,
589564
);
590565

591-
// For mesh renderers, set stage container for clip-space rendering
592-
if (useMeshRenderer && this.app) {
566+
// Set stage container for clip-space rendering
567+
if (this.app) {
593568
this.searchOrchestrator.setStageContainer(this.app.stage);
594569
}
595570
}
@@ -822,7 +797,7 @@ export class FlameChart<E extends EventNode = EventNode> {
822797
startTimeMs: number,
823798
firstTimestampNs: number,
824799
): void {
825-
if (this.axisRenderer instanceof MeshAxisRenderer) {
800+
if (this.axisRenderer) {
826801
this.axisRenderer.setTimeDisplayMode(mode, startTimeMs, firstTimestampNs);
827802

828803
if (!this.state) {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2026 Certinia Inc. All rights reserved.
3+
*/
4+
5+
/**
6+
* MarkerProcessor
7+
*
8+
* Shared utilities for processing timeline markers (truncation regions).
9+
* Consolidates pre-blended color computation and marker processing logic
10+
* used by both MeshMarkerRenderer and TimelineMarkerRenderer.
11+
*/
12+
13+
import type { MarkerType, TimelineMarker } from '../../types/flamechart.types.js';
14+
import { MARKER_ALPHA, MARKER_COLORS, SEVERITY_RANK } from '../../types/flamechart.types.js';
15+
import { blendWithBackground } from '../BucketColorResolver.js';
16+
17+
/**
18+
* Pre-blended opaque marker colors (MARKER_COLORS blended at MARKER_ALPHA opacity).
19+
* Computed once at module load time for performance.
20+
*
21+
* Using pre-blended colors avoids runtime alpha compositing on the GPU,
22+
* which is more efficient for static opacity values.
23+
*/
24+
export const MARKER_COLORS_BLENDED: Record<MarkerType, number> = {
25+
error: blendWithBackground(MARKER_COLORS.error, MARKER_ALPHA),
26+
skip: blendWithBackground(MARKER_COLORS.skip, MARKER_ALPHA),
27+
unexpected: blendWithBackground(MARKER_COLORS.unexpected, MARKER_ALPHA),
28+
};
29+
30+
/**
31+
* Sort markers by startTime, then by severity (higher severity first for stacking).
32+
*
33+
* @param markers - Array of markers to sort
34+
* @returns New sorted array (does not mutate input)
35+
*/
36+
export function sortMarkersByTimeAndSeverity(
37+
markers: readonly TimelineMarker[],
38+
): readonly TimelineMarker[] {
39+
return [...markers].sort((a, b) => {
40+
if (a.startTime !== b.startTime) {
41+
return a.startTime - b.startTime;
42+
}
43+
return SEVERITY_RANK[b.type] - SEVERITY_RANK[a.type];
44+
});
45+
}

log-viewer/src/features/timeline/optimised/markers/MeshMarkerRenderer.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,12 @@
1616
*/
1717

1818
import { Container, Geometry, Mesh, Shader } from 'pixi.js';
19-
import type { MarkerType, TimelineMarker } from '../../types/flamechart.types.js';
20-
import { MARKER_ALPHA, MARKER_COLORS, SEVERITY_RANK } from '../../types/flamechart.types.js';
21-
import { blendWithBackground } from '../BucketColorResolver.js';
19+
import type { TimelineMarker } from '../../types/flamechart.types.js';
2220
import { RectangleGeometry, type ViewportTransform } from '../RectangleGeometry.js';
2321
import { createRectangleShader } from '../RectangleShader.js';
2422
import type { TimelineViewport } from '../TimelineViewport.js';
2523
import { hitTestMarkers, type MarkerIndicator } from './MarkerHitTest.js';
26-
27-
/**
28-
* Pre-blended opaque marker colors (MARKER_COLORS blended at MARKER_ALPHA opacity).
29-
* Computed once at module load time for performance.
30-
*/
31-
const MARKER_COLORS_BLENDED: Record<MarkerType, number> = {
32-
error: blendWithBackground(MARKER_COLORS.error, MARKER_ALPHA),
33-
skip: blendWithBackground(MARKER_COLORS.skip, MARKER_ALPHA),
34-
unexpected: blendWithBackground(MARKER_COLORS.unexpected, MARKER_ALPHA),
35-
};
24+
import { MARKER_COLORS_BLENDED, sortMarkersByTimeAndSeverity } from './MarkerProcessor.js';
3625

3726
/**
3827
* Renders marker indicators as semi-transparent vertical bands using Mesh.
@@ -64,12 +53,7 @@ export class MeshMarkerRenderer {
6453
this.viewport = viewport;
6554

6655
// Sort markers by startTime for efficient end time resolution
67-
this.markers = [...markers].sort((a, b) => {
68-
if (a.startTime !== b.startTime) {
69-
return a.startTime - b.startTime;
70-
}
71-
return SEVERITY_RANK[b.type] - SEVERITY_RANK[a.type];
72-
});
56+
this.markers = sortMarkersByTimeAndSeverity(markers);
7357

7458
// Create geometry and shader
7559
this.geometry = new RectangleGeometry();
@@ -239,7 +223,7 @@ export class MeshMarkerRenderer {
239223
* @param markers - New array of markers
240224
*/
241225
public updateMarkers(markers: readonly TimelineMarker[]): void {
242-
(this.markers as TimelineMarker[]) = [...markers].sort((a, b) => a.startTime - b.startTime);
226+
this.markers = sortMarkersByTimeAndSeverity(markers);
243227
this.visibleIndicators = [];
244228
}
245229

log-viewer/src/features/timeline/optimised/measurement/AreaZoomRenderer.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,11 @@ export class AreaZoomRenderer {
4343
/** HTML container for the label */
4444
private labelElement: HTMLDivElement;
4545

46-
/** Parent HTML container for positioning */
47-
private container: HTMLElement;
48-
4946
/**
5047
* @param pixiContainer - PixiJS container for graphics (worldContainer)
5148
* @param htmlContainer - HTML container for label positioning
5249
*/
5350
constructor(pixiContainer: PIXI.Container, htmlContainer: HTMLElement) {
54-
this.container = htmlContainer;
55-
5651
// Create graphics for overlay - render above frames but below tooltips
5752
this.graphics = new PIXI.Graphics();
5853
this.graphics.zIndex = 5; // Above measurement overlay (4)

log-viewer/src/features/timeline/optimised/minimap/MinimapDensityQuery.ts

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -431,69 +431,6 @@ export class MinimapDensityQuery {
431431
};
432432
}
433433

434-
/**
435-
* Compute density data using per-bucket tree queries.
436-
* O(B × log N) complexity - for each bucket, query the segment tree.
437-
*
438-
* Key insight: queryBucketStats() traverses tree branches, not leaves.
439-
* For a bucket covering 1/1024 of the timeline, it visits O(log N) nodes.
440-
*
441-
* Note: Currently unused but retained for potential future optimizations.
442-
*
443-
* @param bucketCount - Number of output buckets
444-
* @returns MinimapDensityData
445-
*/
446-
private computeDensityFromTree(bucketCount: number): MinimapDensityData {
447-
if (bucketCount <= 0 || this.totalDuration <= 0 || !this.segmentTree) {
448-
return {
449-
buckets: [],
450-
globalMaxDepth: this.globalMaxDepth,
451-
maxEventCount: 0,
452-
totalDuration: this.totalDuration,
453-
};
454-
}
455-
456-
const bucketTimeWidth = this.totalDuration / bucketCount;
457-
const buckets: MinimapDensityBucket[] = new Array(bucketCount);
458-
let maxEventCount = 0;
459-
460-
// Query each bucket using the segment tree
461-
for (let b = 0; b < bucketCount; b++) {
462-
const bucketStart = b * bucketTimeWidth;
463-
const bucketEnd = (b + 1) * bucketTimeWidth;
464-
465-
// Use tree query - O(log N) traversal per bucket
466-
const stats = this.segmentTree.queryBucketStats(bucketStart, bucketEnd);
467-
468-
if (stats.eventCount > maxEventCount) {
469-
maxEventCount = stats.eventCount;
470-
}
471-
472-
// Resolve dominant category using skyline (on-top time) algorithm
473-
const dominantCategory = this.resolveCategoryFromSkyline(
474-
stats.frames,
475-
bucketStart,
476-
bucketEnd,
477-
);
478-
479-
buckets[b] = {
480-
timeStart: bucketStart,
481-
timeEnd: bucketEnd,
482-
maxDepth: stats.maxDepth,
483-
eventCount: stats.eventCount,
484-
dominantCategory,
485-
selfDurationSum: stats.selfDurationSum,
486-
};
487-
}
488-
489-
return {
490-
buckets,
491-
globalMaxDepth: this.globalMaxDepth,
492-
maxEventCount,
493-
totalDuration: this.totalDuration,
494-
};
495-
}
496-
497434
// ============================================================================
498435
// SKYLINE ALGORITHM: On-Top Time Category Resolution
499436
// ============================================================================

0 commit comments

Comments
 (0)