|
3 | 3 | * Licensed under the MIT License. See License.txt in the project root for license information.
|
4 | 4 | *--------------------------------------------------------------------------------------------*/
|
5 | 5 |
|
6 |
| -export function reportInputLatency() { |
7 |
| - // Measures from cursor edit to the end of the task |
8 |
| - window.queueMicrotask(() => { |
9 |
| - if ((window as any).frameStart && (window as any).frameSelection && (window as any).frameRender) { |
10 |
| - performance.mark('inputlatency/end'); |
11 |
| - performance.measure('inputlatency', 'inputlatency/start', 'inputlatency/end'); |
12 |
| - const measure = performance.getEntriesByName('inputlatency')[0]; |
13 |
| - const startMark = performance.getEntriesByName('inputlatency/start')[0]; |
14 |
| - |
15 |
| - performance.measure('render', 'render/start', 'render/end'); |
16 |
| - const measure2 = performance.getEntriesByName('render')[0]; |
17 |
| - |
18 |
| - console.info(`frame stats for ${(startMark as any).detail.padEnd(5, ' ')}, text render time: ${(measure2.duration).toFixed(1).padStart(4, ' ')}ms, latency: ${(measure.duration).toFixed(1).padStart(4, ' ')}ms`); |
19 |
| - |
20 |
| - performance.clearMarks('inputlatency/start'); |
21 |
| - performance.clearMarks('inputlatency/end'); |
22 |
| - performance.clearMeasures('inputlatency'); |
23 |
| - performance.clearMarks('render/start'); |
24 |
| - performance.clearMarks('render/end'); |
25 |
| - performance.clearMeasures('render'); |
26 |
| - |
27 |
| - (window as any).frameStart = false; |
28 |
| - (window as any).frameSelection = false; |
29 |
| - (window as any).frameRender = false; |
30 |
| - } |
31 |
| - }); |
32 |
| -} |
| 6 | +export namespace inputLatency { |
33 | 7 |
|
34 |
| -export function recordRenderStart() { |
35 |
| - if ((window as any).frameStart && !(window as any).frameRender) { |
| 8 | + const enum Constants { |
| 9 | + bufferLength = 256 |
| 10 | + } |
| 11 | + |
| 12 | + const enum EventPhase { |
| 13 | + Before = 0, |
| 14 | + InProgress = 1, |
| 15 | + Finished = 2 |
| 16 | + } |
| 17 | + |
| 18 | + const state = { |
| 19 | + keydown: EventPhase.Before, |
| 20 | + input: EventPhase.Before, |
| 21 | + render: EventPhase.Before, |
| 22 | + selection: EventPhase.Before |
| 23 | + }; |
| 24 | + |
| 25 | + const measurementsKeydown = new Float32Array(Constants.bufferLength); |
| 26 | + const measurementsInput = new Float32Array(Constants.bufferLength); |
| 27 | + const measurementsRender = new Float32Array(Constants.bufferLength); |
| 28 | + const measurementsInputLatency = new Float32Array(Constants.bufferLength); |
| 29 | + let measurementsIndex = 0; |
| 30 | + |
| 31 | + export function markKeydownStart() { |
| 32 | + performance.mark('inputlatency/start'); |
| 33 | + performance.mark('keydown/start'); |
| 34 | + state.keydown = EventPhase.InProgress; |
| 35 | + queueMicrotask(() => markKeydownEnd()); |
| 36 | + } |
| 37 | + |
| 38 | + function markKeydownEnd() { |
36 | 39 | // Only measure the first render after keyboard input
|
37 |
| - performance.mark('render/start'); |
| 40 | + performance.mark('keydown/end'); |
| 41 | + state.keydown = EventPhase.Finished; |
| 42 | + } |
| 43 | + |
| 44 | + export function markInputStart() { |
| 45 | + performance.mark('input/start'); |
| 46 | + state.input = EventPhase.InProgress; |
| 47 | + } |
| 48 | + |
| 49 | + export function markInputEnd() { |
| 50 | + queueMicrotask(() => { |
| 51 | + performance.mark('input/end'); |
| 52 | + state.input = EventPhase.Finished; |
| 53 | + }); |
| 54 | + } |
| 55 | + |
| 56 | + export function markRenderStart() { |
| 57 | + // Render may be triggered during input, but we only measure the following animation frame |
| 58 | + if (state.keydown === EventPhase.Finished && state.input === EventPhase.Finished && state.render === EventPhase.Before) { |
| 59 | + // Only measure the first render after keyboard input |
| 60 | + performance.mark('render/start'); |
| 61 | + state.render = EventPhase.InProgress; |
| 62 | + queueMicrotask(() => markRenderEnd()); |
| 63 | + } |
38 | 64 | }
|
39 |
| -} |
40 | 65 |
|
41 |
| -export function recordRenderEnd() { |
42 |
| - if ((window as any).frameStart && !(window as any).frameRender) { |
| 66 | + function markRenderEnd() { |
43 | 67 | // Only measure the first render after keyboard input
|
44 | 68 | performance.mark('render/end');
|
45 |
| - (window as any).frameRender = true; |
| 69 | + state.render = EventPhase.Finished; |
| 70 | + record(); |
| 71 | + } |
| 72 | + |
| 73 | + export function markTextareaSelection() { |
| 74 | + state.selection = EventPhase.Finished; |
| 75 | + record(); |
46 | 76 | }
|
47 |
| - reportInputLatency(); |
| 77 | + |
| 78 | + function record() { |
| 79 | + // Skip recording this frame if the buffer is full |
| 80 | + if (measurementsIndex >= Constants.bufferLength) { |
| 81 | + return; |
| 82 | + } |
| 83 | + // Selection and render must have finished to record |
| 84 | + if (state.selection !== EventPhase.Finished || state.render !== EventPhase.Finished) { |
| 85 | + return; |
| 86 | + } |
| 87 | + // Measures from cursor edit to the end of the task |
| 88 | + window.queueMicrotask(() => { |
| 89 | + if (state.keydown === EventPhase.Finished && state.input === EventPhase.Finished && state.selection === EventPhase.Finished && state.render === EventPhase.Finished) { |
| 90 | + performance.mark('inputlatency/end'); |
| 91 | + |
| 92 | + performance.measure('keydown', 'keydown/start', 'keydown/end'); |
| 93 | + performance.measure('input', 'input/start', 'input/end'); |
| 94 | + performance.measure('render', 'render/start', 'render/end'); |
| 95 | + performance.measure('inputlatency', 'inputlatency/start', 'inputlatency/end'); |
| 96 | + |
| 97 | + measurementsKeydown[measurementsIndex] = performance.getEntriesByName('keydown')[0].duration; |
| 98 | + measurementsInput[measurementsIndex] = performance.getEntriesByName('input')[0].duration; |
| 99 | + measurementsRender[measurementsIndex] = performance.getEntriesByName('render')[0].duration; |
| 100 | + measurementsInputLatency[measurementsIndex] = performance.getEntriesByName('inputlatency')[0].duration; |
| 101 | + |
| 102 | + console.info( |
| 103 | + `input latency=${measurementsInputLatency[measurementsIndex].toFixed(1)} [` + |
| 104 | + `keydown=${measurementsKeydown[measurementsIndex].toFixed(1)}, ` + |
| 105 | + `input=${measurementsInput[measurementsIndex].toFixed(1)}, ` + |
| 106 | + `render=${measurementsRender[measurementsIndex].toFixed(1)}` + |
| 107 | + `]` |
| 108 | + ); |
| 109 | + |
| 110 | + measurementsIndex++; |
| 111 | + |
| 112 | + reset(); |
| 113 | + } |
| 114 | + }); |
| 115 | + } |
| 116 | + |
| 117 | + function reset() { |
| 118 | + performance.clearMarks('keydown/start'); |
| 119 | + performance.clearMarks('keydown/end'); |
| 120 | + performance.clearMarks('input/start'); |
| 121 | + performance.clearMarks('input/end'); |
| 122 | + performance.clearMarks('render/start'); |
| 123 | + performance.clearMarks('render/end'); |
| 124 | + performance.clearMarks('inputlatency/start'); |
| 125 | + performance.clearMarks('inputlatency/end'); |
| 126 | + |
| 127 | + performance.clearMeasures('keydown'); |
| 128 | + performance.clearMeasures('input'); |
| 129 | + performance.clearMeasures('render'); |
| 130 | + performance.clearMeasures('inputlatency'); |
| 131 | + |
| 132 | + state.keydown = EventPhase.Before; |
| 133 | + state.input = EventPhase.Before; |
| 134 | + state.render = EventPhase.Before; |
| 135 | + state.selection = EventPhase.Before; |
| 136 | + } |
| 137 | + |
48 | 138 | }
|
0 commit comments