| Index: lib/watcher.dart
|
| diff --git a/lib/watcher.dart b/lib/watcher.dart
|
| index febdcb18661c788060e771117d9867d8bb3d1a02..0139d72fca1f779db6b9fe6496b406cbfae9a344 100644
|
| --- a/lib/watcher.dart
|
| +++ b/lib/watcher.dart
|
| @@ -50,6 +50,7 @@ import 'dart:async';
|
| import 'dart:collection' hide LinkedList, LinkedListEntry;
|
| import 'observe.dart';
|
| import 'src/linked_list.dart';
|
| +import 'package:logging/logging.dart';
|
|
|
| /**
|
| * True to use the [observe] library instead of watchers.
|
| @@ -65,6 +66,47 @@ import 'src/linked_list.dart';
|
| bool useObservers = false;
|
|
|
| /**
|
| + * True to enable more verbose debugging messages. This is useful for tracing
|
| + * down errors that produce loops in the watcher evaluation order, for example
|
| + * when you see an error message of the form "_Possible loop in watchers
|
| + * propagation, stopped dispatch_".
|
| + */
|
| +bool verboseDebugMessages = false;
|
| +
|
| +
|
| +/**
|
| + * Function used to record the current stack trace when [verboseDebugMessages]
|
| + * is true. This can be replaced if you prefer to format strack traces
|
| + * differently or filter frames of the stack traces. For
|
| + * example using <http://pub.dartlang.org/packages/stack_trace>, you can do:
|
| + *
|
| + * import 'package:watcher/watcher.dart';
|
| + * import 'package:stack_trace/stack_trace.dart';
|
| + * main() {
|
| + * verboseDebugMessages = true;
|
| + * readCurrentStackTrace = () {
|
| + * try { throw "" ; } catch (e, trace) {
|
| + * return new Trace.from(trace).terse.toString();
|
| + * }
|
| + * };
|
| + * ...
|
| + * }
|
| + */
|
| +Function readCurrentStackTrace = () {
|
| + try {
|
| + throw "";
|
| + } catch (e, trace) {
|
| + return trace.toString();
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * Log for messages produced at runtime by this library. Logging can be
|
| + * configured by accessing Logger.root from the logging library.
|
| + */
|
| +final Logger _logger = new Logger('watcher');
|
| +
|
| +/**
|
| * Watch for changes in [target]. The [callback] function will be called when
|
| * [dispatch] is called and the value represented by [target] had changed. The
|
| * returned function can be used to unregister this watcher.
|
| @@ -92,11 +134,13 @@ bool useObservers = false;
|
| * This is syntactic sugar for using the getter portion of a [Handle].
|
| * watch(handle, ...) // equivalent to `watch(handle._getter, ...)`
|
| */
|
| -ChangeUnobserver watch(target, ChangeObserver callback, [String debugName]) {
|
| +ChangeUnobserver watch(target, ChangeObserver callback,
|
| + [String debugName, String location]) {
|
| if (useObservers) return observe(target, callback);
|
|
|
| if (callback == null) return () {}; // no use in passing null as a callback.
|
| if (_watchers == null) _watchers = new LinkedList<_Watcher>();
|
| + debugName = debugName == null ? '<unnamed>' : debugName;
|
| Function exp;
|
| _WatcherType watcherType = _WatcherType.OTHER;
|
| if (target is Handle) {
|
| @@ -116,10 +160,7 @@ ChangeUnobserver watch(target, ChangeObserver callback, [String debugName]) {
|
| watcherType = _WatcherType.HASH_MAP;
|
| }
|
| } catch (e, trace) { // in case target() throws some error
|
| - // TODO(sigmund): use logging instead of print when logger is in the SDK
|
| - // and available via pub (see dartbug.com/4363)
|
| - print('error: evaluating ${debugName != null ? debugName : "<unnamed>"} '
|
| - 'watcher threw error ($e, $trace)');
|
| + _logger.warning('evaluating $debugName watcher threw error ($e, $trace)');
|
| }
|
| } else if (target is List) {
|
| exp = () => target;
|
| @@ -135,7 +176,12 @@ ChangeUnobserver watch(target, ChangeObserver callback, [String debugName]) {
|
| watcherType = _WatcherType.HASH_MAP;
|
| }
|
|
|
| - var watcher = _createWatcher(watcherType, exp, callback, debugName);
|
| + if (verboseDebugMessages && location == null
|
| + && readCurrentStackTrace != null) {
|
| + location = readCurrentStackTrace();
|
| + }
|
| +
|
| + var watcher = _createWatcher(watcherType, exp, callback, debugName, location);
|
| var node = _watchers.add(watcher);
|
| return node.remove;
|
| }
|
| @@ -145,16 +191,16 @@ ChangeUnobserver watch(target, ChangeObserver callback, [String debugName]) {
|
| * [debugName].
|
| */
|
| _Watcher _createWatcher(_WatcherType type, Function exp,
|
| - ChangeObserver callback, String debugName) {
|
| + ChangeObserver callback, String debugName, String location) {
|
| switch(type) {
|
| case _WatcherType.LIST:
|
| - return new _ListWatcher(exp, callback, debugName);
|
| + return new _ListWatcher(exp, callback, debugName, location);
|
| case _WatcherType.ORDERED_MAP:
|
| - return new _OrderDependantMapWatcher(exp, callback, debugName);
|
| + return new _OrderDependantMapWatcher(exp, callback, debugName, location);
|
| case _WatcherType.HASH_MAP:
|
| - return new _HashMapWatcher(exp, callback, debugName);
|
| + return new _HashMapWatcher(exp, callback, debugName, location);
|
| default:
|
| - return new _Watcher(exp, callback, debugName);
|
| + return new _Watcher(exp, callback, debugName, location);
|
| }
|
| }
|
|
|
| @@ -163,8 +209,9 @@ _Watcher _createWatcher(_WatcherType type, Function exp,
|
| * passed to [callback] will have `null` as the old value, and the current
|
| * evaluation of [exp] as the new value.
|
| */
|
| -ChangeUnobserver watchAndInvoke(exp, callback, [debugName]) {
|
| - var res = watch(exp, callback, debugName);
|
| +ChangeUnobserver watchAndInvoke(exp, ChangeObserver callback,
|
| + [String debugName, String location]) {
|
| + var res = watch(exp, callback, debugName, location);
|
| // TODO(jmesserly): this should be "is Getter" once dart2js bug is fixed.
|
|
|
| var value = exp;
|
| @@ -192,6 +239,13 @@ class _Watcher {
|
| /** Name used to debug. */
|
| final String debugName;
|
|
|
| + /** Location where the watcher was first installed (for debugging). */
|
| + String location;
|
| +
|
| + /** Unique id used for debugging purposes. */
|
| + final int _uniqueId;
|
| + static int _nextId = 0;
|
| +
|
| /** Function that retrieves the value being watched. */
|
| final Getter _getter;
|
|
|
| @@ -201,11 +255,12 @@ class _Watcher {
|
| /** Last value observed on the matched expression. */
|
| var _lastValue;
|
|
|
| - _Watcher(this._getter, this._callback, this.debugName) {
|
| + _Watcher(this._getter, this._callback, this.debugName, this.location)
|
| + : _uniqueId = _nextId++ {
|
| _lastValue = _getter();
|
| }
|
|
|
| - String toString() => debugName == null ? '<unnamed>' : debugName;
|
| + String toString() => '$debugName (id: #$_uniqueId)';
|
|
|
| /** Detect if any changes occurred and if so invoke [_callback]. */
|
| bool compareAndNotify() {
|
| @@ -222,6 +277,14 @@ class _Watcher {
|
| bool _compare(currentValue) => _lastValue != currentValue;
|
|
|
| void _update(currentValue) {
|
| + if (verboseDebugMessages) {
|
| + if (location != null) {
|
| + _logger.info('watcher updated: $this, defined at:\n$location');
|
| + location = null;
|
| + } else {
|
| + _logger.info('watcher updated: $this');
|
| + }
|
| + }
|
| _lastValue = currentValue;
|
| }
|
|
|
| @@ -230,14 +293,14 @@ class _Watcher {
|
| try {
|
| return _getter();
|
| } catch (e, trace) {
|
| - print('error: evaluating $this watcher threw an exception ($e, $trace)');
|
| + _logger.warning('$this watcher threw an exception: $e, $trace');
|
| }
|
| return _lastValue;
|
| }
|
| }
|
|
|
| /** Bound for the [dispatch] algorithm. */
|
| -final int _maxIter = 10;
|
| +final int maxNumIterations = 10;
|
|
|
| /**
|
| * Scan all registered watchers and invoke their callbacks if the watched value
|
| @@ -257,9 +320,9 @@ void dispatch() {
|
| dirty = true;
|
| }
|
| }
|
| - } while (dirty && ++total < _maxIter);
|
| - if (total == _maxIter) {
|
| - print('Possible loop in watchers propagation, stopped dispatch.');
|
| + } while (dirty && ++total < maxNumIterations);
|
| + if (total == maxNumIterations) {
|
| + _logger.warning('Possible loop in watchers propagation, stopped dispatch.');
|
| }
|
| }
|
|
|
| @@ -321,8 +384,9 @@ class Handle<T> {
|
| */
|
| class _ListWatcher<T> extends _Watcher {
|
|
|
| - _ListWatcher(getter, ChangeObserver callback, String debugName)
|
| - : super(getter, callback, debugName) {
|
| + _ListWatcher(getter, ChangeObserver callback, String debugName,
|
| + String location)
|
| + : super(getter, callback, debugName, location) {
|
| _update(_safeRead());
|
| }
|
|
|
| @@ -342,8 +406,9 @@ class _ListWatcher<T> extends _Watcher {
|
| */
|
| class _HashMapWatcher<K, V> extends _Watcher {
|
|
|
| - _HashMapWatcher(getter, ChangeObserver callback, String debugName)
|
| - : super(getter, callback, debugName) {
|
| + _HashMapWatcher(getter, ChangeObserver callback, String debugName,
|
| + String location)
|
| + : super(getter, callback, debugName, location) {
|
| _update(_safeRead());
|
| }
|
|
|
| @@ -371,8 +436,9 @@ class _HashMapWatcher<K, V> extends _Watcher {
|
| */
|
| class _OrderDependantMapWatcher<K, V> extends _Watcher {
|
|
|
| - _OrderDependantMapWatcher(getter, ChangeObserver callback, String debugName)
|
| - : super(getter, callback, debugName) {
|
| + _OrderDependantMapWatcher(getter, ChangeObserver callback, String debugName,
|
| + String location)
|
| + : super(getter, callback, debugName, location) {
|
| _update(_safeRead());
|
| }
|
|
|
|
|