From 59d131dfacbe8f1f8a7aba69294d99715e1131c1 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Wed, 31 Jul 2013 15:51:26 -0700 Subject: [PATCH 1/4] Added bootstrap, scope digest, compiler, block factory and ng_repeat benchmarks --- benchmark/block_factory_benchmark.dart | 57 ++++++++++++++++++++++++++ benchmark/block_factory_benchmark.html | 12 ++++++ benchmark/bootstrap_benchmark.dart | 34 +++++++++++++++ benchmark/bootstrap_benchmark.html | 13 ++++++ benchmark/compiler_benchmark.dart | 50 ++++++++++++++++++++++ benchmark/compiler_benchmark.html | 12 ++++++ benchmark/ng_repeat_benchmark.dart | 49 ++++++++++++++++++++++ benchmark/ng_repeat_benchmark.html | 12 ++++++ benchmark/scope_digest_benchmark.dart | 45 ++++++++++++++++++++ benchmark/scope_digest_benchmark.html | 12 ++++++ pubspec.yaml | 2 + 11 files changed, 298 insertions(+) create mode 100644 benchmark/block_factory_benchmark.dart create mode 100644 benchmark/block_factory_benchmark.html create mode 100644 benchmark/bootstrap_benchmark.dart create mode 100644 benchmark/bootstrap_benchmark.html create mode 100644 benchmark/compiler_benchmark.dart create mode 100644 benchmark/compiler_benchmark.html create mode 100644 benchmark/ng_repeat_benchmark.dart create mode 100644 benchmark/ng_repeat_benchmark.html create mode 100644 benchmark/scope_digest_benchmark.dart create mode 100644 benchmark/scope_digest_benchmark.html diff --git a/benchmark/block_factory_benchmark.dart b/benchmark/block_factory_benchmark.dart new file mode 100644 index 000000000..682abd186 --- /dev/null +++ b/benchmark/block_factory_benchmark.dart @@ -0,0 +1,57 @@ +import 'dart:html'; + +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:angular/angular.dart'; +import 'package:di/di.dart'; + +class BlockFactoryBenchmark extends BenchmarkBase { + BlockFactoryBenchmark() : super("BlockFactory"); + + static void main() { + new BlockFactoryBenchmark().report(); + } + + Injector injector; + BlockFactory blockFactory; + + void run() { + blockFactory(injector); + } + + void setup() { + var module = new AngularModule() + ..directive(TestTranscludingDirective) + ..directive(TestTemplatedComponent); + injector = new Injector([module]); + Compiler compiler = injector.get(Compiler); + StringBuffer sb = new StringBuffer(); + sb.writeln('
'); + for (int i = 0; i < 100; i++) { + sb.writeln('
Hello
'); + sb.writeln('
Transcluded Content' + '
'); + } + sb.writeln('
'); + blockFactory = compiler([new Element.html(sb.toString())]); + } + + void teardown() { + injector = null; + blockFactory = null; + } +} + +@NgDirective(transclude: true) +class TestTranscludingDirective { + TestTranscludingDirective(BoundBlockFactory blockFactory, BlockHole anchor, Scope scope) { + blockFactory(scope).insertAfter(anchor); + } +} + +@NgComponent(template: '
Hello World!
') +class TestTemplatedComponent { +} + +main() { + BlockFactoryBenchmark.main(); +} \ No newline at end of file diff --git a/benchmark/block_factory_benchmark.html b/benchmark/block_factory_benchmark.html new file mode 100644 index 000000000..2467ede1f --- /dev/null +++ b/benchmark/block_factory_benchmark.html @@ -0,0 +1,12 @@ + + + + + block_factory_benchmark + + + + + + + diff --git a/benchmark/bootstrap_benchmark.dart b/benchmark/bootstrap_benchmark.dart new file mode 100644 index 000000000..f24f4a862 --- /dev/null +++ b/benchmark/bootstrap_benchmark.dart @@ -0,0 +1,34 @@ +import 'dart:html'; + +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:angular/angular.dart'; +import 'package:di/di.dart'; + +class BootstrapBenchmark extends BenchmarkBase { + BootstrapBenchmark() : super("Bootstrap"); + + static void main() { + new BootstrapBenchmark().report(); + } + + void run() { + var ngApp = new Element.div(); + ngApp.attributes['ng-app'] = ''; + ngApp.children.add(new Element.div()); + query('#sandbox').children.add(ngApp); + + bootstrapAngular([new AngularModule()]); + + ngApp.remove(); + } + + void setup() { + } + + void teardown() { + } +} + +main() { + BootstrapBenchmark.main(); +} \ No newline at end of file diff --git a/benchmark/bootstrap_benchmark.html b/benchmark/bootstrap_benchmark.html new file mode 100644 index 000000000..12825050b --- /dev/null +++ b/benchmark/bootstrap_benchmark.html @@ -0,0 +1,13 @@ + + + + + module_instantiation_benchmark + + + +
+ + + + diff --git a/benchmark/compiler_benchmark.dart b/benchmark/compiler_benchmark.dart new file mode 100644 index 000000000..9d3a546d9 --- /dev/null +++ b/benchmark/compiler_benchmark.dart @@ -0,0 +1,50 @@ +import 'dart:html'; + +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:angular/angular.dart'; +import 'package:di/di.dart'; + +class CompilerBenchmark extends BenchmarkBase { + CompilerBenchmark() : super("Compiler"); + + static void main() { + new CompilerBenchmark().report(); + } + + Compiler compiler; + + void run() { + StringBuffer sb = new StringBuffer(); + sb.writeln('
'); + for (int i = 0; i < 100; i++) { + sb.writeln('Transcluded Content'); + sb.writeln(''); + } + sb.writeln('
'); + compiler([new Element.html(sb.toString())]); + } + + void setup() { + var module = new AngularModule() + ..directive(TestTranscludingDirective) + ..directive(TestTemplatedComponent); + Injector injector = new Injector([module]); + compiler = injector.get(Compiler); + } + + void teardown() { + compiler = null; + } +} + +@NgDirective(transclude: true) +class TestTranscludingDirective { +} + +@NgComponent(template: '
Hello World!
') +class TestTemplatedComponent { +} + +main() { + CompilerBenchmark.main(); +} \ No newline at end of file diff --git a/benchmark/compiler_benchmark.html b/benchmark/compiler_benchmark.html new file mode 100644 index 000000000..5b430c103 --- /dev/null +++ b/benchmark/compiler_benchmark.html @@ -0,0 +1,12 @@ + + + + + compiler_benchmark + + + + + + + diff --git a/benchmark/ng_repeat_benchmark.dart b/benchmark/ng_repeat_benchmark.dart new file mode 100644 index 000000000..9513e080c --- /dev/null +++ b/benchmark/ng_repeat_benchmark.dart @@ -0,0 +1,49 @@ +import 'dart:html'; + +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:angular/angular.dart'; +import 'package:di/di.dart'; + +class NgRepeatBenchmark extends BenchmarkBase { + NgRepeatBenchmark() : super("NgRepeat"); + + static void main() { + new NgRepeatBenchmark().report(); + } + + Injector injector; + Scope rootScope; + BlockFactory blockFactory; + + void run() { + var vals = []; + for(int i = 0; i < 100; i++) { + vals.add(i); + } + Scope scope = rootScope.$new(true); + scope.vals = vals; + + blockFactory(injector.createChild([new ScopeModule(scope)])); + scope.$digest(); + scope.$destroy(); + } + + void setup() { + var module = new AngularModule(); + injector = new Injector([module]); + rootScope = injector.get(Scope); + Compiler compiler = injector.get(Compiler); + String html = '
{{i}}
'; + blockFactory = compiler([new Element.html(html)]); + } + + void teardown() { + injector = null; + rootScope = null; + blockFactory = null; + } +} + +main() { + NgRepeatBenchmark.main(); +} \ No newline at end of file diff --git a/benchmark/ng_repeat_benchmark.html b/benchmark/ng_repeat_benchmark.html new file mode 100644 index 000000000..afb5a1537 --- /dev/null +++ b/benchmark/ng_repeat_benchmark.html @@ -0,0 +1,12 @@ + + + + + ng_repeat_benchmark + + + + + + + diff --git a/benchmark/scope_digest_benchmark.dart b/benchmark/scope_digest_benchmark.dart new file mode 100644 index 000000000..742c7bb70 --- /dev/null +++ b/benchmark/scope_digest_benchmark.dart @@ -0,0 +1,45 @@ +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:angular/angular.dart'; +import 'package:di/di.dart'; + +class ScopeDigestBenchmark extends BenchmarkBase { + ScopeDigestBenchmark() : super("ScopeDigest"); + + static void main() { + new ScopeDigestBenchmark().report(); + } + + Interpolate interpolator; + Scope rootScope; + + void run() { + var testScope = rootScope.$new(); + // Simulating a repeater repeating a component with a simple template. + for (int i = 0; i < 100; i++) { + var childScope = testScope.$new(); + childScope['hello'] = new TestObject(); + childScope.$watch(interpolator('{{hello.sayHello()}}'), (_) {}); + } + testScope.$digest(); + testScope.$destroy(); + } + + void setup() { + Injector injector = new Injector([new AngularModule()]); + interpolator = injector.get(Interpolate); + rootScope = injector.get(Scope); + } + + void teardown() { + interpolator = null; + rootScope = null; + } +} + +class TestObject { + String sayHello() => 'hello'; +} + +main() { + ScopeDigestBenchmark.main(); +} \ No newline at end of file diff --git a/benchmark/scope_digest_benchmark.html b/benchmark/scope_digest_benchmark.html new file mode 100644 index 000000000..4cab3623a --- /dev/null +++ b/benchmark/scope_digest_benchmark.html @@ -0,0 +1,12 @@ + + + + + scope_digest_benchmark + + + + + + + diff --git a/pubspec.yaml b/pubspec.yaml index 3b8f8515a..8f39773f8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,3 +7,5 @@ dependencies: js: any di: any +dev_dependencies: + benchmark_harness: any \ No newline at end of file From 17605a4df581f65d44cad1ce6945f93437185e7d Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Mon, 5 Aug 2013 15:23:25 -0700 Subject: [PATCH 2/4] chore: minor comment --- benchmark/bootstrap_benchmark.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/benchmark/bootstrap_benchmark.dart b/benchmark/bootstrap_benchmark.dart index f24f4a862..68e3efff0 100644 --- a/benchmark/bootstrap_benchmark.dart +++ b/benchmark/bootstrap_benchmark.dart @@ -4,6 +4,9 @@ import 'package:benchmark_harness/benchmark_harness.dart'; import 'package:angular/angular.dart'; import 'package:di/di.dart'; +// TODO(pavelgj): Ignore this benchmark for now because it does not give a +// good measure of the actual boostrap time, specifically the "cold start" +// of the mirrors. Look into launching boostrap benchmark in an isolate. class BootstrapBenchmark extends BenchmarkBase { BootstrapBenchmark() : super("Bootstrap"); From 9bbc0f00920e1b4e3f2dbcd7740383405015f2ff Mon Sep 17 00:00:00 2001 From: Pavel J Date: Tue, 6 Aug 2013 17:45:32 -0700 Subject: [PATCH 3/4] feature(parser): use reflection for expression evaluation --- lib/angular.dart | 2 ++ lib/interface_typing.dart | 42 +++++++++++++++++++++++++++++++++++++++ lib/parser.dart | 20 +++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/lib/angular.dart b/lib/angular.dart index e7475b9a3..42d7eeaf8 100644 --- a/lib/angular.dart +++ b/lib/angular.dart @@ -37,6 +37,8 @@ part 'scope.dart'; part 'selector.dart'; part 'string_utilities.dart'; +bool useMirrorsInParser = true; + ASSERT(condition) { if (!condition) { throw new AssertionError(); diff --git a/lib/interface_typing.dart b/lib/interface_typing.dart index 9471fad31..237503175 100644 --- a/lib/interface_typing.dart +++ b/lib/interface_typing.dart @@ -23,6 +23,48 @@ _isType(obj, type) { return false; } +var OBJECT_MIRROR = fastReflectClass(Object); + +bool hasField(Type type, Symbol fieldSymbol) { + return _hasField(fastReflectClass(type), fieldSymbol); +} + +bool _hasField(ClassMirror cm, Symbol fieldSymbol) { + if (cm.members.containsKey(fieldSymbol)) { + return true; + } + if (cm.superclass != null && cm.superclass != OBJECT_MIRROR && _hasField(cm.superclass, fieldSymbol)) { + return true; + } + /* + for (ClassMirror interface in cm.superinterfaces) { + if (_hasField(interface, fieldSymbol)) { + return true; + } + }*/ + return false; +} + +bool hasMethod(Type type, Symbol fieldSymbol) { + return _hasMethod(fastReflectClass(type), fieldSymbol); +} + +bool _hasMethod(ClassMirror cm, Symbol fieldSymbol) { + if (cm.methods.containsKey(fieldSymbol)) { + return true; + } + if (cm.superclass != null && cm.superclass != OBJECT_MIRROR && _hasMethod(cm.superclass, fieldSymbol)) { + return true; + } + /* + for (ClassMirror interface in cm.superinterfaces) { + if (_hasMethod(interface, fieldSymbol)) { + return true; + } + }*/ + return false; +} + isInterface(obj, Type type) { if (_isType(obj, type)) return true; diff --git a/lib/parser.dart b/lib/parser.dart index b355ac652..b9dd9cf7b 100644 --- a/lib/parser.dart +++ b/lib/parser.dart @@ -149,6 +149,26 @@ getterChild(value, childKey) { } } + if (useMirrorsInParser) { + if (hasMethod(value.runtimeType, new Symbol('[]')) && + hasMethod(value.runtimeType, new Symbol('containsKey')) && + value.containsKey(childKey)) { + return [true, value[childKey]]; + } else { + InstanceMirror instanceMirror = reflect(value); + Symbol curSym = new Symbol(childKey); + if (hasMethod(value.runtimeType, curSym)) { + return [true, _relaxFnArgs(([a0, a1, a2, a3, a4, a5]) { + var args = stripTrailingNulls([a0, a1, a2, a3, a4, a5]); + return instanceMirror.invoke(curSym, args).reflectee; + })]; + } else if (hasField(value.runtimeType, curSym)) { + return [true, instanceMirror.getField(curSym).reflectee]; + } + } + return [false, null]; + } + // TODO: replace with isInterface(value, Getter) when dart:mirrors // can support mixins. try { From ff80501b535f3f10fb4dfabc0d4b6c17c810d529 Mon Sep 17 00:00:00 2001 From: Pavel Jbanov Date: Tue, 6 Aug 2013 18:02:29 -0700 Subject: [PATCH 4/4] added more profiling info for benchmarks --- benchmark/block_factory_benchmark.dart | 1 + benchmark/bootstrap_benchmark.dart | 1 + benchmark/compiler_benchmark.dart | 1 + benchmark/ng_repeat_benchmark.dart | 1 + benchmark/scope_digest_benchmark.dart | 1 + lib/angular.dart | 71 +++++++++++ lib/block.dart | 14 ++- lib/compiler.dart | 14 ++- lib/directives/ng_repeat.dart | 158 +++++++++++++------------ lib/scope.dart | 144 +++++++++++----------- 10 files changed, 246 insertions(+), 160 deletions(-) diff --git a/benchmark/block_factory_benchmark.dart b/benchmark/block_factory_benchmark.dart index 682abd186..03f99332b 100644 --- a/benchmark/block_factory_benchmark.dart +++ b/benchmark/block_factory_benchmark.dart @@ -38,6 +38,7 @@ class BlockFactoryBenchmark extends BenchmarkBase { void teardown() { injector = null; blockFactory = null; + dumpTimerStats(); } } diff --git a/benchmark/bootstrap_benchmark.dart b/benchmark/bootstrap_benchmark.dart index 68e3efff0..ddf933ef6 100644 --- a/benchmark/bootstrap_benchmark.dart +++ b/benchmark/bootstrap_benchmark.dart @@ -29,6 +29,7 @@ class BootstrapBenchmark extends BenchmarkBase { } void teardown() { + dumpTimerStats(); } } diff --git a/benchmark/compiler_benchmark.dart b/benchmark/compiler_benchmark.dart index 9d3a546d9..d6dbe22be 100644 --- a/benchmark/compiler_benchmark.dart +++ b/benchmark/compiler_benchmark.dart @@ -34,6 +34,7 @@ class CompilerBenchmark extends BenchmarkBase { void teardown() { compiler = null; + dumpTimerStats(); } } diff --git a/benchmark/ng_repeat_benchmark.dart b/benchmark/ng_repeat_benchmark.dart index 9513e080c..cb9217faf 100644 --- a/benchmark/ng_repeat_benchmark.dart +++ b/benchmark/ng_repeat_benchmark.dart @@ -41,6 +41,7 @@ class NgRepeatBenchmark extends BenchmarkBase { injector = null; rootScope = null; blockFactory = null; + dumpTimerStats(); } } diff --git a/benchmark/scope_digest_benchmark.dart b/benchmark/scope_digest_benchmark.dart index 742c7bb70..c16f489e5 100644 --- a/benchmark/scope_digest_benchmark.dart +++ b/benchmark/scope_digest_benchmark.dart @@ -33,6 +33,7 @@ class ScopeDigestBenchmark extends BenchmarkBase { void teardown() { interpolator = null; rootScope = null; + dumpTimerStats(); } } diff --git a/lib/angular.dart b/lib/angular.dart index 42d7eeaf8..0b7519337 100644 --- a/lib/angular.dart +++ b/lib/angular.dart @@ -172,3 +172,74 @@ bootstrapAngular(modules, [rootElementSelector = '[ng-app]']) { $rootScope.$digest(); }); } + +class TimerStat { + int count = 0; + double total = 0.0; + double avg = 0.0; + double min = double.MAX_FINITE; + double max = 0.0; + List top = []; +} + +Map stats = {}; + +int _depth = 0; + +time(desc, fn) { + //_depth++; + desc = _prefix(_depth - 1, desc); + var stat = stats[desc]; + if (stat == null) { + stat = new TimerStat(); + stats[desc] = stat; + } + var start = dom.window.performance.now(); + try { + return fn(); + } finally { + var diff = dom.window.performance.now() - start; + stat.count++; + stat.total += diff; + if (diff < stat.min) stat.min = diff; + if (diff > stat.max) stat.max = diff; + stat.avg = (stat.avg * (stat.count - 1) + diff) / stat.count; + _insertIntoTop20(stat.top, diff); + //_depth--; + } +} + +String _prefix(int depth, String str) { + if (depth > 0) { + return ' - ' + _prefix(depth - 1, str); + } + return str; +} + +_insertIntoTop20(List top, double val) { + int i = 0; + for (; i < top.length; i++) { + if (top[i] >= val) { + break; + } + } + top.insert(i, val); + while (top.length > 10) { + top.removeAt(0); + } +} + +clearTimerStats() { + stats.clear(); +} + +dumpTimerStats() { + print('------------------------'); + double total = 0.0; + stats.forEach((desc, stat) { + print('$desc; count: ${stat.count} total:${stat.total} avg:${stat.avg} min:${stat.min} max:${stat.max} top:${stat.top.map((v) => v.round()).join(', ')}'); + total += stat.total; + }); + print('------------------------'); +} + diff --git a/lib/block.dart b/lib/block.dart index b70c1b4dc..d5cc263bb 100644 --- a/lib/block.dart +++ b/lib/block.dart @@ -128,7 +128,9 @@ class Block implements ElementWrapper { nodeModule.factory(BlockFactory, blockFactory); nodeModule.factory(BoundBlockFactory, boundBlockFactory); var nodeInjector = parentInjector.createChild([nodeModule]); - directiveRefs.forEach((ref) => nodeInjector.get(ref.directive.type)); + time('Block nodeInjector.get', () { + directiveRefs.forEach((ref) => nodeInjector.get(ref.directive.type)); + }); return nodeInjector; } @@ -493,10 +495,12 @@ class BlockFactory { } Block call(Injector injector, [List elements]) { - if (elements == null) { - elements = cloneElements(templateElements); - } - return new Block(injector, elements, directivePositions); + return time('BlockFactory', () { + if (elements == null) { + elements = cloneElements(templateElements); + } + return new Block(injector, elements, directivePositions); + }); } BoundBlockFactory bind(Injector injector) { diff --git a/lib/compiler.dart b/lib/compiler.dart index 9b77274db..99077bdf1 100644 --- a/lib/compiler.dart +++ b/lib/compiler.dart @@ -122,11 +122,13 @@ class Compiler { BlockFactory call(List elements) { List domElements = elements; List templateElements = cloneElements(domElements); - var directivePositions = _compileBlock( - new NodeCursor(domElements), new NodeCursor(templateElements), - null); - - return new BlockFactory(templateElements, - directivePositions == null ? [] : directivePositions); + return time('compiler', () { + var directivePositions = _compileBlock( + new NodeCursor(domElements), new NodeCursor(templateElements), + null); + + return new BlockFactory(templateElements, + directivePositions == null ? [] : directivePositions); + }); } } diff --git a/lib/directives/ng_repeat.dart b/lib/directives/ng_repeat.dart index 03cb354e9..516a43373 100644 --- a/lib/directives/ng_repeat.dart +++ b/lib/directives/ng_repeat.dart @@ -49,95 +49,97 @@ class NgRepeatAttrDirective { keyIdentifier = match.group(2); scope.$watchCollection(listExpr, (collection) { - var previousNode = blockHole.elements[0], // current position of the node - nextNode, - arrayLength, - childScope, - trackById, - newRowOrder = [], - cursor = blockHole; - // Same as lastBlockMap but it has the current state. It will become the - // lastBlockMap on the next iteration. - Map newRows = new Map(); - arrayLength = collection.length; - // locate existing items - var length = newRowOrder.length = collection.length; - for(var index = 0; index < length; index++) { - var value = collection[index]; - trackById = trackByIdFn(index, value, index); - if(lastRows.containsKey(trackById)) { - var row = lastRows[trackById]; - lastRows.remove(trackById); - newRows[trackById] = row; - newRowOrder[index] = row; - } else if (newRows.containsKey(trackById)) { - // restore lastBlockMap - newRowOrder.forEach((row) { - if (row != null && row.startNode != null) { - lastRows[row.id] = row; - } - }); - // This is a duplicate and we need to throw an error - throw "[NgErr50] ngRepeat error! Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: $expression, Duplicate key: $trackById"; - } else { - // new never before seen row - newRowOrder[index] = new Row(trackById); - newRows[trackById] = null; + time('NgRepeat.scope.\$watchCollection', () { + var previousNode = blockHole.elements[0], // current position of the node + nextNode, + arrayLength, + childScope, + trackById, + newRowOrder = [], + cursor = blockHole; + // Same as lastBlockMap but it has the current state. It will become the + // lastBlockMap on the next iteration. + Map newRows = new Map(); + arrayLength = collection.length; + // locate existing items + var length = newRowOrder.length = collection.length; + for(var index = 0; index < length; index++) { + var value = collection[index]; + trackById = trackByIdFn(index, value, index); + if(lastRows.containsKey(trackById)) { + var row = lastRows[trackById]; + lastRows.remove(trackById); + newRows[trackById] = row; + newRowOrder[index] = row; + } else if (newRows.containsKey(trackById)) { + // restore lastBlockMap + newRowOrder.forEach((row) { + if (row != null && row.startNode != null) { + lastRows[row.id] = row; + } + }); + // This is a duplicate and we need to throw an error + throw "[NgErr50] ngRepeat error! Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: $expression, Duplicate key: $trackById"; + } else { + // new never before seen row + newRowOrder[index] = new Row(trackById); + newRows[trackById] = null; + } } - } - // remove existing items - lastRows.forEach((key, row){ - row.block.remove(); - row.scope.$destroy(); - }); + // remove existing items + lastRows.forEach((key, row){ + row.block.remove(); + row.scope.$destroy(); + }); - for (var index = 0, length = collection.length; index < length; index++) { - var key = index; - var value = collection[index]; - Row row = newRowOrder[index]; + for (var index = 0, length = collection.length; index < length; index++) { + var key = index; + var value = collection[index]; + Row row = newRowOrder[index]; - if (row.startNode != null) { - // if we have already seen this object, then we need to reuse the - // associated scope/element - childScope = row.scope; + if (row.startNode != null) { + // if we have already seen this object, then we need to reuse the + // associated scope/element + childScope = row.scope; - nextNode = previousNode; - do { - nextNode = nextNode.nextNode; - } while(nextNode != null); + nextNode = previousNode; + do { + nextNode = nextNode.nextNode; + } while(nextNode != null); - if (row.startNode == nextNode) { - // do nothing + if (row.startNode == nextNode) { + // do nothing + } else { + // existing item which got moved + row.block.moveAfter(cursor); + } + previousNode = row.endNode; } else { - // existing item which got moved - row.block.moveAfter(cursor); + // new item which we don't know about + childScope = scope.$new(); } - previousNode = row.endNode; - } else { - // new item which we don't know about - childScope = scope.$new(); - } - childScope[valueIdentifier] = value; - childScope.$index = index; - childScope.$first = (index == 0); - childScope.$last = (index == (arrayLength - 1)); - childScope.$middle = !(childScope.$first || childScope.$last); + childScope[valueIdentifier] = value; + childScope.$index = index; + childScope.$first = (index == 0); + childScope.$last = (index == (arrayLength - 1)); + childScope.$middle = !(childScope.$first || childScope.$last); - if (row.startNode == null) { - newRows[row.id] = row; - var block = boundBlockFactory(childScope); - row.block = block; - row.scope = childScope; - row.elements = block.elements; - row.startNode = row.elements[0]; - row.endNode = row.elements[row.elements.length - 1]; - block.insertAfter(cursor); + if (row.startNode == null) { + newRows[row.id] = row; + var block = boundBlockFactory(childScope); + row.block = block; + row.scope = childScope; + row.elements = block.elements; + row.startNode = row.elements[0]; + row.endNode = row.elements[row.elements.length - 1]; + block.insertAfter(cursor); + } + cursor = row.block; } - cursor = row.block; - } - lastRows = newRows; + lastRows = newRows; + }); }); } } diff --git a/lib/scope.dart b/lib/scope.dart index dbc1c7ac7..6e86d7b9f 100644 --- a/lib/scope.dart +++ b/lib/scope.dart @@ -174,89 +174,91 @@ class Scope implements Map { $digest() { - var value, last, - asyncQueue = _asyncQueue, - length, - dirty, _ttlLeft = _ttl, - logIdx, logMsg; - List> watchLog = []; - List watchers; - Watch watch; - Scope next, current, target = this; - - _beginPhase('\$digest'); - try { - do { // "while dirty" loop - dirty = false; - current = target; - //asyncQueue = current._asyncQueue; - //dump('aQ: ${asyncQueue.length}'); - - while(asyncQueue.length > 0) { - try { - current.$eval(asyncQueue.removeAt(0)); - } catch (e, s) { - _exceptionHandler(e, s); + return time('Scope.\$digest', () { + var value, last, + asyncQueue = _asyncQueue, + length, + dirty, _ttlLeft = _ttl, + logIdx, logMsg; + List> watchLog = []; + List watchers; + Watch watch; + Scope next, current, target = this; + + _beginPhase('\$digest'); + try { + do { // "while dirty" loop + dirty = false; + current = target; + //asyncQueue = current._asyncQueue; + //dump('aQ: ${asyncQueue.length}'); + + while(asyncQueue.length > 0) { + try { + current.$eval(asyncQueue.removeAt(0)); + } catch (e, s) { + _exceptionHandler(e, s); + } } - } - do { // "traverse the scopes" loop - if ((watchers = current._watchers) != null) { - // process our watches - length = watchers.length; - while (length-- > 0) { - try { - watch = watchers[length]; - if ((value = watch.get(current)) != (last = watch.last) && - !(value is num && last is num && value.isNaN && last.isNaN)) { - dirty = true; - watch.last = value; - watch.fn(value, ((last == initWatchVal) ? value : last), current); - if (_ttlLeft < 5) { - logIdx = 4 - _ttlLeft; - while (watchLog.length <= logIdx) { - watchLog.add([]); + do { // "traverse the scopes" loop + if ((watchers = current._watchers) != null) { + // process our watches + length = watchers.length; + while (length-- > 0) { + try { + watch = watchers[length]; + if ((value = watch.get(current)) != (last = watch.last) && + !(value is num && last is num && value.isNaN && last.isNaN)) { + dirty = true; + watch.last = value; + watch.fn(value, ((last == initWatchVal) ? value : last), current); + if (_ttlLeft < 5) { + logIdx = 4 - _ttlLeft; + while (watchLog.length <= logIdx) { + watchLog.add([]); + } + logMsg = (watch.exp is Function) + ? 'fn: ' + (watch.exp.name || watch.exp.toString()) + : watch.exp; + logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); + watchLog[logIdx].add(logMsg); } - logMsg = (watch.exp is Function) - ? 'fn: ' + (watch.exp.name || watch.exp.toString()) - : watch.exp; - logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); - watchLog[logIdx].add(logMsg); } + } catch (e, s) { + _exceptionHandler(e, s); } - } catch (e, s) { - _exceptionHandler(e, s); } } - } - // Insanity Warning: scope depth-first traversal - // yes, this code is a bit crazy, but it works and we have tests to prove it! - // this piece should be kept in sync with the traversal in $broadcast - if (current._childHead == null) { - if (current == target) { - next = null; - } else { - next = current._nextSibling; - if (next == null) { - while(current != target && (next = current._nextSibling) == null) { - current = current.$parent; + // Insanity Warning: scope depth-first traversal + // yes, this code is a bit crazy, but it works and we have tests to prove it! + // this piece should be kept in sync with the traversal in $broadcast + if (current._childHead == null) { + if (current == target) { + next = null; + } else { + next = current._nextSibling; + if (next == null) { + while(current != target && (next = current._nextSibling) == null) { + current = current.$parent; + } } } + } else { + next = current._childHead; } - } else { - next = current._childHead; - } - } while ((current = next) != null); + } while ((current = next) != null); - if(dirty && (_ttlLeft--) == 0) { - throw '$_ttl \$digest() iterations reached. Aborting!\n' + - 'Watchers fired in the last 5 iterations: ${toJson(watchLog)}'; - } - } while (dirty || asyncQueue.length > 0); - } finally { - _clearPhase(); - } + if(dirty && (_ttlLeft--) == 0) { + throw '$_ttl \$digest() iterations reached. Aborting!\n' + + 'Watchers fired in the last 5 iterations: ${toJson(watchLog)}'; + } + } while (dirty || asyncQueue.length > 0); + } finally { + _clearPhase(); + } + }); }