| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 * This library is used to implement [Observable] types. | 6 * This library is used to implement [Observable] types. |
| 7 * | 7 * |
| 8 * It exposes lower level functionality such as [hasObservers], [observeReads] | 8 * It exposes lower level functionality such as [hasObservers], [observeReads] |
| 9 * [notifyChange] and [notifyRead]. | 9 * [notifyChange] and [notifyRead]. |
| 10 * | 10 * |
| (...skipping 207 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 218 * Returns a function that can be used to stop observation. | 218 * Returns a function that can be used to stop observation. |
| 219 * Calling this makes it possible for the garbage collector to reclaim memory | 219 * Calling this makes it possible for the garbage collector to reclaim memory |
| 220 * associated with the observation and prevents further calls to [callback]. | 220 * associated with the observation and prevents further calls to [callback]. |
| 221 * | 221 * |
| 222 * You can force a synchronous change delivery at any time by calling | 222 * You can force a synchronous change delivery at any time by calling |
| 223 * [deliverChangesSync]. Calling this method if there are no changes has no | 223 * [deliverChangesSync]. Calling this method if there are no changes has no |
| 224 * effect. If changes are delivered by deliverChangesSync, they will not be | 224 * effect. If changes are delivered by deliverChangesSync, they will not be |
| 225 * delivered again asynchronously, unless the value is changed again. | 225 * delivered again asynchronously, unless the value is changed again. |
| 226 */ | 226 */ |
| 227 ChangeUnobserver observeChanges(Observable obj, ChangeRecordObserver observer) { | 227 ChangeUnobserver observeChanges(Observable obj, ChangeRecordObserver observer) { |
| 228 if (obj._observers == null) obj._observers = new LinkedList(); | 228 if (obj.$_observers == null) obj.$_observers = new LinkedList(); |
| 229 var node = obj._observers.add(observer); | 229 var node = obj.$_observers.add(observer); |
| 230 return node.remove; | 230 return node.remove; |
| 231 } | 231 } |
| 232 | 232 |
| 233 | 233 |
| 234 /** | 234 /** |
| 235 * Converts the [Iterable], [Set] or [Map] to an [ObservableList], | 235 * Converts the [Iterable], [Set] or [Map] to an [ObservableList], |
| 236 * [ObservableSet] or [ObservableMap] respectively. | 236 * [ObservableSet] or [ObservableMap] respectively. |
| 237 * | 237 * |
| 238 * The resulting object will contain a shallow copy of the data. | 238 * The resulting object will contain a shallow copy of the data. |
| 239 * If [value] is not one of those collection types, it will be returned | 239 * If [value] is not one of those collection types, it will be returned |
| (...skipping 19 matching lines...) Expand all Loading... |
| 259 | 259 |
| 260 /** | 260 /** |
| 261 * An observable object. This is used by data in model-view architectures | 261 * An observable object. This is used by data in model-view architectures |
| 262 * to notify interested parties of changes. | 262 * to notify interested parties of changes. |
| 263 * | 263 * |
| 264 * Most of the methods for observation are static methods to keep them | 264 * Most of the methods for observation are static methods to keep them |
| 265 * stratified from the objects being observed. This is a similar to the design | 265 * stratified from the objects being observed. This is a similar to the design |
| 266 * of Mirrors. | 266 * of Mirrors. |
| 267 */ | 267 */ |
| 268 class Observable { | 268 class Observable { |
| 269 // TODO(jmesserly): make these fields private once we have mixins in Dart VM. | |
| 270 | |
| 271 /** Observers for this object. Uses a linked-list for fast removal. */ | 269 /** Observers for this object. Uses a linked-list for fast removal. */ |
| 272 LinkedList<ChangeRecordObserver> _observers; | 270 // TODO(jmesserly): make these fields private again once dart2js bugs around |
| 271 // mixins and private fields are fixed. |
| 272 // TODO(jmesserly): removed type annotation here to workaround a VM checked |
| 273 // mode bug. It should be: LinkedList<ChangeRecordObserver> |
| 274 var $_observers; |
| 273 | 275 |
| 274 /** Changes to this object since last batch was delivered. */ | 276 /** Changes to this object since last batch was delivered. */ |
| 275 List<ChangeRecord> _changes; | 277 List<ChangeRecord> $_changes; |
| 276 | 278 |
| 277 final int hashCode = ++Observable._nextHashCode; | 279 final int hashCode = ++Observable.$_nextHashCode; |
| 278 | 280 |
| 279 // TODO(jmessery): workaround for VM bug http://dartbug.com/5746 | 281 // TODO(jmessery): workaround for VM bug http://dartbug.com/5746 |
| 280 // We need hashCode to be fast for _ExpressionObserver to work. | 282 // We need hashCode to be fast for _ExpressionObserver to work. |
| 281 static int _nextHashCode = 0; | 283 static int $_nextHashCode = 0; |
| 282 } | 284 } |
| 283 | 285 |
| 284 // Note: these are not instance methods of Observable, to make it clear that | 286 // Note: these are not instance methods of Observable, to make it clear that |
| 285 // they aren't themselves being observed. It is the same reason that mirrors and | 287 // they aren't themselves being observed. It is the same reason that mirrors and |
| 286 // EcmaScript's Object.observe are stratified. | 288 // EcmaScript's Object.observe are stratified. |
| 287 // TODO(jmesserly): this makes it impossible to proxy an Observable. Is that an | 289 // TODO(jmesserly): this makes it impossible to proxy an Observable. Is that an |
| 288 // acceptable restriction? | 290 // acceptable restriction? |
| 289 | 291 |
| 290 /** | 292 /** |
| 291 * True if [self] has any observers, and should call [notifyChange] for | 293 * True if [self] has any observers, and should call [notifyChange] for |
| 292 * changes. | 294 * changes. |
| 293 * | 295 * |
| 294 * Note: this is used by objects implementing [Observable]. | 296 * Note: this is used by objects implementing [Observable]. |
| 295 * You should not need it if your type is marked `@observable`. | 297 * You should not need it if your type is marked `@observable`. |
| 296 */ | 298 */ |
| 297 bool hasObservers(Observable self) => | 299 bool hasObservers(Observable self) => |
| 298 self._observers != null && self._observers.head != null; | 300 self.$_observers != null && self.$_observers.head != null; |
| 299 | 301 |
| 300 /** | 302 /** |
| 301 * True if we are observing reads. This should be checked before calling | 303 * True if we are observing reads. This should be checked before calling |
| 302 * [notifyRead]. | 304 * [notifyRead]. |
| 303 * | 305 * |
| 304 * Note: this is used by objects implementing [Observable]. | 306 * Note: this is used by objects implementing [Observable]. |
| 305 * You should not need it if your type is marked `@observable`. | 307 * You should not need it if your type is marked `@observable`. |
| 306 */ | 308 */ |
| 307 bool get observeReads => _activeObserver != null; | 309 bool get observeReads => _activeObserver != null; |
| 308 | 310 |
| (...skipping 28 matching lines...) Expand all Loading... |
| 337 // the value actually changed. If not don't signal a change event. | 339 // the value actually changed. If not don't signal a change event. |
| 338 // This helps programmers avoid some common cases of cycles in their code. | 340 // This helps programmers avoid some common cases of cycles in their code. |
| 339 if ((type & (ChangeRecord.INSERT | ChangeRecord.REMOVE)) == 0) { | 341 if ((type & (ChangeRecord.INSERT | ChangeRecord.REMOVE)) == 0) { |
| 340 if (oldValue == newValue) return; | 342 if (oldValue == newValue) return; |
| 341 } | 343 } |
| 342 | 344 |
| 343 if (_changedObjects == null) { | 345 if (_changedObjects == null) { |
| 344 _changedObjects = []; | 346 _changedObjects = []; |
| 345 setImmediate(deliverChangesSync); | 347 setImmediate(deliverChangesSync); |
| 346 } | 348 } |
| 347 if (self._changes == null) { | 349 if (self.$_changes == null) { |
| 348 self._changes = []; | 350 self.$_changes = []; |
| 349 _changedObjects.add(self); | 351 _changedObjects.add(self); |
| 350 } | 352 } |
| 351 self._changes.add(new ChangeRecord(type, key, oldValue, newValue)); | 353 self.$_changes.add(new ChangeRecord(type, key, oldValue, newValue)); |
| 352 } | 354 } |
| 353 | 355 |
| 354 // Optimizations to avoid extra work if observing const/final data. | 356 // Optimizations to avoid extra work if observing const/final data. |
| 355 void _doNothing() {} | 357 void _doNothing() {} |
| 356 | 358 |
| 357 /** | 359 /** |
| 358 * The current observer that is tracking reads, or null if we aren't tracking | 360 * The current observer that is tracking reads, or null if we aren't tracking |
| 359 * reads. Reads are tracked when executing [_ExpressionObserver._observe]. | 361 * reads. Reads are tracked when executing [_ExpressionObserver._observe]. |
| 360 */ | 362 */ |
| 361 _ExpressionObserver _activeObserver; | 363 _ExpressionObserver _activeObserver; |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 405 return; | 407 return; |
| 406 } | 408 } |
| 407 | 409 |
| 408 if (changedObjects != null) { | 410 if (changedObjects != null) { |
| 409 for (var observable in changedObjects) { | 411 for (var observable in changedObjects) { |
| 410 // TODO(jmesserly): freeze the "changes" list? | 412 // TODO(jmesserly): freeze the "changes" list? |
| 411 // If one observer incorrectly mutates it, it will affect what future | 413 // If one observer incorrectly mutates it, it will affect what future |
| 412 // observers see, possibly leading to subtle bugs. | 414 // observers see, possibly leading to subtle bugs. |
| 413 // OTOH, I don't want to add a defensive copy here. Maybe a wrapper that | 415 // OTOH, I don't want to add a defensive copy here. Maybe a wrapper that |
| 414 // prevents mutation, or a ListBuilder of some sort than can be frozen. | 416 // prevents mutation, or a ListBuilder of some sort than can be frozen. |
| 415 var changes = observable._changes; | 417 var changes = observable.$_changes; |
| 416 observable._changes = null; | 418 observable.$_changes = null; |
| 417 | 419 |
| 418 for (var n = observable._observers.head; n != null; n = n.next) { | 420 for (var n = observable.$_observers.head; n != null; n = n.next) { |
| 419 var observer = n.value; | 421 var observer = n.value; |
| 420 try { | 422 try { |
| 421 observer(changes); | 423 observer(changes); |
| 422 } catch (error, trace) { | 424 } catch (error, trace) { |
| 423 onObserveUnhandledError(error, trace, observer, 'from $observable'); | 425 onObserveUnhandledError(error, trace, observer, 'from $observable'); |
| 424 } | 426 } |
| 425 } | 427 } |
| 426 } | 428 } |
| 427 } | 429 } |
| 428 | 430 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 445 */ | 447 */ |
| 446 void _diagnoseCircularLimit(List<Observable> changedObjects, | 448 void _diagnoseCircularLimit(List<Observable> changedObjects, |
| 447 Map<int, _ExpressionObserver> changedExpressions) { | 449 Map<int, _ExpressionObserver> changedExpressions) { |
| 448 // TODO(jmesserly,sigmund): we could do purity checks when running "observe" | 450 // TODO(jmesserly,sigmund): we could do purity checks when running "observe" |
| 449 // itself, to detect if it causes writes to happen. I think that case is less | 451 // itself, to detect if it causes writes to happen. I think that case is less |
| 450 // common than cycles caused by the notifications though. | 452 // common than cycles caused by the notifications though. |
| 451 | 453 |
| 452 var trace = []; | 454 var trace = []; |
| 453 if (changedObjects != null) { | 455 if (changedObjects != null) { |
| 454 for (var observable in changedObjects) { | 456 for (var observable in changedObjects) { |
| 455 var changes = observable._changes; | 457 var changes = observable.$_changes; |
| 456 trace.add('$observable $changes'); | 458 trace.add('$observable $changes'); |
| 457 } | 459 } |
| 458 } | 460 } |
| 459 | 461 |
| 460 if (changedExpressions != null) { | 462 if (changedExpressions != null) { |
| 461 for (var exprObserver in changedExpressions.values) { | 463 for (var exprObserver in changedExpressions.values) { |
| 462 var change = exprObserver._deliver(); | 464 var change = exprObserver._deliver(); |
| 463 if (change != null) trace.add('$exprObserver $change'); | 465 if (change != null) trace.add('$exprObserver $change'); |
| 464 } | 466 } |
| 465 } | 467 } |
| (...skipping 218 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 684 /** | 686 /** |
| 685 * The type of the `@observable` annotation. | 687 * The type of the `@observable` annotation. |
| 686 * | 688 * |
| 687 * Library private because you should be able to use the [observable] field | 689 * Library private because you should be able to use the [observable] field |
| 688 * to get the one and only instance. We could make it public though, if anyone | 690 * to get the one and only instance. We could make it public though, if anyone |
| 689 * needs it for some reason. | 691 * needs it for some reason. |
| 690 */ | 692 */ |
| 691 class _ObservableAnnotation { | 693 class _ObservableAnnotation { |
| 692 const _ObservableAnnotation(); | 694 const _ObservableAnnotation(); |
| 693 } | 695 } |
| OLD | NEW |