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

Side by Side Diff: pkg/observe/lib/src/observable.dart

Issue 19771010: implement dirty checking for @observable objects (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: logging for loops in dirty checking 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 | Annotate | Revision Log
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 part of observe;
6
7 /**
8 * Use `@observable` to make a field automatically observable.
9 */
10 const Object observable = const _ObservableAnnotation();
11
12 /**
13 * Interface representing an observable object. This is used by data in
14 * model-view architectures to notify interested parties of [changes].
15 *
16 * This object does not require any specific technique to implement
17 * observability. However if you implement change notification yourself, you
18 * should also implement [ChangeNotifier], so [dirtyCheck] knows to skip the
19 * object.
20 *
21 * You can use [ObservableBase] or [ObservableMixin] to implement this.
22 */
23 abstract class Observable {
24 /**
25 * The stream of change records to this object. Records will be delivered
26 * asynchronously.
27 *
28 * [deliverChanges] can be called to force synchronous delivery.
29 */
30 Stream<List<ChangeRecord>> get changes;
31
32 /**
33 * Synchronously deliver pending [changes]. Returns true if any records were
34 * delivered, otherwise false.
35 */
36 // TODO(jmesserly): this is a bit different from the ES Harmony version, which
37 // allows delivery of changes to a particular observer:
38 // http://wiki.ecmascript.org/doku.php?id=harmony:observe#object.deliverchange records
39 //
40 // The rationale for that, and for async delivery in general, is the principal
41 // that you shouldn't run code (observers) when it doesn't expect to be run.
42 // If you do that, you risk violating invariants that the code assumes.
43 //
44 // For this reason, we need to match the ES Harmony version. The way we can do
45 // this in Dart is to add a method on StreamSubscription (possibly by
46 // subclassing Stream* types) that immediately delivers records for only
47 // that subscription. Alternatively, we could consider using something other
48 // than Stream to deliver the multicast change records, and provide an
49 // Observable->Stream adapter.
50 bool deliverChanges();
51
52 /**
53 * Performs dirty checking of objects that inherit from [ObservableMixin].
54 * This scans all observed objects using mirrors and determines if any fields
55 * have changed. If they have, it delivers the changes for the object.
56 */
57 static void dirtyCheck() => dirtyCheckObservables();
58 }
59
60 /**
61 * Base class implementing [Observable].
62 *
63 * When a field, property, or indexable item is changed, the change record
64 * will be sent to [changes].
65 */
66 typedef ObservableBase = Object with ObservableMixin;
67
68 /**
69 * Mixin for implementing [Observable] objects.
70 *
71 * When a field, property, or indexable item is changed, the change record
72 * will be sent to [changes].
73 */
74 abstract class ObservableMixin implements Observable {
75 StreamController _changes;
76 InstanceMirror _mirror;
77
78 Map<Symbol, Object> _values;
79
80 Stream<List<ChangeRecord>> get changes {
81 if (_changes == null) {
82 _changes = new StreamController.broadcast(sync: true,
83 onListen: _observed, onCancel: _unobserved);
84 }
85 return _changes.stream;
86 }
87
88 /**
89 * True if this object has any observers, and should call
90 * [notifyPropertyChange] for changes.
91 */
92 bool get hasObservers => _changes != null && _changes.hasListener;
93
94 void _observed() {
95 // Register this object for dirty checking purposes.
96 registerObservable(this);
97
98 var mirror = reflect(this);
99 var values = new Map<Symbol, Object>();
100
101 // TODO(jmesserly): this should consider the superclass. Unfortunately
102 // that is not possible right now because of:
103 // http://code.google.com/p/dart/issues/detail?id=9434
104 for (var field in mirror.type.variables.values) {
105 if (field.isFinal || field.isStatic || field.isPrivate) continue;
106
107 for (var meta in field.metadata) {
108 if (identical(observable, meta.reflectee)) {
109 var name = field.simpleName;
110 // Note: since this is a field, getting the value shouldn't execute
111 // user code, so we don't need to worry about errors.
112 values[name] = mirror.getField(name).reflectee;
113 break;
114 }
115 }
116 }
117
118 _mirror = mirror;
119 _values = values;
120 }
121
122 /** Release data associated with observation. */
123 void _unobserved() {
124 // Note: we don't need to explicitly unregister from the dirty check list.
125 // This will happen automatically at the next call to dirtyCheck.
126 if (_values != null) {
127 _mirror = null;
128 _values = null;
129 }
130 }
131
132 bool deliverChanges() {
133 if (_values == null || !hasObservers) return false;
134
135 List changes = null;
136 _values.forEach((name, oldValue) {
137 var newValue = _mirror.getField(name).reflectee;
138 if (!identical(oldValue, newValue)) {
139 if (changes == null) changes = <PropertyChangeRecord>[];
140 changes.add(new PropertyChangeRecord(name));
141 _values[name] = newValue;
142 }
143 });
144
145 if (changes == null) return false;
146
147 // TODO(jmesserly): make "changes" immutable
148 _changes.add(changes);
149 return true;
150 }
151 }
152
153 /**
154 * The type of the `@observable` annotation.
155 *
156 * Library private because you should be able to use the [observable] field
157 * to get the one and only instance. We could make it public though, if anyone
158 * needs it for some reason.
159 */
160 class _ObservableAnnotation {
161 const _ObservableAnnotation();
162 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698