Skip to content

Commit 65b1956

Browse files
authored
Add benchmark for Mouse region (web) (flutter#59803)
1 parent e48b7e9 commit 65b1956

File tree

4 files changed

+358
-6
lines changed

4 files changed

+358
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:ui';
7+
8+
import 'package:flutter/gestures.dart';
9+
import 'package:flutter/rendering.dart';
10+
import 'package:flutter/widgets.dart';
11+
import 'package:flutter/scheduler.dart';
12+
import 'package:flutter_test/flutter_test.dart';
13+
14+
import 'recorder.dart';
15+
16+
class _NestedMouseRegion extends StatelessWidget {
17+
const _NestedMouseRegion({this.nests, this.child});
18+
19+
final int nests;
20+
final Widget child;
21+
22+
@override
23+
Widget build(BuildContext context) {
24+
Widget current = child;
25+
for (int i = 0; i < nests; i++) {
26+
current = MouseRegion(
27+
onEnter: (_) {},
28+
child: child,
29+
);
30+
}
31+
return current;
32+
}
33+
}
34+
35+
/// Creates a grid of mouse regions, then continuously hover over them.
36+
///
37+
/// Measures our ability to hit test mouse regions.
38+
class BenchMouseRegionGridHover extends WidgetRecorder {
39+
BenchMouseRegionGridHover() : super(name: benchmarkName);
40+
41+
static const String benchmarkName = 'bench_mouse_region_grid_hover';
42+
43+
final _Tester tester = _Tester();
44+
45+
// Use a non-trivial border to force Web to switch painter
46+
Border _getBorder(int columnIndex, int rowIndex) {
47+
const BorderSide defaultBorderSide = BorderSide();
48+
49+
return Border(
50+
left: columnIndex == 0 ? defaultBorderSide : BorderSide.none,
51+
top: rowIndex == 0 ? defaultBorderSide : BorderSide.none,
52+
right: defaultBorderSide,
53+
bottom: defaultBorderSide,
54+
);
55+
}
56+
57+
bool started = false;
58+
59+
@override
60+
void frameDidDraw() {
61+
if (!started) {
62+
started = true;
63+
SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) async {
64+
tester.start();
65+
});
66+
}
67+
super.frameDidDraw();
68+
}
69+
70+
@override
71+
Widget createWidget() {
72+
const int rowsCount = 60;
73+
const int columnsCount = 20;
74+
const double containerSize = 20;
75+
return Directionality(
76+
textDirection: TextDirection.ltr,
77+
child: Align(
78+
alignment: Alignment.topLeft,
79+
child: SizedBox(
80+
width: 400,
81+
height: 400,
82+
child: ListView.builder(
83+
itemCount: rowsCount,
84+
cacheExtent: rowsCount * containerSize,
85+
physics: const ClampingScrollPhysics(),
86+
itemBuilder: (BuildContext context, int rowIndex) => _NestedMouseRegion(
87+
nests: 10,
88+
child: Row(
89+
children: List<Widget>.generate(
90+
columnsCount,
91+
(int columnIndex) => _NestedMouseRegion(
92+
nests: 10,
93+
child: Container(
94+
decoration: BoxDecoration(
95+
border: _getBorder(columnIndex, rowIndex),
96+
color: Color.fromARGB(255, rowIndex * 20 % 256, 127, 127),
97+
),
98+
width: containerSize,
99+
height: containerSize,
100+
),
101+
),
102+
),
103+
),
104+
),
105+
),
106+
),
107+
),
108+
);
109+
}
110+
}
111+
112+
class _UntilNextFrame {
113+
_UntilNextFrame._();
114+
115+
static Completer<void> _completer;
116+
117+
static Future<void> wait() {
118+
if (_UntilNextFrame._completer == null) {
119+
_UntilNextFrame._completer = Completer<void>();
120+
SchedulerBinding.instance.addPostFrameCallback((_) {
121+
_UntilNextFrame._completer.complete(null);
122+
_UntilNextFrame._completer = null;
123+
});
124+
}
125+
return _UntilNextFrame._completer.future;
126+
}
127+
}
128+
129+
class _Tester {
130+
static const Duration hoverDuration = Duration(milliseconds: 20);
131+
132+
bool _stopped = false;
133+
134+
TestGesture get gesture {
135+
return _gesture ??= TestGesture(
136+
dispatcher: (PointerEvent event, HitTestResult result) async {
137+
RendererBinding.instance.dispatchEvent(event, result);
138+
},
139+
hitTester: (Offset location) {
140+
final HitTestResult result = HitTestResult();
141+
RendererBinding.instance.hitTest(result, location);
142+
return result;
143+
},
144+
kind: PointerDeviceKind.mouse,
145+
);
146+
}
147+
TestGesture _gesture;
148+
149+
Duration currentTime = Duration.zero;
150+
151+
Future<void> _hoverTo(Offset location, Duration duration) async {
152+
currentTime += duration;
153+
await gesture.moveTo(location, timeStamp: currentTime);
154+
await _UntilNextFrame.wait();
155+
}
156+
157+
Future<void> start() async {
158+
await Future<void>.delayed(Duration.zero);
159+
while (!_stopped) {
160+
await _hoverTo(const Offset(30, 10), hoverDuration);
161+
await _hoverTo(const Offset(10, 370), hoverDuration);
162+
await _hoverTo(const Offset(370, 390), hoverDuration);
163+
await _hoverTo(const Offset(390, 30), hoverDuration);
164+
}
165+
}
166+
167+
void stop() {
168+
_stopped = true;
169+
}
170+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:ui';
7+
8+
import 'package:flutter/gestures.dart';
9+
import 'package:flutter/rendering.dart';
10+
import 'package:flutter/widgets.dart';
11+
import 'package:flutter/scheduler.dart';
12+
import 'package:flutter_test/flutter_test.dart';
13+
14+
import 'recorder.dart';
15+
16+
/// Creates a grid of mouse regions, then continuously scrolls them up and down.
17+
///
18+
/// Measures our ability to render mouse regions.
19+
class BenchMouseRegionGridScroll extends WidgetRecorder {
20+
BenchMouseRegionGridScroll() : super(name: benchmarkName);
21+
22+
static const String benchmarkName = 'bench_mouse_region_grid_scroll';
23+
24+
final _Tester tester = _Tester();
25+
26+
// Use a non-trivial border to force Web to switch painter
27+
Border _getBorder(int columnIndex, int rowIndex) {
28+
const BorderSide defaultBorderSide = BorderSide();
29+
30+
return Border(
31+
left: columnIndex == 0 ? defaultBorderSide : BorderSide.none,
32+
top: rowIndex == 0 ? defaultBorderSide : BorderSide.none,
33+
right: defaultBorderSide,
34+
bottom: defaultBorderSide,
35+
);
36+
}
37+
38+
bool started = false;
39+
40+
@override
41+
void frameDidDraw() {
42+
if (!started) {
43+
started = true;
44+
SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) async {
45+
tester.start();
46+
final VoidCallback localDidStop = didStop;
47+
didStop = () {
48+
if (localDidStop != null)
49+
localDidStop();
50+
tester.stop();
51+
};
52+
});
53+
}
54+
super.frameDidDraw();
55+
}
56+
57+
@override
58+
Widget createWidget() {
59+
const int rowsCount = 60;
60+
const int columnsCount = 20;
61+
const double containerSize = 20;
62+
return Directionality(
63+
textDirection: TextDirection.ltr,
64+
child: Align(
65+
alignment: Alignment.topLeft,
66+
child: SizedBox(
67+
width: 400,
68+
height: 400,
69+
child: ListView.builder(
70+
itemCount: rowsCount,
71+
cacheExtent: rowsCount * containerSize,
72+
physics: const ClampingScrollPhysics(),
73+
itemBuilder: (BuildContext context, int rowIndex) => Row(
74+
children: List<Widget>.generate(
75+
columnsCount,
76+
(int columnIndex) => MouseRegion(
77+
onEnter: (_) {},
78+
child: Container(
79+
decoration: BoxDecoration(
80+
border: _getBorder(columnIndex, rowIndex),
81+
color: Color.fromARGB(255, rowIndex * 20 % 256, 127, 127),
82+
),
83+
width: containerSize,
84+
height: containerSize,
85+
),
86+
),
87+
),
88+
),
89+
),
90+
),
91+
),
92+
);
93+
}
94+
}
95+
96+
class _UntilNextFrame {
97+
_UntilNextFrame._();
98+
99+
static Completer<void> _completer;
100+
101+
static Future<void> wait() {
102+
if (_UntilNextFrame._completer == null) {
103+
_UntilNextFrame._completer = Completer<void>();
104+
SchedulerBinding.instance.addPostFrameCallback((_) {
105+
_UntilNextFrame._completer.complete(null);
106+
_UntilNextFrame._completer = null;
107+
});
108+
}
109+
return _UntilNextFrame._completer.future;
110+
}
111+
}
112+
113+
class _Tester {
114+
static const int scrollFrequency = 60;
115+
static const Offset dragStartLocation = Offset(200, 200);
116+
static const Offset dragUpOffset = Offset(0, 200);
117+
static const Offset dragDownOffset = Offset(0, -200);
118+
static const Duration dragDuration = Duration(milliseconds: 200);
119+
120+
bool _stopped = false;
121+
122+
TestGesture get gesture {
123+
return _gesture ??= TestGesture(
124+
dispatcher: (PointerEvent event, HitTestResult result) async {
125+
RendererBinding.instance.dispatchEvent(event, result);
126+
},
127+
hitTester: (Offset location) {
128+
final HitTestResult result = HitTestResult();
129+
RendererBinding.instance.hitTest(result, location);
130+
return result;
131+
},
132+
kind: PointerDeviceKind.mouse,
133+
);
134+
}
135+
TestGesture _gesture;
136+
137+
Duration currentTime = Duration.zero;
138+
139+
Future<void> _scroll(Offset start, Offset offset, Duration duration) async {
140+
final int durationMs = duration.inMilliseconds;
141+
final Duration fullFrameDuration = const Duration(seconds: 1) ~/ scrollFrequency;
142+
final int frameDurationMs = fullFrameDuration.inMilliseconds;
143+
144+
final int fullFrames = duration.inMilliseconds ~/ frameDurationMs;
145+
final Offset fullFrameOffset = offset * ((frameDurationMs as double) / durationMs);
146+
147+
final Duration finalFrameDuration = duration - fullFrameDuration * fullFrames;
148+
final Offset finalFrameOffset = offset - fullFrameOffset * (fullFrames as double);
149+
150+
await gesture.down(start, timeStamp: currentTime);
151+
152+
for (int frame = 0; frame < fullFrames; frame += 1) {
153+
currentTime += fullFrameDuration;
154+
await gesture.moveBy(fullFrameOffset, timeStamp: currentTime);
155+
await _UntilNextFrame.wait();
156+
}
157+
158+
if (finalFrameOffset != Offset.zero) {
159+
currentTime += finalFrameDuration;
160+
await gesture.moveBy(finalFrameOffset, timeStamp: currentTime);
161+
await _UntilNextFrame.wait();
162+
}
163+
164+
await gesture.up(timeStamp: currentTime);
165+
}
166+
167+
Future<void> start() async {
168+
await Future<void>.delayed(Duration.zero);
169+
while (!_stopped) {
170+
await _scroll(dragStartLocation, dragUpOffset, dragDuration);
171+
await _scroll(dragStartLocation, dragDownOffset, dragDuration);
172+
}
173+
}
174+
175+
void stop() {
176+
_stopped = true;
177+
}
178+
}

dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart

+4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import 'src/web/bench_child_layers.dart';
1616
import 'src/web/bench_clipped_out_pictures.dart';
1717
import 'src/web/bench_draw_rect.dart';
1818
import 'src/web/bench_dynamic_clip_on_static_picture.dart';
19+
import 'src/web/bench_mouse_region_grid_hover.dart';
20+
import 'src/web/bench_mouse_region_grid_scroll.dart';
1921
import 'src/web/bench_paths.dart';
2022
import 'src/web/bench_picture_recording.dart';
2123
import 'src/web/bench_simple_lazy_text_scroll.dart';
@@ -42,6 +44,8 @@ final Map<String, RecorderFactory> benchmarks = <String, RecorderFactory>{
4244
BenchDynamicClipOnStaticPicture.benchmarkName: () => BenchDynamicClipOnStaticPicture(),
4345
BenchPictureRecording.benchmarkName: () => BenchPictureRecording(),
4446
BenchUpdateManyChildLayers.benchmarkName: () => BenchUpdateManyChildLayers(),
47+
BenchMouseRegionGridScroll.benchmarkName: () => BenchMouseRegionGridScroll(),
48+
BenchMouseRegionGridHover.benchmarkName: () => BenchMouseRegionGridHover(),
4549
if (isCanvasKit)
4650
BenchBuildColorsGrid.canvasKitBenchmarkName: () => BenchBuildColorsGrid.canvasKit(),
4751

0 commit comments

Comments
 (0)