| OLD | NEW |
| (Empty) |
| 1 library angular.watch_group; | |
| 2 | |
| 3 import 'dart:mirrors'; | |
| 4 import 'package:angular/change_detection/change_detection.dart'; | |
| 5 | |
| 6 part 'linked_list.dart'; | |
| 7 part 'ast.dart'; | |
| 8 part 'prototype_map.dart'; | |
| 9 | |
| 10 typedef ReactionFn(value, previousValue); | |
| 11 typedef ChangeLog(String expression, current, previous); | |
| 12 | |
| 13 /** | |
| 14 * Extend this class if you wish to pretend to be a function, but you don't know | |
| 15 * number of arguments with which the function will get called. | |
| 16 */ | |
| 17 abstract class FunctionApply { | |
| 18 // dartbug.com/16401 | |
| 19 // dynamic call() { throw new StateError('Use apply()'); } | |
| 20 dynamic apply(List arguments); | |
| 21 } | |
| 22 | |
| 23 /** | |
| 24 * [WatchGroup] is a logical grouping of a set of watches. [WatchGroup]s are | |
| 25 * organized into a hierarchical tree parent-children configuration. | |
| 26 * [WatchGroup] builds upon [ChangeDetector] and adds expression (field chains | |
| 27 * as in `a.b.c`) support as well as support function/closure/method (function | |
| 28 * invocation as in `a.b()`) watching. | |
| 29 */ | |
| 30 class WatchGroup implements _EvalWatchList, _WatchGroupList { | |
| 31 /** A unique ID for the WatchGroup */ | |
| 32 final String id; | |
| 33 /** | |
| 34 * A marker to be inserted when a group has no watches. We need the marker to | |
| 35 * hold our position information in the linked list of all [Watch]es. | |
| 36 */ | |
| 37 final _EvalWatchRecord _marker = new _EvalWatchRecord.marker(); | |
| 38 | |
| 39 /** All Expressions are evaluated against a context object. */ | |
| 40 final Object context; | |
| 41 | |
| 42 /** [ChangeDetector] used for field watching */ | |
| 43 final ChangeDetectorGroup<_Handler> _changeDetector; | |
| 44 /** A cache for sharing sub expression watching. Watching `a` and `a.b` will | |
| 45 * watch `a` only once. */ | |
| 46 final Map<String, WatchRecord<_Handler>> _cache; | |
| 47 final RootWatchGroup _rootGroup; | |
| 48 | |
| 49 /// STATS: Number of field watchers which are in use. | |
| 50 int _fieldCost = 0; | |
| 51 int _collectionCost = 0; | |
| 52 int _evalCost = 0; | |
| 53 | |
| 54 /// STATS: Number of field watchers which are in use including child [WatchGro
up]s. | |
| 55 int get fieldCost => _fieldCost; | |
| 56 int get totalFieldCost { | |
| 57 var cost = _fieldCost; | |
| 58 WatchGroup group = _watchGroupHead; | |
| 59 while(group != null) { | |
| 60 cost += group.totalFieldCost; | |
| 61 group = group._nextWatchGroup; | |
| 62 } | |
| 63 return cost; | |
| 64 } | |
| 65 | |
| 66 /// STATS: Number of collection watchers which are in use including child [Wat
chGroup]s. | |
| 67 int get collectionCost => _collectionCost; | |
| 68 int get totalCollectionCost { | |
| 69 var cost = _collectionCost; | |
| 70 WatchGroup group = _watchGroupHead; | |
| 71 while(group != null) { | |
| 72 cost += group.totalCollectionCost; | |
| 73 group = group._nextWatchGroup; | |
| 74 } | |
| 75 return cost; | |
| 76 } | |
| 77 | |
| 78 /// STATS: Number of invocation watchers (closures/methods) which are in use. | |
| 79 int get evalCost => _evalCost; | |
| 80 | |
| 81 /// STATS: Number of invocation watchers which are in use including child [Wat
chGroup]s. | |
| 82 int get totalEvalCost { | |
| 83 var cost = _evalCost; | |
| 84 WatchGroup group = _watchGroupHead; | |
| 85 while(group != null) { | |
| 86 cost += group.evalCost; | |
| 87 group = group._nextWatchGroup; | |
| 88 } | |
| 89 return cost; | |
| 90 } | |
| 91 | |
| 92 int _nextChildId = 0; | |
| 93 _EvalWatchRecord _evalWatchHead, _evalWatchTail; | |
| 94 /// Pointer for creating tree of [WatchGroup]s. | |
| 95 WatchGroup _watchGroupHead, _watchGroupTail, _previousWatchGroup, | |
| 96 _nextWatchGroup; | |
| 97 WatchGroup _parentWatchGroup; | |
| 98 | |
| 99 WatchGroup._child(_parentWatchGroup, this._changeDetector, this.context, | |
| 100 this._cache, this._rootGroup) | |
| 101 : _parentWatchGroup = _parentWatchGroup, | |
| 102 id = '${_parentWatchGroup.id}.${_parentWatchGroup._nextChildId++}' | |
| 103 { | |
| 104 _marker.watchGrp = this; | |
| 105 _evalWatchTail = _evalWatchHead = _marker; | |
| 106 } | |
| 107 | |
| 108 WatchGroup._root(this._changeDetector, this.context) | |
| 109 : id = '', | |
| 110 _rootGroup = null, | |
| 111 _parentWatchGroup = null, | |
| 112 _cache = new Map<String, WatchRecord<_Handler>>() | |
| 113 { | |
| 114 _marker.watchGrp = this; | |
| 115 _evalWatchTail = _evalWatchHead = _marker; | |
| 116 } | |
| 117 | |
| 118 get isAttached { | |
| 119 var group = this; | |
| 120 var root = _rootGroup; | |
| 121 while(group != null) { | |
| 122 if (group == root){ | |
| 123 return true; | |
| 124 } | |
| 125 group = group._parentWatchGroup; | |
| 126 } | |
| 127 return false; | |
| 128 } | |
| 129 | |
| 130 Watch watch(AST expression, ReactionFn reactionFn) { | |
| 131 WatchRecord<_Handler> watchRecord = | |
| 132 _cache.putIfAbsent(expression.expression, | |
| 133 () => expression.setupWatch(this)); | |
| 134 return watchRecord.handler.addReactionFn(reactionFn); | |
| 135 } | |
| 136 | |
| 137 /** | |
| 138 * Watch a [name] field on [lhs] represented by [expression]. | |
| 139 * | |
| 140 * - [name] the field to watch. | |
| 141 * - [lhs] left-hand-side of the field. | |
| 142 */ | |
| 143 WatchRecord<_Handler> addFieldWatch(AST lhs, String name, String expression) { | |
| 144 var fieldHandler = new _FieldHandler(this, expression); | |
| 145 | |
| 146 // Create a ChangeRecord for the current field and assign the change record | |
| 147 // to the handler. | |
| 148 var watchRecord = _changeDetector.watch(null, name, fieldHandler); | |
| 149 _fieldCost++; | |
| 150 fieldHandler.watchRecord = watchRecord; | |
| 151 | |
| 152 WatchRecord<_Handler> lhsWR = _cache.putIfAbsent(lhs.expression, | |
| 153 () => lhs.setupWatch(this)); | |
| 154 | |
| 155 // We set a field forwarding handler on LHS. This will allow the change | |
| 156 // objects to propagate to the current WatchRecord. | |
| 157 lhsWR.handler.addForwardHandler(fieldHandler); | |
| 158 | |
| 159 // propagate the value from the LHS to here | |
| 160 fieldHandler.acceptValue(lhsWR.currentValue); | |
| 161 return watchRecord; | |
| 162 } | |
| 163 | |
| 164 WatchRecord<_Handler> addCollectionWatch(AST ast) { | |
| 165 var collectionHandler = new _CollectionHandler(this, ast.expression); | |
| 166 var watchRecord = _changeDetector.watch(null, null, collectionHandler); | |
| 167 _collectionCost++; | |
| 168 collectionHandler.watchRecord = watchRecord; | |
| 169 WatchRecord<_Handler> astWR = _cache.putIfAbsent(ast.expression, | |
| 170 () => ast.setupWatch(this)); | |
| 171 | |
| 172 // We set a field forwarding handler on LHS. This will allow the change | |
| 173 // objects to propagate to the current WatchRecord. | |
| 174 astWR.handler.addForwardHandler(collectionHandler); | |
| 175 | |
| 176 // propagate the value from the LHS to here | |
| 177 collectionHandler.acceptValue(astWR.currentValue); | |
| 178 return watchRecord; | |
| 179 } | |
| 180 | |
| 181 /** | |
| 182 * Watch a [fn] function represented by an [expression]. | |
| 183 * | |
| 184 * - [fn] function to evaluate. | |
| 185 * - [argsAST] list of [AST]es which represent arguments passed to function. | |
| 186 * - [expression] normalized expression used for caching. | |
| 187 */ | |
| 188 _EvalWatchRecord addFunctionWatch(/* dartbug.com/16401 Function */ fn, List<AS
T> argsAST, | |
| 189 String expression) => | |
| 190 _addEvalWatch(null, fn, null, argsAST, expression); | |
| 191 | |
| 192 /** | |
| 193 * Watch a method [name]ed represented by an [expression]. | |
| 194 * | |
| 195 * - [lhs] left-hand-side of the method. | |
| 196 * - [name] name of the method. | |
| 197 * - [argsAST] list of [AST]es which represent arguments passed to method. | |
| 198 * - [expression] normalized expression used for caching. | |
| 199 */ | |
| 200 _EvalWatchRecord addMethodWatch(AST lhs, String name, List<AST> argsAST, | |
| 201 String expression) => | |
| 202 _addEvalWatch(lhs, null, name, argsAST, expression); | |
| 203 | |
| 204 | |
| 205 | |
| 206 _EvalWatchRecord _addEvalWatch(AST lhsAST, /* dartbug.com/16401 Function */ fn
, String name, | |
| 207 List<AST> argsAST, String expression) { | |
| 208 _InvokeHandler invokeHandler = new _InvokeHandler(this, expression); | |
| 209 var evalWatchRecord = new _EvalWatchRecord(this, invokeHandler, fn, name, | |
| 210 argsAST.length); | |
| 211 invokeHandler.watchRecord = evalWatchRecord; | |
| 212 | |
| 213 if (lhsAST != null) { | |
| 214 var lhsWR = _cache.putIfAbsent(lhsAST.expression, | |
| 215 () => lhsAST.setupWatch(this)); | |
| 216 lhsWR.handler.addForwardHandler(invokeHandler); | |
| 217 invokeHandler.acceptValue(lhsWR.currentValue); | |
| 218 } | |
| 219 | |
| 220 // Convert the args from AST to WatchRecords | |
| 221 var i = 0; | |
| 222 argsAST. | |
| 223 map((ast) => _cache.putIfAbsent(ast.expression, | |
| 224 () => ast.setupWatch(this))).forEach((WatchRecord<_Handler> record) { | |
| 225 var argHandler = new _ArgHandler(this, evalWatchRecord, i++); | |
| 226 _ArgHandlerList._add(invokeHandler, argHandler); | |
| 227 record.handler.addForwardHandler(argHandler); | |
| 228 argHandler.acceptValue(record.currentValue); | |
| 229 }); | |
| 230 | |
| 231 // Must be done last | |
| 232 _EvalWatchList._add(this, evalWatchRecord); | |
| 233 _evalCost++; | |
| 234 | |
| 235 return evalWatchRecord; | |
| 236 } | |
| 237 | |
| 238 WatchGroup get _childWatchGroupTail { | |
| 239 var tail = this, nextTail; | |
| 240 while ((nextTail = tail._watchGroupTail) != null) { | |
| 241 tail = nextTail; | |
| 242 } | |
| 243 return tail; | |
| 244 } | |
| 245 | |
| 246 /** | |
| 247 * Create a new child [WatchGroup]. | |
| 248 * | |
| 249 * - [context] if present the the child [WatchGroup] expressions will evaluate | |
| 250 * against the new [context]. If not present than child expressions will | |
| 251 * evaluate on same context allowing the reuse of the expression cache. | |
| 252 */ | |
| 253 WatchGroup newGroup([Object context]) { | |
| 254 _EvalWatchRecord prev = _childWatchGroupTail._evalWatchTail; | |
| 255 _EvalWatchRecord next = prev._nextEvalWatch; | |
| 256 var childGroup = new WatchGroup._child( | |
| 257 this, | |
| 258 _changeDetector.newGroup(), | |
| 259 context == null ? this.context : context, | |
| 260 context == null ? this._cache: <String, WatchRecord<_Handler>>{}, | |
| 261 _rootGroup == null ? this : _rootGroup); | |
| 262 _WatchGroupList._add(this, childGroup); | |
| 263 var marker = childGroup._marker; | |
| 264 | |
| 265 marker._previousEvalWatch = prev; | |
| 266 marker._nextEvalWatch = next; | |
| 267 if (prev != null) prev._nextEvalWatch = marker; | |
| 268 if (next != null) next._previousEvalWatch = marker; | |
| 269 | |
| 270 return childGroup; | |
| 271 } | |
| 272 | |
| 273 /** | |
| 274 * Remove/destroy [WatchGroup] and all of its [Watches]. | |
| 275 */ | |
| 276 void remove() { | |
| 277 // TODO:(misko) This code is not right. | |
| 278 // 1) It fails to release [ChangeDetector] [WatchRecord]s. | |
| 279 // 2) it needs to cleanup caches if the cache is being shared. | |
| 280 | |
| 281 _WatchGroupList._remove(_parentWatchGroup, this); | |
| 282 _changeDetector.remove(); | |
| 283 _rootGroup._removeCount++; | |
| 284 _parentWatchGroup = null; | |
| 285 | |
| 286 // Unlink the _watchRecord | |
| 287 _EvalWatchRecord firstEvalWatch = _evalWatchHead; | |
| 288 _EvalWatchRecord lastEvalWatch = | |
| 289 (_watchGroupTail == null ? this : _watchGroupTail)._evalWatchTail; | |
| 290 _EvalWatchRecord previous = firstEvalWatch._previousEvalWatch; | |
| 291 _EvalWatchRecord next = lastEvalWatch._nextEvalWatch; | |
| 292 if (previous != null) previous._nextEvalWatch = next; | |
| 293 if (next != null) next._previousEvalWatch = previous; | |
| 294 } | |
| 295 | |
| 296 toString() { | |
| 297 var lines = []; | |
| 298 if (this == _rootGroup) { | |
| 299 var allWatches = []; | |
| 300 var watch = _evalWatchHead; | |
| 301 var prev = null; | |
| 302 while (watch != null) { | |
| 303 allWatches.add(watch.toString()); | |
| 304 assert(watch._previousEvalWatch == prev); | |
| 305 prev = watch; | |
| 306 watch = watch._nextEvalWatch; | |
| 307 } | |
| 308 lines.add('WATCHES: ${allWatches.join(', ')}'); | |
| 309 } | |
| 310 | |
| 311 var watches = []; | |
| 312 var watch = _evalWatchHead; | |
| 313 while (watch != _evalWatchTail) { | |
| 314 watches.add(watch.toString()); | |
| 315 watch = watch._nextEvalWatch; | |
| 316 } | |
| 317 watches.add(watch.toString()); | |
| 318 | |
| 319 lines.add('WatchGroup[$id](watches: ${watches.join(', ')})'); | |
| 320 var childGroup = _watchGroupHead; | |
| 321 while (childGroup != null) { | |
| 322 lines.add(' ' + childGroup.toString().split('\n').join('\n ')); | |
| 323 childGroup = childGroup._nextWatchGroup; | |
| 324 } | |
| 325 return lines.join('\n'); | |
| 326 } | |
| 327 } | |
| 328 | |
| 329 /** | |
| 330 * [RootWatchGroup] | |
| 331 */ | |
| 332 class RootWatchGroup extends WatchGroup { | |
| 333 Watch _dirtyWatchHead, _dirtyWatchTail; | |
| 334 | |
| 335 /** | |
| 336 * Every time a [WatchGroup] is destroyed we increment the counter. During | |
| 337 * [detectChanges] we reset the count. Before calling the reaction function, | |
| 338 * we check [_removeCount] and if it is unchanged we can safely call the | |
| 339 * reaction function. If it is changed we only call the reaction function | |
| 340 * if the [WatchGroup] is still attached. | |
| 341 */ | |
| 342 int _removeCount = 0; | |
| 343 | |
| 344 | |
| 345 RootWatchGroup(ChangeDetector changeDetector, Object context): | |
| 346 super._root(changeDetector, context); | |
| 347 | |
| 348 RootWatchGroup get _rootGroup => this; | |
| 349 | |
| 350 /** | |
| 351 * Detect changes and process the [ReactionFn]s. | |
| 352 * | |
| 353 * Algorithm: | |
| 354 * 1) process the [ChangeDetector#collectChanges]. | |
| 355 * 2) process function/closure/method changes | |
| 356 * 3) call an [ReactionFn]s | |
| 357 * | |
| 358 * Each step is called in sequence. ([ReactionFn]s are not called until all | |
| 359 * previous steps are completed). | |
| 360 */ | |
| 361 int detectChanges({ EvalExceptionHandler exceptionHandler, | |
| 362 ChangeLog changeLog, | |
| 363 AvgStopwatch fieldStopwatch, | |
| 364 AvgStopwatch evalStopwatch, | |
| 365 AvgStopwatch processStopwatch}) { | |
| 366 // Process the ChangeRecords from the change detector | |
| 367 ChangeRecord<_Handler> changeRecord = | |
| 368 (_changeDetector as ChangeDetector<_Handler>).collectChanges( | |
| 369 exceptionHandler:exceptionHandler, | |
| 370 stopwatch: fieldStopwatch); | |
| 371 if (processStopwatch != null) processStopwatch.start(); | |
| 372 while (changeRecord != null) { | |
| 373 if (changeLog != null) changeLog(changeRecord.handler.expression, | |
| 374 changeRecord.currentValue, | |
| 375 changeRecord.previousValue); | |
| 376 changeRecord.handler.onChange(changeRecord); | |
| 377 changeRecord = changeRecord.nextChange; | |
| 378 } | |
| 379 if (processStopwatch != null) processStopwatch.stop(); | |
| 380 | |
| 381 if (evalStopwatch != null) evalStopwatch.start(); | |
| 382 // Process our own function evaluations | |
| 383 _EvalWatchRecord evalRecord = _evalWatchHead; | |
| 384 int evalCount = 0; | |
| 385 while (evalRecord != null) { | |
| 386 try { | |
| 387 if (evalStopwatch != null) evalCount++; | |
| 388 var change = evalRecord.check(); | |
| 389 if (change != null && changeLog != null) { | |
| 390 changeLog(evalRecord.handler.expression, | |
| 391 evalRecord.currentValue, | |
| 392 evalRecord.previousValue); | |
| 393 } | |
| 394 } catch (e, s) { | |
| 395 if (exceptionHandler == null) rethrow; else exceptionHandler(e, s); | |
| 396 } | |
| 397 evalRecord = evalRecord._nextEvalWatch; | |
| 398 } | |
| 399 if (evalStopwatch != null) evalStopwatch..stop()..increment(evalCount); | |
| 400 | |
| 401 // Because the handler can forward changes between each other synchronously | |
| 402 // We need to call reaction functions asynchronously. This processes the | |
| 403 // asynchronous reaction function queue. | |
| 404 int count = 0; | |
| 405 if (processStopwatch != null) processStopwatch.stop(); | |
| 406 Watch dirtyWatch = _dirtyWatchHead; | |
| 407 RootWatchGroup root = _rootGroup; | |
| 408 root._removeCount = 0; | |
| 409 while(dirtyWatch != null) { | |
| 410 count++; | |
| 411 try { | |
| 412 if (root._removeCount == 0 || dirtyWatch._watchGroup.isAttached) { | |
| 413 dirtyWatch.invoke(); | |
| 414 } | |
| 415 } catch (e, s) { | |
| 416 if (exceptionHandler == null) rethrow; else exceptionHandler(e, s); | |
| 417 } | |
| 418 dirtyWatch = dirtyWatch._nextDirtyWatch; | |
| 419 } | |
| 420 _dirtyWatchHead = _dirtyWatchTail = null; | |
| 421 if (processStopwatch != null) processStopwatch..stop()..increment(count); | |
| 422 return count; | |
| 423 } | |
| 424 | |
| 425 /** | |
| 426 * Add Watch into the asynchronous queue for later processing. | |
| 427 */ | |
| 428 Watch _addDirtyWatch(Watch watch) { | |
| 429 if (!watch._dirty) { | |
| 430 watch._dirty = true; | |
| 431 if (_dirtyWatchTail == null) { | |
| 432 _dirtyWatchHead = _dirtyWatchTail = watch; | |
| 433 } else { | |
| 434 _dirtyWatchTail._nextDirtyWatch = watch; | |
| 435 _dirtyWatchTail = watch; | |
| 436 } | |
| 437 watch._nextDirtyWatch = null; | |
| 438 } | |
| 439 return watch; | |
| 440 } | |
| 441 } | |
| 442 | |
| 443 /** | |
| 444 * [Watch] corresponds to an individual [watch] registration on the watchGrp. | |
| 445 */ | |
| 446 class Watch { | |
| 447 Watch _previousWatch, _nextWatch; | |
| 448 | |
| 449 final Record<_Handler> _record; | |
| 450 final ReactionFn reactionFn; | |
| 451 final WatchGroup _watchGroup; | |
| 452 | |
| 453 bool _dirty = false; | |
| 454 bool _deleted = false; | |
| 455 Watch _nextDirtyWatch; | |
| 456 | |
| 457 Watch(this._watchGroup, this._record, this.reactionFn); | |
| 458 | |
| 459 get expression => _record.handler.expression; | |
| 460 void invoke() { | |
| 461 if (_deleted || !_dirty) return; | |
| 462 _dirty = false; | |
| 463 reactionFn(_record.currentValue, _record.previousValue); | |
| 464 } | |
| 465 | |
| 466 void remove() { | |
| 467 if (_deleted) throw new StateError('Already deleted!'); | |
| 468 _deleted = true; | |
| 469 var handler = _record.handler; | |
| 470 _WatchList._remove(handler, this); | |
| 471 handler.release(); | |
| 472 } | |
| 473 } | |
| 474 | |
| 475 /** | |
| 476 * This class processes changes from the change detector. The changes are | |
| 477 * forwarded onto the next [_Handler] or queued up in case of reaction function. | |
| 478 * | |
| 479 * Given these two expression: 'a.b.c' => rfn1 and 'a.b' => rfn2 | |
| 480 * The resulting data structure is: | |
| 481 * | |
| 482 * _Handler +--> _Handler +--> _Handler | |
| 483 * - delegateHandler -+ - delegateHandler -+ - delegateHandler = nul
l | |
| 484 * - expression: 'a' - expression: 'a.b' - expression: 'a.b.c' | |
| 485 * - watchObject: context - watchObject: context.a - watchObject: context.
a.b | |
| 486 * - watchRecord: 'a' - watchRecord 'b' - watchRecord 'c' | |
| 487 * - reactionFn: null - reactionFn: rfn1 - reactionFn: rfn2 | |
| 488 * | |
| 489 * Notice how the [_Handler]s coalesce their watching. Also notice that any | |
| 490 * changes detected at one handler are propagated to the next handler. | |
| 491 */ | |
| 492 abstract class _Handler implements _LinkedList, _LinkedListItem, _WatchList { | |
| 493 _Handler _head, _tail; | |
| 494 _Handler _next, _previous; | |
| 495 Watch _watchHead, _watchTail; | |
| 496 | |
| 497 final String expression; | |
| 498 final WatchGroup watchGrp; | |
| 499 | |
| 500 WatchRecord<_Handler> watchRecord; | |
| 501 _Handler forwardingHandler; | |
| 502 | |
| 503 _Handler(this.watchGrp, this.expression) { | |
| 504 assert(watchGrp != null); | |
| 505 assert(expression != null); | |
| 506 } | |
| 507 | |
| 508 Watch addReactionFn(ReactionFn reactionFn) { | |
| 509 assert(_next != this); // verify we are not detached | |
| 510 return watchGrp._rootGroup._addDirtyWatch(_WatchList._add(this, | |
| 511 new Watch(watchGrp, watchRecord, reactionFn))); | |
| 512 } | |
| 513 | |
| 514 void addForwardHandler(_Handler forwardToHandler) { | |
| 515 assert(forwardToHandler.forwardingHandler == null); | |
| 516 _LinkedList._add(this, forwardToHandler); | |
| 517 forwardToHandler.forwardingHandler = this; | |
| 518 } | |
| 519 | |
| 520 void release() { | |
| 521 if (_WatchList._isEmpty(this) && _LinkedList._isEmpty(this)) { | |
| 522 _releaseWatch(); | |
| 523 // Remove ourselves from cache, or else new registrations will go to us, | |
| 524 // but we are dead | |
| 525 watchGrp._cache.remove(expression); | |
| 526 | |
| 527 if (forwardingHandler != null) { | |
| 528 // TODO(misko): why do we need this check? | |
| 529 _LinkedList._remove(forwardingHandler, this); | |
| 530 forwardingHandler.release(); | |
| 531 } | |
| 532 | |
| 533 // We can remove ourselves | |
| 534 assert((_next = _previous = this) == this); // mark ourselves as detached | |
| 535 } | |
| 536 } | |
| 537 | |
| 538 void _releaseWatch() { | |
| 539 watchRecord.remove(); | |
| 540 watchGrp._fieldCost--; | |
| 541 } | |
| 542 acceptValue(object) => null; | |
| 543 | |
| 544 void onChange(ChangeRecord<_Handler> record) { | |
| 545 assert(_next != this); // verify we are not detached | |
| 546 // If we have reaction functions than queue them up for asynchronous | |
| 547 // processing. | |
| 548 Watch watch = _watchHead; | |
| 549 while(watch != null) { | |
| 550 watchGrp._rootGroup._addDirtyWatch(watch); | |
| 551 watch = watch._nextWatch; | |
| 552 } | |
| 553 // If we have a delegateHandler then forward the new value to it. | |
| 554 _Handler delegateHandler = _head; | |
| 555 while (delegateHandler != null) { | |
| 556 delegateHandler.acceptValue(record.currentValue); | |
| 557 delegateHandler = delegateHandler._next; | |
| 558 } | |
| 559 } | |
| 560 } | |
| 561 | |
| 562 class _ConstantHandler extends _Handler { | |
| 563 _ConstantHandler(WatchGroup watchGroup, String expression, dynamic constantVal
ue) | |
| 564 : super(watchGroup, expression) | |
| 565 { | |
| 566 watchRecord = new _EvalWatchRecord.constant(this, constantValue); | |
| 567 } | |
| 568 release() => null; | |
| 569 } | |
| 570 | |
| 571 class _FieldHandler extends _Handler { | |
| 572 _FieldHandler(watchGrp, expression): super(watchGrp, expression); | |
| 573 | |
| 574 /** | |
| 575 * This function forwards the watched object to the next [_Handler] | |
| 576 * synchronously. | |
| 577 */ | |
| 578 void acceptValue(object) { | |
| 579 watchRecord.object = object; | |
| 580 var changeRecord = watchRecord.check(); | |
| 581 if (changeRecord != null) onChange(changeRecord); | |
| 582 } | |
| 583 } | |
| 584 | |
| 585 class _CollectionHandler extends _Handler { | |
| 586 _CollectionHandler(WatchGroup watchGrp, String expression) | |
| 587 : super(watchGrp, expression); | |
| 588 /** | |
| 589 * This function forwards the watched object to the next [_Handler] synchronou
sly. | |
| 590 */ | |
| 591 void acceptValue(object) { | |
| 592 watchRecord.object = object; | |
| 593 var changeRecord = watchRecord.check(); | |
| 594 if (changeRecord != null) onChange(changeRecord); | |
| 595 } | |
| 596 | |
| 597 void _releaseWatch() { | |
| 598 watchRecord.remove(); | |
| 599 watchGrp._collectionCost--; | |
| 600 } | |
| 601 } | |
| 602 | |
| 603 class _ArgHandler extends _Handler { | |
| 604 _ArgHandler _previousArgHandler, _nextArgHandler; | |
| 605 | |
| 606 // TODO(misko): Why do we override parent? | |
| 607 final _EvalWatchRecord watchRecord; | |
| 608 final int index; | |
| 609 | |
| 610 _releaseWatch() => null; | |
| 611 | |
| 612 _ArgHandler(WatchGroup watchGrp, this.watchRecord, int index) | |
| 613 : index = index, | |
| 614 super(watchGrp, 'arg[$index]'); | |
| 615 | |
| 616 void acceptValue(object) { | |
| 617 watchRecord.dirtyArgs = true; | |
| 618 watchRecord.args[index] = object; | |
| 619 } | |
| 620 } | |
| 621 | |
| 622 class _InvokeHandler extends _Handler implements _ArgHandlerList { | |
| 623 _ArgHandler _argHandlerHead, _argHandlerTail; | |
| 624 | |
| 625 _InvokeHandler(WatchGroup watchGrp, String expression) | |
| 626 : super(watchGrp, expression); | |
| 627 | |
| 628 void acceptValue(object) { | |
| 629 watchRecord.object = object; | |
| 630 } | |
| 631 | |
| 632 void onChange(ChangeRecord<_Handler> record) { | |
| 633 super.onChange(record); | |
| 634 } | |
| 635 | |
| 636 void _releaseWatch() { | |
| 637 (watchRecord as _EvalWatchRecord).remove(); | |
| 638 } | |
| 639 | |
| 640 void release() { | |
| 641 super.release(); | |
| 642 _ArgHandler current = _argHandlerHead; | |
| 643 while(current != null) { | |
| 644 current.release(); | |
| 645 current = current._nextArgHandler; | |
| 646 } | |
| 647 } | |
| 648 } | |
| 649 | |
| 650 | |
| 651 class _EvalWatchRecord implements WatchRecord<_Handler>, ChangeRecord<_Handler>
{ | |
| 652 static const int _MODE_DELETED_ = -1; | |
| 653 static const int _MODE_MARKER_ = 0; | |
| 654 static const int _MODE_FUNCTION_ = 1; | |
| 655 static const int _MODE_FUNCTION_APPLY_ = 2; | |
| 656 static const int _MODE_NULL_ = 3; | |
| 657 static const int _MODE_FIELD_CLOSURE_ = 4; | |
| 658 static const int _MODE_MAP_CLOSURE_ = 5; | |
| 659 static const int _MODE_METHOD_ = 6; | |
| 660 WatchGroup watchGrp; | |
| 661 final _Handler handler; | |
| 662 final List args; | |
| 663 final Symbol symbol; | |
| 664 final String name; | |
| 665 int mode; | |
| 666 /* dartbug.com/16401 Function*/ var fn; | |
| 667 InstanceMirror _instanceMirror; | |
| 668 bool dirtyArgs = true; | |
| 669 | |
| 670 dynamic currentValue, previousValue, _object; | |
| 671 _EvalWatchRecord _previousEvalWatch, _nextEvalWatch; | |
| 672 | |
| 673 _EvalWatchRecord(this.watchGrp, this.handler, this.fn, name, int arity) | |
| 674 : args = new List(arity), | |
| 675 name = name, | |
| 676 symbol = name == null ? null : new Symbol(name) { | |
| 677 if (fn is FunctionApply) { | |
| 678 mode = _MODE_FUNCTION_APPLY_; | |
| 679 } else if (fn is Function) { | |
| 680 mode = _MODE_FUNCTION_; | |
| 681 } else { | |
| 682 mode = _MODE_NULL_; | |
| 683 } | |
| 684 } | |
| 685 | |
| 686 _EvalWatchRecord.marker() | |
| 687 : mode = _MODE_MARKER_, | |
| 688 watchGrp = null, | |
| 689 handler = null, | |
| 690 args = null, | |
| 691 fn = null, | |
| 692 symbol = null, | |
| 693 name = null; | |
| 694 | |
| 695 _EvalWatchRecord.constant(_Handler handler, dynamic constantValue) | |
| 696 : mode = _MODE_MARKER_, | |
| 697 handler = handler, | |
| 698 currentValue = constantValue, | |
| 699 watchGrp = null, | |
| 700 args = null, | |
| 701 fn = null, | |
| 702 symbol = null, | |
| 703 name = null; | |
| 704 | |
| 705 get field => '()'; | |
| 706 | |
| 707 get object => _object; | |
| 708 | |
| 709 set object(value) { | |
| 710 assert(mode != _MODE_DELETED_); | |
| 711 assert(mode != _MODE_MARKER_); | |
| 712 assert(mode != _MODE_FUNCTION_); | |
| 713 assert(mode != _MODE_FUNCTION_APPLY_); | |
| 714 assert(symbol != null); | |
| 715 _object = value; | |
| 716 | |
| 717 if (value == null) { | |
| 718 mode = _MODE_NULL_; | |
| 719 } else { | |
| 720 if (value is Map) { | |
| 721 mode = _MODE_MAP_CLOSURE_; | |
| 722 } else { | |
| 723 _instanceMirror = reflect(value); | |
| 724 mode = _hasMethod(_instanceMirror.type, symbol) | |
| 725 ? _MODE_METHOD_ | |
| 726 : _MODE_FIELD_CLOSURE_; | |
| 727 } | |
| 728 } | |
| 729 } | |
| 730 | |
| 731 ChangeRecord<_Handler> check() { | |
| 732 var value; | |
| 733 switch (mode) { | |
| 734 case _MODE_MARKER_: | |
| 735 case _MODE_NULL_: | |
| 736 return null; | |
| 737 case _MODE_FUNCTION_: | |
| 738 if (!dirtyArgs) return null; | |
| 739 value = Function.apply(fn, args); | |
| 740 dirtyArgs = false; | |
| 741 break; | |
| 742 case _MODE_FUNCTION_APPLY_: | |
| 743 if (!dirtyArgs) return null; | |
| 744 value = (fn as FunctionApply).apply(args); | |
| 745 dirtyArgs = false; | |
| 746 break; | |
| 747 case _MODE_FIELD_CLOSURE_: | |
| 748 var closure = _instanceMirror.getField(symbol).reflectee; | |
| 749 value = closure == null ? null : Function.apply(closure, args); | |
| 750 break; | |
| 751 case _MODE_MAP_CLOSURE_: | |
| 752 var closure = object[name]; | |
| 753 value = closure == null ? null : Function.apply(closure, args); | |
| 754 break; | |
| 755 case _MODE_METHOD_: | |
| 756 value = _instanceMirror.invoke(symbol, args).reflectee; | |
| 757 break; | |
| 758 default: | |
| 759 assert(false); | |
| 760 } | |
| 761 | |
| 762 var current = currentValue; | |
| 763 if (!identical(current, value)) { | |
| 764 if (value is String && current is String && value == current) { | |
| 765 // it is really the same, recover and save so next time identity is same | |
| 766 current = value; | |
| 767 } else { | |
| 768 previousValue = current; | |
| 769 currentValue = value; | |
| 770 handler.onChange(this); | |
| 771 return this; | |
| 772 } | |
| 773 } | |
| 774 return null; | |
| 775 } | |
| 776 | |
| 777 get nextChange => null; | |
| 778 | |
| 779 void remove() { | |
| 780 assert(mode != _MODE_DELETED_); | |
| 781 assert((mode = _MODE_DELETED_) == _MODE_DELETED_); // Mark as deleted. | |
| 782 watchGrp._evalCost--; | |
| 783 _EvalWatchList._remove(watchGrp, this); | |
| 784 } | |
| 785 | |
| 786 String toString() { | |
| 787 if (mode == _MODE_MARKER_) return 'MARKER[$currentValue]'; | |
| 788 return '${watchGrp.id}:${handler.expression}'; | |
| 789 } | |
| 790 | |
| 791 static final Function _hasMethod = (() { | |
| 792 var objectClassMirror = reflectClass(Object); | |
| 793 Set<Symbol> objectClassInstanceMethods = | |
| 794 new Set<Symbol>.from([#toString, #noSuchMethod]); | |
| 795 try { | |
| 796 // Use ClassMirror.instanceMembers if available. It contains local | |
| 797 // as well as inherited members. | |
| 798 objectClassMirror.instanceMembers; | |
| 799 // For SDK 1.2 we have to use a somewhat complicated helper for this | |
| 800 // to work around bugs in the dart2js implementation. | |
| 801 return (type, symbol) { | |
| 802 // Always allow instance methods found in the Object class. This makes | |
| 803 // it easier to work around a few bugs in the dart2js implementation. | |
| 804 if (objectClassInstanceMethods.contains(symbol)) return true; | |
| 805 // Work around http://dartbug.com/16309 which causes access to the | |
| 806 // instance members of certain builtin types to throw exceptions | |
| 807 // while traversing the superclass chain. | |
| 808 var mirror; | |
| 809 try { | |
| 810 mirror = type.instanceMembers[symbol]; | |
| 811 } on UnsupportedError catch (e) { | |
| 812 mirror = type.declarations[symbol]; | |
| 813 } | |
| 814 // Work around http://dartbug.com/15760 which causes noSuchMethod | |
| 815 // forwarding stubs to be treated as members of all classes. We have | |
| 816 // already checked for the real instance methods in Object, so if the | |
| 817 // owner of this method is Object we simply filter it out. | |
| 818 if (mirror is !MethodMirror) return false; | |
| 819 return mirror.owner != objectClassMirror; | |
| 820 }; | |
| 821 } on NoSuchMethodError catch (e) { | |
| 822 // For SDK 1.0 we fall back to just using the local members. | |
| 823 return (type, symbol) => type.members[symbol] is MethodMirror; | |
| 824 } on UnimplementedError catch (e) { | |
| 825 // For SDK 1.1 we fall back to just using the local declarations. | |
| 826 return (type, symbol) => type.declarations[symbol] is MethodMirror; | |
| 827 } | |
| 828 return null; | |
| 829 })(); | |
| 830 | |
| 831 } | |
| OLD | NEW |