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

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: fix example code in the library comment 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 synchronousdelivery.
Siggi Cherem (dart-lang) 2013/07/22 21:47:22 add space to split words: synchronous delivery
Jennifer Messerly 2013/07/22 22:38:39 Done.
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 try {
111 values[name] = mirror.getField(name).reflectee;
112 } catch (e, s) {
113 new Completer().completeError(e, s);
Siggi Cherem (dart-lang) 2013/07/22 21:47:22 is the idea that this exception gets attached to t
Jennifer Messerly 2013/07/22 22:38:39 yeah, essentially if we end up running user code (
114 }
115 break;
116 }
117 }
118 }
119
120 _mirror = mirror;
121 _values = values;
122 }
123
124 /** Release data associated with observation. */
125 void _unobserved() {
126 // Note: we don't need to explicitly unregister from the dirty check list.
127 // This will happen automatically at the next call to dirtyCheck.
128 if (_values != null) {
129 _mirror = null;
130 _values = null;
131 }
132 }
133
134 bool deliverChanges() {
135 if (_values == null || !hasObservers) return false;
136
137 List changes = null;
138 _values.forEach((name, oldValue) {
139 var newValue;
140 try {
141 newValue = _mirror.getField(name).reflectee;
142 } catch (e, s) {
143 new Completer().completeError(e, s);
144 return;
145 }
146 if (!identical(oldValue, newValue)) {
147 if (changes == null) changes = <PropertyChangeRecord>[];
148 changes.add(new PropertyChangeRecord(name));
149 _values[name] = newValue;
150 }
151 });
152
153 if (changes == null) return false;
154
155 // TODO(jmesserly): make "changes" immutable
156 _changes.add(changes);
157 return true;
158 }
159 }
160
161 /**
162 * The type of the `@observable` annotation.
163 *
164 * Library private because you should be able to use the [observable] field
165 * to get the one and only instance. We could make it public though, if anyone
166 * needs it for some reason.
167 */
168 class _ObservableAnnotation {
169 const _ObservableAnnotation();
170 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698