Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7)

Side by Side Diff: runtime/observatory/lib/src/app/view_model.dart

Issue 1833453004: Dramatically increase the performance of Observatory's profile UI (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « runtime/observatory/lib/app.dart ('k') | runtime/observatory/lib/src/elements/cpu_profile.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of app; 5 part of app;
6 6
7 abstract class VirtualTreeRow {
8 // Number of ems each subtree is indented.
9 static const subtreeIndent = 1.05;
10
11 static const redColor = '#F44336';
12 static const blueColor = '#3F51B5';
13 static const purpleColor = '#673AB7';
14 static const greenColor = '#4CAF50';
15 static const orangeColor = '#FF9800';
16 static const lightGrayColor = '#FAFAFA';
17
18 List backgroundColors = const [
19 purpleColor,
20 redColor,
21 greenColor,
22 blueColor,
23 orangeColor,
24 ];
25
26 final VirtualTree tree;
27 final List<VirtualTreeRow> children = [];
28 final List<StreamSubscription> _listeners = [];
29 final int depth;
30 bool _expanded = false;
31
32 VirtualTreeRow(this.tree, this.depth);
33
34 bool get expanded => _expanded;
35
36 set expanded(bool expanded) {
37 var changed = _expanded != expanded;
38 _expanded = expanded;
39 if (!changed) {
40 return;
41 }
42 if (_expanded) {
43 _expand();
44 } else {
45 _collapse();
46 }
47 }
48
49 Element makeColorBar() {
50 var element = new SpanElement();
51 element.style.paddingLeft = '2px';
52 element.style.paddingRight = '2px';
53 element.style.flexBasis = '2px';
54 element.style.height = '${tree.rowHeight}px';
55 element.style.minHeight = '${tree.rowHeight}px';
56 if (depth > 0) {
57 var colorIndex = (depth - 1) % backgroundColors.length;
58 element.style.backgroundColor = backgroundColors[colorIndex];
59 }
60 return element;
61 }
62
63 Element makeExpander() {
64 SpanElement element = new SpanElement();
65 element.style.flexBasis = '2em';
66 if (!hasChildren()) {
67 element.style.visibility = 'hidden';
68 } else {
69 element.style.visibility = 'visible';
70 element.children.add(expanded ?
71 new Element.tag('icon-expand-more') :
72 new Element.tag('icon-chevron-right'));
73 }
74 _listeners.add(element.onClick.listen((e) {
75 e.stopPropagation();
76 toggle();
77 }));
78 return element;
79 }
80
81 Element makeIndenter({colored: true}) {
82 SpanElement element = new SpanElement();
83 element.style.paddingLeft = '${subtreeIndent * depth}em';
84 element.style.flexBasis = '${subtreeIndent * depth}em';
85 element.style.height = '${tree.rowHeight}px';
86 element.style.minHeight = '${tree.rowHeight}px';
87 if (colored) {
88 if (depth > 0) {
89 var colorIndex = (depth - 1) % backgroundColors.length;
90 element.style.backgroundColor = backgroundColors[colorIndex];
91 }
92 }
93 return element;
94 }
95
96 Element makeText(String text, {String toolTip, String flexBasis: '7em'}) {
97 SpanElement element = new SpanElement();
98 element.text = text;
99 if (toolTip != null) {
100 element.title = toolTip;
101 }
102 if (flexBasis != null) {
103 element.style.flexBasis = flexBasis;
104 }
105 return element;
106 }
107
108 Element makeGap({double ems: 0.5}) {
109 SpanElement element = new SpanElement();
110 var flexBasis = '${ems}em';
111 element.style.flexBasis = flexBasis;
112 return element;
113 }
114
115 void _cleanupListeners() {
116 for (var listener in _listeners) {
117 listener.cancel();
118 }
119 _listeners.clear();
120 }
121
122 void _expand() {
123 onShow();
124 tree._onExpand(this);
125 if (children.length == 1) {
126 children[0]._expand();
127 }
128 _expanded = true;
129 }
130
131 void _collapse() {
132 _expanded = false;
133 for (var i = 0; i < children.length; i++) {
134 if (children[i].expanded) {
135 children[i]._collapse();
136 }
137 }
138 tree._onCollapse(this);
139 }
140
141 void toggle() {
142 expanded = !expanded;
143 }
144
145 void _render(DivElement rowDiv) {
146 rowDiv.style.display = 'flex';
147 rowDiv.style.alignItems = 'center';
148 _cleanupListeners();
149 onShow();
150 onRender(rowDiv);
151 }
152
153 /// Called when you should render into [rowDiv].
154 void onRender(DivElement rowDiv);
155
156 // Called when this row is visible.
157 void onShow();
158
159 // Return the number of children this node has.
160 int get childCount => 0;
161
162 // Return true if this node can be expanded.
163 bool hasChildren() => childCount > 0;
164
165 // Called when this row is not visible.
166 void onHide() {
167 _cleanupListeners();
168 }
169 }
170
171 class VirtualTree {
172 final int rowHeight;
173 final List<VirtualTreeRow> rows = [];
174 final DivElement root;
175 final Stopwatch _clock = new Stopwatch();
176
177 DivElement _treeHeightElement;
178 DivElement _tree;
179
180 StreamSubscription _scrollSubscription;
181 StreamSubscription _resizeSubscription;
182
183 // Height of [root] in pixels.
184 int viewHeight;
185
186 // Number of pixels view can be scrolled before a redraw occurs.
187 int redrawThresholdPixels;
188
189 // Number of rows visible at any given time.
190 int numVisibleRows;
191 // Number of rows above the current view that are in the dom.
192 int extraRowsAbove;
193 // Number of rows below the current view that are in the dom.
194 int extraRowsBelow;
195
196 // The scroll top of the last scroll event.
197 int lastPaintScrollTop;
198
199 // The starting row of the last paint.
200 int lastPaintStartingRow = 0;
201
202 bool paintScheduled = false;
203
204 bool scrolled = false;
205
206 static const scrollStopThresholdMilliseconds = 100;
207
208 VirtualTree(this.rowHeight, this.root) {
209 _clock.start();
210 _install();
211 _resize();
212 _schedulePaint(0);
213 }
214
215 void uninstall() => _uninstall();
216
217 void refresh() {
218 _resize();
219 _schedulePaint(lastPaintStartingRow);
220 }
221
222 // Clear the tree.
223 void clear() {
224 rows.clear();
225 _resize();
226 }
227
228 void _onExpand(VirtualTreeRow parent) {
229 int index = rows.indexOf(parent);
230 if (index == -1) {
231 return;
232 }
233 rows.insertAll(index + 1, parent.children);
234 refresh();
235 }
236
237 void _onCollapse(VirtualTreeRow parent) {
238 int index = rows.indexOf(parent);
239 if (index == -1) {
240 return;
241 }
242 int start = index + 1;
243 int end = start + parent.children.length;
244 rows.removeRange(start, end);
245 refresh();
246 }
247
248 void _resize() {
249 if (viewHeight != root.offsetHeight) {
250 viewHeight = root.offsetHeight;
251 numVisibleRows = (viewHeight ~/ rowHeight) + 1;
252 extraRowsAbove = numVisibleRows ~/ 2;
253 extraRowsBelow = numVisibleRows - extraRowsAbove;
254 redrawThresholdPixels =
255 math.min(extraRowsAbove, extraRowsBelow) * rowHeight;
256 }
257 _treeHeightElement.style.height = '${_treeHeight()}px';
258 }
259
260 int _treeHeight() {
261 return rows.length * rowHeight;
262 }
263
264 int _pixelsFromLastScroll(int currentScrollTop) {
265 if (lastPaintScrollTop == null) {
266 return currentScrollTop;
267 }
268
269 return (currentScrollTop - lastPaintScrollTop).abs();
270 }
271
272 int _pixelToRow(int pixelY) {
273 int result = pixelY ~/ rowHeight;
274 return result;
275 }
276
277 void _install() {
278 // This element controls the height of the tree's scrollable region.
279 // It is one pixel wide and the height is set to rowHeight * numRows.
280 _treeHeightElement = new DivElement();
281 _treeHeightElement.style.position = 'absolute';
282 _treeHeightElement.style.top = '0';
283 _treeHeightElement.style.left = '0';
284 _treeHeightElement.style.width = '1px';
285
286 // This element holds the visible tree rows and the height controlling
287 // element. It takes the full width and height of its parent element.
288 _tree = new DivElement();
289 _tree.children.add(_treeHeightElement);
290 _tree.style.width = '100%';
291 _tree.style.height = '100%';
292 _tree.style.position = 'relative';
293 _tree.style.overflow = 'auto';
294
295 // Listen for scroll events on the tree.
296 _scrollSubscription = _tree.onScroll.listen(_onScroll);
297
298 root.children.add(_tree);
299
300 // Listen for resize events.
301 _resizeSubscription = window.onResize.listen((_) {
302 _resize();
303 _schedulePaint(lastPaintStartingRow);
304 });
305 }
306
307 void _uninstall() {
308 root.children.clear();
309 _scrollSubscription?.cancel();
310 _scrollSubscription = null;
311 _resizeSubscription?.cancel();
312 _resizeSubscription = null;
313 }
314
315 void _onScroll(Event scrollEvent) {
316 Element target = scrollEvent.target;
317 int scrollTop = target.scrollTop;
318 if (_pixelsFromLastScroll(scrollTop) > redrawThresholdPixels) {
319 _schedulePaint(lastPaintStartingRow);
320 scrolled = true;
321 }
322 scrollEvent.preventDefault();
323 }
324
325 void _schedulePaint(int startingRow) {
326 if (paintScheduled) {
327 return;
328 }
329 paintScheduled = true;
330 window.requestAnimationFrame(
331 (timestamp) => _onRenderFrame(timestamp, startingRow));
332 }
333
334 void _onRenderFrame(int timestamp, int startingRow) {
335 paintScheduled = false;
336 _paint(startingRow);
337 }
338
339 void _paint(int startingRow) {
340 if (scrolled) {
341 startingRow = math.max(_pixelToRow(_tree.scrollTop), 0);
342 scrolled = false;
343 }
344 lastPaintScrollTop = _tree.scrollTop;
345 lastPaintStartingRow = startingRow;
346
347 int endingRow =
348 math.min(rows.length, startingRow + numVisibleRows + extraRowsBelow);
349
350 startingRow =
351 math.max(0, startingRow - extraRowsAbove);
352
353 print('PAINT $startingRow $endingRow');
354
355 for (int i = startingRow; i < endingRow; i++) {
356 // We add 1 because _tree.children[0] contains the height control element.
357 int cacheIndex = (i - startingRow) + 1;
358 DivElement row;
359 if (cacheIndex < _tree.children.length) {
360 // re-use existing row.
361 row = _tree.children[cacheIndex];
362 row.children.clear();
363 } else {
364 // Allocate a new row.
365 row = new DivElement();
366 row.style.position = 'absolute';
367 row.style.height = '${rowHeight}px';
368 row.style.maxHeight = '${rowHeight}px';
369 row.style.margin = '0';
370 row.style.width = '100%';
371 row.style.left = '0';
372 _tree.children.add(row);
373 }
374 row.style.top = '${(i * rowHeight)}px';
375 // Render the row.
376 rows[i]._render(row);
377 }
378 int necessaryChildren = (endingRow - startingRow) + 1;
379 while (_tree.children.length > necessaryChildren) {
380 _tree.children.removeLast();
381 }
382 }
383 }
384
7 abstract class TableTreeRow extends Observable { 385 abstract class TableTreeRow extends Observable {
8 static const arrowRight = '\u2192'; 386 static const arrowRight = '\u2192';
9 static const arrowDownRight = '\u21b3'; 387 static const arrowDownRight = '\u21b3';
10 // Number of ems each subtree is indented. 388 // Number of ems each subtree is indented.
11 static const subtreeIndent = 2; 389 static const subtreeIndent = 2;
12 390
13 TableTreeRow(this.tree, TableTreeRow parent) : 391 TableTreeRow(this.tree, TableTreeRow parent) :
14 parent = parent, 392 parent = parent,
15 depth = parent != null ? parent.depth + 1 : 0 { 393 depth = parent != null ? parent.depth + 1 : 0 {
16 } 394 }
(...skipping 346 matching lines...) Expand 10 before | Expand all | Expand 10 after
363 if (column != _sortColumnIndex) { 741 if (column != _sortColumnIndex) {
364 return columns[column].label + '\u2003'; 742 return columns[column].label + '\u2003';
365 } 743 }
366 return columns[column].label + (_sortDescending ? arrowUp : arrowDown); 744 return columns[column].label + (_sortDescending ? arrowUp : arrowDown);
367 } 745 }
368 746
369 dynamic getValue(int row, int column) { 747 dynamic getValue(int row, int column) {
370 return rows[row].values[column]; 748 return rows[row].values[column];
371 } 749 }
372 } 750 }
OLDNEW
« no previous file with comments | « runtime/observatory/lib/app.dart ('k') | runtime/observatory/lib/src/elements/cpu_profile.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698