| Index: third_party/pkg/angular/lib/change_detection/watch_group.dart
|
| diff --git a/third_party/pkg/angular/lib/change_detection/watch_group.dart b/third_party/pkg/angular/lib/change_detection/watch_group.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..f1427b9ba9934f4d510ce844610ad07a7890ea1a
|
| --- /dev/null
|
| +++ b/third_party/pkg/angular/lib/change_detection/watch_group.dart
|
| @@ -0,0 +1,831 @@
|
| +library angular.watch_group;
|
| +
|
| +import 'dart:mirrors';
|
| +import 'package:angular/change_detection/change_detection.dart';
|
| +
|
| +part 'linked_list.dart';
|
| +part 'ast.dart';
|
| +part 'prototype_map.dart';
|
| +
|
| +typedef ReactionFn(value, previousValue);
|
| +typedef ChangeLog(String expression, current, previous);
|
| +
|
| +/**
|
| + * Extend this class if you wish to pretend to be a function, but you don't know
|
| + * number of arguments with which the function will get called.
|
| + */
|
| +abstract class FunctionApply {
|
| + // dartbug.com/16401
|
| + // dynamic call() { throw new StateError('Use apply()'); }
|
| + dynamic apply(List arguments);
|
| +}
|
| +
|
| +/**
|
| + * [WatchGroup] is a logical grouping of a set of watches. [WatchGroup]s are
|
| + * organized into a hierarchical tree parent-children configuration.
|
| + * [WatchGroup] builds upon [ChangeDetector] and adds expression (field chains
|
| + * as in `a.b.c`) support as well as support function/closure/method (function
|
| + * invocation as in `a.b()`) watching.
|
| + */
|
| +class WatchGroup implements _EvalWatchList, _WatchGroupList {
|
| + /** A unique ID for the WatchGroup */
|
| + final String id;
|
| + /**
|
| + * A marker to be inserted when a group has no watches. We need the marker to
|
| + * hold our position information in the linked list of all [Watch]es.
|
| + */
|
| + final _EvalWatchRecord _marker = new _EvalWatchRecord.marker();
|
| +
|
| + /** All Expressions are evaluated against a context object. */
|
| + final Object context;
|
| +
|
| + /** [ChangeDetector] used for field watching */
|
| + final ChangeDetectorGroup<_Handler> _changeDetector;
|
| + /** A cache for sharing sub expression watching. Watching `a` and `a.b` will
|
| + * watch `a` only once. */
|
| + final Map<String, WatchRecord<_Handler>> _cache;
|
| + final RootWatchGroup _rootGroup;
|
| +
|
| + /// STATS: Number of field watchers which are in use.
|
| + int _fieldCost = 0;
|
| + int _collectionCost = 0;
|
| + int _evalCost = 0;
|
| +
|
| + /// STATS: Number of field watchers which are in use including child [WatchGroup]s.
|
| + int get fieldCost => _fieldCost;
|
| + int get totalFieldCost {
|
| + var cost = _fieldCost;
|
| + WatchGroup group = _watchGroupHead;
|
| + while(group != null) {
|
| + cost += group.totalFieldCost;
|
| + group = group._nextWatchGroup;
|
| + }
|
| + return cost;
|
| + }
|
| +
|
| + /// STATS: Number of collection watchers which are in use including child [WatchGroup]s.
|
| + int get collectionCost => _collectionCost;
|
| + int get totalCollectionCost {
|
| + var cost = _collectionCost;
|
| + WatchGroup group = _watchGroupHead;
|
| + while(group != null) {
|
| + cost += group.totalCollectionCost;
|
| + group = group._nextWatchGroup;
|
| + }
|
| + return cost;
|
| + }
|
| +
|
| + /// STATS: Number of invocation watchers (closures/methods) which are in use.
|
| + int get evalCost => _evalCost;
|
| +
|
| + /// STATS: Number of invocation watchers which are in use including child [WatchGroup]s.
|
| + int get totalEvalCost {
|
| + var cost = _evalCost;
|
| + WatchGroup group = _watchGroupHead;
|
| + while(group != null) {
|
| + cost += group.evalCost;
|
| + group = group._nextWatchGroup;
|
| + }
|
| + return cost;
|
| + }
|
| +
|
| + int _nextChildId = 0;
|
| + _EvalWatchRecord _evalWatchHead, _evalWatchTail;
|
| + /// Pointer for creating tree of [WatchGroup]s.
|
| + WatchGroup _watchGroupHead, _watchGroupTail, _previousWatchGroup,
|
| + _nextWatchGroup;
|
| + WatchGroup _parentWatchGroup;
|
| +
|
| + WatchGroup._child(_parentWatchGroup, this._changeDetector, this.context,
|
| + this._cache, this._rootGroup)
|
| + : _parentWatchGroup = _parentWatchGroup,
|
| + id = '${_parentWatchGroup.id}.${_parentWatchGroup._nextChildId++}'
|
| + {
|
| + _marker.watchGrp = this;
|
| + _evalWatchTail = _evalWatchHead = _marker;
|
| + }
|
| +
|
| + WatchGroup._root(this._changeDetector, this.context)
|
| + : id = '',
|
| + _rootGroup = null,
|
| + _parentWatchGroup = null,
|
| + _cache = new Map<String, WatchRecord<_Handler>>()
|
| + {
|
| + _marker.watchGrp = this;
|
| + _evalWatchTail = _evalWatchHead = _marker;
|
| + }
|
| +
|
| + get isAttached {
|
| + var group = this;
|
| + var root = _rootGroup;
|
| + while(group != null) {
|
| + if (group == root){
|
| + return true;
|
| + }
|
| + group = group._parentWatchGroup;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + Watch watch(AST expression, ReactionFn reactionFn) {
|
| + WatchRecord<_Handler> watchRecord =
|
| + _cache.putIfAbsent(expression.expression,
|
| + () => expression.setupWatch(this));
|
| + return watchRecord.handler.addReactionFn(reactionFn);
|
| + }
|
| +
|
| + /**
|
| + * Watch a [name] field on [lhs] represented by [expression].
|
| + *
|
| + * - [name] the field to watch.
|
| + * - [lhs] left-hand-side of the field.
|
| + */
|
| + WatchRecord<_Handler> addFieldWatch(AST lhs, String name, String expression) {
|
| + var fieldHandler = new _FieldHandler(this, expression);
|
| +
|
| + // Create a ChangeRecord for the current field and assign the change record
|
| + // to the handler.
|
| + var watchRecord = _changeDetector.watch(null, name, fieldHandler);
|
| + _fieldCost++;
|
| + fieldHandler.watchRecord = watchRecord;
|
| +
|
| + WatchRecord<_Handler> lhsWR = _cache.putIfAbsent(lhs.expression,
|
| + () => lhs.setupWatch(this));
|
| +
|
| + // We set a field forwarding handler on LHS. This will allow the change
|
| + // objects to propagate to the current WatchRecord.
|
| + lhsWR.handler.addForwardHandler(fieldHandler);
|
| +
|
| + // propagate the value from the LHS to here
|
| + fieldHandler.acceptValue(lhsWR.currentValue);
|
| + return watchRecord;
|
| + }
|
| +
|
| + WatchRecord<_Handler> addCollectionWatch(AST ast) {
|
| + var collectionHandler = new _CollectionHandler(this, ast.expression);
|
| + var watchRecord = _changeDetector.watch(null, null, collectionHandler);
|
| + _collectionCost++;
|
| + collectionHandler.watchRecord = watchRecord;
|
| + WatchRecord<_Handler> astWR = _cache.putIfAbsent(ast.expression,
|
| + () => ast.setupWatch(this));
|
| +
|
| + // We set a field forwarding handler on LHS. This will allow the change
|
| + // objects to propagate to the current WatchRecord.
|
| + astWR.handler.addForwardHandler(collectionHandler);
|
| +
|
| + // propagate the value from the LHS to here
|
| + collectionHandler.acceptValue(astWR.currentValue);
|
| + return watchRecord;
|
| + }
|
| +
|
| + /**
|
| + * Watch a [fn] function represented by an [expression].
|
| + *
|
| + * - [fn] function to evaluate.
|
| + * - [argsAST] list of [AST]es which represent arguments passed to function.
|
| + * - [expression] normalized expression used for caching.
|
| + */
|
| + _EvalWatchRecord addFunctionWatch(/* dartbug.com/16401 Function */ fn, List<AST> argsAST,
|
| + String expression) =>
|
| + _addEvalWatch(null, fn, null, argsAST, expression);
|
| +
|
| + /**
|
| + * Watch a method [name]ed represented by an [expression].
|
| + *
|
| + * - [lhs] left-hand-side of the method.
|
| + * - [name] name of the method.
|
| + * - [argsAST] list of [AST]es which represent arguments passed to method.
|
| + * - [expression] normalized expression used for caching.
|
| + */
|
| + _EvalWatchRecord addMethodWatch(AST lhs, String name, List<AST> argsAST,
|
| + String expression) =>
|
| + _addEvalWatch(lhs, null, name, argsAST, expression);
|
| +
|
| +
|
| +
|
| + _EvalWatchRecord _addEvalWatch(AST lhsAST, /* dartbug.com/16401 Function */ fn, String name,
|
| + List<AST> argsAST, String expression) {
|
| + _InvokeHandler invokeHandler = new _InvokeHandler(this, expression);
|
| + var evalWatchRecord = new _EvalWatchRecord(this, invokeHandler, fn, name,
|
| + argsAST.length);
|
| + invokeHandler.watchRecord = evalWatchRecord;
|
| +
|
| + if (lhsAST != null) {
|
| + var lhsWR = _cache.putIfAbsent(lhsAST.expression,
|
| + () => lhsAST.setupWatch(this));
|
| + lhsWR.handler.addForwardHandler(invokeHandler);
|
| + invokeHandler.acceptValue(lhsWR.currentValue);
|
| + }
|
| +
|
| + // Convert the args from AST to WatchRecords
|
| + var i = 0;
|
| + argsAST.
|
| + map((ast) => _cache.putIfAbsent(ast.expression,
|
| + () => ast.setupWatch(this))).forEach((WatchRecord<_Handler> record) {
|
| + var argHandler = new _ArgHandler(this, evalWatchRecord, i++);
|
| + _ArgHandlerList._add(invokeHandler, argHandler);
|
| + record.handler.addForwardHandler(argHandler);
|
| + argHandler.acceptValue(record.currentValue);
|
| + });
|
| +
|
| + // Must be done last
|
| + _EvalWatchList._add(this, evalWatchRecord);
|
| + _evalCost++;
|
| +
|
| + return evalWatchRecord;
|
| + }
|
| +
|
| + WatchGroup get _childWatchGroupTail {
|
| + var tail = this, nextTail;
|
| + while ((nextTail = tail._watchGroupTail) != null) {
|
| + tail = nextTail;
|
| + }
|
| + return tail;
|
| + }
|
| +
|
| + /**
|
| + * Create a new child [WatchGroup].
|
| + *
|
| + * - [context] if present the the child [WatchGroup] expressions will evaluate
|
| + * against the new [context]. If not present than child expressions will
|
| + * evaluate on same context allowing the reuse of the expression cache.
|
| + */
|
| + WatchGroup newGroup([Object context]) {
|
| + _EvalWatchRecord prev = _childWatchGroupTail._evalWatchTail;
|
| + _EvalWatchRecord next = prev._nextEvalWatch;
|
| + var childGroup = new WatchGroup._child(
|
| + this,
|
| + _changeDetector.newGroup(),
|
| + context == null ? this.context : context,
|
| + context == null ? this._cache: <String, WatchRecord<_Handler>>{},
|
| + _rootGroup == null ? this : _rootGroup);
|
| + _WatchGroupList._add(this, childGroup);
|
| + var marker = childGroup._marker;
|
| +
|
| + marker._previousEvalWatch = prev;
|
| + marker._nextEvalWatch = next;
|
| + if (prev != null) prev._nextEvalWatch = marker;
|
| + if (next != null) next._previousEvalWatch = marker;
|
| +
|
| + return childGroup;
|
| + }
|
| +
|
| + /**
|
| + * Remove/destroy [WatchGroup] and all of its [Watches].
|
| + */
|
| + void remove() {
|
| + // TODO:(misko) This code is not right.
|
| + // 1) It fails to release [ChangeDetector] [WatchRecord]s.
|
| + // 2) it needs to cleanup caches if the cache is being shared.
|
| +
|
| + _WatchGroupList._remove(_parentWatchGroup, this);
|
| + _changeDetector.remove();
|
| + _rootGroup._removeCount++;
|
| + _parentWatchGroup = null;
|
| +
|
| + // Unlink the _watchRecord
|
| + _EvalWatchRecord firstEvalWatch = _evalWatchHead;
|
| + _EvalWatchRecord lastEvalWatch =
|
| + (_watchGroupTail == null ? this : _watchGroupTail)._evalWatchTail;
|
| + _EvalWatchRecord previous = firstEvalWatch._previousEvalWatch;
|
| + _EvalWatchRecord next = lastEvalWatch._nextEvalWatch;
|
| + if (previous != null) previous._nextEvalWatch = next;
|
| + if (next != null) next._previousEvalWatch = previous;
|
| + }
|
| +
|
| + toString() {
|
| + var lines = [];
|
| + if (this == _rootGroup) {
|
| + var allWatches = [];
|
| + var watch = _evalWatchHead;
|
| + var prev = null;
|
| + while (watch != null) {
|
| + allWatches.add(watch.toString());
|
| + assert(watch._previousEvalWatch == prev);
|
| + prev = watch;
|
| + watch = watch._nextEvalWatch;
|
| + }
|
| + lines.add('WATCHES: ${allWatches.join(', ')}');
|
| + }
|
| +
|
| + var watches = [];
|
| + var watch = _evalWatchHead;
|
| + while (watch != _evalWatchTail) {
|
| + watches.add(watch.toString());
|
| + watch = watch._nextEvalWatch;
|
| + }
|
| + watches.add(watch.toString());
|
| +
|
| + lines.add('WatchGroup[$id](watches: ${watches.join(', ')})');
|
| + var childGroup = _watchGroupHead;
|
| + while (childGroup != null) {
|
| + lines.add(' ' + childGroup.toString().split('\n').join('\n '));
|
| + childGroup = childGroup._nextWatchGroup;
|
| + }
|
| + return lines.join('\n');
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * [RootWatchGroup]
|
| + */
|
| +class RootWatchGroup extends WatchGroup {
|
| + Watch _dirtyWatchHead, _dirtyWatchTail;
|
| +
|
| + /**
|
| + * Every time a [WatchGroup] is destroyed we increment the counter. During
|
| + * [detectChanges] we reset the count. Before calling the reaction function,
|
| + * we check [_removeCount] and if it is unchanged we can safely call the
|
| + * reaction function. If it is changed we only call the reaction function
|
| + * if the [WatchGroup] is still attached.
|
| + */
|
| + int _removeCount = 0;
|
| +
|
| +
|
| + RootWatchGroup(ChangeDetector changeDetector, Object context):
|
| + super._root(changeDetector, context);
|
| +
|
| + RootWatchGroup get _rootGroup => this;
|
| +
|
| + /**
|
| + * Detect changes and process the [ReactionFn]s.
|
| + *
|
| + * Algorithm:
|
| + * 1) process the [ChangeDetector#collectChanges].
|
| + * 2) process function/closure/method changes
|
| + * 3) call an [ReactionFn]s
|
| + *
|
| + * Each step is called in sequence. ([ReactionFn]s are not called until all
|
| + * previous steps are completed).
|
| + */
|
| + int detectChanges({ EvalExceptionHandler exceptionHandler,
|
| + ChangeLog changeLog,
|
| + AvgStopwatch fieldStopwatch,
|
| + AvgStopwatch evalStopwatch,
|
| + AvgStopwatch processStopwatch}) {
|
| + // Process the ChangeRecords from the change detector
|
| + ChangeRecord<_Handler> changeRecord =
|
| + (_changeDetector as ChangeDetector<_Handler>).collectChanges(
|
| + exceptionHandler:exceptionHandler,
|
| + stopwatch: fieldStopwatch);
|
| + if (processStopwatch != null) processStopwatch.start();
|
| + while (changeRecord != null) {
|
| + if (changeLog != null) changeLog(changeRecord.handler.expression,
|
| + changeRecord.currentValue,
|
| + changeRecord.previousValue);
|
| + changeRecord.handler.onChange(changeRecord);
|
| + changeRecord = changeRecord.nextChange;
|
| + }
|
| + if (processStopwatch != null) processStopwatch.stop();
|
| +
|
| + if (evalStopwatch != null) evalStopwatch.start();
|
| + // Process our own function evaluations
|
| + _EvalWatchRecord evalRecord = _evalWatchHead;
|
| + int evalCount = 0;
|
| + while (evalRecord != null) {
|
| + try {
|
| + if (evalStopwatch != null) evalCount++;
|
| + var change = evalRecord.check();
|
| + if (change != null && changeLog != null) {
|
| + changeLog(evalRecord.handler.expression,
|
| + evalRecord.currentValue,
|
| + evalRecord.previousValue);
|
| + }
|
| + } catch (e, s) {
|
| + if (exceptionHandler == null) rethrow; else exceptionHandler(e, s);
|
| + }
|
| + evalRecord = evalRecord._nextEvalWatch;
|
| + }
|
| + if (evalStopwatch != null) evalStopwatch..stop()..increment(evalCount);
|
| +
|
| + // Because the handler can forward changes between each other synchronously
|
| + // We need to call reaction functions asynchronously. This processes the
|
| + // asynchronous reaction function queue.
|
| + int count = 0;
|
| + if (processStopwatch != null) processStopwatch.stop();
|
| + Watch dirtyWatch = _dirtyWatchHead;
|
| + RootWatchGroup root = _rootGroup;
|
| + root._removeCount = 0;
|
| + while(dirtyWatch != null) {
|
| + count++;
|
| + try {
|
| + if (root._removeCount == 0 || dirtyWatch._watchGroup.isAttached) {
|
| + dirtyWatch.invoke();
|
| + }
|
| + } catch (e, s) {
|
| + if (exceptionHandler == null) rethrow; else exceptionHandler(e, s);
|
| + }
|
| + dirtyWatch = dirtyWatch._nextDirtyWatch;
|
| + }
|
| + _dirtyWatchHead = _dirtyWatchTail = null;
|
| + if (processStopwatch != null) processStopwatch..stop()..increment(count);
|
| + return count;
|
| + }
|
| +
|
| + /**
|
| + * Add Watch into the asynchronous queue for later processing.
|
| + */
|
| + Watch _addDirtyWatch(Watch watch) {
|
| + if (!watch._dirty) {
|
| + watch._dirty = true;
|
| + if (_dirtyWatchTail == null) {
|
| + _dirtyWatchHead = _dirtyWatchTail = watch;
|
| + } else {
|
| + _dirtyWatchTail._nextDirtyWatch = watch;
|
| + _dirtyWatchTail = watch;
|
| + }
|
| + watch._nextDirtyWatch = null;
|
| + }
|
| + return watch;
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * [Watch] corresponds to an individual [watch] registration on the watchGrp.
|
| + */
|
| +class Watch {
|
| + Watch _previousWatch, _nextWatch;
|
| +
|
| + final Record<_Handler> _record;
|
| + final ReactionFn reactionFn;
|
| + final WatchGroup _watchGroup;
|
| +
|
| + bool _dirty = false;
|
| + bool _deleted = false;
|
| + Watch _nextDirtyWatch;
|
| +
|
| + Watch(this._watchGroup, this._record, this.reactionFn);
|
| +
|
| + get expression => _record.handler.expression;
|
| + void invoke() {
|
| + if (_deleted || !_dirty) return;
|
| + _dirty = false;
|
| + reactionFn(_record.currentValue, _record.previousValue);
|
| + }
|
| +
|
| + void remove() {
|
| + if (_deleted) throw new StateError('Already deleted!');
|
| + _deleted = true;
|
| + var handler = _record.handler;
|
| + _WatchList._remove(handler, this);
|
| + handler.release();
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * This class processes changes from the change detector. The changes are
|
| + * forwarded onto the next [_Handler] or queued up in case of reaction function.
|
| + *
|
| + * Given these two expression: 'a.b.c' => rfn1 and 'a.b' => rfn2
|
| + * The resulting data structure is:
|
| + *
|
| + * _Handler +--> _Handler +--> _Handler
|
| + * - delegateHandler -+ - delegateHandler -+ - delegateHandler = null
|
| + * - expression: 'a' - expression: 'a.b' - expression: 'a.b.c'
|
| + * - watchObject: context - watchObject: context.a - watchObject: context.a.b
|
| + * - watchRecord: 'a' - watchRecord 'b' - watchRecord 'c'
|
| + * - reactionFn: null - reactionFn: rfn1 - reactionFn: rfn2
|
| + *
|
| + * Notice how the [_Handler]s coalesce their watching. Also notice that any
|
| + * changes detected at one handler are propagated to the next handler.
|
| + */
|
| +abstract class _Handler implements _LinkedList, _LinkedListItem, _WatchList {
|
| + _Handler _head, _tail;
|
| + _Handler _next, _previous;
|
| + Watch _watchHead, _watchTail;
|
| +
|
| + final String expression;
|
| + final WatchGroup watchGrp;
|
| +
|
| + WatchRecord<_Handler> watchRecord;
|
| + _Handler forwardingHandler;
|
| +
|
| + _Handler(this.watchGrp, this.expression) {
|
| + assert(watchGrp != null);
|
| + assert(expression != null);
|
| + }
|
| +
|
| + Watch addReactionFn(ReactionFn reactionFn) {
|
| + assert(_next != this); // verify we are not detached
|
| + return watchGrp._rootGroup._addDirtyWatch(_WatchList._add(this,
|
| + new Watch(watchGrp, watchRecord, reactionFn)));
|
| + }
|
| +
|
| + void addForwardHandler(_Handler forwardToHandler) {
|
| + assert(forwardToHandler.forwardingHandler == null);
|
| + _LinkedList._add(this, forwardToHandler);
|
| + forwardToHandler.forwardingHandler = this;
|
| + }
|
| +
|
| + void release() {
|
| + if (_WatchList._isEmpty(this) && _LinkedList._isEmpty(this)) {
|
| + _releaseWatch();
|
| + // Remove ourselves from cache, or else new registrations will go to us,
|
| + // but we are dead
|
| + watchGrp._cache.remove(expression);
|
| +
|
| + if (forwardingHandler != null) {
|
| + // TODO(misko): why do we need this check?
|
| + _LinkedList._remove(forwardingHandler, this);
|
| + forwardingHandler.release();
|
| + }
|
| +
|
| + // We can remove ourselves
|
| + assert((_next = _previous = this) == this); // mark ourselves as detached
|
| + }
|
| + }
|
| +
|
| + void _releaseWatch() {
|
| + watchRecord.remove();
|
| + watchGrp._fieldCost--;
|
| + }
|
| + acceptValue(object) => null;
|
| +
|
| + void onChange(ChangeRecord<_Handler> record) {
|
| + assert(_next != this); // verify we are not detached
|
| + // If we have reaction functions than queue them up for asynchronous
|
| + // processing.
|
| + Watch watch = _watchHead;
|
| + while(watch != null) {
|
| + watchGrp._rootGroup._addDirtyWatch(watch);
|
| + watch = watch._nextWatch;
|
| + }
|
| + // If we have a delegateHandler then forward the new value to it.
|
| + _Handler delegateHandler = _head;
|
| + while (delegateHandler != null) {
|
| + delegateHandler.acceptValue(record.currentValue);
|
| + delegateHandler = delegateHandler._next;
|
| + }
|
| + }
|
| +}
|
| +
|
| +class _ConstantHandler extends _Handler {
|
| + _ConstantHandler(WatchGroup watchGroup, String expression, dynamic constantValue)
|
| + : super(watchGroup, expression)
|
| + {
|
| + watchRecord = new _EvalWatchRecord.constant(this, constantValue);
|
| + }
|
| + release() => null;
|
| +}
|
| +
|
| +class _FieldHandler extends _Handler {
|
| + _FieldHandler(watchGrp, expression): super(watchGrp, expression);
|
| +
|
| + /**
|
| + * This function forwards the watched object to the next [_Handler]
|
| + * synchronously.
|
| + */
|
| + void acceptValue(object) {
|
| + watchRecord.object = object;
|
| + var changeRecord = watchRecord.check();
|
| + if (changeRecord != null) onChange(changeRecord);
|
| + }
|
| +}
|
| +
|
| +class _CollectionHandler extends _Handler {
|
| + _CollectionHandler(WatchGroup watchGrp, String expression)
|
| + : super(watchGrp, expression);
|
| + /**
|
| + * This function forwards the watched object to the next [_Handler] synchronously.
|
| + */
|
| + void acceptValue(object) {
|
| + watchRecord.object = object;
|
| + var changeRecord = watchRecord.check();
|
| + if (changeRecord != null) onChange(changeRecord);
|
| + }
|
| +
|
| + void _releaseWatch() {
|
| + watchRecord.remove();
|
| + watchGrp._collectionCost--;
|
| + }
|
| +}
|
| +
|
| +class _ArgHandler extends _Handler {
|
| + _ArgHandler _previousArgHandler, _nextArgHandler;
|
| +
|
| + // TODO(misko): Why do we override parent?
|
| + final _EvalWatchRecord watchRecord;
|
| + final int index;
|
| +
|
| + _releaseWatch() => null;
|
| +
|
| + _ArgHandler(WatchGroup watchGrp, this.watchRecord, int index)
|
| + : index = index,
|
| + super(watchGrp, 'arg[$index]');
|
| +
|
| + void acceptValue(object) {
|
| + watchRecord.dirtyArgs = true;
|
| + watchRecord.args[index] = object;
|
| + }
|
| +}
|
| +
|
| +class _InvokeHandler extends _Handler implements _ArgHandlerList {
|
| + _ArgHandler _argHandlerHead, _argHandlerTail;
|
| +
|
| + _InvokeHandler(WatchGroup watchGrp, String expression)
|
| + : super(watchGrp, expression);
|
| +
|
| + void acceptValue(object) {
|
| + watchRecord.object = object;
|
| + }
|
| +
|
| + void onChange(ChangeRecord<_Handler> record) {
|
| + super.onChange(record);
|
| + }
|
| +
|
| + void _releaseWatch() {
|
| + (watchRecord as _EvalWatchRecord).remove();
|
| + }
|
| +
|
| + void release() {
|
| + super.release();
|
| + _ArgHandler current = _argHandlerHead;
|
| + while(current != null) {
|
| + current.release();
|
| + current = current._nextArgHandler;
|
| + }
|
| + }
|
| +}
|
| +
|
| +
|
| +class _EvalWatchRecord implements WatchRecord<_Handler>, ChangeRecord<_Handler> {
|
| + static const int _MODE_DELETED_ = -1;
|
| + static const int _MODE_MARKER_ = 0;
|
| + static const int _MODE_FUNCTION_ = 1;
|
| + static const int _MODE_FUNCTION_APPLY_ = 2;
|
| + static const int _MODE_NULL_ = 3;
|
| + static const int _MODE_FIELD_CLOSURE_ = 4;
|
| + static const int _MODE_MAP_CLOSURE_ = 5;
|
| + static const int _MODE_METHOD_ = 6;
|
| + WatchGroup watchGrp;
|
| + final _Handler handler;
|
| + final List args;
|
| + final Symbol symbol;
|
| + final String name;
|
| + int mode;
|
| + /* dartbug.com/16401 Function*/ var fn;
|
| + InstanceMirror _instanceMirror;
|
| + bool dirtyArgs = true;
|
| +
|
| + dynamic currentValue, previousValue, _object;
|
| + _EvalWatchRecord _previousEvalWatch, _nextEvalWatch;
|
| +
|
| + _EvalWatchRecord(this.watchGrp, this.handler, this.fn, name, int arity)
|
| + : args = new List(arity),
|
| + name = name,
|
| + symbol = name == null ? null : new Symbol(name) {
|
| + if (fn is FunctionApply) {
|
| + mode = _MODE_FUNCTION_APPLY_;
|
| + } else if (fn is Function) {
|
| + mode = _MODE_FUNCTION_;
|
| + } else {
|
| + mode = _MODE_NULL_;
|
| + }
|
| + }
|
| +
|
| + _EvalWatchRecord.marker()
|
| + : mode = _MODE_MARKER_,
|
| + watchGrp = null,
|
| + handler = null,
|
| + args = null,
|
| + fn = null,
|
| + symbol = null,
|
| + name = null;
|
| +
|
| + _EvalWatchRecord.constant(_Handler handler, dynamic constantValue)
|
| + : mode = _MODE_MARKER_,
|
| + handler = handler,
|
| + currentValue = constantValue,
|
| + watchGrp = null,
|
| + args = null,
|
| + fn = null,
|
| + symbol = null,
|
| + name = null;
|
| +
|
| + get field => '()';
|
| +
|
| + get object => _object;
|
| +
|
| + set object(value) {
|
| + assert(mode != _MODE_DELETED_);
|
| + assert(mode != _MODE_MARKER_);
|
| + assert(mode != _MODE_FUNCTION_);
|
| + assert(mode != _MODE_FUNCTION_APPLY_);
|
| + assert(symbol != null);
|
| + _object = value;
|
| +
|
| + if (value == null) {
|
| + mode = _MODE_NULL_;
|
| + } else {
|
| + if (value is Map) {
|
| + mode = _MODE_MAP_CLOSURE_;
|
| + } else {
|
| + _instanceMirror = reflect(value);
|
| + mode = _hasMethod(_instanceMirror.type, symbol)
|
| + ? _MODE_METHOD_
|
| + : _MODE_FIELD_CLOSURE_;
|
| + }
|
| + }
|
| + }
|
| +
|
| + ChangeRecord<_Handler> check() {
|
| + var value;
|
| + switch (mode) {
|
| + case _MODE_MARKER_:
|
| + case _MODE_NULL_:
|
| + return null;
|
| + case _MODE_FUNCTION_:
|
| + if (!dirtyArgs) return null;
|
| + value = Function.apply(fn, args);
|
| + dirtyArgs = false;
|
| + break;
|
| + case _MODE_FUNCTION_APPLY_:
|
| + if (!dirtyArgs) return null;
|
| + value = (fn as FunctionApply).apply(args);
|
| + dirtyArgs = false;
|
| + break;
|
| + case _MODE_FIELD_CLOSURE_:
|
| + var closure = _instanceMirror.getField(symbol).reflectee;
|
| + value = closure == null ? null : Function.apply(closure, args);
|
| + break;
|
| + case _MODE_MAP_CLOSURE_:
|
| + var closure = object[name];
|
| + value = closure == null ? null : Function.apply(closure, args);
|
| + break;
|
| + case _MODE_METHOD_:
|
| + value = _instanceMirror.invoke(symbol, args).reflectee;
|
| + break;
|
| + default:
|
| + assert(false);
|
| + }
|
| +
|
| + var current = currentValue;
|
| + if (!identical(current, value)) {
|
| + if (value is String && current is String && value == current) {
|
| + // it is really the same, recover and save so next time identity is same
|
| + current = value;
|
| + } else {
|
| + previousValue = current;
|
| + currentValue = value;
|
| + handler.onChange(this);
|
| + return this;
|
| + }
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + get nextChange => null;
|
| +
|
| + void remove() {
|
| + assert(mode != _MODE_DELETED_);
|
| + assert((mode = _MODE_DELETED_) == _MODE_DELETED_); // Mark as deleted.
|
| + watchGrp._evalCost--;
|
| + _EvalWatchList._remove(watchGrp, this);
|
| + }
|
| +
|
| + String toString() {
|
| + if (mode == _MODE_MARKER_) return 'MARKER[$currentValue]';
|
| + return '${watchGrp.id}:${handler.expression}';
|
| + }
|
| +
|
| + static final Function _hasMethod = (() {
|
| + var objectClassMirror = reflectClass(Object);
|
| + Set<Symbol> objectClassInstanceMethods =
|
| + new Set<Symbol>.from([#toString, #noSuchMethod]);
|
| + try {
|
| + // Use ClassMirror.instanceMembers if available. It contains local
|
| + // as well as inherited members.
|
| + objectClassMirror.instanceMembers;
|
| + // For SDK 1.2 we have to use a somewhat complicated helper for this
|
| + // to work around bugs in the dart2js implementation.
|
| + return (type, symbol) {
|
| + // Always allow instance methods found in the Object class. This makes
|
| + // it easier to work around a few bugs in the dart2js implementation.
|
| + if (objectClassInstanceMethods.contains(symbol)) return true;
|
| + // Work around http://dartbug.com/16309 which causes access to the
|
| + // instance members of certain builtin types to throw exceptions
|
| + // while traversing the superclass chain.
|
| + var mirror;
|
| + try {
|
| + mirror = type.instanceMembers[symbol];
|
| + } on UnsupportedError catch (e) {
|
| + mirror = type.declarations[symbol];
|
| + }
|
| + // Work around http://dartbug.com/15760 which causes noSuchMethod
|
| + // forwarding stubs to be treated as members of all classes. We have
|
| + // already checked for the real instance methods in Object, so if the
|
| + // owner of this method is Object we simply filter it out.
|
| + if (mirror is !MethodMirror) return false;
|
| + return mirror.owner != objectClassMirror;
|
| + };
|
| + } on NoSuchMethodError catch (e) {
|
| + // For SDK 1.0 we fall back to just using the local members.
|
| + return (type, symbol) => type.members[symbol] is MethodMirror;
|
| + } on UnimplementedError catch (e) {
|
| + // For SDK 1.1 we fall back to just using the local declarations.
|
| + return (type, symbol) => type.declarations[symbol] is MethodMirror;
|
| + }
|
| + return null;
|
| + })();
|
| +
|
| +}
|
|
|