@@ -157,15 +157,12 @@ class Scope {
157
157
// such as # of watches, checks/1ms, field checks, function checks, etc
158
158
final WatchGroup _readWriteGroup;
159
159
final WatchGroup _readOnlyGroup;
160
- final int _depth;
161
- final int _index;
162
160
163
161
Scope _childHead, _childTail, _next, _prev;
164
162
_Streams _streams;
165
- int _nextChildIndex = 0 ;
166
163
167
- Scope (Object this .context, this .rootScope, this ._parentScope, this ._depth,
168
- this ._index, this . _readWriteGroup, this ._readOnlyGroup);
164
+ Scope (Object this .context, this .rootScope, this ._parentScope,
165
+ this ._readWriteGroup, this ._readOnlyGroup);
169
166
170
167
/**
171
168
* A [watch] sets up a watch in the [digest] phase of the [apply] cycle.
@@ -175,7 +172,7 @@ class Scope {
175
172
*/
176
173
Watch watch (expression, ReactionFn reactionFn,
177
174
{context, FilterMap filters, bool readOnly: false }) {
178
- assert (isAttached );
175
+ _assertInternalStateConsistency ( );
179
176
assert (expression != null );
180
177
AST ast;
181
178
Watch watch;
@@ -207,7 +204,7 @@ class Scope {
207
204
}
208
205
209
206
dynamic eval (expression, [Map locals]) {
210
- assert (isAttached );
207
+ _assertInternalStateConsistency ( );
211
208
assert (expression == null ||
212
209
expression is String ||
213
210
expression is Function );
@@ -239,22 +236,21 @@ class Scope {
239
236
}
240
237
241
238
ScopeEvent emit (String name, [data]) {
242
- assert (isAttached );
239
+ _assertInternalStateConsistency ( );
243
240
return _Streams .emit (this , name, data);
244
241
}
245
242
ScopeEvent broadcast (String name, [data]) {
246
- assert (isAttached );
243
+ _assertInternalStateConsistency ( );
247
244
return _Streams .broadcast (this , name, data);
248
245
}
249
246
ScopeStream on (String name) {
250
- assert (isAttached );
247
+ _assertInternalStateConsistency ( );
251
248
return _Streams .on (this , rootScope._exceptionHandler, name);
252
249
}
253
250
254
251
Scope createChild (Object childContext) {
255
- assert (isAttached );
252
+ _assertInternalStateConsistency ( );
256
253
var child = new Scope (childContext, rootScope, this ,
257
- _depth + 1 , _nextChildIndex++ ,
258
254
_readWriteGroup.newGroup (childContext),
259
255
_readOnlyGroup.newGroup (childContext));
260
256
var next = null ;
@@ -267,7 +263,7 @@ class Scope {
267
263
}
268
264
269
265
void destroy () {
270
- assert (isAttached );
266
+ _assertInternalStateConsistency ( );
271
267
broadcast (ScopeEvent .DESTROY );
272
268
_Streams .destroy (this );
273
269
@@ -289,9 +285,54 @@ class Scope {
289
285
_readWriteGroup.remove ();
290
286
_readOnlyGroup.remove ();
291
287
_parentScope = null ;
288
+ _assertInternalStateConsistency ();
289
+ }
290
+
291
+ _assertInternalStateConsistency () {
292
+ assert ((() {
293
+ rootScope._verifyStreams (null , '' , []);
294
+ return true ;
295
+ })());
296
+ }
297
+
298
+ Map <bool ,int > _verifyStreams (parentScope, prefix, log) {
299
+ assert (_parentScope == parentScope);
300
+ var counts = {};
301
+ var typeCounts = _streams == null ? {} : _streams._typeCounts;
302
+ log..add (prefix)..add (hashCode)..add (' ' )..add (typeCounts)..add ('\n ' );
303
+ if (_streams == null ) {
304
+ } else if (_streams._scope == this ) {
305
+ _streams._streams.keys.forEach ((k){
306
+ counts[k] = 1 + (counts.containsKey (k) ? counts[k] : 0 );
307
+ });
308
+ }
309
+ var childScope = _childHead;
310
+ while (childScope != null ) {
311
+ childScope._verifyStreams (this , ' $prefix ' , log).forEach ((k, v) {
312
+ counts[k] = v + (counts.containsKey (k) ? counts[k] : 0 );
313
+ });
314
+ childScope = childScope._next;
315
+ }
316
+ if (! _mapEqual (counts, typeCounts)) {
317
+ throw 'Streams actual: $counts != bookkeeping: $typeCounts \n '
318
+ 'Offending scope: [scope: ${this .hashCode }]\n '
319
+ '${log .join ('' )}' ;
320
+ }
321
+ return counts;
292
322
}
293
323
}
294
324
325
+ _mapEqual (Map a, Map b) {
326
+ if (a.length == b.length) {
327
+ var equal = true ;
328
+ a.forEach ((k, v) {
329
+ equal = equal && b.containsKey (k) && v == b[k];
330
+ });
331
+ return equal;
332
+ } else {
333
+ return false ;
334
+ }
335
+ }
295
336
296
337
class RootScope extends Scope {
297
338
static final STATE_APPLY = 'apply' ;
@@ -314,7 +355,7 @@ class RootScope extends Scope {
314
355
RootScope (Object context, this ._astParser, this ._parser,
315
356
GetterCache cacheGetter, FilterMap filterMap,
316
357
this ._exceptionHandler, this ._ttl, this ._zone)
317
- : super (context, null , null , 0 , 0 ,
358
+ : super (context, null , null ,
318
359
new RootWatchGroup (new DirtyCheckingChangeDetector (cacheGetter), context),
319
360
new RootWatchGroup (new DirtyCheckingChangeDetector (cacheGetter), context))
320
361
{
@@ -558,15 +599,19 @@ class _Streams {
558
599
559
600
static void destroy (Scope scope) {
560
601
var toBeDeletedStreams = scope._streams;
561
- if (toBeDeletedStreams == null ) return ;
562
- scope = scope._parentScope; // skip current state as not to delete listeners
563
- while (scope != null && scope._streams == toBeDeletedStreams) {
564
- scope._streams = null ;
565
- scope = scope._parentScope;
602
+ if (toBeDeletedStreams == null ) return ; // no streams to clean up
603
+ var parentScope = scope._parentScope; // skip current scope as not to delete listeners
604
+ // find the parent-most scope which still has our stream to be deleted.
605
+ while (parentScope != null && parentScope._streams == toBeDeletedStreams) {
606
+ parentScope._streams = null ;
607
+ parentScope = parentScope._parentScope;
566
608
}
567
- if (scope == null ) return ;
568
- var parentStreams = scope._streams;
609
+ // At this point scope is the parent-most scope which has its own typeCounts
610
+ if (parentScope == null ) return ;
611
+ var parentStreams = parentScope._streams;
569
612
assert (parentStreams != toBeDeletedStreams);
613
+ // remove typeCounts from the scope to be destroyed from the parent
614
+ // typeCounts
570
615
toBeDeletedStreams._typeCounts.forEach (
571
616
(name, count) => parentStreams._addCount (name, - count));
572
617
}
@@ -592,6 +637,7 @@ class _Streams {
592
637
assert (count >= 0 );
593
638
if (count == 0 ) {
594
639
lastStreams._typeCounts.remove (name);
640
+ if (_scope == scope) _streams.remove (name);
595
641
} else {
596
642
lastStreams._typeCounts[name] = count;
597
643
}
0 commit comments