@kylincloud/flamegraph 0.35.27 → 0.35.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/FlameGraph/FlameGraphComponent/DiffLegend.d.ts.map +1 -1
  3. package/dist/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.d.ts.map +1 -1
  4. package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts +16 -2
  5. package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts.map +1 -1
  6. package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts +15 -2
  7. package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts.map +1 -1
  8. package/dist/FlameGraph/FlameGraphComponent/Highlight.d.ts.map +1 -1
  9. package/dist/FlameGraph/FlameGraphComponent/index.d.ts.map +1 -1
  10. package/dist/FlameGraph/normalize.d.ts.map +1 -1
  11. package/dist/FlameGraph/uniqueness.d.ts.map +1 -1
  12. package/dist/Icons.d.ts.map +1 -1
  13. package/dist/ProfilerTable.d.ts.map +1 -1
  14. package/dist/SharedQueryInput.d.ts.map +1 -1
  15. package/dist/Toolbar.d.ts.map +1 -1
  16. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  17. package/dist/flamegraphRenderWorker.js +2 -0
  18. package/dist/flamegraphRenderWorker.js.map +1 -0
  19. package/dist/index.cjs.js +4 -4
  20. package/dist/index.cjs.js.map +1 -1
  21. package/dist/index.esm.js +4 -4
  22. package/dist/index.esm.js.map +1 -1
  23. package/dist/index.node.cjs.js +4 -4
  24. package/dist/index.node.cjs.js.map +1 -1
  25. package/dist/index.node.esm.js +4 -4
  26. package/dist/index.node.esm.js.map +1 -1
  27. package/dist/shims/Table.d.ts +15 -1
  28. package/dist/shims/Table.d.ts.map +1 -1
  29. package/dist/shims/Tooltip.d.ts.map +1 -1
  30. package/dist/workers/createFlamegraphRenderWorker.d.ts +2 -0
  31. package/dist/workers/createFlamegraphRenderWorker.d.ts.map +1 -0
  32. package/dist/workers/flamegraphRenderWorker.d.ts +2 -0
  33. package/dist/workers/flamegraphRenderWorker.d.ts.map +1 -0
  34. package/dist/workers/profilerTableWorker.d.ts +73 -0
  35. package/dist/workers/profilerTableWorker.d.ts.map +1 -0
  36. package/package.json +1 -1
  37. package/src/FlameGraph/FlameGraphComponent/DiffLegend.module.css +8 -2
  38. package/src/FlameGraph/FlameGraphComponent/DiffLegend.tsx +12 -1
  39. package/src/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.module.css +93 -10
  40. package/src/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.tsx +9 -4
  41. package/src/FlameGraph/FlameGraphComponent/Flamegraph.ts +33 -8
  42. package/src/FlameGraph/FlameGraphComponent/Flamegraph_render.ts +289 -85
  43. package/src/FlameGraph/FlameGraphComponent/Highlight.tsx +43 -17
  44. package/src/FlameGraph/FlameGraphComponent/index.tsx +208 -57
  45. package/src/FlameGraph/FlameGraphComponent/styles.module.scss +8 -0
  46. package/src/FlameGraph/normalize.ts +9 -7
  47. package/src/FlameGraph/uniqueness.ts +69 -59
  48. package/src/Icons.tsx +18 -9
  49. package/src/ProfilerTable.tsx +463 -33
  50. package/src/SharedQueryInput.module.scss +50 -0
  51. package/src/SharedQueryInput.tsx +18 -3
  52. package/src/Toolbar.module.scss +90 -0
  53. package/src/Toolbar.tsx +30 -16
  54. package/src/Tooltip/Tooltip.tsx +49 -16
  55. package/src/i18n.tsx +1 -1
  56. package/src/sass/_common.scss +22 -3
  57. package/src/sass/_css-variables.scss +5 -1
  58. package/src/sass/flamegraph.scss +26 -23
  59. package/src/shims/Table.module.scss +91 -13
  60. package/src/shims/Table.tsx +202 -7
  61. package/src/shims/Tooltip.module.scss +40 -0
  62. package/src/shims/Tooltip.tsx +31 -3
  63. package/src/workers/createFlamegraphRenderWorker.ts +7 -0
  64. package/src/workers/flamegraphRenderWorker.ts +198 -0
  65. package/src/workers/profilerTableWorker.ts +368 -0
