Skip to content

Commit f52ca62

Browse files
committed
Add the example app for performance tests
1 parent d1d6e57 commit f52ca62

File tree

1 file changed

+195
-0
lines changed

1 file changed

+195
-0
lines changed

example/lib/04.performance/main.dart

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// This web-only app is used to assess the performance.
2+
// It allowed us to file this issue: https://github.com/flutter/flutter/issues/128575
3+
// And to see how much overhead our editor adds on top of
4+
// the Flutter's raw widget and controller.
5+
6+
// ignore_for_file: avoid_print
7+
8+
import 'dart:js' as js; // ignore: avoid_web_libraries_in_flutter
9+
import 'package:flutter/material.dart';
10+
import 'package:flutter_code_editor/flutter_code_editor.dart';
11+
import 'package:flutter_highlight/themes/vs2015.dart';
12+
import 'package:highlight/languages/java.dart';
13+
14+
const attempts = 5;
15+
const maxKLines = 10;
16+
const text = 'int efghijklmnopqrstuvwxy;\n';
17+
final texts = [
18+
for (int i = 0; i <= maxKLines; i++) text * i * 1000,
19+
];
20+
21+
const span = TextSpan(
22+
children: [
23+
TextSpan(text: 'int', style: TextStyle(color: Colors.red)),
24+
TextSpan(
25+
text: ' efghijklmnopqrstuvwxy;\n',
26+
style: TextStyle(color: Colors.grey),
27+
),
28+
],
29+
);
30+
final spans = [
31+
for (int i = 0; i <= maxKLines; i++)
32+
TextSpan(children: List.filled(i * 1000, span)),
33+
];
34+
35+
final controllers = {
36+
'TextEditingController': TextEditingController(),
37+
'TextEditingController + colors': ColoredTextEditingController(),
38+
'CodeController, no lang': CodeController(),
39+
'CodeController, Java': CodeController(language: java),
40+
};
41+
42+
typedef FieldFactory = Widget Function({
43+
required TextEditingController controller,
44+
required bool expands,
45+
});
46+
47+
final fieldFactories = <String, FieldFactory>{
48+
'TextField': ({required controller, required expands}) => TextField(
49+
controller: controller,
50+
maxLines: null,
51+
expands: expands,
52+
),
53+
'CodeField': ({required controller, required expands}) => CodeField(
54+
controller: controller as CodeController,
55+
expands: expands,
56+
),
57+
};
58+
59+
final renderer = js.context['flutterCanvasKit'] == null ? 'HTML' : 'CanvasKit';
60+
61+
void main() {
62+
runApp(CodeEditor());
63+
}
64+
65+
class CodeEditor extends StatefulWidget {
66+
@override
67+
State<CodeEditor> createState() => _CodeEditorState();
68+
}
69+
70+
class _CodeEditorState extends State<CodeEditor> {
71+
bool _expands = false;
72+
73+
String _controllerKey = controllers.keys.first;
74+
TextEditingController _controller = controllers.values.first;
75+
76+
String _fieldFactoryKey = fieldFactories.keys.first;
77+
78+
@override
79+
Widget build(BuildContext context) {
80+
return MaterialApp(
81+
debugShowCheckedModeBanner: false,
82+
home: Scaffold(
83+
appBar: AppBar(
84+
actions: [
85+
for (int i = 0; i <= maxKLines; i++)
86+
ElevatedButton(
87+
child: Text('${i}k'),
88+
onPressed: () async => _setText(i),
89+
),
90+
ElevatedButton(
91+
onPressed: _setTexts,
92+
child: const Text('1-${maxKLines}k'),
93+
),
94+
ElevatedButton(
95+
onPressed: _runAll,
96+
child: const Text('All'),
97+
),
98+
],
99+
),
100+
body: CodeTheme(
101+
data: CodeThemeData(styles: vs2015Theme),
102+
child: _getWidget(),
103+
),
104+
),
105+
);
106+
}
107+
108+
Widget _getWidget() {
109+
final field = fieldFactories[_fieldFactoryKey]!(
110+
controller: _controller,
111+
expands: _expands,
112+
);
113+
114+
return _expands ? field : SingleChildScrollView(child: field);
115+
}
116+
117+
String _getName() {
118+
return '$renderer, $_fieldFactoryKey, $_controllerKey, expands=$_expands';
119+
}
120+
121+
Future<double> _setText(int klines) async {
122+
final sw = Stopwatch();
123+
sw.start();
124+
125+
for (int attempt = attempts; --attempt >= 0;) {
126+
_controller.text = '';
127+
_controller.text = texts[klines];
128+
129+
// Give up the control to render.
130+
await Future.delayed(const Duration(milliseconds: 100));
131+
}
132+
133+
final result = sw.elapsed.inMilliseconds / attempts;
134+
print('Set ${klines}k lines in $result ms');
135+
return result;
136+
}
137+
138+
Future<String> _setTexts() async {
139+
final durations = <double>[];
140+
141+
for (int i = 1; i <= maxKLines; i++) {
142+
durations.add(await _setText(i));
143+
}
144+
145+
final name = _getName();
146+
final result = '$name\t${durations.join('\t')}';
147+
print(result);
148+
return result;
149+
}
150+
151+
Future<void> _runAll() async {
152+
final results = <String>[];
153+
154+
for (final controllerKey in controllers.keys) {
155+
for (final fieldFactoryKey in fieldFactories.keys) {
156+
for (final expands in [/*true,*/ false]) {
157+
final controller = controllers[controllerKey]!;
158+
159+
if (['CodeField'].contains(fieldFactoryKey) &&
160+
controller is! CodeController) {
161+
continue; // CodeField can only work with CodeController.
162+
}
163+
164+
setState(() {
165+
_controllerKey = controllerKey;
166+
_controller = controller;
167+
_fieldFactoryKey = fieldFactoryKey;
168+
_expands = expands;
169+
});
170+
171+
controller.text = '';
172+
await Future.delayed(const Duration(milliseconds: 100));
173+
174+
print(_getName());
175+
results.add(await _setTexts());
176+
}
177+
}
178+
}
179+
180+
print('');
181+
print(results.join('\n'));
182+
}
183+
}
184+
185+
class ColoredTextEditingController extends TextEditingController {
186+
@override
187+
TextSpan buildTextSpan({
188+
required BuildContext context,
189+
TextStyle? style,
190+
required bool withComposing,
191+
}) {
192+
final klines = (this.text.length / 27 / 1000).floor();
193+
return spans[klines];
194+
}
195+
}

0 commit comments

Comments
 (0)