OLD | NEW |
1 part of angular.core; | 1 part of angular.core_internal; |
2 | 2 |
3 NOT_IMPLEMENTED() { | |
4 throw new StateError('Not Implemented'); | |
5 } | |
6 | |
7 typedef EvalFunction0(); | 3 typedef EvalFunction0(); |
8 typedef EvalFunction1(context); | 4 typedef EvalFunction1(context); |
9 | 5 |
10 /** | 6 /** |
11 * Injected into the listener function within [Scope.on] to provide | 7 * Injected into the listener function within [Scope.on] to provide |
12 * event-specific details to the scope listener. | 8 * event-specific details to the scope listener. |
13 */ | 9 */ |
14 class ScopeEvent { | 10 class ScopeEvent { |
15 static final String DESTROY = 'ng-destroy'; | 11 static final String DESTROY = 'ng-destroy'; |
16 | 12 |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
74 } | 70 } |
75 | 71 |
76 /** | 72 /** |
77 * Allows the configuration of [Scope.digest] iteration maximum time-to-live | 73 * Allows the configuration of [Scope.digest] iteration maximum time-to-live |
78 * value. Digest keeps checking the state of the watcher getters until it | 74 * value. Digest keeps checking the state of the watcher getters until it |
79 * can execute one full iteration with no watchers triggering. TTL is used | 75 * can execute one full iteration with no watchers triggering. TTL is used |
80 * to prevent an infinite loop where watch A triggers watch B which in turn | 76 * to prevent an infinite loop where watch A triggers watch B which in turn |
81 * triggers watch A. If the system does not stabilize in TTL iterations then | 77 * triggers watch A. If the system does not stabilize in TTL iterations then |
82 * the digest is stopped and an exception is thrown. | 78 * the digest is stopped and an exception is thrown. |
83 */ | 79 */ |
84 @NgInjectableService() | 80 @Injectable() |
85 class ScopeDigestTTL { | 81 class ScopeDigestTTL { |
86 final int ttl; | 82 final int ttl; |
87 ScopeDigestTTL(): ttl = 5; | 83 ScopeDigestTTL(): ttl = 5; |
88 ScopeDigestTTL.value(this.ttl); | 84 ScopeDigestTTL.value(this.ttl); |
89 } | 85 } |
90 | 86 |
91 //TODO(misko): I don't think this should be in scope. | 87 //TODO(misko): I don't think this should be in scope. |
92 class ScopeLocals implements Map { | 88 class ScopeLocals implements Map { |
93 static wrapper(scope, Map<String, Object> locals) => | 89 static wrapper(scope, Map<String, Object> locals) => |
94 new ScopeLocals(scope, locals); | 90 new ScopeLocals(scope, locals); |
95 | 91 |
96 Map _scope; | 92 Map _scope; |
97 Map<String, Object> _locals; | 93 Map<String, Object> _locals; |
98 | 94 |
99 ScopeLocals(this._scope, this._locals); | 95 ScopeLocals(this._scope, this._locals); |
100 | 96 |
101 void operator []=(String name, value) { | 97 void operator []=(String name, value) { |
102 _scope[name] = value; | 98 _scope[name] = value; |
103 } | 99 } |
104 dynamic operator [](String name) => | 100 dynamic operator [](String name) => |
105 (_locals.containsKey(name) ? _locals : _scope)[name]; | 101 // as Map needed to clear Dart2js warning |
| 102 ((_locals.containsKey(name) ? _locals : _scope) as Map)[name]; |
106 | 103 |
107 bool get isEmpty => _scope.isEmpty && _locals.isEmpty; | 104 bool get isEmpty => _scope.isEmpty && _locals.isEmpty; |
108 bool get isNotEmpty => _scope.isNotEmpty || _locals.isNotEmpty; | 105 bool get isNotEmpty => _scope.isNotEmpty || _locals.isNotEmpty; |
109 List<String> get keys => _scope.keys; | 106 List<String> get keys => _scope.keys; |
110 List get values => _scope.values; | 107 List get values => _scope.values; |
111 int get length => _scope.length; | 108 int get length => _scope.length; |
112 | 109 |
113 void forEach(fn) { | 110 void forEach(fn) { |
114 _scope.forEach(fn); | 111 _scope.forEach(fn); |
115 } | 112 } |
116 dynamic remove(key) => _scope.remove(key); | 113 dynamic remove(key) => _scope.remove(key); |
117 void clear() { | 114 void clear() { |
118 _scope.clear; | 115 _scope.clear; |
119 } | 116 } |
120 bool containsKey(key) => _scope.containsKey(key); | 117 bool containsKey(key) => _scope.containsKey(key); |
121 bool containsValue(key) => _scope.containsValue(key); | 118 bool containsValue(key) => _scope.containsValue(key); |
122 void addAll(map) { | 119 void addAll(map) { |
123 _scope.addAll(map); | 120 _scope.addAll(map); |
124 } | 121 } |
125 dynamic putIfAbsent(key, fn) => _scope.putIfAbsent(key, fn); | 122 dynamic putIfAbsent(key, fn) => _scope.putIfAbsent(key, fn); |
126 } | 123 } |
127 | 124 |
128 /** | 125 /** |
129 * [Scope] is represents a collection of [watch]es [observe]ers, and [context] | 126 * [Scope] is represents a collection of [watch]es [observe]ers, and [context] |
130 * for the watchers, observers and [eval]uations. Scopes structure loosely | 127 * for the watchers, observers and [eval]uations. Scopes structure loosely |
131 * mimics the DOM structure. Scopes and [Block]s are bound to each other. | 128 * mimics the DOM structure. Scopes and [View]s are bound to each other. |
132 * As scopes are created and destroyed by [BlockFactory] they are responsible | 129 * As scopes are created and destroyed by [ViewFactory] they are responsible |
133 * for change detection, change processing and memory management. | 130 * for change detection, change processing and memory management. |
134 */ | 131 */ |
135 class Scope { | 132 class Scope { |
| 133 final String id; |
| 134 int _childScopeNextId = 0; |
136 | 135 |
137 /** | 136 /** |
138 * The default execution context for [watch]es [observe]ers, and [eval]uation. | 137 * The default execution context for [watch]es [observe]ers, and [eval]uation. |
139 */ | 138 */ |
140 final context; | 139 final context; |
141 | 140 |
142 /** | 141 /** |
143 * The [RootScope] of the application. | 142 * The [RootScope] of the application. |
144 */ | 143 */ |
145 final RootScope rootScope; | 144 final RootScope rootScope; |
146 | 145 |
147 Scope _parentScope; | 146 Scope _parentScope; |
148 | 147 |
149 /** | 148 /** |
150 * The parent [Scope]. | 149 * The parent [Scope]. |
151 */ | 150 */ |
152 Scope get parentScope => _parentScope; | 151 Scope get parentScope => _parentScope; |
153 | 152 |
| 153 final ScopeStats _stats; |
| 154 |
154 /** | 155 /** |
155 * Return `true` if the scope has been destroyed. Once scope is destroyed | 156 * Return `true` if the scope has been destroyed. Once scope is destroyed |
156 * No operations are allowed on it. | 157 * No operations are allowed on it. |
157 */ | 158 */ |
158 bool get isDestroyed { | 159 bool get isDestroyed { |
159 var scope = this; | 160 var scope = this; |
160 while(scope != null) { | 161 while (scope != null) { |
161 if (scope == rootScope) return false; | 162 if (scope == rootScope) return false; |
162 scope = scope._parentScope; | 163 scope = scope._parentScope; |
163 } | 164 } |
164 return true; | 165 return true; |
165 } | 166 } |
166 | 167 |
167 /** | 168 /** |
168 * Returns true if the scope is still attached to the [RootScope]. | 169 * Returns true if the scope is still attached to the [RootScope]. |
169 */ | 170 */ |
170 bool get isAttached => !isDestroyed; | 171 bool get isAttached => !isDestroyed; |
171 | 172 |
172 // TODO(misko): WatchGroup should be private. | 173 // TODO(misko): WatchGroup should be private. |
173 // Instead we should expose performance stats about the watches | 174 // Instead we should expose performance stats about the watches |
174 // such as # of watches, checks/1ms, field checks, function checks, etc | 175 // such as # of watches, checks/1ms, field checks, function checks, etc |
175 final WatchGroup _readWriteGroup; | 176 final WatchGroup _readWriteGroup; |
176 final WatchGroup _readOnlyGroup; | 177 final WatchGroup _readOnlyGroup; |
177 | 178 |
178 Scope _childHead, _childTail, _next, _prev; | 179 Scope _childHead, _childTail, _next, _prev; |
179 _Streams _streams; | 180 _Streams _streams; |
180 | 181 |
181 /// Do not use. Exposes internal state for testing. | 182 /// Do not use. Exposes internal state for testing. |
182 bool get hasOwnStreams => _streams != null && _streams._scope == this; | 183 bool get hasOwnStreams => _streams != null && _streams._scope == this; |
183 | 184 |
184 Scope(Object this.context, this.rootScope, this._parentScope, | 185 Scope(Object this.context, this.rootScope, this._parentScope, |
185 this._readWriteGroup, this._readOnlyGroup); | 186 this._readWriteGroup, this._readOnlyGroup, this.id, |
| 187 this._stats); |
186 | 188 |
187 /** | 189 /** |
188 * A [watch] sets up a watch in the [digest] phase of the [apply] cycle. | 190 * Use [watch] to set up change detection on an expression. |
189 * | 191 * |
190 * Use [watch] if the reaction function can cause updates to model. In your | 192 * * [expression]: The expression to watch for changes. |
191 * controller code you will most likely use [watch]. | 193 * * [reactionFn]: The reaction function to execute when a change is detected
in the watched |
| 194 * expression. |
| 195 * * [context]: The object against which the expression is evaluated. This def
aults to the |
| 196 * [Scope.context] if no context is specified. |
| 197 * * [formatters]: If the watched expression contains formatters, |
| 198 * this map specifies the set of formatters that are used by the expression. |
| 199 * * [canChangeModel]: Specifies whether the [reactionFn] changes the model. R
eaction |
| 200 * functions that change the model are processed as part of the [digest] cyc
le. Otherwise, |
| 201 * they are processed as part of the [flush] cycle. |
| 202 * * [collection]: If [:true:], then the expression points to a collection (a
list or a map), |
| 203 * and the collection should be shallow watched. If [:false:] then the expre
ssion is watched |
| 204 * by reference. When watching a collection, the reaction function receives
a |
| 205 * [CollectionChangeItem] that lists all the changes. |
192 */ | 206 */ |
193 Watch watch(expression, ReactionFn reactionFn, | 207 Watch watch(String expression, ReactionFn reactionFn, {context, |
194 {context, FilterMap filters, bool readOnly: false}) { | 208 FormatterMap formatters, bool canChangeModel: true, bool collection: false
}) { |
195 assert(isAttached); | 209 assert(isAttached); |
196 assert(expression != null); | 210 assert(expression is String); |
197 AST ast; | 211 assert(canChangeModel is bool); |
| 212 |
198 Watch watch; | 213 Watch watch; |
199 ReactionFn fn = reactionFn; | 214 ReactionFn fn = reactionFn; |
200 if (expression is AST) { | 215 if (expression.isEmpty) { |
201 ast = expression; | 216 expression = '""'; |
202 } else if (expression is String) { | 217 } else { |
203 if (expression.startsWith('::')) { | 218 if (expression.startsWith('::')) { |
204 expression = expression.substring(2); | 219 expression = expression.substring(2); |
205 fn = (value, last) { | 220 fn = (value, last) { |
206 if (value != null) { | 221 if (value != null) { |
207 watch.remove(); | 222 watch.remove(); |
208 return reactionFn(value, last); | 223 return reactionFn(value, last); |
209 } | 224 } |
210 }; | 225 }; |
211 } else if (expression.startsWith(':')) { | 226 } else if (expression.startsWith(':')) { |
212 expression = expression.substring(1); | 227 expression = expression.substring(1); |
213 fn = (value, last) => value == null ? null : reactionFn(value, last); | 228 fn = (value, last) { |
| 229 if (value != null) reactionFn(value, last); |
| 230 }; |
214 } | 231 } |
215 ast = rootScope._astParser(expression, context: context, filters: filters)
; | |
216 } else { | |
217 throw 'expressions must be String or AST got $expression.'; | |
218 } | 232 } |
219 return watch = (readOnly ? _readOnlyGroup : _readWriteGroup).watch(ast, fn); | 233 |
| 234 AST ast = rootScope._astParser(expression, context: context, |
| 235 formatters: formatters, collection: collection); |
| 236 |
| 237 WatchGroup group = canChangeModel ? _readWriteGroup : _readOnlyGroup; |
| 238 return watch = group.watch(ast, fn); |
220 } | 239 } |
221 | 240 |
222 dynamic eval(expression, [Map locals]) { | 241 dynamic eval(expression, [Map locals]) { |
223 assert(isAttached); | 242 assert(isAttached); |
224 assert(expression == null || | 243 assert(expression == null || |
225 expression is String || | 244 expression is String || |
226 expression is Function); | 245 expression is Function); |
227 if (expression is String && expression.isNotEmpty) { | 246 if (expression is String && expression.isNotEmpty) { |
228 var obj = locals == null ? context : new ScopeLocals(context, locals); | 247 var obj = locals == null ? context : new ScopeLocals(context, locals); |
229 return rootScope._parser(expression).eval(obj); | 248 return rootScope._parser(expression).eval(obj); |
230 } | 249 } |
231 | 250 |
232 assert(locals == null); | 251 assert(locals == null); |
233 if (expression is EvalFunction1) return expression(context); | 252 if (expression is EvalFunction1) return expression(context); |
234 if (expression is EvalFunction0) return expression(); | 253 if (expression is EvalFunction0) return expression(); |
235 return null; | 254 return null; |
236 } | 255 } |
237 | 256 |
238 dynamic applyInZone([expression, Map locals]) => | |
239 rootScope._zone.run(() => apply(expression, locals)); | |
240 | |
241 dynamic apply([expression, Map locals]) { | 257 dynamic apply([expression, Map locals]) { |
242 _assertInternalStateConsistency(); | 258 _assertInternalStateConsistency(); |
243 rootScope._transitionState(null, RootScope.STATE_APPLY); | 259 rootScope._transitionState(null, RootScope.STATE_APPLY); |
244 try { | 260 try { |
245 return eval(expression, locals); | 261 return eval(expression, locals); |
246 } catch (e, s) { | 262 } catch (e, s) { |
247 rootScope._exceptionHandler(e, s); | 263 rootScope._exceptionHandler(e, s); |
248 } finally { | 264 } finally { |
249 rootScope | 265 rootScope.._transitionState(RootScope.STATE_APPLY, null) |
250 .._transitionState(RootScope.STATE_APPLY, null) | 266 ..digest() |
251 ..digest() | 267 ..flush(); |
252 ..flush(); | |
253 } | 268 } |
254 } | 269 } |
255 | 270 |
256 ScopeEvent emit(String name, [data]) { | 271 ScopeEvent emit(String name, [data]) { |
257 assert(isAttached); | 272 assert(isAttached); |
258 return _Streams.emit(this, name, data); | 273 return _Streams.emit(this, name, data); |
259 } | 274 } |
| 275 |
260 ScopeEvent broadcast(String name, [data]) { | 276 ScopeEvent broadcast(String name, [data]) { |
261 assert(isAttached); | 277 assert(isAttached); |
262 return _Streams.broadcast(this, name, data); | 278 return _Streams.broadcast(this, name, data); |
263 } | 279 } |
| 280 |
264 ScopeStream on(String name) { | 281 ScopeStream on(String name) { |
265 assert(isAttached); | 282 assert(isAttached); |
266 return _Streams.on(this, rootScope._exceptionHandler, name); | 283 return _Streams.on(this, rootScope._exceptionHandler, name); |
267 } | 284 } |
268 | 285 |
269 Scope createChild(Object childContext) { | 286 Scope createChild(Object childContext) { |
270 assert(isAttached); | 287 assert(isAttached); |
271 var child = new Scope(childContext, rootScope, this, | 288 var child = new Scope(childContext, rootScope, this, |
272 _readWriteGroup.newGroup(childContext), | 289 _readWriteGroup.newGroup(childContext), |
273 _readOnlyGroup.newGroup(childContext)); | 290 _readOnlyGroup.newGroup(childContext), |
274 var next = null; | 291 '$id:${_childScopeNextId++}', |
| 292 _stats); |
| 293 |
275 var prev = _childTail; | 294 var prev = _childTail; |
276 child._next = next; | |
277 child._prev = prev; | 295 child._prev = prev; |
278 if (prev == null) _childHead = child; else prev._next = child; | 296 if (prev == null) _childHead = child; else prev._next = child; |
279 if (next == null) _childTail = child; else next._prev = child; | 297 _childTail = child; |
280 return child; | 298 return child; |
281 } | 299 } |
282 | 300 |
283 void destroy() { | 301 void destroy() { |
284 assert(isAttached); | 302 assert(isAttached); |
285 broadcast(ScopeEvent.DESTROY); | 303 broadcast(ScopeEvent.DESTROY); |
286 _Streams.destroy(this); | 304 _Streams.destroy(this); |
287 | 305 |
288 if (_prev == null) { | 306 if (_prev == null) { |
289 _parentScope._childHead = _next; | 307 _parentScope._childHead = _next; |
290 } else { | 308 } else { |
291 _prev._next = _next; | 309 _prev._next = _next; |
292 } | 310 } |
293 if (_next == null) { | 311 if (_next == null) { |
294 _parentScope._childTail = _prev; | 312 _parentScope._childTail = _prev; |
295 } else { | 313 } else { |
296 _next._prev = _prev; | 314 _next._prev = _prev; |
297 } | 315 } |
298 | 316 |
299 _next = _prev = null; | 317 _next = _prev = null; |
300 | 318 |
301 _readWriteGroup.remove(); | 319 _readWriteGroup.remove(); |
302 _readOnlyGroup.remove(); | 320 _readOnlyGroup.remove(); |
303 _parentScope = null; | 321 _parentScope = null; |
304 _assertInternalStateConsistency(); | |
305 } | 322 } |
306 | 323 |
307 _assertInternalStateConsistency() { | 324 _assertInternalStateConsistency() { |
308 assert((() { | 325 assert((() { |
309 rootScope._verifyStreams(null, '', []); | 326 rootScope._verifyStreams(null, '', []); |
310 return true; | 327 return true; |
311 })()); | 328 })()); |
312 } | 329 } |
313 | 330 |
314 Map<bool,int> _verifyStreams(parentScope, prefix, log) { | 331 Map<bool,int> _verifyStreams(parentScope, prefix, log) { |
315 assert(_parentScope == parentScope); | 332 assert(_parentScope == parentScope); |
316 var counts = {}; | 333 var counts = {}; |
317 var typeCounts = _streams == null ? {} : _streams._typeCounts; | 334 var typeCounts = _streams == null ? {} : _streams._typeCounts; |
318 var connection = _streams != null && _streams._scope == this ? '=' : '-'; | 335 var connection = _streams != null && _streams._scope == this ? '=' : '-'; |
319 log..add(prefix)..add(hashCode)..add(connection)..add(typeCounts)..add('\n')
; | 336 log..add(prefix)..add(hashCode)..add(connection)..add(typeCounts)..add('\n')
; |
320 if (_streams == null) { | 337 if (_streams == null) { |
321 } else if (_streams._scope == this) { | 338 } else if (_streams._scope == this) { |
322 _streams._streams.forEach((k, ScopeStream stream){ | 339 _streams._streams.forEach((k, ScopeStream stream){ |
323 if (stream.subscriptions.isNotEmpty) { | 340 if (stream.subscriptions.isNotEmpty) { |
324 counts[k] = 1 + (counts.containsKey(k) ? counts[k] : 0); | 341 counts[k] = 1 + (counts.containsKey(k) ? counts[k] : 0); |
325 } | 342 } |
326 }); | 343 }); |
327 } | 344 } |
328 var childScope = _childHead; | 345 var childScope = _childHead; |
329 while(childScope != null) { | 346 while (childScope != null) { |
330 childScope._verifyStreams(this, ' $prefix', log).forEach((k, v) { | 347 childScope._verifyStreams(this, ' $prefix', log).forEach((k, v) { |
331 counts[k] = v + (counts.containsKey(k) ? counts[k] : 0); | 348 counts[k] = v + (counts.containsKey(k) ? counts[k] : 0); |
332 }); | 349 }); |
333 childScope = childScope._next; | 350 childScope = childScope._next; |
334 } | 351 } |
335 if (!_mapEqual(counts, typeCounts)) { | 352 if (!_mapEqual(counts, typeCounts)) { |
336 throw 'Streams actual: $counts != bookkeeping: $typeCounts\n' | 353 throw 'Streams actual: $counts != bookkeeping: $typeCounts\n' |
337 'Offending scope: [scope: ${this.hashCode}]\n' | 354 'Offending scope: [scope: ${this.hashCode}]\n' |
338 '${log.join('')}'; | 355 '${log.join('')}'; |
339 } | 356 } |
340 return counts; | 357 return counts; |
341 } | 358 } |
342 } | 359 } |
343 | 360 |
344 _mapEqual(Map a, Map b) => a.length == b.length && | 361 _mapEqual(Map a, Map b) => a.length == b.length && |
345 a.keys.every((k) => b.containsKey(k) && a[k] == b[k]); | 362 a.keys.every((k) => b.containsKey(k) && a[k] == b[k]); |
346 | 363 |
| 364 /** |
| 365 * ScopeStats collects and emits statistics about a [Scope]. |
| 366 * |
| 367 * ScopeStats supports emitting the results. Result emission can be started or |
| 368 * stopped at runtime. The result emission can is configured by supplying a |
| 369 * [ScopeStatsEmitter]. |
| 370 */ |
| 371 @Injectable() |
347 class ScopeStats { | 372 class ScopeStats { |
348 bool report = true; | 373 final fieldStopwatch = new AvgStopwatch(); |
349 final nf = new NumberFormat.decimalPattern(); | 374 final evalStopwatch = new AvgStopwatch(); |
| 375 final processStopwatch = new AvgStopwatch(); |
350 | 376 |
351 final digestFieldStopwatch = new AvgStopwatch(); | 377 List<int> _digestLoopTimes = []; |
352 final digestEvalStopwatch = new AvgStopwatch(); | 378 int _flushPhaseDuration = 0 ; |
353 final digestProcessStopwatch = new AvgStopwatch(); | 379 int _assertFlushPhaseDuration = 0; |
354 int _digestLoopNo = 0; | |
355 | 380 |
356 final flushFieldStopwatch = new AvgStopwatch(); | 381 int _loopNo = 0; |
357 final flushEvalStopwatch = new AvgStopwatch(); | 382 ScopeStatsEmitter _emitter; |
358 final flushProcessStopwatch = new AvgStopwatch(); | 383 ScopeStatsConfig _config; |
359 | 384 |
360 ScopeStats({this.report: false}) { | 385 /** |
361 nf.maximumFractionDigits = 0; | 386 * Construct a new instance of ScopeStats. |
| 387 */ |
| 388 ScopeStats(this._emitter, this._config); |
| 389 |
| 390 void digestStart() { |
| 391 _digestLoopTimes = []; |
| 392 _stopwatchReset(); |
| 393 _loopNo = 0; |
362 } | 394 } |
363 | 395 |
364 void digestStart() { | 396 int _allStagesDuration() { |
365 _digestStopwatchReset(); | 397 return fieldStopwatch.elapsedMicroseconds + |
366 _digestLoopNo = 0; | 398 evalStopwatch.elapsedMicroseconds + |
| 399 processStopwatch.elapsedMicroseconds; |
367 } | 400 } |
368 | 401 |
369 _digestStopwatchReset() { | 402 _stopwatchReset() { |
370 digestFieldStopwatch.reset(); | 403 fieldStopwatch.reset(); |
371 digestEvalStopwatch.reset(); | 404 evalStopwatch.reset(); |
372 digestProcessStopwatch.reset(); | 405 processStopwatch.reset(); |
373 } | 406 } |
374 | 407 |
375 void digestLoop(int changeCount) { | 408 void digestLoop(int changeCount) { |
376 _digestLoopNo++; | 409 _loopNo++; |
377 if (report) { | 410 if (_config.emit && _emitter != null) { |
378 print(this); | 411 _emitter.emit(_loopNo.toString(), fieldStopwatch, evalStopwatch, |
| 412 processStopwatch); |
379 } | 413 } |
380 _digestStopwatchReset(); | 414 _digestLoopTimes.add( _allStagesDuration() ); |
381 } | 415 _stopwatchReset(); |
382 | |
383 String _stat(AvgStopwatch s) { | |
384 return '${nf.format(s.count)}' | |
385 ' / ${nf.format(s.elapsedMicroseconds)} us' | |
386 ' = ${nf.format(s.ratePerMs)} #/ms'; | |
387 } | 416 } |
388 | 417 |
389 void digestEnd() { | 418 void digestEnd() { |
390 } | 419 } |
391 | 420 |
392 toString() => | 421 void domWriteStart() {} |
393 'digest #$_digestLoopNo:' | 422 void domWriteEnd() {} |
394 'Field: ${_stat(digestFieldStopwatch)} ' | 423 void domReadStart() {} |
395 'Eval: ${_stat(digestEvalStopwatch)} ' | 424 void domReadEnd() {} |
396 'Process: ${_stat(digestProcessStopwatch)}'; | 425 void flushStart() { |
| 426 _stopwatchReset(); |
| 427 } |
| 428 void flushEnd() { |
| 429 if (_config.emit && _emitter != null) { |
| 430 _emitter.emit(RootScope.STATE_FLUSH, fieldStopwatch, evalStopwatch, |
| 431 processStopwatch); |
| 432 } |
| 433 _flushPhaseDuration = _allStagesDuration(); |
| 434 } |
| 435 void flushAssertStart() { |
| 436 _stopwatchReset(); |
| 437 } |
| 438 void flushAssertEnd() { |
| 439 if (_config.emit && _emitter != null) { |
| 440 _emitter.emit(RootScope.STATE_FLUSH_ASSERT, fieldStopwatch, evalStopwatch, |
| 441 processStopwatch); |
| 442 } |
| 443 _assertFlushPhaseDuration = _allStagesDuration(); |
| 444 } |
| 445 |
| 446 void cycleEnd() { |
| 447 } |
397 } | 448 } |
398 | 449 |
| 450 /** |
| 451 * ScopeStatsEmitter is in charge of formatting the [ScopeStats] and outputting |
| 452 * a message. |
| 453 */ |
| 454 @Injectable() |
| 455 class ScopeStatsEmitter { |
| 456 static String _PAD_ = ' '; |
| 457 static String _HEADER_ = pad('APPLY', 7) + ':'+ |
| 458 pad('FIELD', 19) + pad('|', 20) + |
| 459 pad('EVAL', 19) + pad('|', 20) + |
| 460 pad('REACTION', 19) + pad('|', 20) + |
| 461 pad('TOTAL', 10) + '\n'; |
| 462 final _nfDec = new NumberFormat("0.00", "en_US"); |
| 463 final _nfInt = new NumberFormat("0", "en_US"); |
399 | 464 |
| 465 static pad(String str, int size) => _PAD_.substring(0, max(size - str.length,
0)) + str; |
| 466 |
| 467 _ms(num value) => '${pad(_nfDec.format(value), 9)} ms'; |
| 468 _us(num value) => _ms(value / 1000); |
| 469 _tally(num value) => '${pad(_nfInt.format(value), 6)}'; |
| 470 |
| 471 /** |
| 472 * Emit a message based on the phase and state of stopwatches. |
| 473 */ |
| 474 void emit(String phaseOrLoopNo, AvgStopwatch fieldStopwatch, |
| 475 AvgStopwatch evalStopwatch, AvgStopwatch processStopwatch) { |
| 476 var total = fieldStopwatch.elapsedMicroseconds + |
| 477 evalStopwatch.elapsedMicroseconds + |
| 478 processStopwatch.elapsedMicroseconds; |
| 479 print('${_formatPrefix(phaseOrLoopNo)} ' |
| 480 '${_stat(fieldStopwatch)} | ' |
| 481 '${_stat(evalStopwatch)} | ' |
| 482 '${_stat(processStopwatch)} | ' |
| 483 '${_ms(total/1000)}'); |
| 484 } |
| 485 |
| 486 String _formatPrefix(String prefix) { |
| 487 if (prefix == RootScope.STATE_FLUSH) return ' flush:'; |
| 488 if (prefix == RootScope.STATE_FLUSH_ASSERT) return ' assert:'; |
| 489 |
| 490 return (prefix == '1' ? _HEADER_ : '') + ' #$prefix:'; |
| 491 } |
| 492 |
| 493 String _stat(AvgStopwatch s) { |
| 494 return '${_tally(s.count)} / ${_us(s.elapsedMicroseconds)} @(${_tally(s.rate
PerMs)} #/ms)'; |
| 495 } |
| 496 } |
| 497 |
| 498 /** |
| 499 * ScopeStatsConfig is used to modify behavior of [ScopeStats]. You can use this |
| 500 * object to modify behavior at runtime too. |
| 501 */ |
| 502 class ScopeStatsConfig { |
| 503 var emit = false; |
| 504 |
| 505 ScopeStatsConfig(); |
| 506 ScopeStatsConfig.enabled() { |
| 507 emit = true; |
| 508 } |
| 509 } |
| 510 /** |
| 511 * |
| 512 * Every Angular application has exactly one RootScope. RootScope extends Scope,
adding |
| 513 * services related to change detection, async unit-of-work processing, and DOM
read/write queues. |
| 514 * The RootScope can not be destroyed. |
| 515 * |
| 516 * ## Lifecycle |
| 517 * |
| 518 * All work in Angular must be done within a context of a VmTurnZone. VmTurnZone
detects the end |
| 519 * of the VM turn, and calls the Apply method to process the changes at the end
of VM turn. |
| 520 * |
| 521 */ |
| 522 @Injectable() |
400 class RootScope extends Scope { | 523 class RootScope extends Scope { |
401 static final STATE_APPLY = 'apply'; | 524 static final STATE_APPLY = 'apply'; |
402 static final STATE_DIGEST = 'digest'; | 525 static final STATE_DIGEST = 'digest'; |
403 static final STATE_FLUSH = 'digest'; | 526 static final STATE_FLUSH = 'flush'; |
| 527 static final STATE_FLUSH_ASSERT = 'assert'; |
404 | 528 |
405 final ExceptionHandler _exceptionHandler; | 529 final ExceptionHandler _exceptionHandler; |
406 final AstParser _astParser; | 530 final _AstParser _astParser; |
407 final Parser _parser; | 531 final Parser _parser; |
408 final ScopeDigestTTL _ttl; | 532 final ScopeDigestTTL _ttl; |
409 final ExpressionVisitor visitor = new ExpressionVisitor(); // TODO(misko): del
ete me | 533 final VmTurnZone _zone; |
410 final NgZone _zone; | |
411 | 534 |
412 _FunctionChain _runAsyncHead, _runAsyncTail; | 535 _FunctionChain _runAsyncHead, _runAsyncTail; |
413 _FunctionChain _domWriteHead, _domWriteTail; | 536 _FunctionChain _domWriteHead, _domWriteTail; |
414 _FunctionChain _domReadHead, _domReadTail; | 537 _FunctionChain _domReadHead, _domReadTail; |
415 | 538 |
416 final ScopeStats _scopeStats; | 539 final ScopeStats _scopeStats; |
417 | 540 |
418 String _state; | 541 String _state; |
419 | 542 |
420 RootScope(Object context, this._astParser, this._parser, | 543 /** |
421 GetterCache cacheGetter, FilterMap filterMap, | 544 * |
422 this._exceptionHandler, this._ttl, this._zone, | 545 * While processing data bindings, Angular passes through multiple states. Whe
n testing or |
423 this._scopeStats) | 546 * debugging, it can be useful to access the current `state`, which is one of
the following: |
424 : super(context, null, null, | 547 * |
425 new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), con
text), | 548 * * null |
426 new RootWatchGroup(new DirtyCheckingChangeDetector(cacheGetter), con
text)) | 549 * * apply |
| 550 * * digest |
| 551 * * flush |
| 552 * * assert |
| 553 * |
| 554 * ##null |
| 555 * |
| 556 * Angular is not currently processing changes |
| 557 * |
| 558 * ##apply |
| 559 * |
| 560 * The apply state begins by executing the optional expression within the cont
ext of |
| 561 * angular change detection mechanism. Any exceptions are delegated to [Except
ionHandler]. At the |
| 562 * end of apply state RootScope enters the digest followed by flush phase (opt
ionally if asserts |
| 563 * enabled run assert phase.) |
| 564 * |
| 565 * ##digest |
| 566 * |
| 567 * The apply state begins by processing the async queue, |
| 568 * followed by change detection |
| 569 * on non-DOM listeners. Any changes detected are process using the reaction f
unction. The digest |
| 570 * phase is repeated as long as at least one change has been detected. By defa
ult, after 5 |
| 571 * iterations the model is considered unstable and angular exists with an exce
ption. (See |
| 572 * ScopeDigestTTL) |
| 573 * |
| 574 * ##flush |
| 575 * |
| 576 * The flush phase consists of these steps: |
| 577 * |
| 578 * 1. processing the DOM write queue |
| 579 * 2. change detection on DOM only updates (these are reaction functions which
must |
| 580 * not change the model state and hence don't need stabilization as in dige
st phase). |
| 581 * 3. processing the DOM read queue |
| 582 * 4. repeat steps 1 and 3 (not 2) until queues are empty |
| 583 * |
| 584 * ##assert |
| 585 * |
| 586 * Optionally if Dart assert is on, verify that flush reaction functions did n
ot make any changes |
| 587 * to model and throw error if changes detected. |
| 588 * |
| 589 */ |
| 590 String get state => _state; |
| 591 |
| 592 RootScope(Object context, Parser parser, FieldGetterFactory fieldGetterFactory
, |
| 593 FormatterMap formatters, this._exceptionHandler, this._ttl, this._zo
ne, |
| 594 ScopeStats _scopeStats, ClosureMap closureMap) |
| 595 : _scopeStats = _scopeStats, |
| 596 _parser = parser, |
| 597 _astParser = new _AstParser(parser, closureMap), |
| 598 super(context, null, null, |
| 599 new RootWatchGroup(fieldGetterFactory, |
| 600 new DirtyCheckingChangeDetector(fieldGetterFactory), context), |
| 601 new RootWatchGroup(fieldGetterFactory, |
| 602 new DirtyCheckingChangeDetector(fieldGetterFactory), context), |
| 603 '', |
| 604 _scopeStats) |
427 { | 605 { |
428 _zone.onTurnDone = apply; | 606 _zone.onTurnDone = apply; |
429 _zone.onError = (e, s, ls) => _exceptionHandler(e, s); | 607 _zone.onError = (e, s, ls) => _exceptionHandler(e, s); |
430 } | 608 } |
431 | 609 |
432 RootScope get rootScope => this; | 610 RootScope get rootScope => this; |
433 bool get isAttached => true; | 611 bool get isAttached => true; |
434 | 612 |
| 613 /** |
| 614 * Propagates changes between different parts of the application model. Normall
y called by |
| 615 * [VMTurnZone] right before DOM rendering to initiate data binding. May also b
e called directly |
| 616 * for unit testing. |
| 617 * |
| 618 * Before each iteration of change detection, [digest] first processes the asyn
c queue. Any |
| 619 * work scheduled on the queue is executed before change detection. Since work
scheduled on |
| 620 * the queue may generate more async calls, [digest] must process the queue mul
tiple times before |
| 621 * it completes. The async queue must be empty before the model is considered s
table. |
| 622 * |
| 623 * Next, [digest] collects the changes that have occurred in the model. For eac
h change, |
| 624 * [digest] calls the associated [ReactionFn]. Since a [ReactionFn] may further
change the model, |
| 625 * [digest] processes changes multiple times until no more changes are detected
. |
| 626 * |
| 627 * If the model does not stabilize within 5 iterations, an exception is thrown.
See |
| 628 * [ScopeDigestTTL]. |
| 629 */ |
435 void digest() { | 630 void digest() { |
436 _transitionState(null, STATE_DIGEST); | 631 _transitionState(null, STATE_DIGEST); |
437 try { | 632 try { |
438 var rootWatchGroup = (_readWriteGroup as RootWatchGroup); | 633 var rootWatchGroup = _readWriteGroup as RootWatchGroup; |
439 | 634 |
440 int digestTTL = _ttl.ttl; | 635 int digestTTL = _ttl.ttl; |
441 const int LOG_COUNT = 3; | 636 const int LOG_COUNT = 3; |
442 List log; | 637 List log; |
443 List digestLog; | 638 List digestLog; |
444 var count; | 639 var count; |
445 ChangeLog changeLog; | 640 ChangeLog changeLog; |
446 _scopeStats.digestStart(); | 641 _scopeStats.digestStart(); |
447 do { | 642 do { |
448 while(_runAsyncHead != null) { | 643 while (_runAsyncHead != null) { |
449 try { | 644 try { |
450 _runAsyncHead.fn(); | 645 _runAsyncHead.fn(); |
451 } catch (e, s) { | 646 } catch (e, s) { |
452 _exceptionHandler(e, s); | 647 _exceptionHandler(e, s); |
453 } | 648 } |
454 _runAsyncHead = _runAsyncHead._next; | 649 _runAsyncHead = _runAsyncHead._next; |
455 } | 650 } |
| 651 _runAsyncTail = null; |
456 | 652 |
457 digestTTL--; | 653 digestTTL--; |
458 count = rootWatchGroup.detectChanges( | 654 count = rootWatchGroup.detectChanges( |
459 exceptionHandler: _exceptionHandler, | 655 exceptionHandler: _exceptionHandler, |
460 changeLog: changeLog, | 656 changeLog: changeLog, |
461 fieldStopwatch: _scopeStats.digestFieldStopwatch, | 657 fieldStopwatch: _scopeStats.fieldStopwatch, |
462 evalStopwatch: _scopeStats.digestEvalStopwatch, | 658 evalStopwatch: _scopeStats.evalStopwatch, |
463 processStopwatch: _scopeStats.digestProcessStopwatch); | 659 processStopwatch: _scopeStats.processStopwatch); |
464 | 660 |
465 if (digestTTL <= LOG_COUNT) { | 661 if (digestTTL <= LOG_COUNT) { |
466 if (changeLog == null) { | 662 if (changeLog == null) { |
467 log = []; | 663 log = []; |
468 digestLog = []; | 664 digestLog = []; |
469 changeLog = (e, c, p) => digestLog.add('$e: $c <= $p'); | 665 changeLog = (e, c, p) => digestLog.add('$e: $c <= $p'); |
470 } else { | 666 } else { |
471 log.add(digestLog.join(', ')); | 667 log.add(digestLog.join(', ')); |
472 digestLog.clear(); | 668 digestLog.clear(); |
473 } | 669 } |
474 } | 670 } |
475 if (digestTTL == 0) { | 671 if (digestTTL == 0) { |
476 throw 'Model did not stabilize in ${_ttl.ttl} digests. ' | 672 throw 'Model did not stabilize in ${_ttl.ttl} digests. ' |
477 'Last $LOG_COUNT iterations:\n${log.join('\n')}'; | 673 'Last $LOG_COUNT iterations:\n${log.join('\n')}'; |
478 } | 674 } |
479 _scopeStats.digestLoop(count); | 675 _scopeStats.digestLoop(count); |
480 } while (count > 0); | 676 } while (count > 0); |
481 } finally { | 677 } finally { |
482 _scopeStats.digestEnd(); | 678 _scopeStats.digestEnd(); |
483 _transitionState(STATE_DIGEST, null); | 679 _transitionState(STATE_DIGEST, null); |
484 } | 680 } |
485 } | 681 } |
486 | 682 |
487 void flush() { | 683 void flush() { |
| 684 _stats.flushStart(); |
488 _transitionState(null, STATE_FLUSH); | 685 _transitionState(null, STATE_FLUSH); |
489 var observeGroup = this._readOnlyGroup as RootWatchGroup; | 686 RootWatchGroup readOnlyGroup = this._readOnlyGroup as RootWatchGroup; |
490 bool runObservers = true; | 687 bool runObservers = true; |
491 try { | 688 try { |
492 do { | 689 do { |
493 while(_domWriteHead != null) { | 690 if (_domWriteHead != null) _stats.domWriteStart(); |
| 691 while (_domWriteHead != null) { |
494 try { | 692 try { |
495 _domWriteHead.fn(); | 693 _domWriteHead.fn(); |
496 } catch (e, s) { | 694 } catch (e, s) { |
497 _exceptionHandler(e, s); | 695 _exceptionHandler(e, s); |
498 } | 696 } |
499 _domWriteHead = _domWriteHead._next; | 697 _domWriteHead = _domWriteHead._next; |
| 698 if (_domWriteHead == null) _stats.domWriteEnd(); |
500 } | 699 } |
| 700 _domWriteTail = null; |
501 if (runObservers) { | 701 if (runObservers) { |
502 runObservers = false; | 702 runObservers = false; |
503 observeGroup.detectChanges(exceptionHandler:_exceptionHandler); | 703 readOnlyGroup.detectChanges(exceptionHandler:_exceptionHandler, |
| 704 fieldStopwatch: _scopeStats.fieldStopwatch, |
| 705 evalStopwatch: _scopeStats.evalStopwatch, |
| 706 processStopwatch: _scopeStats.processStopwatch); |
504 } | 707 } |
505 while(_domReadHead != null) { | 708 if (_domReadHead != null) _stats.domReadStart(); |
| 709 while (_domReadHead != null) { |
506 try { | 710 try { |
507 _domReadHead.fn(); | 711 _domReadHead.fn(); |
508 } catch (e, s) { | 712 } catch (e, s) { |
509 _exceptionHandler(e, s); | 713 _exceptionHandler(e, s); |
510 } | 714 } |
511 _domReadHead = _domReadHead._next; | 715 _domReadHead = _domReadHead._next; |
| 716 if (_domReadHead == null) _stats.domReadEnd(); |
512 } | 717 } |
| 718 _domReadTail = null; |
513 } while (_domWriteHead != null || _domReadHead != null); | 719 } while (_domWriteHead != null || _domReadHead != null); |
| 720 _stats.flushEnd(); |
514 assert((() { | 721 assert((() { |
515 var watchLog = []; | 722 _stats.flushAssertStart(); |
516 var observeLog = []; | 723 var digestLog = []; |
| 724 var flushLog = []; |
517 (_readWriteGroup as RootWatchGroup).detectChanges( | 725 (_readWriteGroup as RootWatchGroup).detectChanges( |
518 changeLog: (s, c, p) => watchLog.add('$s: $c <= $p')); | 726 changeLog: (s, c, p) => digestLog.add('$s: $c <= $p'), |
519 (observeGroup as RootWatchGroup).detectChanges( | 727 fieldStopwatch: _scopeStats.fieldStopwatch, |
520 changeLog: (s, c, p) => watchLog.add('$s: $c <= $p')); | 728 evalStopwatch: _scopeStats.evalStopwatch, |
521 if (watchLog.isNotEmpty || observeLog.isNotEmpty) { | 729 processStopwatch: _scopeStats.processStopwatch); |
| 730 (_readOnlyGroup as RootWatchGroup).detectChanges( |
| 731 changeLog: (s, c, p) => flushLog.add('$s: $c <= $p'), |
| 732 fieldStopwatch: _scopeStats.fieldStopwatch, |
| 733 evalStopwatch: _scopeStats.evalStopwatch, |
| 734 processStopwatch: _scopeStats.processStopwatch); |
| 735 if (digestLog.isNotEmpty || flushLog.isNotEmpty) { |
522 throw 'Observer reaction functions should not change model. \n' | 736 throw 'Observer reaction functions should not change model. \n' |
523 'These watch changes were detected: ${watchLog.join('; ')}\n' | 737 'These watch changes were detected: ${digestLog.join('; ')}\n' |
524 'These observe changes were detected: ${observeLog.join('; ')}'; | 738 'These observe changes were detected: ${flushLog.join('; ')}'; |
525 } | 739 } |
| 740 _stats.flushAssertEnd(); |
526 return true; | 741 return true; |
527 })()); | 742 })()); |
528 } finally { | 743 } finally { |
| 744 _stats.cycleEnd(); |
529 _transitionState(STATE_FLUSH, null); | 745 _transitionState(STATE_FLUSH, null); |
530 } | 746 } |
531 | |
532 } | 747 } |
533 | 748 |
534 // QUEUES | 749 // QUEUES |
535 void runAsync(fn()) { | 750 void runAsync(fn()) { |
536 var chain = new _FunctionChain(fn); | 751 var chain = new _FunctionChain(fn); |
537 if (_runAsyncHead == null) { | 752 if (_runAsyncHead == null) { |
538 _runAsyncHead = _runAsyncTail = chain; | 753 _runAsyncHead = _runAsyncTail = chain; |
539 } else { | 754 } else { |
540 _runAsyncTail = _runAsyncTail._next = chain; | 755 _runAsyncTail = _runAsyncTail._next = chain; |
541 } | 756 } |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
600 final Map<String, int> _typeCounts; | 815 final Map<String, int> _typeCounts; |
601 | 816 |
602 _Streams(this._scope, this._exceptionHandler, _Streams inheritStreams) | 817 _Streams(this._scope, this._exceptionHandler, _Streams inheritStreams) |
603 : _typeCounts = inheritStreams == null | 818 : _typeCounts = inheritStreams == null |
604 ? <String, int>{} | 819 ? <String, int>{} |
605 : new Map.from(inheritStreams._typeCounts); | 820 : new Map.from(inheritStreams._typeCounts); |
606 | 821 |
607 static ScopeEvent emit(Scope scope, String name, data) { | 822 static ScopeEvent emit(Scope scope, String name, data) { |
608 var event = new ScopeEvent(name, scope, data); | 823 var event = new ScopeEvent(name, scope, data); |
609 var scopeCursor = scope; | 824 var scopeCursor = scope; |
610 while(scopeCursor != null) { | 825 while (scopeCursor != null) { |
611 if (scopeCursor._streams != null && | 826 if (scopeCursor._streams != null && |
612 scopeCursor._streams._scope == scopeCursor) { | 827 scopeCursor._streams._scope == scopeCursor) { |
613 ScopeStream stream = scopeCursor._streams._streams[name]; | 828 ScopeStream stream = scopeCursor._streams._streams[name]; |
614 if (stream != null) { | 829 if (stream != null) { |
615 event._currentScope = scopeCursor; | 830 event._currentScope = scopeCursor; |
616 stream._fire(event); | 831 stream._fire(event); |
617 if (event.propagationStopped) return event; | 832 if (event.propagationStopped) return event; |
618 } | 833 } |
619 } | 834 } |
620 scopeCursor = scopeCursor._parentScope; | 835 scopeCursor = scopeCursor._parentScope; |
(...skipping 10 matching lines...) Expand all Loading... |
631 scope = queue.removeFirst(); | 846 scope = queue.removeFirst(); |
632 scopeStreams = scope._streams; | 847 scopeStreams = scope._streams; |
633 assert(scopeStreams._scope == scope); | 848 assert(scopeStreams._scope == scope); |
634 if (scopeStreams._streams.containsKey(name)) { | 849 if (scopeStreams._streams.containsKey(name)) { |
635 var stream = scopeStreams._streams[name]; | 850 var stream = scopeStreams._streams[name]; |
636 event._currentScope = scope; | 851 event._currentScope = scope; |
637 stream._fire(event); | 852 stream._fire(event); |
638 } | 853 } |
639 // Reverse traversal so that when the queue is read it is correct order. | 854 // Reverse traversal so that when the queue is read it is correct order. |
640 var childScope = scope._childTail; | 855 var childScope = scope._childTail; |
641 while(childScope != null) { | 856 while (childScope != null) { |
642 scopeStreams = childScope._streams; | 857 scopeStreams = childScope._streams; |
643 if (scopeStreams != null && | 858 if (scopeStreams != null && |
644 scopeStreams._typeCounts.containsKey(name)) { | 859 scopeStreams._typeCounts.containsKey(name)) { |
645 queue.addFirst(scopeStreams._scope); | 860 queue.addFirst(scopeStreams._scope); |
646 } | 861 } |
647 childScope = childScope._prev; | 862 childScope = childScope._prev; |
648 } | 863 } |
649 } | 864 } |
650 } | 865 } |
651 return event; | 866 return event; |
652 } | 867 } |
653 | 868 |
654 static ScopeStream on(Scope scope, | 869 static async.Stream<ScopeEvent> on(Scope scope, |
655 ExceptionHandler _exceptionHandler, | 870 ExceptionHandler _exceptionHandler, |
656 String name) { | 871 String name) { |
657 _forceNewScopeStream(scope, _exceptionHandler); | 872 _forceNewScopeStream(scope, _exceptionHandler); |
658 return scope._streams._get(scope, name); | 873 return scope._streams._get(scope, name); |
659 } | 874 } |
660 | 875 |
661 static void _forceNewScopeStream(scope, _exceptionHandler) { | 876 static void _forceNewScopeStream(scope, _exceptionHandler) { |
662 _Streams streams = scope._streams; | 877 _Streams streams = scope._streams; |
663 Scope scopeCursor = scope; | 878 Scope scopeCursor = scope; |
664 bool splitMode = false; | 879 bool splitMode = false; |
665 while(scopeCursor != null) { | 880 while (scopeCursor != null) { |
666 _Streams cursorStreams = scopeCursor._streams; | 881 _Streams cursorStreams = scopeCursor._streams; |
667 var hasStream = cursorStreams != null; | 882 var hasStream = cursorStreams != null; |
668 var hasOwnStream = hasStream && cursorStreams._scope == scopeCursor; | 883 var hasOwnStream = hasStream && cursorStreams._scope == scopeCursor; |
669 if (hasOwnStream) return; | 884 if (hasOwnStream) return; |
670 | 885 |
671 if (!splitMode && (streams == null || (hasStream && !hasOwnStream))) { | 886 if (!splitMode && (streams == null || (hasStream && !hasOwnStream))) { |
672 if (hasStream && !hasOwnStream) { | 887 if (hasStream && !hasOwnStream) { |
673 splitMode = true; | 888 splitMode = true; |
674 } | 889 } |
675 streams = new _Streams(scopeCursor, _exceptionHandler, cursorStreams); | 890 streams = new _Streams(scopeCursor, _exceptionHandler, cursorStreams); |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
727 scope = scope._parentScope; | 942 scope = scope._parentScope; |
728 } | 943 } |
729 } | 944 } |
730 } | 945 } |
731 | 946 |
732 class ScopeStream extends async.Stream<ScopeEvent> { | 947 class ScopeStream extends async.Stream<ScopeEvent> { |
733 final ExceptionHandler _exceptionHandler; | 948 final ExceptionHandler _exceptionHandler; |
734 final _Streams _streams; | 949 final _Streams _streams; |
735 final String _name; | 950 final String _name; |
736 final subscriptions = <ScopeStreamSubscription>[]; | 951 final subscriptions = <ScopeStreamSubscription>[]; |
| 952 final List<Function> _work = <Function>[]; |
| 953 bool _firing = false; |
| 954 |
737 | 955 |
738 ScopeStream(this._streams, this._exceptionHandler, this._name); | 956 ScopeStream(this._streams, this._exceptionHandler, this._name); |
739 | 957 |
740 ScopeStreamSubscription listen(void onData(ScopeEvent event), | 958 ScopeStreamSubscription listen(void onData(ScopeEvent event), |
741 { Function onError, | 959 { Function onError, |
742 void onDone(), | 960 void onDone(), |
743 bool cancelOnError }) { | 961 bool cancelOnError }) { |
744 if (subscriptions.isEmpty) _streams._addCount(_name, 1); | |
745 var subscription = new ScopeStreamSubscription(this, onData); | 962 var subscription = new ScopeStreamSubscription(this, onData); |
746 subscriptions.add(subscription); | 963 _concurrentSafeWork(() { |
| 964 if (subscriptions.isEmpty) _streams._addCount(_name, 1); |
| 965 subscriptions.add(subscription); |
| 966 }); |
747 return subscription; | 967 return subscription; |
748 } | 968 } |
749 | 969 |
| 970 void _concurrentSafeWork([fn]) { |
| 971 if (fn != null) _work.add(fn); |
| 972 while(!_firing && _work.isNotEmpty) { |
| 973 _work.removeLast()(); |
| 974 } |
| 975 } |
| 976 |
750 void _fire(ScopeEvent event) { | 977 void _fire(ScopeEvent event) { |
751 for (ScopeStreamSubscription subscription in subscriptions) { | 978 _firing = true; |
752 try { | 979 try { |
753 subscription._onData(event); | 980 for (ScopeStreamSubscription subscription in subscriptions) { |
754 } catch (e, s) { | 981 try { |
755 _exceptionHandler(e, s); | 982 subscription._onData(event); |
| 983 } catch (e, s) { |
| 984 _exceptionHandler(e, s); |
| 985 } |
756 } | 986 } |
| 987 } finally { |
| 988 _firing = false; |
| 989 _concurrentSafeWork(); |
757 } | 990 } |
758 } | 991 } |
759 | 992 |
760 void _remove(ScopeStreamSubscription subscription) { | 993 void _remove(ScopeStreamSubscription subscription) { |
761 assert(subscription._scopeStream == this); | 994 _concurrentSafeWork(() { |
762 if (subscriptions.remove(subscription)) { | 995 assert(subscription._scopeStream == this); |
763 if (subscriptions.isEmpty) _streams._addCount(_name, -1); | 996 if (subscriptions.remove(subscription)) { |
764 } else { | 997 if (subscriptions.isEmpty) _streams._addCount(_name, -1); |
765 throw new StateError('AlreadyCanceled'); | 998 } else { |
766 } | 999 throw new StateError('AlreadyCanceled'); |
| 1000 } |
| 1001 }); |
767 } | 1002 } |
768 } | 1003 } |
769 | 1004 |
770 class ScopeStreamSubscription implements async.StreamSubscription<ScopeEvent> { | 1005 class ScopeStreamSubscription implements async.StreamSubscription<ScopeEvent> { |
771 final ScopeStream _scopeStream; | 1006 final ScopeStream _scopeStream; |
772 final Function _onData; | 1007 final Function _onData; |
773 ScopeStreamSubscription(this._scopeStream, this._onData); | 1008 ScopeStreamSubscription(this._scopeStream, this._onData); |
774 | 1009 |
775 // TODO(vbe) should return a Future | 1010 async.Future cancel() { |
776 cancel() => _scopeStream._remove(this); | 1011 _scopeStream._remove(this); |
| 1012 return null; |
| 1013 } |
777 | 1014 |
778 void onData(void handleData(ScopeEvent data)) => NOT_IMPLEMENTED(); | 1015 void onData(void handleData(ScopeEvent data)) => _NOT_IMPLEMENTED(); |
779 void onError(Function handleError) => NOT_IMPLEMENTED(); | 1016 void onError(Function handleError) => _NOT_IMPLEMENTED(); |
780 void onDone(void handleDone()) => NOT_IMPLEMENTED(); | 1017 void onDone(void handleDone()) => _NOT_IMPLEMENTED(); |
781 void pause([async.Future resumeSignal]) => NOT_IMPLEMENTED(); | 1018 void pause([async.Future resumeSignal]) => _NOT_IMPLEMENTED(); |
782 void resume() => NOT_IMPLEMENTED(); | 1019 void resume() => _NOT_IMPLEMENTED(); |
783 bool get isPaused => NOT_IMPLEMENTED(); | 1020 bool get isPaused => _NOT_IMPLEMENTED(); |
784 async.Future asFuture([var futureValue]) => NOT_IMPLEMENTED(); | 1021 async.Future asFuture([var futureValue]) => _NOT_IMPLEMENTED(); |
785 } | 1022 } |
786 | 1023 |
| 1024 _NOT_IMPLEMENTED() { |
| 1025 throw new StateError('Not Implemented'); |
| 1026 } |
| 1027 |
| 1028 |
787 class _FunctionChain { | 1029 class _FunctionChain { |
788 final Function fn; | 1030 final Function fn; |
789 _FunctionChain _next; | 1031 _FunctionChain _next; |
790 | 1032 |
791 _FunctionChain(fn()) | 1033 _FunctionChain(fn()): fn = fn { |
792 : fn = fn | |
793 { | |
794 assert(fn != null); | 1034 assert(fn != null); |
795 } | 1035 } |
796 } | 1036 } |
797 | 1037 |
798 class AstParser { | 1038 class _AstParser { |
799 final Parser _parser; | 1039 final Parser _parser; |
800 int _id = 0; | 1040 int _id = 0; |
801 ExpressionVisitor _visitor = new ExpressionVisitor(); | 1041 final ExpressionVisitor _visitor; |
802 | 1042 |
803 AstParser(this._parser); | 1043 _AstParser(this._parser, ClosureMap closureMap) |
| 1044 : _visitor = new ExpressionVisitor(closureMap); |
804 | 1045 |
805 AST call(String exp, { FilterMap filters, | 1046 AST call(String input, {FormatterMap formatters, |
806 bool collection:false, | 1047 bool collection: false, |
807 Object context:null }) { | 1048 Object context: null }) { |
808 _visitor.filters = filters; | 1049 _visitor.formatters = formatters; |
809 AST contextRef = _visitor.contextRef; | 1050 AST contextRef = _visitor.contextRef; |
810 try { | 1051 try { |
811 if (context != null) { | 1052 if (context != null) { |
812 _visitor.contextRef = new ConstantAST(context, '#${_id++}'); | 1053 _visitor.contextRef = new ConstantAST(context, '#${_id++}'); |
813 } | 1054 } |
814 var ast = _parser(exp); | 1055 var exp = _parser(input); |
815 return collection ? _visitor.visitCollection(ast) : _visitor.visit(ast); | 1056 return collection ? _visitor.visitCollection(exp) : _visitor.visit(exp); |
816 } finally { | 1057 } finally { |
817 _visitor.contextRef = contextRef; | 1058 _visitor.contextRef = contextRef; |
818 _visitor.filters = null; | 1059 _visitor.formatters = null; |
819 } | 1060 } |
820 } | 1061 } |
821 } | 1062 } |
822 | 1063 |
823 class ExpressionVisitor implements Visitor { | 1064 class ExpressionVisitor implements Visitor { |
824 static final ContextReferenceAST scopeContextRef = new ContextReferenceAST(); | 1065 static final ContextReferenceAST scopeContextRef = new ContextReferenceAST(); |
| 1066 final ClosureMap _closureMap; |
825 AST contextRef = scopeContextRef; | 1067 AST contextRef = scopeContextRef; |
826 | 1068 |
| 1069 |
| 1070 ExpressionVisitor(this._closureMap); |
| 1071 |
827 AST ast; | 1072 AST ast; |
828 FilterMap filters; | 1073 FormatterMap formatters; |
829 | 1074 |
830 AST visit(Expression exp) { | 1075 AST visit(Expression exp) { |
831 exp.accept(this); | 1076 exp.accept(this); |
832 assert(this.ast != null); | 1077 assert(ast != null); |
833 try { | 1078 try { |
834 return ast; | 1079 return ast; |
835 } finally { | 1080 } finally { |
836 ast = null; | 1081 ast = null; |
837 } | 1082 } |
838 } | 1083 } |
839 | 1084 |
840 AST visitCollection(Expression exp) => new CollectionAST(visit(exp)); | 1085 AST visitCollection(Expression exp) => new CollectionAST(visit(exp)); |
841 AST _mapToAst(Expression expression) => visit(expression); | 1086 AST _mapToAst(Expression expression) => visit(expression); |
842 | 1087 |
843 List<AST> _toAst(List<Expression> expressions) => | 1088 List<AST> _toAst(List<Expression> expressions) => |
844 expressions.map(_mapToAst).toList(); | 1089 expressions.map(_mapToAst).toList(); |
845 | 1090 |
| 1091 Map<Symbol, AST> _toAstMap(Map<String, Expression> expressions) { |
| 1092 if (expressions.isEmpty) return const {}; |
| 1093 Map<Symbol, AST> result = new Map<Symbol, AST>(); |
| 1094 expressions.forEach((String name, Expression expression) { |
| 1095 result[_closureMap.lookupSymbol(name)] = _mapToAst(expression); |
| 1096 }); |
| 1097 return result; |
| 1098 } |
| 1099 |
846 void visitCallScope(CallScope exp) { | 1100 void visitCallScope(CallScope exp) { |
847 ast = new MethodAST(contextRef, exp.name, _toAst(exp.arguments)); | 1101 List<AST> positionals = _toAst(exp.arguments.positionals); |
| 1102 Map<Symbol, AST> named = _toAstMap(exp.arguments.named); |
| 1103 ast = new MethodAST(contextRef, exp.name, positionals, named); |
848 } | 1104 } |
849 void visitCallMember(CallMember exp) { | 1105 void visitCallMember(CallMember exp) { |
850 ast = new MethodAST(visit(exp.object), exp.name, _toAst(exp.arguments)); | 1106 List<AST> positionals = _toAst(exp.arguments.positionals); |
| 1107 Map<Symbol, AST> named = _toAstMap(exp.arguments.named); |
| 1108 ast = new MethodAST(visit(exp.object), exp.name, positionals, named); |
851 } | 1109 } |
852 visitAccessScope(AccessScope exp) { | 1110 visitAccessScope(AccessScope exp) { |
853 ast = new FieldReadAST(contextRef, exp.name); | 1111 ast = new FieldReadAST(contextRef, exp.name); |
854 } | 1112 } |
855 visitAccessMember(AccessMember exp) { | 1113 visitAccessMember(AccessMember exp) { |
856 ast = new FieldReadAST(visit(exp.object), exp.name); | 1114 ast = new FieldReadAST(visit(exp.object), exp.name); |
857 } | 1115 } |
858 visitBinary(Binary exp) { | 1116 visitBinary(Binary exp) { |
859 ast = new PureFunctionAST(exp.operation, | 1117 ast = new PureFunctionAST(exp.operation, |
860 _operationToFunction(exp.operation), | 1118 _operationToFunction(exp.operation), |
861 [visit(exp.left), visit(exp.right)]); | 1119 [visit(exp.left), visit(exp.right)]); |
862 } | 1120 } |
863 void visitPrefix(Prefix exp) { | 1121 void visitPrefix(Prefix exp) { |
864 ast = new PureFunctionAST(exp.operation, | 1122 ast = new PureFunctionAST(exp.operation, |
865 _operationToFunction(exp.operation), | 1123 _operationToFunction(exp.operation), |
866 [visit(exp.expression)]); | 1124 [visit(exp.expression)]); |
867 } | 1125 } |
868 void visitConditional(Conditional exp) { | 1126 void visitConditional(Conditional exp) { |
869 ast = new PureFunctionAST('?:', _operation_ternary, | 1127 ast = new PureFunctionAST('?:', _operation_ternary, |
870 [visit(exp.condition), visit(exp.yes), | 1128 [visit(exp.condition), visit(exp.yes), |
871 visit(exp.no)]); | 1129 visit(exp.no)]); |
872 } | 1130 } |
873 void visitAccessKeyed(AccessKeyed exp) { | 1131 void visitAccessKeyed(AccessKeyed exp) { |
874 ast = new PureFunctionAST('[]', _operation_bracket, | 1132 ast = new ClosureAST('[]', _operation_bracket, |
875 [visit(exp.object), visit(exp.key)]); | 1133 [visit(exp.object), visit(exp.key)]); |
876 } | 1134 } |
877 void visitLiteralPrimitive(LiteralPrimitive exp) { | 1135 void visitLiteralPrimitive(LiteralPrimitive exp) { |
878 ast = new ConstantAST(exp.value); | 1136 ast = new ConstantAST(exp.value); |
879 } | 1137 } |
880 void visitLiteralString(LiteralString exp) { | 1138 void visitLiteralString(LiteralString exp) { |
881 ast = new ConstantAST(exp.value); | 1139 ast = new ConstantAST(exp.value); |
882 } | 1140 } |
883 void visitLiteralArray(LiteralArray exp) { | 1141 void visitLiteralArray(LiteralArray exp) { |
884 List<AST> items = _toAst(exp.elements); | 1142 List<AST> items = _toAst(exp.elements); |
885 ast = new PureFunctionAST('[${items.join(', ')}]', new ArrayFn(), items); | 1143 ast = new PureFunctionAST('[${items.join(', ')}]', new ArrayFn(), items); |
886 } | 1144 } |
887 | 1145 |
888 void visitLiteralObject(LiteralObject exp) { | 1146 void visitLiteralObject(LiteralObject exp) { |
889 List<String> keys = exp.keys; | 1147 List<String> keys = exp.keys; |
890 List<AST> values = _toAst(exp.values); | 1148 List<AST> values = _toAst(exp.values); |
891 assert(keys.length == values.length); | 1149 assert(keys.length == values.length); |
892 var kv = <String>[]; | 1150 var kv = <String>[]; |
893 for (var i = 0; i < keys.length; i++) { | 1151 for (var i = 0; i < keys.length; i++) { |
894 kv.add('${keys[i]}: ${values[i]}'); | 1152 kv.add('${keys[i]}: ${values[i]}'); |
895 } | 1153 } |
896 ast = new PureFunctionAST('{${kv.join(', ')}}', new MapFn(keys), values); | 1154 ast = new PureFunctionAST('{${kv.join(', ')}}', new MapFn(keys), values); |
897 } | 1155 } |
898 | 1156 |
899 void visitFilter(Filter exp) { | 1157 void visitFilter(Filter exp) { |
900 Function filterFunction = filters(exp.name); | 1158 if (formatters == null) { |
| 1159 throw new Exception("No formatters have been registered"); |
| 1160 } |
| 1161 Function filterFunction = formatters(exp.name); |
901 List<AST> args = [visitCollection(exp.expression)]; | 1162 List<AST> args = [visitCollection(exp.expression)]; |
902 args.addAll(_toAst(exp.arguments).map((ast) => new CollectionAST(ast))); | 1163 args.addAll(_toAst(exp.arguments).map((ast) => new CollectionAST(ast))); |
903 ast = new PureFunctionAST('|${exp.name}', | 1164 ast = new PureFunctionAST('|${exp.name}', |
904 new _FilterWrapper(filterFunction, args.length), args); | 1165 new _FilterWrapper(filterFunction, args.length), args); |
905 } | 1166 } |
906 | 1167 |
907 // TODO(misko): this is a corner case. Choosing not to implement for now. | 1168 // TODO(misko): this is a corner case. Choosing not to implement for now. |
908 void visitCallFunction(CallFunction exp) { | 1169 void visitCallFunction(CallFunction exp) { |
909 _notSupported("function's returing functions"); | 1170 _notSupported("function's returing functions"); |
910 } | 1171 } |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
944 case '^' : return _operation_power; | 1205 case '^' : return _operation_power; |
945 case '&' : return _operation_bitwise_and; | 1206 case '&' : return _operation_bitwise_and; |
946 case '&&' : return _operation_logical_and; | 1207 case '&&' : return _operation_logical_and; |
947 case '||' : return _operation_logical_or; | 1208 case '||' : return _operation_logical_or; |
948 default: throw new StateError(operation); | 1209 default: throw new StateError(operation); |
949 } | 1210 } |
950 } | 1211 } |
951 | 1212 |
952 _operation_negate(value) => !toBool(value); | 1213 _operation_negate(value) => !toBool(value); |
953 _operation_add(left, right) => autoConvertAdd(left, right); | 1214 _operation_add(left, right) => autoConvertAdd(left, right); |
954 _operation_subtract(left, right) => left - right; | 1215 _operation_subtract(left, right) => (left != null && right != null
) ? left - right : (left != null ? left : (right != null ? 0 - right : 0)); |
955 _operation_multiply(left, right) => left * right; | 1216 _operation_multiply(left, right) => (left == null || right == null
) ? null : left * right; |
956 _operation_divide(left, right) => left / right; | 1217 _operation_divide(left, right) => (left == null || right == null
) ? null : left / right; |
957 _operation_divide_int(left, right) => left ~/ right; | 1218 _operation_divide_int(left, right) => (left == null || right == null
) ? null : left ~/ right; |
958 _operation_remainder(left, right) => left % right; | 1219 _operation_remainder(left, right) => (left == null || right == null
) ? null : left % right; |
959 _operation_equals(left, right) => left == right; | 1220 _operation_equals(left, right) => left == right; |
960 _operation_not_equals(left, right) => left != right; | 1221 _operation_not_equals(left, right) => left != right; |
961 _operation_less_then(left, right) => left < right; | 1222 _operation_less_then(left, right) => (left == null || right == null
) ? null : left < right; |
962 _operation_greater_then(left, right) => (left == null || right == null
) ? false : left > right; | 1223 _operation_greater_then(left, right) => (left == null || right == null
) ? null : left > right; |
963 _operation_less_or_equals_then(left, right) => left <= right; | 1224 _operation_less_or_equals_then(left, right) => (left == null || right == null
) ? null : left <= right; |
964 _operation_greater_or_equals_then(left, right) => left >= right; | 1225 _operation_greater_or_equals_then(left, right) => (left == null || right == null
) ? null : left >= right; |
965 _operation_power(left, right) => left ^ right; | 1226 _operation_power(left, right) => (left == null || right == null
) ? null : left ^ right; |
966 _operation_bitwise_and(left, right) => left & right; | 1227 _operation_bitwise_and(left, right) => (left == null || right == null
) ? null : left & right; |
967 // TODO(misko): these should short circuit the evaluation. | 1228 // TODO(misko): these should short circuit the evaluation. |
968 _operation_logical_and(left, right) => toBool(left) && toBool(right); | 1229 _operation_logical_and(left, right) => toBool(left) && toBool(right); |
969 _operation_logical_or(left, right) => toBool(left) || toBool(right); | 1230 _operation_logical_or(left, right) => toBool(left) || toBool(right); |
970 | 1231 |
971 _operation_ternary(condition, yes, no) => toBool(condition) ? yes : no; | 1232 _operation_ternary(condition, yes, no) => toBool(condition) ? yes : no; |
972 _operation_bracket(obj, key) => obj == null ? null : obj[key]; | 1233 _operation_bracket(obj, key) => obj == null ? null : obj[key]; |
973 | 1234 |
974 class ArrayFn extends FunctionApply { | 1235 class ArrayFn extends FunctionApply { |
975 // TODO(misko): figure out why do we need to make a copy? | 1236 // TODO(misko): figure out why do we need to make a copy? |
976 apply(List args) => new List.from(args); | 1237 apply(List args) => new List.from(args); |
977 } | 1238 } |
978 | 1239 |
979 class MapFn extends FunctionApply { | 1240 class MapFn extends FunctionApply { |
980 final List<String> keys; | 1241 final List<String> keys; |
981 | 1242 |
982 MapFn(this.keys); | 1243 MapFn(this.keys); |
983 | 1244 |
984 apply(List values) { | 1245 Map apply(List values) { |
985 // TODO(misko): figure out why do we need to make a copy instead of reusing
instance? | 1246 // TODO(misko): figure out why do we need to make a copy instead of reusing
instance? |
986 assert(values.length == keys.length); | 1247 assert(values.length == keys.length); |
987 return new Map.fromIterables(keys, values); | 1248 return new Map.fromIterables(keys, values); |
988 } | 1249 } |
989 } | 1250 } |
990 | 1251 |
991 class _FilterWrapper extends FunctionApply { | 1252 class _FilterWrapper extends FunctionApply { |
992 final Function filterFn; | 1253 final Function filterFn; |
993 final List args; | 1254 final List args; |
994 final List<Watch> argsWatches; | 1255 final List<Watch> argsWatches; |
995 _FilterWrapper(this.filterFn, length): | 1256 _FilterWrapper(this.filterFn, length): |
996 args = new List(length), | 1257 args = new List(length), |
997 argsWatches = new List(length); | 1258 argsWatches = new List(length); |
998 | 1259 |
999 apply(List values) { | 1260 apply(List values) { |
1000 for (var i=0; i < values.length; i++) { | 1261 for (var i=0; i < values.length; i++) { |
1001 var value = values[i]; | 1262 var value = values[i]; |
1002 var lastValue = args[i]; | 1263 var lastValue = args[i]; |
1003 if (!identical(value, lastValue)) { | 1264 if (!identical(value, lastValue)) { |
1004 if (value is CollectionChangeRecord) { | 1265 if (value is CollectionChangeRecord) { |
1005 args[i] = (value as CollectionChangeRecord).iterable; | 1266 args[i] = (value as CollectionChangeRecord).iterable; |
| 1267 } else if (value is MapChangeRecord) { |
| 1268 args[i] = (value as MapChangeRecord).map; |
1006 } else { | 1269 } else { |
1007 args[i] = value; | 1270 args[i] = value; |
1008 } | 1271 } |
1009 } | 1272 } |
1010 } | 1273 } |
1011 var value = Function.apply(filterFn, args); | 1274 var value = Function.apply(filterFn, args); |
1012 if (value is Iterable) { | 1275 if (value is Iterable) { |
1013 // Since filters are pure we can guarantee that this well never change. | 1276 // Since formatters are pure we can guarantee that this well never change. |
1014 // By wrapping in UnmodifiableListView we can hint to the dirty checker | 1277 // By wrapping in UnmodifiableListView we can hint to the dirty checker |
1015 // and short circuit the iterator. | 1278 // and short circuit the iterator. |
1016 value = new UnmodifiableListView(value); | 1279 value = new UnmodifiableListView(value); |
1017 } | 1280 } |
1018 return value; | 1281 return value; |
1019 } | 1282 } |
1020 } | 1283 } |
OLD | NEW |