@@ -1,6 +1,14 @@
1
1
  // src/shims/Table.tsx
2
2
  /* eslint-disable react/jsx-props-no-spreading */
3
- import React, { useState, useRef, ReactNode, CSSProperties, RefObject } from 'react';
3
+ import React, {
4
+ useState,
5
+ useRef,
6
+ ReactNode,
7
+ CSSProperties,
8
+ RefObject,
9
+ useEffect,
10
+ useLayoutEffect,
11
+ } from 'react';
4
12
  import { faChevronLeft } from '@fortawesome/free-solid-svg-icons/faChevronLeft';
5
13
  import { faChevronRight } from '@fortawesome/free-solid-svg-icons/faChevronRight';
6
14
  import clsx from 'clsx';
@@ -46,6 +54,13 @@ export type TableBodyType =
46
54
  | {
47
55
  type: 'filled';
48
56
  bodyRows: BodyRow[];
57
+ }
58
+ | {
59
+ type: 'virtual';
60
+ rowCount: number;
61
+ rows: BodyRow[];
62
+ range: { start: number; end: number };
63
+ onRangeChange?: (start: number, end: number) => void;
49
64
  };
50
65
 
51
66
  type Table = TableBodyType & {
@@ -92,6 +107,13 @@ interface TableProps {
92
107
  isLoading?: boolean;
93
108
  /* enables pagination */
94
109
  itemsPerPage?: number;
110
+ /* enables virtualization */
111
+ virtualize?: boolean;
112
+ virtualizeRowHeight?: number;
113
+ virtualizeOverscan?: number;
114
+ scrollRef?: RefObject<HTMLElement>;
115
+ /* debug: disable sort interactions */
116
+ disableSort?: boolean;
95
117
  }
96
118
 
97
119
  /**
@@ -143,9 +165,145 @@ function Table({
143
165
  className,
144
166
  isLoading,
145
167
  itemsPerPage,
168
+ virtualize,
169
+ virtualizeRowHeight,
170
+ virtualizeOverscan = 6,
171
+ scrollRef,
172
+ disableSort,
146
173
  }: TableProps) {
147
- const hasSort = sortByDirection && sortBy && updateSortParams;
174
+ const hasSort = !disableSort && sortByDirection && sortBy && updateSortParams;
148
175
  const [currPage, setCurrPage] = useState(0);
176
+ const [scrollTop, setScrollTop] = useState(0);
177
+ const [measuredRowHeight, setMeasuredRowHeight] = useState<number | null>(null);
178
+ const [scrollEl, setScrollEl] = useState<HTMLElement | null>(null);
179
+ const rafRef = useRef<number | null>(null);
180
+
181
+ useLayoutEffect(() => {
182
+ if (scrollRef?.current && scrollRef.current !== scrollEl) {
183
+ setScrollEl(scrollRef.current);
184
+ }
185
+ }, [scrollRef?.current, scrollEl]);
186
+
187
+ useEffect(() => {
188
+ if (!scrollEl || !virtualize) {
189
+ return () => {};
190
+ }
191
+ const onScroll = () => {
192
+ if (rafRef.current) {
193
+ cancelAnimationFrame(rafRef.current);
194
+ }
195
+ rafRef.current = requestAnimationFrame(() => {
196
+ setScrollTop(scrollEl.scrollTop || 0);
197
+ });
198
+ };
199
+ scrollEl.addEventListener('scroll', onScroll, { passive: true });
200
+ return () => {
201
+ scrollEl.removeEventListener('scroll', onScroll as EventListener);
202
+ if (rafRef.current) {
203
+ cancelAnimationFrame(rafRef.current);
204
+ }
205
+ };
206
+ }, [scrollEl, virtualize]);
207
+
208
+ useEffect(() => {
209
+ if (!virtualize || measuredRowHeight || !tableBodyRef?.current) {
210
+ return;
211
+ }
212
+ const rows = tableBodyRef.current.querySelectorAll('tr[data-row]');
213
+ if (!rows.length) {
214
+ return;
215
+ }
216
+ const rect = rows[0].getBoundingClientRect();
217
+ if (rect.height) {
218
+ setMeasuredRowHeight(rect.height);
219
+ }
220
+ }, [virtualize, measuredRowHeight, tableBodyRef?.current, table]);
221
+
222
+ const bodyRows =
223
+ table.type === 'filled'
224
+ ? paginate(table.bodyRows, currPage, itemsPerPage)
225
+ : null;
226
+
227
+ const totalRows =
228
+ table.type === 'virtual'
229
+ ? table.rowCount
230
+ : bodyRows
231
+ ? bodyRows.length
232
+ : 0;
233
+
234
+ const rowHeight = virtualizeRowHeight || measuredRowHeight || 0;
235
+ const canVirtualize =
236
+ !!virtualize &&
237
+ !!scrollEl &&
238
+ (table.type === 'filled' || table.type === 'virtual') &&
239
+ rowHeight > 0;
240
+
241
+ const virtualState = canVirtualize
242
+ ? (() => {
243
+ const viewportHeight = scrollEl!.clientHeight || 0;
244
+ const startIndex = Math.max(
245
+ 0,
246
+ Math.floor(scrollTop / rowHeight) - virtualizeOverscan
247
+ );
248
+ const endIndex = Math.min(
249
+ totalRows,
250
+ Math.ceil((scrollTop + viewportHeight) / rowHeight) + virtualizeOverscan
251
+ );
252
+ const topSpacer = startIndex * rowHeight;
253
+ const bottomSpacer = Math.max(0, (totalRows - endIndex) * rowHeight);
254
+ const visibleRows =
255
+ table.type === 'filled' && bodyRows
256
+ ? bodyRows.slice(startIndex, endIndex)
257
+ : [];
258
+
259
+ return {
260
+ viewportHeight,
261
+ totalRows,
262
+ startIndex,
263
+ endIndex,
264
+ topSpacer,
265
+ bottomSpacer,
266
+ visibleRows,
267
+ };
268
+ })()
269
+ : null;
270
+ const shouldLimitRows = !!virtualize && !canVirtualize && table.type === 'filled';
271
+ const limitedRows = shouldLimitRows
272
+ ? (bodyRows || []).slice(0, Math.max(24, virtualizeOverscan * 4))
273
+ : null;
274
+
275
+ useEffect(() => {
276
+ if (table.type !== 'virtual' || !virtualState) {
277
+ return;
278
+ }
279
+ table.onRangeChange?.(virtualState.startIndex, virtualState.endIndex);
280
+ }, [
281
+ table,
282
+ virtualState?.startIndex,
283
+ virtualState?.endIndex,
284
+ ]);
285
+
286
+ const placeholderRowCount =
287
+ table.type === 'virtual' && virtualState
288
+ ? Math.max(0, virtualState.endIndex - virtualState.startIndex)
289
+ : 0;
290
+ const placeholderRows: BodyRow[] =
291
+ table.type === 'virtual' && placeholderRowCount > 0
292
+ ? Array.from({ length: placeholderRowCount }, () => ({
293
+ cells: Array.from({ length: table.headRow.length }, () => ({
294
+ value: '\u00A0',
295
+ })),
296
+ }))
297
+ : [];
298
+
299
+ const renderVirtualRows =
300
+ table.type === 'virtual' &&
301
+ virtualState &&
302
+ table.range &&
303
+ table.range.start === virtualState.startIndex &&
304
+ table.range.end === virtualState.endIndex
305
+ ? table.rows
306
+ : placeholderRows;
149
307
 
150
308
  return isLoading ? (
151
309
  <div className={styles.loadingSpinner}>
@@ -179,9 +337,14 @@ function Table({
179
337
  {label}
180
338
  <span
181
339
  className={clsx(styles.sortArrow, {
182
- [styles[sortByDirection]]: sortBy === name,
340
+ [styles.active]: sortBy === name,
341
+ [styles.asc]: sortBy === name && sortByDirection === 'asc',
342
+ [styles.desc]: sortBy === name && sortByDirection === 'desc',
183
343
  })}
184
- />
344
+ >
345
+ <span className={styles.sortArrowUp} />
346
+ <span className={styles.sortArrowDown} />
347
+ </span>
185
348
  </th>
186
349
  )
187
350
  )}
@@ -192,11 +355,43 @@ function Table({
192
355
  <tr className={table?.bodyClassName}>
193
356
  <td colSpan={table.headRow.length}>{table.value}</td>
194
357
  </tr>
358
+ ) : virtualState ? (
359
+ <>
360
+ {virtualState.topSpacer > 0 && (
361
+ <tr aria-hidden="true" className={styles.virtualSpacer}>
362
+ <td
363
+ colSpan={table.headRow.length}
364
+ style={{ height: virtualState.topSpacer, padding: 0, border: 0 }}
365
+ />
366
+ </tr>
367
+ )}
368
+ {(table.type === 'virtual'
369
+ ? renderVirtualRows
370
+ : virtualState.visibleRows
371
+ ).map((row, idx) => (
372
+ <TableRow
373
+ key={row['data-row'] ?? `row-${virtualState.startIndex + idx}`}
374
+ row={row}
375
+ />
376
+ ))}
377
+ {virtualState.bottomSpacer > 0 && (
378
+ <tr aria-hidden="true" className={styles.virtualSpacer}>
379
+ <td
380
+ colSpan={table.headRow.length}
381
+ style={{ height: virtualState.bottomSpacer, padding: 0, border: 0 }}
382
+ />
383
+ </tr>
384
+ )}
385
+ </>
195
386
  ) : (
196
- paginate(table.bodyRows, currPage, itemsPerPage).map(
197
- (row, idx) => (
387
+ shouldLimitRows ? (
388
+ (limitedRows || []).map((row, idx) => (
389
+ <TableRow key={row['data-row'] ?? `row-${idx}`} row={row} />
390
+ ))
391
+ ) : (
392
+ (bodyRows || []).map((row, idx) => (
198
393
  <TableRow key={row['data-row'] ?? `row-${idx}`} row={row} />
199
- )
394
+ ))
200
395
  )
201
396
  )}
202
397
  </tbody>
@@ -0,0 +1,40 @@
1
+ .tooltipWrapper {
2
+ position: relative;
3
+ display: inline-flex;
4
+ align-items: center;
5
+ }
6
+
7
+ .tooltipBubble {
8
+ position: absolute;
9
+ left: 50%;
10
+ bottom: 100%;
11
+ transform: translate(-50%, -8px);
12
+ padding: 6px 10px;
13
+ border-radius: 4px;
14
+ background: rgba(0, 0, 0, 0.85);
15
+ color: #ffffff;
16
+ font-size: 12px;
17
+ line-height: 1.4;
18
+ white-space: nowrap;
19
+ pointer-events: none;
20
+ opacity: 0;
21
+ transition: opacity 120ms ease, transform 120ms ease;
22
+ z-index: 5;
23
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
24
+ }
25
+
26
+ .tooltipArrow {
27
+ position: absolute;
28
+ left: 50%;
29
+ top: 100%;
30
+ transform: translate(-50%, -2px);
31
+ border-width: 6px 6px 0 6px;
32
+ border-style: solid;
33
+ border-color: rgba(0, 0, 0, 0.85) transparent transparent transparent;
34
+ pointer-events: none;
35
+ }
36
+
37
+ .tooltipWrapper:hover .tooltipBubble {
38
+ opacity: 1;
39
+ transform: translate(-50%, -14px);
40
+ }
@@ -1,5 +1,6 @@
1
1
  // src/shims/Tooltip.tsx
2
- import React from 'react'
2
+ import React, { useEffect, useRef, useState } from 'react'
3
+ import styles from './Tooltip.module.scss'
3
4
 
4
5
  export interface TooltipProps {
5
6
  placement?: string
@@ -34,18 +35,45 @@ function nodeToPlainText(node: React.ReactNode): string | undefined {
34
35
  }
35
36
 
36
37
  export const Tooltip: React.FC<TooltipProps> = ({
37
- placement, // 目前不用,但保留参数以兼容原版
38
+ placement, // 目前只处理 top
38
39
  title,
39
40
  content,
40
41
  children,
41
42
  }) => {
42
43
  const text = title ?? content
43
44
  const titleStr = nodeToPlainText(text)
45
+ const wrapperRef = useRef<HTMLSpanElement | null>(null)
46
+ const [isKylin, setIsKylin] = useState(false)
47
+
48
+ useEffect(() => {
49
+ const el = wrapperRef.current
50
+ if (!el) return
51
+ const kylinEl = el.closest(
52
+ "[data-theme='kylin'], [data-flamegraph-color-mode='kylin']"
53
+ )
54
+ setIsKylin(!!kylinEl)
55
+ }, [])
44
56
 
45
57
  // 没有文本就不加 title,避免空 tooltip
46
58
  if (!titleStr) {
47
59
  return <>{children}</>
48
60
  }
49
61
 
50
- return <span title={titleStr}>{children}</span>
62
+ return (
63
+ <span
64
+ ref={wrapperRef}
65
+ className={styles.tooltipWrapper}
66
+ data-placement={placement || 'top'}
67
+ data-tooltip={isKylin ? titleStr : undefined}
68
+ title={isKylin ? undefined : titleStr}
69
+ >
70
+ {children}
71
+ {isKylin ? (
72
+ <span className={styles.tooltipBubble} role="tooltip">
73
+ {titleStr}
74
+ <span className={styles.tooltipArrow} />
75
+ </span>
76
+ ) : null}
77
+ </span>
78
+ )
51
79
  }
@@ -0,0 +1,7 @@
1
+ export function createFlamegraphRenderWorker(): Worker {
2
+ const fromSrc = import.meta.url.includes('/src/');
3
+ const workerUrl = fromSrc
4
+ ? new URL('./flamegraphRenderWorker.ts', import.meta.url)
5
+ : new URL('./flamegraphRenderWorker.js', import.meta.url);
6
+ return new Worker(workerUrl, { type: 'module' });
7
+ }
@@ -0,0 +1,198 @@
1
+ import Color from 'color';
2
+ import { Maybe } from 'true-myth';
3
+ import type { Flamebearer } from '../models';
4
+ import Flamegraph from '../FlameGraph/FlameGraphComponent/Flamegraph';
5
+ import type { CanvasI18nMessages } from '../FlameGraph/FlameGraphComponent/Flamegraph_render';
6
+ import type { FlamegraphPalette } from '../FlameGraph/FlameGraphComponent/colorPalette';
7
+
8
+ type SerializablePalette = {
9
+ name: string;
10
+ goodColor: [number, number, number];
11
+ neutralColor: [number, number, number];
12
+ badColor: [number, number, number];
13
+ colors: [number, number, number][];
14
+ };
15
+
16
+ type FocusNode = { i: number; j: number } | null;
17
+
18
+ type FlamegraphRenderInit = {
19
+ type: 'init';
20
+ payload: {
21
+ kind: 'rect' | 'text';
22
+ canvas: OffscreenCanvas;
23
+ };
24
+ };
25
+
26
+ type FlamegraphRenderRequest = {
27
+ type: 'render';
28
+ payload: {
29
+ kind: 'rect' | 'text';
30
+ flamebearer: Flamebearer;
31
+ focusedNode: FocusNode;
32
+ fitMode: 'HEAD' | 'TAIL';
33
+ highlightQuery: string;
34
+ zoom: FocusNode;
35
+ palette: SerializablePalette;
36
+ messages?: CanvasI18nMessages;
37
+ renderRects?: boolean;
38
+ renderText?: boolean;
39
+ width: number;
40
+ devicePixelRatio: number;
41
+ };
42
+ };
43
+
44
+ type FlamegraphWorkerMessage = FlamegraphRenderInit | FlamegraphRenderRequest;
45
+
46
+ const state: {
47
+ rectCanvas: OffscreenCanvas | null;
48
+ textCanvas: OffscreenCanvas | null;
49
+ renderState: Record<
50
+ 'rect' | 'text',
51
+ {
52
+ token: number;
53
+ running: boolean;
54
+ nextI: number;
55
+ nextJ: number;
56
+ firstChunk: boolean;
57
+ payload: FlamegraphRenderRequest['payload'] | null;
58
+ flamegraph: Flamegraph | null;
59
+ }
60
+ >;
61
+ } = {
62
+ rectCanvas: null,
63
+ textCanvas: null,
64
+ renderState: {
65
+ rect: {
66
+ token: 0,
67
+ running: false,
68
+ nextI: 0,
69
+ nextJ: 0,
70
+ firstChunk: true,
71
+ payload: null,
72
+ flamegraph: null,
73
+ },
74
+ text: {
75
+ token: 0,
76
+ running: false,
77
+ nextI: 0,
78
+ nextJ: 0,
79
+ firstChunk: true,
80
+ payload: null,
81
+ flamegraph: null,
82
+ },
83
+ },
84
+ };
85
+
86
+ const buildPalette = (payload: SerializablePalette): FlamegraphPalette => ({
87
+ name: payload.name,
88
+ goodColor: Color.rgb(...payload.goodColor),
89
+ neutralColor: Color.rgb(...payload.neutralColor),
90
+ badColor: Color.rgb(...payload.badColor),
91
+ colors: payload.colors.map((c) => Color.rgb(...c)),
92
+ });
93
+
94
+ const toMaybe = (node: FocusNode) =>
95
+ node
96
+ ? Maybe.just(node)
97
+ : Maybe.nothing<{ i: number; j: number }>();
98
+
99
+ const RECT_BUDGET_MS = 12;
100
+ const TEXT_BUDGET_MS = 8;
101
+
102
+ const runChunk = (kind: 'rect' | 'text', token: number) => {
103
+ const renderState = state.renderState[kind];
104
+ const payload = renderState.payload;
105
+ if (!payload || renderState.token !== token) {
106
+ return;
107
+ }
108
+ const canvas = kind === 'rect' ? state.rectCanvas : state.textCanvas;
109
+ if (!canvas) {
110
+ // eslint-disable-next-line no-console
111
+ console.debug('[flamegraph-worker] missing canvas for', kind);
112
+ renderState.running = false;
113
+ return;
114
+ }
115
+ if (!renderState.flamegraph) {
116
+ renderState.flamegraph = new Flamegraph(
117
+ payload.flamebearer,
118
+ canvas,
119
+ toMaybe(payload.focusedNode),
120
+ payload.fitMode,
121
+ payload.highlightQuery,
122
+ toMaybe(payload.zoom),
123
+ buildPalette(payload.palette),
124
+ payload.messages
125
+ );
126
+ }
127
+
128
+ const result = renderState.flamegraph.render({
129
+ renderRects: payload.renderRects,
130
+ renderText: payload.renderText,
131
+ timeBudgetMs: kind === 'rect' ? RECT_BUDGET_MS : TEXT_BUDGET_MS,
132
+ startI: renderState.nextI,
133
+ startJ: renderState.nextJ,
134
+ skipCanvasResize: !renderState.firstChunk,
135
+ skipDprScale: !renderState.firstChunk,
136
+ devicePixelRatio: payload.devicePixelRatio,
137
+ });
138
+
139
+ renderState.firstChunk = false;
140
+ if (!result.done) {
141
+ renderState.nextI = result.nextI;
142
+ renderState.nextJ = result.nextJ;
143
+ setTimeout(() => runChunk(kind, token), 0);
144
+ return;
145
+ }
146
+
147
+ renderState.running = false;
148
+ renderState.nextI = 0;
149
+ renderState.nextJ = 0;
150
+ renderState.firstChunk = true;
151
+ renderState.flamegraph = null;
152
+ };
153
+
154
+ const renderFlamegraph = (payload: FlamegraphRenderRequest['payload']) => {
155
+ const canvas =
156
+ payload.kind === 'rect' ? state.rectCanvas : state.textCanvas;
157
+ if (!canvas) {
158
+ // eslint-disable-next-line no-console
159
+ console.debug('[flamegraph-worker] missing canvas for', payload.kind);
160
+ return;
161
+ }
162
+
163
+ if (payload.width > 0) {
164
+ canvas.width = payload.width;
165
+ }
166
+
167
+ const renderState = state.renderState[payload.kind];
168
+ renderState.token += 1;
169
+ renderState.running = true;
170
+ renderState.nextI = 0;
171
+ renderState.nextJ = 0;
172
+ renderState.firstChunk = true;
173
+ renderState.payload = payload;
174
+ renderState.flamegraph = null;
175
+
176
+ const token = renderState.token;
177
+ setTimeout(() => runChunk(payload.kind, token), 0);
178
+ };
179
+
180
+ self.onmessage = (event: MessageEvent<FlamegraphWorkerMessage>) => {
181
+ const msg = event.data;
182
+ if (!msg || !msg.type) {
183
+ return;
184
+ }
185
+ if (msg.type === 'init') {
186
+ if (msg.payload.kind === 'rect') {
187
+ state.rectCanvas = msg.payload.canvas;
188
+ } else {
189
+ state.textCanvas = msg.payload.canvas;
190
+ }
191
+ // eslint-disable-next-line no-console
192
+ console.debug('[flamegraph-worker] init', msg.payload.kind);
193
+ return;
194
+ }
195
+ if (msg.type === 'render') {
196
+ renderFlamegraph(msg.payload);
197
+ }
198
+ };