Skip to content

Commit 06f2b34

Browse files
committed
perf: element event batch triggering
1 parent f08291e commit 06f2b34

File tree

32 files changed

+742
-237
lines changed

32 files changed

+742
-237
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
export interface BenchmarkResult {
2+
[key: string]: string | number;
3+
}
4+
5+
export class BenchmarkPanel {
6+
private container: HTMLDivElement;
7+
private title: string;
8+
private configInfo: string | null = null;
9+
10+
constructor(title: string) {
11+
this.title = title;
12+
13+
// Create result container on page
14+
this.container = document.createElement('div');
15+
this.container.style.position = 'absolute';
16+
this.container.style.bottom = '10px';
17+
this.container.style.left = '10px';
18+
this.container.style.backgroundColor = 'rgba(0,0,0,0.8)';
19+
this.container.style.color = 'white';
20+
this.container.style.padding = '10px';
21+
this.container.style.borderRadius = '4px';
22+
this.container.style.fontFamily = 'monospace';
23+
this.container.style.fontSize = '12px';
24+
this.container.style.zIndex = '1000';
25+
this.container.style.maxWidth = '500px';
26+
this.container.id = 'benchmark-results-panel';
27+
28+
document.body.appendChild(this.container);
29+
30+
// Show initial status
31+
this.showRunningStatus();
32+
}
33+
34+
showRunningStatus(configInfo?: string) {
35+
// Store config info for later use in updateResultsDisplay
36+
this.configInfo = configInfo || null;
37+
38+
this.container.innerHTML = `
39+
<h3>${this.title}</h3>
40+
${configInfo ? `<div>${configInfo}</div><hr />` : ''}
41+
<div>Running benchmark tests... Please wait.</div>
42+
`;
43+
}
44+
45+
updateResultsDisplay(results: BenchmarkResult[]) {
46+
// Debug: log the actual structure of results
47+
console.log('Benchmark results structure:', results);
48+
49+
// Generate table dynamically based on the keys in the first result
50+
let tableHtml =
51+
'<table style="width:100%; border-collapse: collapse; margin: 10px 0;">';
52+
tableHtml += '<thead><tr style="background-color: #333;">';
53+
54+
if (results && results.length > 0) {
55+
const firstResult = results[0];
56+
Object.keys(firstResult).forEach((key) => {
57+
tableHtml += `<th style="border: 1px solid #666; padding: 4px; text-align: left;">${key}</th>`;
58+
});
59+
}
60+
61+
tableHtml += '</tr></thead><tbody>';
62+
63+
if (results && results.length > 0) {
64+
// Create table rows for each result
65+
results.forEach((result, index) => {
66+
// Alternate row colors
67+
const bgColor =
68+
index % 2 === 0 ? 'rgba(50, 50, 50, 0.3)' : 'rgba(70, 70, 70, 0.3)';
69+
tableHtml += `<tr style="background-color: ${bgColor};">`;
70+
71+
Object.keys(result).forEach((key) => {
72+
const value = result[key];
73+
tableHtml += `<td style="border: 1px solid #666; padding: 4px;">${value}</td>`;
74+
});
75+
76+
tableHtml += '</tr>';
77+
});
78+
} else {
79+
tableHtml += '<tr><td colspan="3">No results available</td></tr>';
80+
}
81+
82+
tableHtml += '</tbody></table>';
83+
84+
this.container.innerHTML = `
85+
<h3>${this.title}</h3>
86+
${this.configInfo ? `<div>${this.configInfo}</div><hr />` : ''}
87+
${tableHtml}
88+
`;
89+
}
90+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { Canvas, ElementEvent, Rect, Group, CustomEvent } from '@antv/g';
2+
import * as tinybench from 'tinybench';
3+
import * as lil from 'lil-gui';
4+
import { BenchmarkPanel, BenchmarkResult } from './benchmark-panel';
5+
6+
/**
7+
* Custom Event Performance Test
8+
* Compare performance between sharing a single event instance and creating new event instances each time
9+
*/
10+
export async function customEvent(context: { canvas: Canvas; gui: lil.GUI }) {
11+
const { canvas, gui } = context;
12+
console.log(canvas);
13+
14+
await canvas.ready;
15+
16+
const { width, height } = canvas.getConfig();
17+
const root = new Group();
18+
let count = 1e4;
19+
let rects = [];
20+
21+
// Shared event instance
22+
const sharedEvent = new CustomEvent(ElementEvent.BOUNDS_CHANGED);
23+
24+
function render() {
25+
root.destroyChildren();
26+
rects = [];
27+
28+
for (let i = 0; i < count; i++) {
29+
const x = Math.random() * width;
30+
const y = Math.random() * height;
31+
const size = 10 + Math.random() * 40;
32+
33+
const rect = new Rect({
34+
style: {
35+
x,
36+
y,
37+
width: size,
38+
height: size,
39+
fill: 'white',
40+
stroke: '#000',
41+
lineWidth: 1,
42+
},
43+
});
44+
root.appendChild(rect);
45+
rects[i] = { x, y, size, el: rect };
46+
}
47+
}
48+
49+
render();
50+
canvas.appendChild(root);
51+
52+
// benchmark
53+
// ----------
54+
const bench = new tinybench.Bench({
55+
name: 'Custom Event Performance Comparison',
56+
time: 1e3,
57+
iterations: 100,
58+
});
59+
60+
// Test performance of shared event instance
61+
// Update event properties each time to simulate realistic usage
62+
bench.add('Shared Event Instance', () => {
63+
rects.forEach((rect) => {
64+
// Update event properties to simulate realistic usage
65+
sharedEvent.detail = {
66+
affectChildren: true,
67+
timestamp: performance.now(),
68+
};
69+
rect.el.dispatchEvent(sharedEvent);
70+
});
71+
});
72+
73+
// Test performance of creating new event instances each time
74+
bench.add('New Event Instance', () => {
75+
rects.forEach((rect) => {
76+
const event = new CustomEvent(ElementEvent.BOUNDS_CHANGED);
77+
event.detail = {
78+
affectChildren: true,
79+
timestamp: performance.now(),
80+
};
81+
rect.el.dispatchEvent(event);
82+
});
83+
});
84+
85+
// Test performance of creating same number of events but dispatching only once on root
86+
bench.add('Create Events, Dispatch Once on Root', () => {
87+
const events = [];
88+
// Create same number of event instances
89+
for (let i = 0; i < count; i++) {
90+
const event = new CustomEvent(ElementEvent.BOUNDS_CHANGED);
91+
event.detail = {
92+
affectChildren: true,
93+
timestamp: performance.now(),
94+
};
95+
events.push(event);
96+
}
97+
// But dispatch only once on root
98+
root.dispatchEvent(events[0]);
99+
});
100+
101+
// Test performance of dispatching event on each element without sharing event instance
102+
bench.add('Dispatch on Each Element', () => {
103+
rects.forEach((rect) => {
104+
const event = new CustomEvent(ElementEvent.BOUNDS_CHANGED);
105+
event.detail = {
106+
affectChildren: true,
107+
timestamp: performance.now(),
108+
};
109+
rect.el.dispatchEvent(event);
110+
});
111+
});
112+
113+
// Create benchmark panel
114+
const benchmarkPanel = new BenchmarkPanel(bench.name);
115+
116+
// Show initial status with object count
117+
benchmarkPanel.showRunningStatus(`Object Count: ${count}`);
118+
119+
// ----------
120+
121+
// GUI
122+
const config = {
123+
objectCount: count,
124+
runBenchmark: async () => {
125+
benchmarkPanel.showRunningStatus(`Object Count: ${count}`);
126+
127+
setTimeout(async () => {
128+
await bench.run();
129+
console.log(bench.name);
130+
console.table(bench.table());
131+
132+
benchmarkPanel.updateResultsDisplay(
133+
bench.table() as unknown as BenchmarkResult[],
134+
);
135+
}, 1e2);
136+
},
137+
};
138+
139+
gui.add(config, 'objectCount', 100, 50000, 100).onChange((value) => {
140+
count = value;
141+
render();
142+
});
143+
144+
gui.add(config, 'runBenchmark');
145+
}

__tests__/demos/perf/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export { canvasApi } from './canvas-api';
77
export { javascript } from './javascript';
88
export { event } from './event';
99
export { destroyEvent } from './destroy-event';
10+
export { customEvent } from './custom-event';

__tests__/demos/perf/javascript.ts

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export async function javascript(context: { canvas: Canvas; gui: lil.GUI }) {
1313
// ----------
1414
const bench = new tinybench.Bench({
1515
name: 'javascript benchmark',
16-
time: 1e2,
16+
time: 1e3,
1717
});
1818
const array = [
1919
'stroke',
@@ -98,6 +98,64 @@ export async function javascript(context: { canvas: Canvas; gui: lil.GUI }) {
9898
// bench.add('typeof - isNil', async () => {
9999
// !(typeof value === 'undefined' || value === null);
100100
// });
101+
102+
// region attr assign ---------------------------------------
103+
// Performance comparison: direct property access vs method calls
104+
class TestClass {
105+
public prop1: number = 0;
106+
private _prop2: number = 0;
107+
108+
setProp2(value: number) {
109+
this._prop2 = value;
110+
}
111+
112+
getProp2() {
113+
return this._prop2;
114+
}
115+
}
116+
117+
const testObj = new TestClass();
118+
const iterations = 1000000;
119+
120+
bench.add('attr assign - Direct property assignment', () => {
121+
for (let i = 0; i < iterations; i++) {
122+
testObj.prop1 = i;
123+
}
124+
});
125+
126+
bench.add('attr assign - Method call assignment', () => {
127+
for (let i = 0; i < iterations; i++) {
128+
testObj.setProp2(i);
129+
}
130+
});
131+
132+
bench.add('attr assign - Direct property access', () => {
133+
let sum = 0;
134+
for (let i = 0; i < iterations; i++) {
135+
sum += testObj.prop1;
136+
}
137+
return sum;
138+
});
139+
140+
bench.add('attr assign - Method call access', () => {
141+
let sum = 0;
142+
for (let i = 0; i < iterations; i++) {
143+
sum += testObj.getProp2();
144+
}
145+
return sum;
146+
});
147+
// endregion ---------------------------------------
148+
149+
// region typeof ---------------------------------------
150+
const testTypeof = undefined;
151+
bench.add('typeof - typeof', async () => {
152+
typeof testTypeof !== 'undefined';
153+
});
154+
bench.add('typeof - !==', async () => {
155+
testTypeof !== undefined;
156+
});
157+
// endregion ---------------------------------------
158+
101159
// bench.add('@antv/util - isNil', async () => {
102160
// !isNil(value);
103161
// });
@@ -114,10 +172,7 @@ export async function javascript(context: { canvas: Canvas; gui: lil.GUI }) {
114172

115173
await bench.run();
116174

117-
console.log(bench.name);
118175
console.table(bench.table());
119-
console.log(bench.results);
120-
console.log(bench.tasks);
121176

122177
// ----------
123178
}

__tests__/unit/css/properties/transform.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ describe('CSSPropertyTransform', () => {
240240
cy: 10,
241241
r: 50,
242242
transform: 'scale(0)',
243+
transformOrigin: 'center',
243244
},
244245
});
245246

__tests__/unit/dom/event.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ describe('Event API', () => {
220220
ul.appendChild(li2);
221221

222222
const event = new CustomEvent('build', { detail: { prop1: 'xx' } });
223+
223224
// delegate to parent
224225
ul.addEventListener('build', (e) => {
225226
expect(e.target).toBe(li1);

0 commit comments

Comments
 (0)