diff --git a/demo/bouncing_balls/web/bouncy_balls.dart b/demo/bouncing_balls/web/bouncy_balls.dart
index 621fc5c80..783769740 100644
--- a/demo/bouncing_balls/web/bouncy_balls.dart
+++ b/demo/bouncing_balls/web/bouncy_balls.dart
@@ -66,14 +66,14 @@ class BounceController {
balls.removeAt(0);
count++;
}
- tick();
+ //tick();
}
void timeDigest() {
var start = window.performance.now();
digestTime = currentDigestTime;
scope.rootScope.domRead(() {
- currentDigestTime = (window.performance.now() - start).round();
+ currentDigestTime = window.performance.now() - start;
});
}
@@ -100,7 +100,7 @@ class BounceController {
@NgDirective(
selector: '[ball-position]',
map: const {
- "ballPosition": '=>position'})
+ "ball-position": '=>position'})
class BallPositionDirective {
final Element element;
final Scope scope;
@@ -118,6 +118,17 @@ class MyModule extends Module {
MyModule() {
type(BounceController);
type(BallPositionDirective);
+ value(GetterCache, new GetterCache({
+ 'x': (o) => o.x,
+ 'y': (o) => o.y,
+ 'bounce': (o) => o.bounce,
+ 'fps': (o) => o.fps,
+ 'balls': (o) => o.balls,
+ 'length': (o) => o.length,
+ 'digestTime': (o) => o.digestTime,
+ 'ballClassName': (o) => o.ballClassName
+ }));
+ value(ScopeStats, new ScopeStats(report: true));
}
}
diff --git a/demo/bouncing_balls/web/index.html b/demo/bouncing_balls/web/index.html
index 568d6336d..1f765d11f 100644
--- a/demo/bouncing_balls/web/index.html
+++ b/demo/bouncing_balls/web/index.html
@@ -50,7 +50,7 @@
- {{bounce.fps}} fps. ({{bounce.balls.length}} balls) [{{(1000/bounce.fps).round()}} ms]
+ {{bounce.fps}} fps. ({{bounce.balls.length}} balls) [{{1000/bounce.fps}} ms]
Digest: {{bounce.digestTime}} ms
+1
+10
diff --git a/lib/change_detection/change_detection.dart b/lib/change_detection/change_detection.dart
index 2eb49fe2e..5db6300cd 100644
--- a/lib/change_detection/change_detection.dart
+++ b/lib/change_detection/change_detection.dart
@@ -54,7 +54,8 @@ abstract class ChangeDetector extends ChangeDetectorGroup {
* linked list of [ChangeRecord]s. The [ChangeRecord]s are returned in the
* same order as they were registered.
*/
- ChangeRecord collectChanges([EvalExceptionHandler exceptionHandler]);
+ ChangeRecord collectChanges({ EvalExceptionHandler exceptionHandler,
+ AvgStopwatch stopwatch });
}
abstract class Record {
@@ -238,3 +239,20 @@ abstract class MovedItem extends CollectionChangeItem {
abstract class RemovedItem extends CollectionChangeItem {
RemovedItem get nextRemovedItem;
}
+
+class AvgStopwatch extends Stopwatch {
+ int _count = 0;
+
+ int get count => _count;
+
+ void reset() {
+ _count = 0;
+ super.reset();
+ }
+
+ int increment(int count) => _count += count;
+
+ double get ratePerMs => elapsedMicroseconds == 0
+ ? 0.0
+ : _count / elapsedMicroseconds * 1000;
+}
diff --git a/lib/change_detection/dirty_checking_change_detector.dart b/lib/change_detection/dirty_checking_change_detector.dart
index 37be9f29b..a51c74687 100644
--- a/lib/change_detection/dirty_checking_change_detector.dart
+++ b/lib/change_detection/dirty_checking_change_detector.dart
@@ -281,11 +281,14 @@ class DirtyCheckingChangeDetector extends DirtyCheckingChangeDetectorGroup
return true;
}
- DirtyCheckingRecord collectChanges([EvalExceptionHandler exceptionHandler]) {
+ DirtyCheckingRecord collectChanges({ EvalExceptionHandler exceptionHandler,
+ AvgStopwatch stopwatch}) {
+ if (stopwatch != null) stopwatch.start();
DirtyCheckingRecord changeHead = null;
DirtyCheckingRecord changeTail = null;
DirtyCheckingRecord current = _recordHead; // current index
+ int count = 0;
while (current != null) {
try {
if (current.check() != null) {
@@ -295,6 +298,7 @@ class DirtyCheckingChangeDetector extends DirtyCheckingChangeDetectorGroup
changeTail = changeTail.nextChange = current;
}
}
+ if (stopwatch != null) count++;
} catch (e, s) {
if (exceptionHandler == null) {
rethrow;
@@ -305,6 +309,7 @@ class DirtyCheckingChangeDetector extends DirtyCheckingChangeDetectorGroup
current = current._nextRecord;
}
if (changeTail != null) changeTail.nextChange = null;
+ if (stopwatch != null) stopwatch..stop()..increment(count);
return changeHead;
}
diff --git a/lib/change_detection/watch_group.dart b/lib/change_detection/watch_group.dart
index 6b1434276..f1427b9ba 100644
--- a/lib/change_detection/watch_group.dart
+++ b/lib/change_detection/watch_group.dart
@@ -358,12 +358,17 @@ class RootWatchGroup extends WatchGroup {
* Each step is called in sequence. ([ReactionFn]s are not called until all
* previous steps are completed).
*/
- int detectChanges({EvalExceptionHandler exceptionHandler,
- ChangeLog changeLog}) {
+ int detectChanges({ EvalExceptionHandler exceptionHandler,
+ ChangeLog changeLog,
+ AvgStopwatch fieldStopwatch,
+ AvgStopwatch evalStopwatch,
+ AvgStopwatch processStopwatch}) {
// Process the ChangeRecords from the change detector
ChangeRecord<_Handler> changeRecord =
- (_changeDetector as ChangeDetector<_Handler>)
- .collectChanges(exceptionHandler);
+ (_changeDetector as ChangeDetector<_Handler>).collectChanges(
+ exceptionHandler:exceptionHandler,
+ stopwatch: fieldStopwatch);
+ if (processStopwatch != null) processStopwatch.start();
while (changeRecord != null) {
if (changeLog != null) changeLog(changeRecord.handler.expression,
changeRecord.currentValue,
@@ -371,12 +376,15 @@ class RootWatchGroup extends WatchGroup {
changeRecord.handler.onChange(changeRecord);
changeRecord = changeRecord.nextChange;
}
+ if (processStopwatch != null) processStopwatch.stop();
- int count = 0;
+ if (evalStopwatch != null) evalStopwatch.start();
// Process our own function evaluations
_EvalWatchRecord evalRecord = _evalWatchHead;
+ int evalCount = 0;
while (evalRecord != null) {
try {
+ if (evalStopwatch != null) evalCount++;
var change = evalRecord.check();
if (change != null && changeLog != null) {
changeLog(evalRecord.handler.expression,
@@ -388,10 +396,13 @@ class RootWatchGroup extends WatchGroup {
}
evalRecord = evalRecord._nextEvalWatch;
}
+ if (evalStopwatch != null) evalStopwatch..stop()..increment(evalCount);
// Because the handler can forward changes between each other synchronously
// We need to call reaction functions asynchronously. This processes the
// asynchronous reaction function queue.
+ int count = 0;
+ if (processStopwatch != null) processStopwatch.stop();
Watch dirtyWatch = _dirtyWatchHead;
RootWatchGroup root = _rootGroup;
root._removeCount = 0;
@@ -407,6 +418,7 @@ class RootWatchGroup extends WatchGroup {
dirtyWatch = dirtyWatch._nextDirtyWatch;
}
_dirtyWatchHead = _dirtyWatchTail = null;
+ if (processStopwatch != null) processStopwatch..stop()..increment(count);
return count;
}
diff --git a/lib/core/module.dart b/lib/core/module.dart
index 70ee0c5ae..4558a3d5f 100644
--- a/lib/core/module.dart
+++ b/lib/core/module.dart
@@ -3,6 +3,7 @@ library angular.core;
import 'dart:async' as async;
import 'dart:collection';
import 'dart:mirrors';
+import 'package:intl/intl.dart';
import 'package:di/di.dart';
@@ -40,15 +41,10 @@ class NgCoreModule extends Module {
type(FilterMap);
type(Interpolate);
type(RootScope);
+ factory(Scope, (injector) => injector.get(RootScope));
+ value(ScopeStats, new ScopeStats());
value(GetterCache, new GetterCache({}));
value(Object, {}); // RootScope context
- factory(Scope, (injector) {
-// try { throw null; }
-// catch (e, s) {
-// print('DEPRECATED reference to Scope:\n$s');
-// }
- return injector.get(RootScope);
- });
type(AstParser);
type(NgZone);
diff --git a/lib/core/scope.dart b/lib/core/scope.dart
index 3b4db78b5..db063051d 100644
--- a/lib/core/scope.dart
+++ b/lib/core/scope.dart
@@ -344,6 +344,59 @@ class Scope {
_mapEqual(Map a, Map b) => a.length == b.length &&
a.keys.every((k) => b.containsKey(k) && a[k] == b[k]);
+class ScopeStats {
+ bool report = true;
+ final nf = new NumberFormat.decimalPattern();
+
+ final digestFieldStopwatch = new AvgStopwatch();
+ final digestEvalStopwatch = new AvgStopwatch();
+ final digestProcessStopwatch = new AvgStopwatch();
+ int _digestLoopNo = 0;
+
+ final flushFieldStopwatch = new AvgStopwatch();
+ final flushEvalStopwatch = new AvgStopwatch();
+ final flushProcessStopwatch = new AvgStopwatch();
+
+ ScopeStats({this.report: false}) {
+ nf.maximumFractionDigits = 0;
+ }
+
+ void digestStart() {
+ _digestStopwatchReset();
+ _digestLoopNo = 0;
+ }
+
+ _digestStopwatchReset() {
+ digestFieldStopwatch.reset();
+ digestEvalStopwatch.reset();
+ digestProcessStopwatch.reset();
+ }
+
+ void digestLoop(int changeCount) {
+ _digestLoopNo++;
+ if (report) {
+ print(this);
+ }
+ _digestStopwatchReset();
+ }
+
+ String _stat(AvgStopwatch s) {
+ return '${nf.format(s.count)}'
+ ' / ${nf.format(s.elapsedMicroseconds)} us'
+ ' = ${nf.format(s.ratePerMs)} #/ms';
+ }
+
+ void digestEnd() {
+ }
+
+ toString() =>
+ 'digest #$_digestLoopNo:'
+ 'Field: ${_stat(digestFieldStopwatch)} '
+ 'Eval: ${_stat(digestEvalStopwatch)} '
+ 'Process: ${_stat(digestProcessStopwatch)}';
+}
+
+
class RootScope extends Scope {
static final STATE_APPLY = 'apply';
static final STATE_DIGEST = 'digest';
@@ -360,11 +413,14 @@ class RootScope extends Scope {
_FunctionChain _domWriteHead, _domWriteTail;
_FunctionChain _domReadHead, _domReadTail;
+ final ScopeStats _scopeStats;
+
String _state;
RootScope(Object context, this._astParser, this._parser,
GetterCache cacheGetter, FilterMap filterMap,
- this._exceptionHandler, this._ttl, this._zone)
+ this._exceptionHandler, this._ttl, this._zone,
+ this._scopeStats)
: super(context, null, null,
new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context),
new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), context))
@@ -387,6 +443,7 @@ class RootScope extends Scope {
List digestLog;
var count;
ChangeLog changeLog;
+ _scopeStats.digestStart();
do {
while(_runAsyncHead != null) {
try {
@@ -399,7 +456,11 @@ class RootScope extends Scope {
digestTTL--;
count = rootWatchGroup.detectChanges(
- exceptionHandler: _exceptionHandler, changeLog: changeLog);
+ exceptionHandler: _exceptionHandler,
+ changeLog: changeLog,
+ fieldStopwatch: _scopeStats.digestFieldStopwatch,
+ evalStopwatch: _scopeStats.digestEvalStopwatch,
+ processStopwatch: _scopeStats.digestProcessStopwatch);
if (digestTTL <= LOG_COUNT) {
if (changeLog == null) {
@@ -415,8 +476,10 @@ class RootScope extends Scope {
throw 'Model did not stabilize in ${_ttl.ttl} digests. '
'Last $LOG_COUNT iterations:\n${log.join('\n')}';
}
+ _scopeStats.digestLoop(count);
} while (count > 0);
} finally {
+ _scopeStats.digestEnd();
_transitionState(STATE_DIGEST, null);
}
}