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

Unified 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, 9 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 side-by-side diff with in-line comments
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 »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: runtime/observatory/lib/src/app/view_model.dart
diff --git a/runtime/observatory/lib/src/app/view_model.dart b/runtime/observatory/lib/src/app/view_model.dart
index 94edb496bac325876f8b946aac51a476a80b54c7..6562e1f1f6da783ac57208f9cbc6afa5a4dede3c 100644
--- a/runtime/observatory/lib/src/app/view_model.dart
+++ b/runtime/observatory/lib/src/app/view_model.dart
@@ -4,6 +4,384 @@
part of app;
+abstract class VirtualTreeRow {
+ // Number of ems each subtree is indented.
+ static const subtreeIndent = 1.05;
+
+ static const redColor = '#F44336';
+ static const blueColor = '#3F51B5';
+ static const purpleColor = '#673AB7';
+ static const greenColor = '#4CAF50';
+ static const orangeColor = '#FF9800';
+ static const lightGrayColor = '#FAFAFA';
+
+ List backgroundColors = const [
+ purpleColor,
+ redColor,
+ greenColor,
+ blueColor,
+ orangeColor,
+ ];
+
+ final VirtualTree tree;
+ final List<VirtualTreeRow> children = [];
+ final List<StreamSubscription> _listeners = [];
+ final int depth;
+ bool _expanded = false;
+
+ VirtualTreeRow(this.tree, this.depth);
+
+ bool get expanded => _expanded;
+
+ set expanded(bool expanded) {
+ var changed = _expanded != expanded;
+ _expanded = expanded;
+ if (!changed) {
+ return;
+ }
+ if (_expanded) {
+ _expand();
+ } else {
+ _collapse();
+ }
+ }
+
+ Element makeColorBar() {
+ var element = new SpanElement();
+ element.style.paddingLeft = '2px';
+ element.style.paddingRight = '2px';
+ element.style.flexBasis = '2px';
+ element.style.height = '${tree.rowHeight}px';
+ element.style.minHeight = '${tree.rowHeight}px';
+ if (depth > 0) {
+ var colorIndex = (depth - 1) % backgroundColors.length;
+ element.style.backgroundColor = backgroundColors[colorIndex];
+ }
+ return element;
+ }
+
+ Element makeExpander() {
+ SpanElement element = new SpanElement();
+ element.style.flexBasis = '2em';
+ if (!hasChildren()) {
+ element.style.visibility = 'hidden';
+ } else {
+ element.style.visibility = 'visible';
+ element.children.add(expanded ?
+ new Element.tag('icon-expand-more') :
+ new Element.tag('icon-chevron-right'));
+ }
+ _listeners.add(element.onClick.listen((e) {
+ e.stopPropagation();
+ toggle();
+ }));
+ return element;
+ }
+
+ Element makeIndenter({colored: true}) {
+ SpanElement element = new SpanElement();
+ element.style.paddingLeft = '${subtreeIndent * depth}em';
+ element.style.flexBasis = '${subtreeIndent * depth}em';
+ element.style.height = '${tree.rowHeight}px';
+ element.style.minHeight = '${tree.rowHeight}px';
+ if (colored) {
+ if (depth > 0) {
+ var colorIndex = (depth - 1) % backgroundColors.length;
+ element.style.backgroundColor = backgroundColors[colorIndex];
+ }
+ }
+ return element;
+ }
+
+ Element makeText(String text, {String toolTip, String flexBasis: '7em'}) {
+ SpanElement element = new SpanElement();
+ element.text = text;
+ if (toolTip != null) {
+ element.title = toolTip;
+ }
+ if (flexBasis != null) {
+ element.style.flexBasis = flexBasis;
+ }
+ return element;
+ }
+
+ Element makeGap({double ems: 0.5}) {
+ SpanElement element = new SpanElement();
+ var flexBasis = '${ems}em';
+ element.style.flexBasis = flexBasis;
+ return element;
+ }
+
+ void _cleanupListeners() {
+ for (var listener in _listeners) {
+ listener.cancel();
+ }
+ _listeners.clear();
+ }
+
+ void _expand() {
+ onShow();
+ tree._onExpand(this);
+ if (children.length == 1) {
+ children[0]._expand();
+ }
+ _expanded = true;
+ }
+
+ void _collapse() {
+ _expanded = false;
+ for (var i = 0; i < children.length; i++) {
+ if (children[i].expanded) {
+ children[i]._collapse();
+ }
+ }
+ tree._onCollapse(this);
+ }
+
+ void toggle() {
+ expanded = !expanded;
+ }
+
+ void _render(DivElement rowDiv) {
+ rowDiv.style.display = 'flex';
+ rowDiv.style.alignItems = 'center';
+ _cleanupListeners();
+ onShow();
+ onRender(rowDiv);
+ }
+
+ /// Called when you should render into [rowDiv].
+ void onRender(DivElement rowDiv);
+
+ // Called when this row is visible.
+ void onShow();
+
+ // Return the number of children this node has.
+ int get childCount => 0;
+
+ // Return true if this node can be expanded.
+ bool hasChildren() => childCount > 0;
+
+ // Called when this row is not visible.
+ void onHide() {
+ _cleanupListeners();
+ }
+}
+
+class VirtualTree {
+ final int rowHeight;
+ final List<VirtualTreeRow> rows = [];
+ final DivElement root;
+ final Stopwatch _clock = new Stopwatch();
+
+ DivElement _treeHeightElement;
+ DivElement _tree;
+
+ StreamSubscription _scrollSubscription;
+ StreamSubscription _resizeSubscription;
+
+ // Height of [root] in pixels.
+ int viewHeight;
+
+ // Number of pixels view can be scrolled before a redraw occurs.
+ int redrawThresholdPixels;
+
+ // Number of rows visible at any given time.
+ int numVisibleRows;
+ // Number of rows above the current view that are in the dom.
+ int extraRowsAbove;
+ // Number of rows below the current view that are in the dom.
+ int extraRowsBelow;
+
+ // The scroll top of the last scroll event.
+ int lastPaintScrollTop;
+
+ // The starting row of the last paint.
+ int lastPaintStartingRow = 0;
+
+ bool paintScheduled = false;
+
+ bool scrolled = false;
+
+ static const scrollStopThresholdMilliseconds = 100;
+
+ VirtualTree(this.rowHeight, this.root) {
+ _clock.start();
+ _install();
+ _resize();
+ _schedulePaint(0);
+ }
+
+ void uninstall() => _uninstall();
+
+ void refresh() {
+ _resize();
+ _schedulePaint(lastPaintStartingRow);
+ }
+
+ // Clear the tree.
+ void clear() {
+ rows.clear();
+ _resize();
+ }
+
+ void _onExpand(VirtualTreeRow parent) {
+ int index = rows.indexOf(parent);
+ if (index == -1) {
+ return;
+ }
+ rows.insertAll(index + 1, parent.children);
+ refresh();
+ }
+
+ void _onCollapse(VirtualTreeRow parent) {
+ int index = rows.indexOf(parent);
+ if (index == -1) {
+ return;
+ }
+ int start = index + 1;
+ int end = start + parent.children.length;
+ rows.removeRange(start, end);
+ refresh();
+ }
+
+ void _resize() {
+ if (viewHeight != root.offsetHeight) {
+ viewHeight = root.offsetHeight;
+ numVisibleRows = (viewHeight ~/ rowHeight) + 1;
+ extraRowsAbove = numVisibleRows ~/ 2;
+ extraRowsBelow = numVisibleRows - extraRowsAbove;
+ redrawThresholdPixels =
+ math.min(extraRowsAbove, extraRowsBelow) * rowHeight;
+ }
+ _treeHeightElement.style.height = '${_treeHeight()}px';
+ }
+
+ int _treeHeight() {
+ return rows.length * rowHeight;
+ }
+
+ int _pixelsFromLastScroll(int currentScrollTop) {
+ if (lastPaintScrollTop == null) {
+ return currentScrollTop;
+ }
+
+ return (currentScrollTop - lastPaintScrollTop).abs();
+ }
+
+ int _pixelToRow(int pixelY) {
+ int result = pixelY ~/ rowHeight;
+ return result;
+ }
+
+ void _install() {
+ // This element controls the height of the tree's scrollable region.
+ // It is one pixel wide and the height is set to rowHeight * numRows.
+ _treeHeightElement = new DivElement();
+ _treeHeightElement.style.position = 'absolute';
+ _treeHeightElement.style.top = '0';
+ _treeHeightElement.style.left = '0';
+ _treeHeightElement.style.width = '1px';
+
+ // This element holds the visible tree rows and the height controlling
+ // element. It takes the full width and height of its parent element.
+ _tree = new DivElement();
+ _tree.children.add(_treeHeightElement);
+ _tree.style.width = '100%';
+ _tree.style.height = '100%';
+ _tree.style.position = 'relative';
+ _tree.style.overflow = 'auto';
+
+ // Listen for scroll events on the tree.
+ _scrollSubscription = _tree.onScroll.listen(_onScroll);
+
+ root.children.add(_tree);
+
+ // Listen for resize events.
+ _resizeSubscription = window.onResize.listen((_) {
+ _resize();
+ _schedulePaint(lastPaintStartingRow);
+ });
+ }
+
+ void _uninstall() {
+ root.children.clear();
+ _scrollSubscription?.cancel();
+ _scrollSubscription = null;
+ _resizeSubscription?.cancel();
+ _resizeSubscription = null;
+ }
+
+ void _onScroll(Event scrollEvent) {
+ Element target = scrollEvent.target;
+ int scrollTop = target.scrollTop;
+ if (_pixelsFromLastScroll(scrollTop) > redrawThresholdPixels) {
+ _schedulePaint(lastPaintStartingRow);
+ scrolled = true;
+ }
+ scrollEvent.preventDefault();
+ }
+
+ void _schedulePaint(int startingRow) {
+ if (paintScheduled) {
+ return;
+ }
+ paintScheduled = true;
+ window.requestAnimationFrame(
+ (timestamp) => _onRenderFrame(timestamp, startingRow));
+ }
+
+ void _onRenderFrame(int timestamp, int startingRow) {
+ paintScheduled = false;
+ _paint(startingRow);
+ }
+
+ void _paint(int startingRow) {
+ if (scrolled) {
+ startingRow = math.max(_pixelToRow(_tree.scrollTop), 0);
+ scrolled = false;
+ }
+ lastPaintScrollTop = _tree.scrollTop;
+ lastPaintStartingRow = startingRow;
+
+ int endingRow =
+ math.min(rows.length, startingRow + numVisibleRows + extraRowsBelow);
+
+ startingRow =
+ math.max(0, startingRow - extraRowsAbove);
+
+ print('PAINT $startingRow $endingRow');
+
+ for (int i = startingRow; i < endingRow; i++) {
+ // We add 1 because _tree.children[0] contains the height control element.
+ int cacheIndex = (i - startingRow) + 1;
+ DivElement row;
+ if (cacheIndex < _tree.children.length) {
+ // re-use existing row.
+ row = _tree.children[cacheIndex];
+ row.children.clear();
+ } else {
+ // Allocate a new row.
+ row = new DivElement();
+ row.style.position = 'absolute';
+ row.style.height = '${rowHeight}px';
+ row.style.maxHeight = '${rowHeight}px';
+ row.style.margin = '0';
+ row.style.width = '100%';
+ row.style.left = '0';
+ _tree.children.add(row);
+ }
+ row.style.top = '${(i * rowHeight)}px';
+ // Render the row.
+ rows[i]._render(row);
+ }
+ int necessaryChildren = (endingRow - startingRow) + 1;
+ while (_tree.children.length > necessaryChildren) {
+ _tree.children.removeLast();
+ }
+ }
+}
+
abstract class TableTreeRow extends Observable {
static const arrowRight = '\u2192';
static const arrowDownRight = '\u21b3';
« 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