Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(58)

Side by Side Diff: lib/watcher.dart

Issue 5885170347409408: Add location information to watchers to make them more debuggable. (Closed) Base URL: git@github.com:dart-lang/web-ui.git@master
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /** 5 /**
6 * A library to observe changes on Dart objects. 6 * A library to observe changes on Dart objects.
7 * 7 *
8 * Similar to the principle of watchers in AngularJS, this library provides the 8 * Similar to the principle of watchers in AngularJS, this library provides the
9 * mechanisms to observe and react to changes that happen in an application's 9 * mechanisms to observe and react to changes that happen in an application's
10 * data model. 10 * data model.
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
43 * A common design pattern for MVC applications is to call [dispatch] at the end 43 * A common design pattern for MVC applications is to call [dispatch] at the end
44 * of each event loop (e.g. after each UI event is fired). Our view library does 44 * of each event loop (e.g. after each UI event is fired). Our view library does
45 * this automatically. 45 * this automatically.
46 */ 46 */
47 library watcher; 47 library watcher;
48 48
49 import 'dart:async'; 49 import 'dart:async';
50 import 'dart:collection' hide LinkedList, LinkedListEntry; 50 import 'dart:collection' hide LinkedList, LinkedListEntry;
51 import 'observe.dart'; 51 import 'observe.dart';
52 import 'src/linked_list.dart'; 52 import 'src/linked_list.dart';
53 import 'package:logging/logging.dart';
53 54
54 /** 55 /**
55 * True to use the [observe] library instead of watchers. 56 * True to use the [observe] library instead of watchers.
56 * 57 *
57 * Observers require the [observable] annotation on objects and for collection 58 * Observers require the [observable] annotation on objects and for collection
58 * types to be observable, such as [ObservableList]. But in return they offer 59 * types to be observable, such as [ObservableList]. But in return they offer
59 * better performance and more precise change tracking. [dispatch] is not 60 * better performance and more precise change tracking. [dispatch] is not
60 * required with observers, and changes to observable objects are always 61 * required with observers, and changes to observable objects are always
61 * detected. 62 * detected.
62 * 63 *
63 * Currently this flag is experimental, but it may be the default in the future. 64 * Currently this flag is experimental, but it may be the default in the future.
64 */ 65 */
65 bool useObservers = false; 66 bool useObservers = false;
66 67
67 /** 68 /**
69 * True to enable more verbose debugging messages. This is useful for tracing
70 * down errors that produce loops in the watcher evaluation order, for example
71 * when you see an error message of the form "_Possible loop in watchers
72 * propagation, stopped dispatch_".
73 */
74 bool verboseDebugMessages = false;
75
76 /**
77 * Log for messages produced at runtime by this library. Logging can be
78 * configured by accessing Logger.root from the logging library.
79 */
80 final Logger _logger = new Logger('watcher');
81
82 /**
68 * Watch for changes in [target]. The [callback] function will be called when 83 * Watch for changes in [target]. The [callback] function will be called when
69 * [dispatch] is called and the value represented by [target] had changed. The 84 * [dispatch] is called and the value represented by [target] had changed. The
70 * returned function can be used to unregister this watcher. 85 * returned function can be used to unregister this watcher.
71 * 86 *
72 * There are several values you can use for [target]: 87 * There are several values you can use for [target]:
73 * 88 *
74 * * A [Getter] function. 89 * * A [Getter] function.
75 * Use this to watch expressions as they change. For instance, to watch 90 * Use this to watch expressions as they change. For instance, to watch
76 * whether `a.b.c` changes, wrap it in a getter and call [watch] as follows: 91 * whether `a.b.c` changes, wrap it in a getter and call [watch] as follows:
77 * watch(() => a.b.c, ...) 92 * watch(() => a.b.c, ...)
78 * These targets are tracked to check for equality. If calling `target()` 93 * These targets are tracked to check for equality. If calling `target()`
79 * returns the same result, then the [callback] will not be invoked. In the 94 * returns the same result, then the [callback] will not be invoked. In the
80 * special case whe the getter returns a [List], we will treat the value in a 95 * special case whe the getter returns a [List], we will treat the value in a
81 * special way, similar to passing [List] directly as [target]. 96 * special way, similar to passing [List] directly as [target].
82 * **Important**: this library assumes that [Getter] is a read-only function 97 * **Important**: this library assumes that [Getter] is a read-only function
83 * and that it will consistently return the same value if called multiple 98 * and that it will consistently return the same value if called multiple
84 * times in a row. 99 * times in a row.
85 * 100 *
86 * * A [List]. 101 * * A [List].
87 * Use this to watch whether a list object changes. For instance, to detect 102 * Use this to watch whether a list object changes. For instance, to detect
88 * if an element is added or changed in a list can call [watch] as follows: 103 * if an element is added or changed in a list can call [watch] as follows:
89 * watch(list, ...) 104 * watch(list, ...)
90 * 105 *
91 * * A [Handle]. 106 * * A [Handle].
92 * This is syntactic sugar for using the getter portion of a [Handle]. 107 * This is syntactic sugar for using the getter portion of a [Handle].
93 * watch(handle, ...) // equivalent to `watch(handle._getter, ...)` 108 * watch(handle, ...) // equivalent to `watch(handle._getter, ...)`
94 */ 109 */
95 ChangeUnobserver watch(target, ChangeObserver callback, [String debugName]) { 110 ChangeUnobserver watch(target, ChangeObserver callback,
111 [String debugName, String location]) {
96 if (useObservers) return observe(target, callback); 112 if (useObservers) return observe(target, callback);
97 113
98 if (callback == null) return () {}; // no use in passing null as a callback. 114 if (callback == null) return () {}; // no use in passing null as a callback.
99 if (_watchers == null) _watchers = new LinkedList<_Watcher>(); 115 if (_watchers == null) _watchers = new LinkedList<_Watcher>();
116 debugName = debugName == null ? '<unnamed>' : debugName;
100 Function exp; 117 Function exp;
101 _WatcherType watcherType = _WatcherType.OTHER; 118 _WatcherType watcherType = _WatcherType.OTHER;
102 if (target is Handle) { 119 if (target is Handle) {
103 exp = (target as Handle)._getter; 120 exp = (target as Handle)._getter;
104 } else if (target is Function) { 121 } else if (target is Function) {
105 exp = target; 122 exp = target;
106 try { 123 try {
107 var val = target(); 124 var val = target();
108 if (val is List) { 125 if (val is List) {
109 watcherType = _WatcherType.LIST; 126 watcherType = _WatcherType.LIST;
110 } else if (val is Iterable) { 127 } else if (val is Iterable) {
111 watcherType = _WatcherType.LIST; 128 watcherType = _WatcherType.LIST;
112 exp = () => target().toList(); 129 exp = () => target().toList();
113 } else if ((val is LinkedHashMap) || (val is SplayTreeMap)) { 130 } else if ((val is LinkedHashMap) || (val is SplayTreeMap)) {
114 watcherType = _WatcherType.ORDERED_MAP; 131 watcherType = _WatcherType.ORDERED_MAP;
115 } else if (val is Map) { 132 } else if (val is Map) {
116 watcherType = _WatcherType.HASH_MAP; 133 watcherType = _WatcherType.HASH_MAP;
117 } 134 }
118 } catch (e, trace) { // in case target() throws some error 135 } catch (e, trace) { // in case target() throws some error
119 // TODO(sigmund): use logging instead of print when logger is in the SDK 136 _logger.warning('evaluating $debugName watcher threw error ($e, $trace)');
120 // and available via pub (see dartbug.com/4363)
121 print('error: evaluating ${debugName != null ? debugName : "<unnamed>"} '
122 'watcher threw error ($e, $trace)');
123 } 137 }
124 } else if (target is List) { 138 } else if (target is List) {
125 exp = () => target; 139 exp = () => target;
126 watcherType = _WatcherType.LIST; 140 watcherType = _WatcherType.LIST;
127 } else if (target is Iterable) { 141 } else if (target is Iterable) {
128 exp = () => target.toList(); 142 exp = () => target.toList();
129 watcherType = _WatcherType.LIST; 143 watcherType = _WatcherType.LIST;
130 } else if ((target is LinkedHashMap) || (target is SplayTreeMap)) { 144 } else if ((target is LinkedHashMap) || (target is SplayTreeMap)) {
131 exp = () => target; 145 exp = () => target;
132 watcherType = _WatcherType.ORDERED_MAP; 146 watcherType = _WatcherType.ORDERED_MAP;
133 } else if (target is Map) { 147 } else if (target is Map) {
134 exp = () => target; 148 exp = () => target;
135 watcherType = _WatcherType.HASH_MAP; 149 watcherType = _WatcherType.HASH_MAP;
136 } 150 }
137 151
138 var watcher = _createWatcher(watcherType, exp, callback, debugName); 152 if (verboseDebugMessages && location == null) {
153 // Record the current stack trace to help diagnoze loops.
154 try { throw ""; } catch (e, trace) {
Jennifer Messerly 2013/07/11 21:27:23 use _readCurrentStackTrace?
Siggi Cherem (dart-lang) 2013/07/11 22:07:01 Done.
155 location = trace.toString();
156 }
157 }
158
159 var watcher = _createWatcher(watcherType, exp, callback, debugName, location);
139 var node = _watchers.add(watcher); 160 var node = _watchers.add(watcher);
140 return node.remove; 161 return node.remove;
141 } 162 }
142 163
143 /** 164 /**
144 * Creates a watcher for [exp] of [type] with [callback] function and 165 * Creates a watcher for [exp] of [type] with [callback] function and
145 * [debugName]. 166 * [debugName].
146 */ 167 */
147 _Watcher _createWatcher(_WatcherType type, Function exp, 168 _Watcher _createWatcher(_WatcherType type, Function exp,
148 ChangeObserver callback, String debugName) { 169 ChangeObserver callback, String debugName, String location) {
149 switch(type) { 170 switch(type) {
150 case _WatcherType.LIST: 171 case _WatcherType.LIST:
151 return new _ListWatcher(exp, callback, debugName); 172 return new _ListWatcher(exp, callback, debugName, location);
152 case _WatcherType.ORDERED_MAP: 173 case _WatcherType.ORDERED_MAP:
153 return new _OrderDependantMapWatcher(exp, callback, debugName); 174 return new _OrderDependantMapWatcher(exp, callback, debugName, location);
154 case _WatcherType.HASH_MAP: 175 case _WatcherType.HASH_MAP:
155 return new _HashMapWatcher(exp, callback, debugName); 176 return new _HashMapWatcher(exp, callback, debugName, location);
156 default: 177 default:
157 return new _Watcher(exp, callback, debugName); 178 return new _Watcher(exp, callback, debugName, location);
158 } 179 }
159 } 180 }
160 181
161 /** 182 /**
162 * Add a watcher for [exp] and immediatly invoke [callback]. The watch event 183 * Add a watcher for [exp] and immediatly invoke [callback]. The watch event
163 * passed to [callback] will have `null` as the old value, and the current 184 * passed to [callback] will have `null` as the old value, and the current
164 * evaluation of [exp] as the new value. 185 * evaluation of [exp] as the new value.
165 */ 186 */
166 ChangeUnobserver watchAndInvoke(exp, callback, [debugName]) { 187 ChangeUnobserver watchAndInvoke(exp, ChangeObserver callback,
167 var res = watch(exp, callback, debugName); 188 [String debugName, String location]) {
189 var res = watch(exp, callback, debugName, location);
168 // TODO(jmesserly): this should be "is Getter" once dart2js bug is fixed. 190 // TODO(jmesserly): this should be "is Getter" once dart2js bug is fixed.
169 191
170 var value = exp; 192 var value = exp;
171 if (value is Function) { 193 if (value is Function) {
172 value = value(); 194 value = value();
173 } 195 }
174 if (value is Iterable && value is! List) { 196 if (value is Iterable && value is! List) {
175 // TODO(jmesserly): we do this for compat with watch and observe, see the 197 // TODO(jmesserly): we do this for compat with watch and observe, see the
176 // respective methods. 198 // respective methods.
177 value = value.toList(); 199 value = value.toList();
178 } 200 }
179 callback(new ChangeNotification(null, value)); 201 callback(new ChangeNotification(null, value));
180 return res; 202 return res;
181 } 203 }
182 204
183 /** Internal set of active watchers. */ 205 /** Internal set of active watchers. */
184 LinkedList<_Watcher> _watchers; 206 LinkedList<_Watcher> _watchers;
185 207
186 /** 208 /**
187 * An internal representation of a watcher. Contains the expression it watches, 209 * An internal representation of a watcher. Contains the expression it watches,
188 * the last value seen for it, and a callback to invoke when a change is 210 * the last value seen for it, and a callback to invoke when a change is
189 * detected. 211 * detected.
190 */ 212 */
191 class _Watcher { 213 class _Watcher {
192 /** Name used to debug. */ 214 /** Name used to debug. */
193 final String debugName; 215 final String debugName;
194 216
217 /** Location where the watcher was first installed (for debugging). */
218 String location;
219
220 /** Unique id used for debugging purposes. */
221 final int _uniqueId;
222 static int _nextId = 0;
223
195 /** Function that retrieves the value being watched. */ 224 /** Function that retrieves the value being watched. */
196 final Getter _getter; 225 final Getter _getter;
197 226
198 /** Callback to invoke when the value changes. */ 227 /** Callback to invoke when the value changes. */
199 final ChangeObserver _callback; 228 final ChangeObserver _callback;
200 229
201 /** Last value observed on the matched expression. */ 230 /** Last value observed on the matched expression. */
202 var _lastValue; 231 var _lastValue;
203 232
204 _Watcher(this._getter, this._callback, this.debugName) { 233 _Watcher(this._getter, this._callback, this.debugName, this.location)
234 : _uniqueId = _nextId++ {
205 _lastValue = _getter(); 235 _lastValue = _getter();
206 } 236 }
207 237
208 String toString() => debugName == null ? '<unnamed>' : debugName; 238 String toString() => '$debugName (id: #$_uniqueId)';
209 239
210 /** Detect if any changes occurred and if so invoke [_callback]. */ 240 /** Detect if any changes occurred and if so invoke [_callback]. */
211 bool compareAndNotify() { 241 bool compareAndNotify() {
212 var currentValue = _safeRead(); 242 var currentValue = _safeRead();
213 if (_compare(currentValue)) { 243 if (_compare(currentValue)) {
214 var oldValue = _lastValue; 244 var oldValue = _lastValue;
215 _update(currentValue); 245 _update(currentValue);
216 _callback(new ChangeNotification(oldValue, currentValue)); 246 _callback(new ChangeNotification(oldValue, currentValue));
217 return true; 247 return true;
218 } 248 }
219 return false; 249 return false;
220 } 250 }
221 251
222 bool _compare(currentValue) => _lastValue != currentValue; 252 bool _compare(currentValue) => _lastValue != currentValue;
223 253
224 void _update(currentValue) { 254 void _update(currentValue) {
255 if (verboseDebugMessages) {
256 if (location != null) {
257 _logger.info('watcher updated: $this, defined at:\n$location');
258 location = null;
259 } else {
260 _logger.info('watcher updated: $this');
261 }
262 }
225 _lastValue = currentValue; 263 _lastValue = currentValue;
226 } 264 }
227 265
228 /** Read [_getter] but detect whether exceptions were thrown. */ 266 /** Read [_getter] but detect whether exceptions were thrown. */
229 _safeRead() { 267 _safeRead() {
230 try { 268 try {
231 return _getter(); 269 return _getter();
232 } catch (e, trace) { 270 } catch (e, trace) {
233 print('error: evaluating $this watcher threw an exception ($e, $trace)'); 271 _logger.warning('$this watcher threw an exception: $e, $trace');
234 } 272 }
235 return _lastValue; 273 return _lastValue;
236 } 274 }
237 } 275 }
238 276
239 /** Bound for the [dispatch] algorithm. */ 277 /** Bound for the [dispatch] algorithm. */
240 final int _maxIter = 10; 278 final int maxNumIterations = 10;
241 279
242 /** 280 /**
243 * Scan all registered watchers and invoke their callbacks if the watched value 281 * Scan all registered watchers and invoke their callbacks if the watched value
244 * has changed. Because we allow listeners to modify other watched expressions, 282 * has changed. Because we allow listeners to modify other watched expressions,
245 * [dispatch] will reiterate until no changes occur or until we reach a 283 * [dispatch] will reiterate until no changes occur or until we reach a
246 * particular limit (10) to ensure termination. 284 * particular limit (10) to ensure termination.
247 */ 285 */
248 void dispatch() { 286 void dispatch() {
249 if (_watchers == null) return; 287 if (_watchers == null) return;
250 bool dirty; 288 bool dirty;
251 int total = 0; 289 int total = 0;
252 do { 290 do {
253 dirty = false; 291 dirty = false;
254 for (var watcher in _watchers) { 292 for (var watcher in _watchers) {
255 // Get the next node just in case this node gets remove by the watcher 293 // Get the next node just in case this node gets remove by the watcher
256 if (watcher.compareAndNotify()) { 294 if (watcher.compareAndNotify()) {
257 dirty = true; 295 dirty = true;
258 } 296 }
259 } 297 }
260 } while (dirty && ++total < _maxIter); 298 } while (dirty && ++total < maxNumIterations);
261 if (total == _maxIter) { 299 if (total == maxNumIterations) {
262 print('Possible loop in watchers propagation, stopped dispatch.'); 300 _logger.warning('Possible loop in watchers propagation, stopped dispatch.');
263 } 301 }
264 } 302 }
265 303
266 /** 304 /**
267 * An indirect getter. Basically a simple closure that returns a value, which is 305 * An indirect getter. Basically a simple closure that returns a value, which is
268 * the most common argument in [watch]. 306 * the most common argument in [watch].
269 */ 307 */
270 typedef T Getter<T>(); 308 typedef T Getter<T>();
271 309
272 /** An indirect setter. */ 310 /** An indirect setter. */
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
314 } 352 }
315 } 353 }
316 } 354 }
317 355
318 /** 356 /**
319 * A watcher for list objects. It stores as the last value a shallow copy of the 357 * A watcher for list objects. It stores as the last value a shallow copy of the
320 * list as it was when we last detected any changes. 358 * list as it was when we last detected any changes.
321 */ 359 */
322 class _ListWatcher<T> extends _Watcher { 360 class _ListWatcher<T> extends _Watcher {
323 361
324 _ListWatcher(getter, ChangeObserver callback, String debugName) 362 _ListWatcher(getter, ChangeObserver callback, String debugName,
325 : super(getter, callback, debugName) { 363 String location)
364 : super(getter, callback, debugName, location) {
326 _update(_safeRead()); 365 _update(_safeRead());
327 } 366 }
328 367
329 bool _compare(List<T> currentValue) { 368 bool _compare(List<T> currentValue) {
330 return _iterablesNotEqual(_lastValue, currentValue); 369 return _iterablesNotEqual(_lastValue, currentValue);
331 } 370 }
332 371
333 void _update(currentValue) { 372 void _update(currentValue) {
334 _lastValue = new List<T>.from(currentValue); 373 _lastValue = new List<T>.from(currentValue);
335 } 374 }
336 } 375 }
337 376
338 /** 377 /**
339 * A watcher for hash map objects. It stores as the last value a shallow copy 378 * A watcher for hash map objects. It stores as the last value a shallow copy
340 * of the map as it was when we last detected any changes. Order for the map 379 * of the map as it was when we last detected any changes. Order for the map
341 * does not matter for equality. 380 * does not matter for equality.
342 */ 381 */
343 class _HashMapWatcher<K, V> extends _Watcher { 382 class _HashMapWatcher<K, V> extends _Watcher {
344 383
345 _HashMapWatcher(getter, ChangeObserver callback, String debugName) 384 _HashMapWatcher(getter, ChangeObserver callback, String debugName,
346 : super(getter, callback, debugName) { 385 String location)
386 : super(getter, callback, debugName, location) {
347 _update(_safeRead()); 387 _update(_safeRead());
348 } 388 }
349 389
350 bool _compare(Map<K, V> currentValue) { 390 bool _compare(Map<K, V> currentValue) {
351 Iterable<K> keys = _lastValue.keys; 391 Iterable<K> keys = _lastValue.keys;
352 if (keys.length != currentValue.keys.length) return true; 392 if (keys.length != currentValue.keys.length) return true;
353 393
354 Iterator<K> keyIterator = keys.iterator; 394 Iterator<K> keyIterator = keys.iterator;
355 while (keyIterator.moveNext()) { 395 while (keyIterator.moveNext()) {
356 K key = keyIterator.current; 396 K key = keyIterator.current;
357 if (!currentValue.containsKey(key)) return true; 397 if (!currentValue.containsKey(key)) return true;
358 if (_lastValue[key] != currentValue[key]) return true; 398 if (_lastValue[key] != currentValue[key]) return true;
359 } 399 }
360 return false; 400 return false;
361 } 401 }
362 402
363 void _update(currentValue) { 403 void _update(currentValue) {
364 _lastValue = new Map<K, V>.from(currentValue); 404 _lastValue = new Map<K, V>.from(currentValue);
365 } 405 }
366 } 406 }
367 407
368 /** 408 /**
369 * A watcher for maps where key order matters. It stores as the last value a 409 * A watcher for maps where key order matters. It stores as the last value a
370 * shallow copy of the map as it was when we last detected any changes. 410 * shallow copy of the map as it was when we last detected any changes.
371 */ 411 */
372 class _OrderDependantMapWatcher<K, V> extends _Watcher { 412 class _OrderDependantMapWatcher<K, V> extends _Watcher {
373 413
374 _OrderDependantMapWatcher(getter, ChangeObserver callback, String debugName) 414 _OrderDependantMapWatcher(getter, ChangeObserver callback, String debugName,
375 : super(getter, callback, debugName) { 415 String location)
416 : super(getter, callback, debugName, location) {
376 _update(_safeRead()); 417 _update(_safeRead());
377 } 418 }
378 419
379 bool _compare(Map<K, V> currentValue) { 420 bool _compare(Map<K, V> currentValue) {
380 return _iterablesNotEqual(currentValue.keys, _lastValue.keys) || 421 return _iterablesNotEqual(currentValue.keys, _lastValue.keys) ||
381 _iterablesNotEqual(currentValue.values, _lastValue.values); 422 _iterablesNotEqual(currentValue.values, _lastValue.values);
382 } 423 }
383 424
384 void _update(currentValue) { 425 void _update(currentValue) {
385 _lastValue = new LinkedHashMap.from(currentValue); 426 _lastValue = new LinkedHashMap.from(currentValue);
(...skipping 19 matching lines...) Expand all
405 class _WatcherType { 446 class _WatcherType {
406 final _value; 447 final _value;
407 const _WatcherType._internal(this._value); 448 const _WatcherType._internal(this._value);
408 toString() => 'Enum.$_value'; 449 toString() => 'Enum.$_value';
409 450
410 static const LIST = const _WatcherType._internal('LIST'); 451 static const LIST = const _WatcherType._internal('LIST');
411 static const HASH_MAP = const _WatcherType._internal('HASH_MAP'); 452 static const HASH_MAP = const _WatcherType._internal('HASH_MAP');
412 static const ORDERED_MAP = const _WatcherType._internal('ORDERED_MAP'); 453 static const ORDERED_MAP = const _WatcherType._internal('ORDERED_MAP');
413 static const OTHER = const _WatcherType._internal('OTHER'); 454 static const OTHER = const _WatcherType._internal('OTHER');
414 } 455 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698