From fcbdf027678ddd9be3647116b6f88e1590b303fd Mon Sep 17 00:00:00 2001 From: Yegor Jbanov Date: Thu, 5 Feb 2015 18:33:57 -0800 Subject: [PATCH] feat(perf): port table scrolling benchmark to Angular 2 --- .../change_detection/proto_change_detector.js | 2 +- modules/angular2/src/core/compiler/view.js | 3 +- modules/angular2/src/facade/collection.dart | 5 +- modules/angular2/src/facade/collection.es6 | 3 + modules/angular2/src/facade/math.dart | 4 + .../e2e_test/naive_infinite_scroll_perf.es6 | 37 +++ .../e2e_test/naive_infinite_scroll_spec.es6 | 22 ++ modules/benchmarks/src/index.html | 3 + .../src/naive_infinite_scroll/app.js | 108 +++++++++ .../src/naive_infinite_scroll/cells.js | 220 ++++++++++++++++++ .../src/naive_infinite_scroll/common.js | 205 ++++++++++++++++ .../src/naive_infinite_scroll/index.html | 16 ++ .../src/naive_infinite_scroll/index.js | 212 +++++++++++++++++ .../src/naive_infinite_scroll/random_data.js | 77 ++++++ .../src/naive_infinite_scroll/scroll_area.js | 90 +++++++ .../src/naive_infinite_scroll/scroll_item.js | 106 +++++++++ .../e2e_test/naive_infinite_scroll_perf.es6 | 6 +- modules/benchmarks_external/pubspec.yaml | 1 - .../src/naive_infinite_scroll/common.dart | 5 +- .../naive_infinite_scroll/random_data.dart | 3 +- protractor-shared.js | 2 +- 21 files changed, 1119 insertions(+), 11 deletions(-) create mode 100644 modules/benchmarks/e2e_test/naive_infinite_scroll_perf.es6 create mode 100644 modules/benchmarks/e2e_test/naive_infinite_scroll_spec.es6 create mode 100644 modules/benchmarks/src/naive_infinite_scroll/app.js create mode 100644 modules/benchmarks/src/naive_infinite_scroll/cells.js create mode 100644 modules/benchmarks/src/naive_infinite_scroll/common.js create mode 100644 modules/benchmarks/src/naive_infinite_scroll/index.html create mode 100644 modules/benchmarks/src/naive_infinite_scroll/index.js create mode 100644 modules/benchmarks/src/naive_infinite_scroll/random_data.js create mode 100644 modules/benchmarks/src/naive_infinite_scroll/scroll_area.js create mode 100644 modules/benchmarks/src/naive_infinite_scroll/scroll_item.js diff --git a/modules/angular2/src/change_detection/proto_change_detector.js b/modules/angular2/src/change_detection/proto_change_detector.js index cfccdfd884..9a2ce224a7 100644 --- a/modules/angular2/src/change_detection/proto_change_detector.js +++ b/modules/angular2/src/change_detection/proto_change_detector.js @@ -360,7 +360,7 @@ function _operationToFunction(operation:string):Function { } function s(v) { - return isPresent(v) ? '' + v : ''; + return isPresent(v) ? `${v}` : ''; } function _interpolationFn(strings:List) { diff --git a/modules/angular2/src/core/compiler/view.js b/modules/angular2/src/core/compiler/view.js index 494cdf8239..317bb4df92 100644 --- a/modules/angular2/src/core/compiler/view.js +++ b/modules/angular2/src/core/compiler/view.js @@ -411,7 +411,8 @@ export class ProtoView { // view might get dehydrated, in between the event queuing up and // firing. if (view.hydrated()) { - MapWrapper.set(locals, '\$event', event); + // HACK + MapWrapper.set(locals, `$event`, event); var context = new ContextWithVariableBindings(view.context, locals); expr.eval(context); } diff --git a/modules/angular2/src/facade/collection.dart b/modules/angular2/src/facade/collection.dart index 338b1ab0ba..3a4234f956 100644 --- a/modules/angular2/src/facade/collection.dart +++ b/modules/angular2/src/facade/collection.dart @@ -71,7 +71,7 @@ class StringMapWrapper { } class ListWrapper { - static List clone(List l) => new List.from(l); + static List clone(Iterable l) => new List.from(l); static List create() => new List(); static List createFixedSize(int size) => new List(size); static get(List m, int k) => m[k]; @@ -137,6 +137,9 @@ class ListWrapper { } return true; } + static List slice(List l, int from, int to) { + return l.sublist(from, to); + } } bool isListLikeIterable(obj) => obj is Iterable; diff --git a/modules/angular2/src/facade/collection.es6 b/modules/angular2/src/facade/collection.es6 index 6b673a9e3f..58fb3b1457 100644 --- a/modules/angular2/src/facade/collection.es6 +++ b/modules/angular2/src/facade/collection.es6 @@ -176,6 +176,9 @@ export class ListWrapper { } return true; } + static slice(l:List, from:int, to:int):List { + return l.slice(from, to); + } } export function isListLikeIterable(obj):boolean { diff --git a/modules/angular2/src/facade/math.dart b/modules/angular2/src/facade/math.dart index f4b952821f..c251c9f8c5 100644 --- a/modules/angular2/src/facade/math.dart +++ b/modules/angular2/src/facade/math.dart @@ -6,4 +6,8 @@ class Math { static num pow(num x, num exponent) { return math.pow(x, exponent); } + + static num min(num a, num b) => math.min(a, b); + + static num floor(num a) => a.floor(); } diff --git a/modules/benchmarks/e2e_test/naive_infinite_scroll_perf.es6 b/modules/benchmarks/e2e_test/naive_infinite_scroll_perf.es6 new file mode 100644 index 0000000000..072d0ef2f9 --- /dev/null +++ b/modules/benchmarks/e2e_test/naive_infinite_scroll_perf.es6 @@ -0,0 +1,37 @@ +var perfUtil = require('../../angular2/e2e_test/perf_util'); + +describe('ng2 naive infinite scroll benchmark', function () { + + var URL = 'benchmarks/src/naive_infinite_scroll/index.html'; + + afterEach(perfUtil.verifyNoBrowserErrors); + + [1, 2, 4].forEach(function(appSize) { + it('should run scroll benchmark and collect stats for appSize = ' + + appSize, function() { + perfUtil.runBenchmark({ + url: URL, + id: 'ng2.naive_infinite_scroll', + work: function() { + browser.executeScript( + 'document.querySelector("scroll-app /deep/ #reset-btn").click()'); + browser.executeScript( + 'document.querySelector("scroll-app /deep/ #run-btn").click()'); + browser.wait(() => { + return $('#done').getText().then( + function() { return true; }, + function() { return false; }); + }, 10000); + }, + params: [{ + name: 'appSize', value: appSize + }, { + name: 'iterationCount', value: 20, scale: 'linear' + }, { + name: 'scrollIncrement', value: 40 + }] + }); + }); + }); + +}); diff --git a/modules/benchmarks/e2e_test/naive_infinite_scroll_spec.es6 b/modules/benchmarks/e2e_test/naive_infinite_scroll_spec.es6 new file mode 100644 index 0000000000..b27826f4d8 --- /dev/null +++ b/modules/benchmarks/e2e_test/naive_infinite_scroll_spec.es6 @@ -0,0 +1,22 @@ +var testUtil = require('../../angular2/e2e_test/test_util'); + +describe('ng2 naive infinite scroll benchmark', function () { + + var URL = 'benchmarks/src/naive_infinite_scroll/index.html'; + + afterEach(testUtil.verifyNoBrowserErrors); + + it('should not throw errors', function() { + browser.get(URL); + browser.executeScript( + 'document.querySelector("scroll-app /deep/ #reset-btn").click()'); + browser.executeScript( + 'document.querySelector("scroll-app /deep/ #run-btn").click()'); + browser.wait(() => { + return $('#done').getText().then( + function() { return true; }, + function() { return false; }); + }, 10000); + }); + +}); diff --git a/modules/benchmarks/src/index.html b/modules/benchmarks/src/index.html index 9af6c61036..2317eab34e 100644 --- a/modules/benchmarks/src/index.html +++ b/modules/benchmarks/src/index.html @@ -20,6 +20,9 @@
  • Tree benchmark
  • +
  • + Naive infinite scroll benchmark +
  • diff --git a/modules/benchmarks/src/naive_infinite_scroll/app.js b/modules/benchmarks/src/naive_infinite_scroll/app.js new file mode 100644 index 0000000000..67aacdd45c --- /dev/null +++ b/modules/benchmarks/src/naive_infinite_scroll/app.js @@ -0,0 +1,108 @@ +import {int, isPresent} from 'angular2/src/facade/lang'; +import {reflector} from 'angular2/src/reflection/reflection'; +import {getIntParameter, bindAction} from 'angular2/src/test_lib/benchmark_util'; +import {bootstrap, Component, Template, TemplateConfig, ViewPort, Compiler} + from 'angular2/angular2'; +import {PromiseWrapper} from 'angular2/src/facade/async'; +import {ListWrapper} from 'angular2/src/facade/collection'; +import {ScrollAreaComponent} from './scroll_area'; +import {NgIf, NgRepeat} from 'angular2/directives'; +import {DOM, document, Element} from 'angular2/src/facade/dom'; + +export class App { + scrollAreas:List; + iterationCount:int; + scrollIncrement:int; + + constructor() { + var appSize = getIntParameter('appSize'); + this.iterationCount = getIntParameter('iterationCount'); + this.scrollIncrement = getIntParameter('scrollIncrement'); + appSize = appSize > 1 ? appSize - 1 : 0; // draw at least one table + this.scrollAreas = []; + for (var i = 0; i < appSize; i++) { + ListWrapper.push(this.scrollAreas, i); + } + // TODO(tbosch): change to bindAction when it works in pub serve + DOM.on(DOM.query('scroll-app /deep/ #run-btn'), 'click', (_) => { + this.runBenchmark(); + }); + DOM.on(DOM.query('scroll-app /deep/ #reset-btn'), 'click', (_) => { + this._getScrollDiv().scrollTop = 0; + var existingMarker = this._locateFinishedMarker(); + if (isPresent(existingMarker)) { + DOM.removeChild(document.body, existingMarker); + } + }); + } + + runBenchmark() { + var scrollDiv = this._getScrollDiv(); + var n:int = this.iterationCount; + var scheduleScroll; + scheduleScroll = () => { + PromiseWrapper.setTimeout(() => { + scrollDiv.scrollTop += this.scrollIncrement; + n--; + if (n > 0) { + scheduleScroll(); + } else { + this._scheduleFinishedMarker(); + } + }, 0); + } + scheduleScroll(); + } + + // Puts a marker indicating that the test is finished. + _scheduleFinishedMarker() { + var existingMarker = this._locateFinishedMarker(); + if (isPresent(existingMarker)) { + // Nothing to do, the marker is already there + return; + } + PromiseWrapper.setTimeout(() => { + var finishedDiv = DOM.createElement('div'); + finishedDiv.id = 'done'; + DOM.setInnerHTML(finishedDiv, 'Finished'); + DOM.appendChild(document.body, finishedDiv); + }, 0); + } + + _locateFinishedMarker():Element { + return DOM.querySelector(document.body, '#done'); + } + + _getScrollDiv() { + return DOM.query('body /deep/ #testArea /deep/ #scrollDiv'); + } +} + +export function setupReflectorForApp() { + reflector.registerType(App, { + 'factory': () => { return new App(); }, + 'parameters': [], + 'annotations': [ + new Component({ + selector: 'scroll-app', + template: new TemplateConfig({ + directives: [ScrollAreaComponent, NgIf, NgRepeat], + inline: ` +
    +
    + +
    + + +
    +
    +
    +

    Following tables are only here to add weight to the UI:

    + +
    +
    ` + }) + }) + ] + }); +} diff --git a/modules/benchmarks/src/naive_infinite_scroll/cells.js b/modules/benchmarks/src/naive_infinite_scroll/cells.js new file mode 100644 index 0000000000..6fcd51a478 --- /dev/null +++ b/modules/benchmarks/src/naive_infinite_scroll/cells.js @@ -0,0 +1,220 @@ +import {int} from 'angular2/src/facade/lang'; +import {reflector} from 'angular2/src/reflection/reflection'; +import {getIntParameter, bindAction} from 'angular2/src/test_lib/benchmark_util'; +import {bootstrap, Component, Template, TemplateConfig, ViewPort, Compiler} + from 'angular2/angular2'; +import {PromiseWrapper} from 'angular2/src/facade/async'; +import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; +import {Company, Opportunity, Offering, Account, CustomDate, STATUS_LIST} + from './common'; +import {NgRepeat} from 'angular2/directives'; + +export class HasStyle { + style:Map; + + constructor() { + this.style = MapWrapper.create(); + } + + set width(w) { + MapWrapper.set(this.style, 'width', w); + } +} + +export class CompanyNameComponent extends HasStyle { + company:Company; +} + +export class OpportunityNameComponent extends HasStyle { + opportunity:Opportunity; +} + +export class OfferingNameComponent extends HasStyle { + offering:Offering; +} + +export class Stage { + name:string; + isDisabled:boolean; + style:Map; + apply:Function; +} + +export class StageButtonsComponent extends HasStyle { + _offering:Offering; + stages:List; + + get offering():Offering { return this._offering; } + + set offering(offering:Offering) { + this._offering = offering; + this._computeStageButtons(); + } + + setStage(stage:Stage) { + this._offering.status = stage.name; + this._computeStageButtons(); + } + + _computeStageButtons() { + var disabled = true; + this.stages = ListWrapper.clone(STATUS_LIST + .map((status) => { + var isCurrent = this._offering.status == status; + var stage = new Stage(); + stage.name = status; + stage.isDisabled = disabled; + var stageStyle = MapWrapper.create(); + MapWrapper.set(stageStyle, 'background-color', + disabled + ? '#DDD' + : isCurrent + ? '#DDF' + : '#FDD'); + stage.style = stageStyle; + if (isCurrent) { + disabled = false; + } + return stage; + })); + } +} + +export class AccountCellComponent extends HasStyle { + account:Account; +} + +export class FormattedCellComponent extends HasStyle { + formattedValue:string; + + set value(value) { + if (value instanceof CustomDate) { + this.formattedValue = `${value.month}/${value.day}/${value.year}`; + } else { + this.formattedValue = value.toString(); + } + } +} + +export function setupReflectorForCells() { + reflector.registerType(CompanyNameComponent, { + 'factory': () => new CompanyNameComponent(), + 'parameters': [], + 'annotations': [ + new Component({ + selector: 'company-name', + template: new TemplateConfig({ + directives: [], + inline: `
    {{company.name}}
    ` + }), + bind: { + 'cell-width': 'width', + 'company': 'company' + } + }) + ] + }); + + reflector.registerType(OpportunityNameComponent, { + 'factory': () => new OpportunityNameComponent(), + 'parameters': [], + 'annotations': [ + new Component({ + selector: 'opportunity-name', + template: new TemplateConfig({ + directives: [], + inline: `
    {{opportunity.name}}
    ` + }), + bind: { + 'cell-width': 'width', + 'opportunity': 'opportunity' + } + }) + ] + }); + + reflector.registerType(OfferingNameComponent, { + 'factory': () => new OfferingNameComponent(), + 'parameters': [], + 'annotations': [ + new Component({ + selector: 'offering-name', + template: new TemplateConfig({ + directives: [], + inline: `
    {{offering.name}}
    ` + }), + bind: { + 'cell-width': 'width', + 'offering': 'offering' + } + }) + ] + }); + + reflector.registerType(StageButtonsComponent, { + 'factory': () => new StageButtonsComponent(), + 'parameters': [], + 'annotations': [ + new Component({ + selector: 'stage-buttons', + template: new TemplateConfig({ + directives: [NgRepeat], + inline: ` +
    + +
    ` + }), + bind: { + 'cell-width': 'width', + 'offering': 'offering' + } + }) + ] + }); + + reflector.registerType(AccountCellComponent, { + 'factory': () => new AccountCellComponent(), + 'parameters': [], + 'annotations': [ + new Component({ + selector: 'account-cell', + template: new TemplateConfig({ + directives: [], + inline: ` +
    + + {{account.accountId}} + +
    ` + }), + bind: { + 'cell-width': 'width', + 'account': 'account' + } + }) + ] + }); + + reflector.registerType(FormattedCellComponent, { + 'factory': () => new FormattedCellComponent(), + 'parameters': [], + 'annotations': [ + new Component({ + selector: 'formatted-cell', + template: new TemplateConfig({ + directives: [], + inline: `
    {{formattedValue}}
    ` + }), + bind: { + 'cell-width': 'width', + 'value': 'value' + } + }) + ] + }); +} diff --git a/modules/benchmarks/src/naive_infinite_scroll/common.js b/modules/benchmarks/src/naive_infinite_scroll/common.js new file mode 100644 index 0000000000..41106252a5 --- /dev/null +++ b/modules/benchmarks/src/naive_infinite_scroll/common.js @@ -0,0 +1,205 @@ +import {int} from 'angular2/src/facade/lang'; +import {Math} from 'angular2/src/facade/math'; + +import {PromiseWrapper} from 'angular2/src/facade/async'; +import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; + +export var ITEMS = 1000; +export var ITEM_HEIGHT = 40; +export var VISIBLE_ITEMS = 17; + +export var HEIGHT = ITEMS * ITEM_HEIGHT; +export var VIEW_PORT_HEIGHT = ITEM_HEIGHT * VISIBLE_ITEMS; + +export var COMPANY_NAME_WIDTH = 100; +export var OPPORTUNITY_NAME_WIDTH = 100; +export var OFFERING_NAME_WIDTH = 100; +export var ACCOUNT_CELL_WIDTH = 50; +export var BASE_POINTS_WIDTH = 50; +export var KICKER_POINTS_WIDTH = 50; +export var STAGE_BUTTONS_WIDTH = 220; +export var BUNDLES_WIDTH = 120; +export var DUE_DATE_WIDTH = 100; +export var END_DATE_WIDTH = 100; +export var AAT_STATUS_WIDTH = 100; +export var ROW_WIDTH = COMPANY_NAME_WIDTH + + OPPORTUNITY_NAME_WIDTH + + OFFERING_NAME_WIDTH + + ACCOUNT_CELL_WIDTH + + BASE_POINTS_WIDTH + + KICKER_POINTS_WIDTH + + STAGE_BUTTONS_WIDTH + + BUNDLES_WIDTH + + DUE_DATE_WIDTH + + END_DATE_WIDTH + + AAT_STATUS_WIDTH; + +export var STATUS_LIST = [ + 'Planned', 'Pitched', 'Won', 'Lost' +]; + +export var AAT_STATUS_LIST = [ + 'Active', 'Passive', 'Abandoned' +]; + +// Imitate Streamy entities. + +// Just a non-trivial object. Nothing fancy or correct. +export class CustomDate { + year: int; + month: int; + day: int; + + constructor(y:int, m:int, d:int) { + this.year = y; + this.month = m; + this.day = d; + } + + addDays(days:int):CustomDate { + var newDay = this.day + days; + var newMonth = this.month + Math.floor(newDay / 30); + newDay = newDay % 30; + var newYear = this.year + Math.floor(newMonth / 12); + return new CustomDate(newYear, newMonth, newDay); + } + + static now():CustomDate { + return new CustomDate(2014, 1, 28); + } +} + +export class RawEntity { + + _data:Map; + + constructor() { + this._data = MapWrapper.create(); + } + + get(key:string) { + if (key.indexOf('.') == -1) { + return this._data[key]; + } + var pieces = key.split('.'); + var last = ListWrapper.last(pieces); + pieces.length = pieces.length - 1; + var target = _resolve(pieces, this); + if (target == null) { + return null; + } + return target[last]; + } + + set(key:string, value) { + if (key.indexOf('.') == -1) { + this._data[key] = value; + return; + } + var pieces = key.split('.'); + var last = ListWrapper.last(pieces); + pieces.length = pieces.length - 1; + var target = _resolve(pieces, this); + target[last] = value; + } + + remove(key:string) { + if (!key.contains('.')) { + return this._data.remove(key); + } + var pieces = key.split('.'); + var last = ListWrapper.last(pieces); + pieces.length = pieces.length - 1; + var target = _resolve(pieces, this); + return target.remove(last); + } + + _resolve(pieces, start) { + var cur = start; + for (var i = 0; i < pieces.length; i++) { + cur = cur[pieces[i]]; + if (cur == null) { + return null; + } + } + return cur; + } +} + +export class Company extends RawEntity { + get name():string { return this.get('name'); } + set name(val:string) { + this.set('name', val); + } +} + +export class Offering extends RawEntity { + get name():string { return this.get('name'); } + set name(val:string) { + this.set('name', val); + } + + get company():Company { return this.get('company'); } + set company(val:Company) { + this.set('company', val); + } + + get opportunity():Opportunity { return this.get('opportunity'); } + set opportunity(val:Opportunity) { + this.set('opportunity', val); + } + + get account():Account { return this.get('account'); } + set account(val:Account) { + this.set('account', val); + } + + get basePoints():int { return this.get('basePoints'); } + set basePoints(val:int) { + this.set('basePoints', val); + } + + get kickerPoints():int { return this.get('kickerPoints'); } + set kickerPoints(val:int) { + this.set('kickerPoints', val); + } + + get status():string { return this.get('status'); } + set status(val:string) { + this.set('status', val); + } + + get bundles():string { return this.get('bundles'); } + set bundles(val:string) { + this.set('bundles', val); + } + + get dueDate():CustomDate { return this.get('dueDate'); } + set dueDate(val:CustomDate) { + this.set('dueDate', val); + } + + get endDate():CustomDate { return this.get('endDate'); } + set endDate(val:CustomDate) { + this.set('endDate', val); + } + + get aatStatus():string { return this.get('aatStatus'); } + set aatStatus(val:string) { + this.set('aatStatus', val); + } +} + +export class Opportunity extends RawEntity { + get name():string { return this.get('name'); } + set name(val:string) { + this.set('name', val); + } +} + +export class Account extends RawEntity { + get accountId():int { return this.get('accountId'); } + set accountId(val:int) { + this.set('accountId', val); + } +} diff --git a/modules/benchmarks/src/naive_infinite_scroll/index.html b/modules/benchmarks/src/naive_infinite_scroll/index.html new file mode 100644 index 0000000000..bbdb070cec --- /dev/null +++ b/modules/benchmarks/src/naive_infinite_scroll/index.html @@ -0,0 +1,16 @@ + + + + AngularDart Scrolling Benchmark + + +
    + App size:
    + Iteration count:
    + Scroll increment:
    +
    + + + $SCRIPTS$ + + diff --git a/modules/benchmarks/src/naive_infinite_scroll/index.js b/modules/benchmarks/src/naive_infinite_scroll/index.js new file mode 100644 index 0000000000..f8727b17ad --- /dev/null +++ b/modules/benchmarks/src/naive_infinite_scroll/index.js @@ -0,0 +1,212 @@ +import {int, isBlank} from 'angular2/src/facade/lang'; +import {Element} from 'angular2/src/facade/dom'; +import {MapWrapper} from 'angular2/src/facade/collection'; + +import {Parser, Lexer, ChangeDetector, ChangeDetection} + from 'angular2/change_detection'; +import {bootstrap, Component, Template, TemplateConfig, ViewPort, Compiler} + from 'angular2/angular2'; +import {reflector} from 'angular2/src/reflection/reflection'; +import {CompilerCache} from 'angular2/src/core/compiler/compiler'; +import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; +import {TemplateLoader} from 'angular2/src/core/compiler/template_loader'; +import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle'; + +import {NgIf, NgRepeat} from 'angular2/directives'; +import {App, setupReflectorForApp} from './app'; +import {ScrollAreaComponent, setupReflectorForScrollArea} from './scroll_area'; +import {ScrollItemComponent, setupReflectorForScrollItem} from './scroll_item'; +import {CompanyNameComponent, OpportunityNameComponent, OfferingNameComponent, + AccountCellComponent, StageButtonsComponent, FormattedCellComponent, + setupReflectorForCells} + from './cells'; + +export function main() { + setupReflector(); + bootstrap(App); +} + +export function setupReflector() { + setupReflectorForAngular(); + setupReflectorForApp(); + setupReflectorForScrollArea(); + setupReflectorForScrollItem(); + setupReflectorForCells(); + + // TODO: the transpiler is not able to compiles templates used as keys + var evt = `$event`; + + reflector.registerGetters({ + 'scrollAreas': (o) => o.scrollAreas, + 'length': (o) => o.length, + 'iterable': (o) => o.iterable, + 'scrollArea': (o) => o.scrollArea, + 'item': (o) => o.item, + 'visibleItems': (o) => o.visibleItems, + 'condition': (o) => o.condition, + 'width': (o) => o.width, + 'value': (o) => o.value, + 'href': (o) => o.href, + 'company': (o) => o.company, + 'formattedValue': (o) => o.formattedValue, + 'name': (o) => o.name, + 'style': (o) => o.style, + 'offering': (o) => o.offering, + 'account': (o) => o.account, + 'accountId': (o) => o.accountId, + 'companyNameWidth': (o) => o.companyNameWidth, + 'opportunityNameWidth': (o) => o.opportunityNameWidth, + 'offeringNameWidth': (o) => o.offeringNameWidth, + 'accountCellWidth': (o) => o.accountCellWidth, + 'basePointsWidth': (o) => o.basePointsWidth, + 'scrollDivStyle': (o) => o.scrollDivStyle, + 'paddingStyle': (o) => o.paddingStyle, + 'innerStyle': (o) => o.innerStyle, + 'opportunity': (o) => o.opportunity, + 'itemStyle': (o) => o.itemStyle, + 'dueDateWidth': (o) => o.dueDateWidth, + 'basePoints': (o) => o.basePoints, + 'kickerPoints': (o) => o.kickerPoints, + 'kickerPointsWidth': (o) => o.kickerPointsWidth, + 'bundles': (o) => o.bundles, + 'stageButtonsWidth': (o) => o.stageButtonsWidth, + 'bundlesWidth': (o) => o.bundlesWidth, + 'disabled': (o) => o.disabled, + 'isDisabled': (o) => o.isDisabled, + 'dueDate': (o) => o.dueDate, + 'endDate': (o) => o.endDate, + 'aatStatus': (o) => o.aatStatus, + 'stage': (o) => o.stage, + 'stages': (o) => o.stages, + 'aatStatusWidth': (o) => o.aatStatusWidth, + 'endDateWidth': (o) => o.endDateWidth, + evt: (o) => null + }); + + reflector.registerSetters({ + 'scrollAreas': (o, v) => o.scrollAreas = v, + 'length': (o, v) => o.length = v, + 'condition': (o, v) => o.condition = v, + 'scrollArea': (o, v) => o.scrollArea = v, + 'item': (o, v) => o.item = v, + 'visibleItems': (o, v) => o.visibleItems = v, + 'iterable': (o, v) => o.iterable = v, + 'width': (o, v) => o.width = v, + 'value': (o, v) => o.value = v, + 'company': (o, v) => o.company = v, + 'name': (o, v) => o.name = v, + 'offering': (o, v) => o.offering = v, + 'account': (o, v) => o.account = v, + 'accountId': (o, v) => o.accountId = v, + 'formattedValue': (o, v) => o.formattedValue = v, + 'stage': (o, v) => o.stage = v, + 'stages': (o, v) => o.stages = v, + 'disabled': (o, v) => o.disabled = v, + 'isDisabled': (o, v) => o.isDisabled = v, + 'href': (o, v) => o.href = v, + 'companyNameWidth': (o, v) => o.companyNameWidth = v, + 'opportunityNameWidth': (o, v) => o.opportunityNameWidth = v, + 'offeringNameWidth': (o, v) => o.offeringNameWidth = v, + 'accountCellWidth': (o, v) => o.accountCellWidth = v, + 'basePointsWidth': (o, v) => o.basePointsWidth = v, + 'scrollDivStyle': (o, v) => o.scrollDivStyle = v, + 'paddingStyle': (o, v) => o.paddingStyle = v, + 'innerStyle': (o, v) => o.innerStyle = v, + 'opportunity': (o, v) => o.opportunity = v, + 'itemStyle': (o, v) => o.itemStyle = v, + 'basePoints': (o, v) => o.basePoints = v, + 'kickerPoints': (o, v) => o.kickerPoints = v, + 'kickerPointsWidth': (o, v) => o.kickerPointsWidth = v, + 'stageButtonsWidth': (o, v) => o.stageButtonsWidth = v, + 'dueDate': (o, v) => o.dueDate = v, + 'dueDateWidth': (o, v) => o.dueDateWidth = v, + 'endDate': (o, v) => o.endDate = v, + 'endDateWidth': (o, v) => o.endDate = v, + 'aatStatus': (o, v) => o.aatStatus = v, + 'aatStatusWidth': (o, v) => o.aatStatusWidth = v, + 'bundles': (o, v) => o.bundles = v, + 'bundlesWidth': (o, v) => o.bundlesWidth = v, + evt: (o, v) => null, + 'style': (o, m) => { + //if (isBlank(m)) return; + // HACK + MapWrapper.forEach(m, function(v, k) { + o.style.setProperty(k, v); + }); + } + }); + + reflector.registerMethods({ + 'onScroll': (o, args) => { + // HACK + o.onScroll(args[0]); + }, + 'setStage': (o, args) => o.setStage(args[0]) + }); +} + +export function setupReflectorForAngular() { + reflector.registerType(NgIf, { + 'factory': (vp) => new NgIf(vp), + 'parameters': [[ViewPort]], + 'annotations' : [new Template({ + selector: '[ng-if]', + bind: { + 'ng-if': 'condition' + } + })] + }); + + reflector.registerType(NgRepeat, { + 'factory': (vp) => new NgRepeat(vp), + 'parameters': [[ViewPort]], + 'annotations' : [new Template({ + selector: '[ng-repeat]', + bind: { + 'in': 'iterable[]' + } + })] + }); + + reflector.registerType(Compiler, { + 'factory': (changeDetection, templateLoader, reader, parser, compilerCache) => new Compiler(changeDetection, templateLoader, reader, parser, compilerCache), + 'parameters': [[ChangeDetection], [TemplateLoader], [DirectiveMetadataReader], [Parser], [CompilerCache]], + 'annotations': [] + }); + + reflector.registerType(CompilerCache, { + 'factory': () => new CompilerCache(), + 'parameters': [], + 'annotations': [] + }); + + reflector.registerType(Parser, { + 'factory': (lexer) => new Parser(lexer), + 'parameters': [[Lexer]], + 'annotations': [] + }); + + reflector.registerType(TemplateLoader, { + 'factory': () => new TemplateLoader(), + 'parameters': [], + 'annotations': [] + }); + + reflector.registerType(DirectiveMetadataReader, { + 'factory': () => new DirectiveMetadataReader(), + 'parameters': [], + 'annotations': [] + }); + + reflector.registerType(Lexer, { + 'factory': () => new Lexer(), + 'parameters': [], + 'annotations': [] + }); + + reflector.registerType(LifeCycle, { + "factory": (cd) => new LifeCycle(cd), + "parameters": [[ChangeDetector]], + "annotations": [] + }); +} diff --git a/modules/benchmarks/src/naive_infinite_scroll/random_data.js b/modules/benchmarks/src/naive_infinite_scroll/random_data.js new file mode 100644 index 0000000000..e5e119ef83 --- /dev/null +++ b/modules/benchmarks/src/naive_infinite_scroll/random_data.js @@ -0,0 +1,77 @@ +import {int, StringWrapper} from 'angular2/src/facade/lang'; +import {List, ListWrapper} from 'angular2/src/facade/collection'; +import {CustomDate, Offering, Company, Opportunity, Account, STATUS_LIST, + AAT_STATUS_LIST} from './common'; + +export function generateOfferings(count:int):List { + var res = []; + for(var i = 0; i < count; i++) { + ListWrapper.push(res, generateOffering(i)); + } + return res; +} + +export function generateOffering(seed:int):Offering { + var res = new Offering(); + res.name = generateName(seed++); + res.company = generateCompany(seed++); + res.opportunity = generateOpportunity(seed++); + res.account = generateAccount(seed++); + res.basePoints = seed % 10; + res.kickerPoints = seed % 4; + res.status = STATUS_LIST[seed % STATUS_LIST.length]; + res.bundles = randomString(seed++); + res.dueDate = randomDate(seed++); + res.endDate = randomDate(seed++, res.dueDate); + res.aatStatus = AAT_STATUS_LIST[seed % AAT_STATUS_LIST.length]; + return res; +} + +export function generateCompany(seed:int):Company { + var res = new Company(); + res.name = generateName(seed); + return res; +} + +export function generateOpportunity(seed:int):Opportunity { + var res = new Opportunity(); + res.name = generateName(seed); + return res; +} + +export function generateAccount(seed:int):Account { + var res = new Account(); + res.accountId = seed; + return res; +} + +var names = [ + 'Foo', 'Bar', 'Baz', 'Qux', 'Quux', 'Garply', 'Waldo', 'Fred', 'Plugh', + 'Xyzzy', 'Thud', 'Cruft', 'Stuff' +]; + +function generateName(seed:int):string { + return names[seed % names.length]; +} + +var offsets = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + +function randomDate(seed:int, minDate:CustomDate = null):CustomDate { + if (minDate == null) { + minDate = CustomDate.now(); + } + + return minDate.addDays(offsets[seed % offsets.length]); +} + +var stringLengths = [5, 7, 9, 11, 13]; +var charCodeOffsets = [0, 1, 2, 3, 4, 5, 6, 7, 8]; + +function randomString(seed:int):string { + var len = stringLengths[seed % 5]; + var str = ''; + for (var i = 0; i < len; i++) { + str += StringWrapper.fromCharCode(97 + charCodeOffsets[seed % 9] + i); + } + return str; +} diff --git a/modules/benchmarks/src/naive_infinite_scroll/scroll_area.js b/modules/benchmarks/src/naive_infinite_scroll/scroll_area.js new file mode 100644 index 0000000000..7f18407db5 --- /dev/null +++ b/modules/benchmarks/src/naive_infinite_scroll/scroll_area.js @@ -0,0 +1,90 @@ +import {int, FINAL} from 'angular2/src/facade/lang'; +import {reflector} from 'angular2/src/reflection/reflection'; +import {getIntParameter, bindAction} from 'angular2/src/test_lib/benchmark_util'; +import {Component, Template, TemplateConfig, ViewPort, Compiler} + from 'angular2/angular2'; +import {PromiseWrapper} from 'angular2/src/facade/async'; +import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; +import {Element} from 'angular2/src/facade/dom'; +import {Math} from 'angular2/src/facade/math'; + +import {Offering, ITEMS, ITEM_HEIGHT, VISIBLE_ITEMS, VIEW_PORT_HEIGHT, + ROW_WIDTH, HEIGHT} from './common'; +import {generateOfferings} from './random_data'; +import {ScrollItemComponent} from './scroll_item'; +import {NgRepeat} from 'angular2/directives'; + +export class ScrollAreaComponent { + _fullList:List; + visibleItems:List; + + scrollDivStyle; + paddingDiv; + innerDiv; + + constructor() { + this._fullList = generateOfferings(ITEMS); + this.visibleItems = []; + this.scrollDivStyle = MapWrapper.createFromPairs([ + ['height', `${VIEW_PORT_HEIGHT}px`], + ['width', '1000px'], + ['border', '1px solid #000'], + ['overflow', 'scroll'] + ]); + this.onScroll(null); + } + + onScroll(evt) { + var scrollTop = 0; + if (evt != null) { + var scrollDiv = evt.target; + if (this.paddingDiv == null) { + this.paddingDiv = scrollDiv.querySelector('#padding'); + } + if (this.innerDiv == null) { + this.innerDiv = scrollDiv.querySelector('#inner'); + this.innerDiv.style.setProperty('width', `${ROW_WIDTH}px`); + } + scrollTop = scrollDiv.scrollTop; + } + var iStart = Math.floor(scrollTop / ITEM_HEIGHT); + var iEnd = Math.min(iStart + VISIBLE_ITEMS + 1, this._fullList.length); + var padding = iStart * ITEM_HEIGHT; + if (this.innerDiv != null) { + this.innerDiv.style.setProperty('height', `${HEIGHT - padding}px`); + } + if (this.paddingDiv != null) { + this.paddingDiv.style.setProperty('height', `${padding}px`); + } + this.visibleItems = ListWrapper.slice(this._fullList, iStart, iEnd); + } +} + +export function setupReflectorForScrollArea() { + reflector.registerType(ScrollAreaComponent, { + 'factory': () => new ScrollAreaComponent(), + 'parameters': [], + 'annotations': [ + new Component({ + selector: 'scroll-area', + template: new TemplateConfig({ + directives: [ScrollItemComponent, NgRepeat], + inline: ` +
    +
    +
    +
    + + +
    +
    +
    ` + }) + }) + ] + }); +} diff --git a/modules/benchmarks/src/naive_infinite_scroll/scroll_item.js b/modules/benchmarks/src/naive_infinite_scroll/scroll_item.js new file mode 100644 index 0000000000..e1cbac75d8 --- /dev/null +++ b/modules/benchmarks/src/naive_infinite_scroll/scroll_item.js @@ -0,0 +1,106 @@ +import {int} from 'angular2/src/facade/lang'; +import {reflector} from 'angular2/src/reflection/reflection'; +import {Component, Template, TemplateConfig, ViewPort, Compiler} + from 'angular2/angular2'; +import {PromiseWrapper} from 'angular2/src/facade/async'; +import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; +import {Element} from 'angular2/src/facade/dom'; +import {Math} from 'angular2/src/facade/math'; +import {CompanyNameComponent, OpportunityNameComponent, + OfferingNameComponent, StageButtonsComponent, AccountCellComponent, + FormattedCellComponent} from './cells'; + +import {Offering, ITEM_HEIGHT, COMPANY_NAME_WIDTH, OPPORTUNITY_NAME_WIDTH, + OFFERING_NAME_WIDTH, ACCOUNT_CELL_WIDTH, BASE_POINTS_WIDTH, + KICKER_POINTS_WIDTH, STAGE_BUTTONS_WIDTH, BUNDLES_WIDTH, DUE_DATE_WIDTH, + END_DATE_WIDTH, AAT_STATUS_WIDTH} from './common'; +import {generateOfferings} from './random_data'; + +export class ScrollItemComponent { + + offering:Offering; + itemStyle; + + constructor() { + this.itemStyle = MapWrapper.createFromPairs([ + ['height', `${ITEM_HEIGHT}px`], + ['line-height', `${ITEM_HEIGHT}px`], + ['font-size', '18px'], + ['display', 'flex'], + ['justify-content', 'space-between'] + ]); + } + + get companyNameWidth() { return `${COMPANY_NAME_WIDTH}px`; } + get opportunityNameWidth() { return `${OPPORTUNITY_NAME_WIDTH}px`; } + get offeringNameWidth() { return `${OFFERING_NAME_WIDTH}px`; } + get accountCellWidth() { return `${ACCOUNT_CELL_WIDTH}px`; } + get basePointsWidth() { return `${BASE_POINTS_WIDTH}px`; } + get kickerPointsWidth() { return `${KICKER_POINTS_WIDTH}px`; } + get stageButtonsWidth() { return `${STAGE_BUTTONS_WIDTH}px`; } + get bundlesWidth() { return `${BUNDLES_WIDTH}px`; } + get dueDateWidth() { return `${DUE_DATE_WIDTH}px`; } + get endDateWidth() { return `${END_DATE_WIDTH}px`; } + get aatStatusWidth() { return `${AAT_STATUS_WIDTH}px`; } +} + +export function setupReflectorForScrollItem() { + reflector.registerType(ScrollItemComponent, { + 'factory': () => new ScrollItemComponent(), + 'parameters': [], + 'annotations': [ + new Component({ + selector: 'scroll-item', + template: new TemplateConfig({ + directives: [ + CompanyNameComponent, + OpportunityNameComponent, + OfferingNameComponent, + StageButtonsComponent, + AccountCellComponent, + FormattedCellComponent + ], + inline: ` +
    + + + + + + + + + + + + + + + + + + + + + + +
    ` + }), + bind: { + 'offering': 'offering' + } + }) + ] + }); +} diff --git a/modules/benchmarks_external/e2e_test/naive_infinite_scroll_perf.es6 b/modules/benchmarks_external/e2e_test/naive_infinite_scroll_perf.es6 index 8266cfa09d..1d6b837e85 100644 --- a/modules/benchmarks_external/e2e_test/naive_infinite_scroll_perf.es6 +++ b/modules/benchmarks_external/e2e_test/naive_infinite_scroll_perf.es6 @@ -17,7 +17,11 @@ describe('ng-dart1.x naive infinite scroll benchmark', function () { 'document.querySelector("scroll-app /deep/ #reset-btn").click()'); browser.executeScript( 'document.querySelector("scroll-app /deep/ #run-btn").click()'); - browser.sleep(1000); + var s = 1000; + if (appSize > 4) { + s = s + appSize * 100; + } + browser.sleep(s); }, params: [{ name: 'appSize', value: appSize diff --git a/modules/benchmarks_external/pubspec.yaml b/modules/benchmarks_external/pubspec.yaml index 43cfd74c38..0d776256fc 100644 --- a/modules/benchmarks_external/pubspec.yaml +++ b/modules/benchmarks_external/pubspec.yaml @@ -4,7 +4,6 @@ environment: dependencies: angular: '>=1.0.0 <2.0.0' browser: '>=0.10.0 <0.11.0' - fixnum: '>=0.9.0 <1.0.0' dev_dependencies: angular2: path: ../angular2 diff --git a/modules/benchmarks_external/src/naive_infinite_scroll/common.dart b/modules/benchmarks_external/src/naive_infinite_scroll/common.dart index 540d8896e3..6b48e2c61e 100644 --- a/modules/benchmarks_external/src/naive_infinite_scroll/common.dart +++ b/modules/benchmarks_external/src/naive_infinite_scroll/common.dart @@ -2,7 +2,6 @@ library common.stuff; import 'dart:async'; import 'dart:collection'; -import 'package:fixnum/fixnum.dart'; import 'package:observe/observe.dart'; const ITEMS = 1000; @@ -204,8 +203,8 @@ class Opportunity extends RawEntity { } class Account extends RawEntity { - Int64 get accountId => this['accountId']; - set accountId(Int64 val) { + int get accountId => this['accountId']; + set accountId(int val) { this['accountId'] = val; } } diff --git a/modules/benchmarks_external/src/naive_infinite_scroll/random_data.dart b/modules/benchmarks_external/src/naive_infinite_scroll/random_data.dart index 1cdfe2fabf..ea980a97d7 100644 --- a/modules/benchmarks_external/src/naive_infinite_scroll/random_data.dart +++ b/modules/benchmarks_external/src/naive_infinite_scroll/random_data.dart @@ -1,7 +1,6 @@ library random_data; import 'common.dart'; -import 'package:fixnum/fixnum.dart'; List generateOfferings(int count) => new List.generate(count, generateOffering); @@ -34,7 +33,7 @@ Opportunity generateOpportunity(int seed) { Account generateAccount(int seed) { return new Account() - ..accountId = new Int64(seed); + ..accountId = seed; } String generateName(int seed) { diff --git a/protractor-shared.js b/protractor-shared.js index 19cd324af2..0c6a504534 100644 --- a/protractor-shared.js +++ b/protractor-shared.js @@ -10,7 +10,7 @@ var config = exports.config = { onPrepare: function() { browser.ignoreSynchronization = true; var _get = browser.get; - var sleepInterval = process.env.TRAVIS || process.env.JENKINS_URL ? 5000 : 2000; + var sleepInterval = process.env.TRAVIS || process.env.JENKINS_URL ? 7000 : 3000; browser.get = function() { var result = _get.apply(this, arguments); browser.sleep(sleepInterval);