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 library observe.src.observable; | 5 library observe.src.observable; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:collection'; | 8 import 'dart:collection'; |
9 | 9 |
10 // TODO(sigmund): figure out how to remove this annotation entirely | |
10 // Note: ObservableProperty is in this list only for the unusual use case of | 11 // Note: ObservableProperty is in this list only for the unusual use case of |
11 // dart2js without deploy tool. The deploy tool (see "transformer.dart") will | 12 // dart2js without deploy tool. The deploy tool (see "transformer.dart") will |
12 // add the @reflectable annotation, which makes it work with Polymer's | 13 // add the @reflectable annotation, which makes it work with Polymer's |
13 // @published. | 14 // @published. |
14 @MirrorsUsed(metaTargets: const [Reflectable, ObservableProperty], | 15 @unused.MirrorsUsed( |
15 override: 'observe.src.observable') | 16 metaTargets: const [Reflectable, ObservableProperty], |
16 import 'dart:mirrors'; | 17 override: 'smoke.mirrors') |
17 | 18 import 'dart:mirrors' as unused; |
Jennifer Messerly
2014/02/20 21:46:40
import 'dart:mirrors' show MirrorsUsed?
| |
19 import 'package:smoke/smoke.dart' as smoke; | |
18 import 'package:observe/observe.dart'; | 20 import 'package:observe/observe.dart'; |
19 | 21 |
20 // Note: this is an internal library so we can import it from tests. | 22 // Note: this is an internal library so we can import it from tests. |
21 // TODO(jmesserly): ideally we could import this with a prefix, but it caused | 23 // TODO(jmesserly): ideally we could import this with a prefix, but it caused |
22 // strange problems on the VM when I tested out the dirty-checking example | 24 // strange problems on the VM when I tested out the dirty-checking example |
23 // above. | 25 // above. |
24 import 'dirty_check.dart'; | 26 import 'dirty_check.dart'; |
25 | 27 |
26 /** | 28 /** |
27 * Represents an object with observable properties. This is used by data in | 29 * Represents an object with observable properties. This is used by data in |
(...skipping 11 matching lines...) Expand all Loading... | |
39 */ | 41 */ |
40 abstract class Observable { | 42 abstract class Observable { |
41 /** | 43 /** |
42 * Performs dirty checking of objects that inherit from [Observable]. | 44 * Performs dirty checking of objects that inherit from [Observable]. |
43 * This scans all observed objects using mirrors and determines if any fields | 45 * This scans all observed objects using mirrors and determines if any fields |
44 * have changed. If they have, it delivers the changes for the object. | 46 * have changed. If they have, it delivers the changes for the object. |
45 */ | 47 */ |
46 static void dirtyCheck() => dirtyCheckObservables(); | 48 static void dirtyCheck() => dirtyCheckObservables(); |
47 | 49 |
48 StreamController _changes; | 50 StreamController _changes; |
49 InstanceMirror _mirror; | |
50 | 51 |
51 Map<Symbol, Object> _values; | 52 Map<Symbol, Object> _values; |
52 List<ChangeRecord> _records; | 53 List<ChangeRecord> _records; |
53 | 54 |
54 /** | 55 /** |
55 * The stream of change records to this object. Records will be delivered | 56 * The stream of change records to this object. Records will be delivered |
56 * asynchronously. | 57 * asynchronously. |
57 * | 58 * |
58 * [deliverChanges] can be called to force synchronous delivery. | 59 * [deliverChanges] can be called to force synchronous delivery. |
59 */ | 60 */ |
60 Stream<List<ChangeRecord>> get changes { | 61 Stream<List<ChangeRecord>> get changes { |
61 if (_changes == null) { | 62 if (_changes == null) { |
62 _changes = new StreamController.broadcast(sync: true, | 63 _changes = new StreamController.broadcast(sync: true, |
63 onListen: _observed, onCancel: _unobserved); | 64 onListen: _observed, onCancel: _unobserved); |
64 } | 65 } |
65 return _changes.stream; | 66 return _changes.stream; |
66 } | 67 } |
67 | 68 |
68 /** | 69 /** |
69 * True if this object has any observers, and should call | 70 * True if this object has any observers, and should call |
70 * [notifyChange] for changes. | 71 * [notifyChange] for changes. |
71 */ | 72 */ |
72 bool get hasObservers => _changes != null && _changes.hasListener; | 73 bool get hasObservers => _changes != null && _changes.hasListener; |
73 | 74 |
74 void _observed() { | 75 void _observed() { |
75 // Register this object for dirty checking purposes. | 76 // Register this object for dirty checking purposes. |
76 registerObservable(this); | 77 registerObservable(this); |
77 | 78 |
78 var mirror = reflect(this); | |
79 var values = new Map<Symbol, Object>(); | 79 var values = new Map<Symbol, Object>(); |
80 | 80 |
81 // Note: we scan for @observable regardless of whether the base type | 81 // Note: we scan for @observable regardless of whether the base type |
82 // actually includes this mixin. While perhaps too inclusive, it lets us | 82 // actually includes this mixin. While perhaps too inclusive, it lets us |
83 // avoid complex logic that walks "with" and "implements" clauses. | 83 // avoid complex logic that walks "with" and "implements" clauses. |
84 for (var type = mirror.type; type != objectType; type = type.superclass) { | 84 // TODO(sigmund): should we exclude getters here? users should only use |
85 for (var field in type.declarations.values) { | 85 // @observable on fields, but 'smoke' includes getters with fields (might |
86 if (field is! VariableMirror || | 86 // need to extend smoke for this). See also note below on `smoke.read`. |
87 field.isFinal || | 87 var queryOptions = new smoke.QueryOptions(includeInherited: true, |
Jennifer Messerly
2014/02/20 21:46:40
hmmm, yeah, it's pretty important not to find gett
Siggi Cherem (dart-lang)
2014/02/21 03:50:27
yeah, I had a feeling this was going to be necessa
| |
88 field.isStatic || | 88 withAnnotations: const [ObservableProperty]); |
89 field.isPrivate) continue; | 89 for (var decl in smoke.query(this.runtimeType, queryOptions)) { |
90 | 90 var name = decl.name; |
91 for (var meta in field.metadata) { | 91 // Note: since this is a field, getting the value shouldn't execute |
92 if (meta.reflectee is ObservableProperty) { | 92 // user code, so we don't need to worry about errors. |
93 var name = field.simpleName; | 93 values[name] = smoke.read(this, name); |
94 // Note: since this is a field, getting the value shouldn't execute | |
95 // user code, so we don't need to worry about errors. | |
96 values[name] = mirror.getField(name).reflectee; | |
97 break; | |
98 } | |
99 } | |
100 } | |
101 } | 94 } |
102 | 95 |
103 _mirror = mirror; | |
104 _values = values; | 96 _values = values; |
105 } | 97 } |
106 | 98 |
107 /** Release data associated with observation. */ | 99 /** Release data associated with observation. */ |
108 void _unobserved() { | 100 void _unobserved() { |
109 // Note: we don't need to explicitly unregister from the dirty check list. | 101 // Note: we don't need to explicitly unregister from the dirty check list. |
110 // This will happen automatically at the next call to dirtyCheck. | 102 // This will happen automatically at the next call to dirtyCheck. |
111 if (_values != null) { | 103 if (_values != null) { |
112 _mirror = null; | |
113 _values = null; | 104 _values = null; |
114 } | 105 } |
115 } | 106 } |
116 | 107 |
117 /** | 108 /** |
118 * Synchronously deliver pending [changes]. Returns true if any records were | 109 * Synchronously deliver pending [changes]. Returns true if any records were |
119 * delivered, otherwise false. | 110 * delivered, otherwise false. |
120 */ | 111 */ |
121 // TODO(jmesserly): this is a bit different from the ES Harmony version, which | 112 // TODO(jmesserly): this is a bit different from the ES Harmony version, which |
122 // allows delivery of changes to a particular observer: | 113 // allows delivery of changes to a particular observer: |
(...skipping 15 matching lines...) Expand all Loading... | |
138 // Harmony as well as predictability for app developers. | 129 // Harmony as well as predictability for app developers. |
139 bool deliverChanges() { | 130 bool deliverChanges() { |
140 if (_values == null || !hasObservers) return false; | 131 if (_values == null || !hasObservers) return false; |
141 | 132 |
142 // Start with manually notified records (computed properties, etc), | 133 // Start with manually notified records (computed properties, etc), |
143 // then scan all fields for additional changes. | 134 // then scan all fields for additional changes. |
144 List records = _records; | 135 List records = _records; |
145 _records = null; | 136 _records = null; |
146 | 137 |
147 _values.forEach((name, oldValue) { | 138 _values.forEach((name, oldValue) { |
148 var newValue = _mirror.getField(name).reflectee; | 139 var newValue = smoke.read(this, name); |
149 if (oldValue != newValue) { | 140 if (oldValue != newValue) { |
150 if (records == null) records = []; | 141 if (records == null) records = []; |
151 records.add(new PropertyChangeRecord(this, name, oldValue, newValue)); | 142 records.add(new PropertyChangeRecord(this, name, oldValue, newValue)); |
152 _values[name] = newValue; | 143 _values[name] = newValue; |
153 } | 144 } |
154 }); | 145 }); |
155 | 146 |
156 if (records == null) return false; | 147 if (records == null) return false; |
157 | 148 |
158 _changes.add(new UnmodifiableListView<ChangeRecord>(records)); | 149 _changes.add(new UnmodifiableListView<ChangeRecord>(records)); |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
195 // public instead? | 186 // public instead? |
196 // NOTE: this is not exported publically. | 187 // NOTE: this is not exported publically. |
197 notifyPropertyChangeHelper(Observable obj, Symbol field, Object oldValue, | 188 notifyPropertyChangeHelper(Observable obj, Symbol field, Object oldValue, |
198 Object newValue) { | 189 Object newValue) { |
199 | 190 |
200 if (obj.hasObservers && oldValue != newValue) { | 191 if (obj.hasObservers && oldValue != newValue) { |
201 obj.notifyChange(new PropertyChangeRecord(obj, field, oldValue, newValue)); | 192 obj.notifyChange(new PropertyChangeRecord(obj, field, oldValue, newValue)); |
202 } | 193 } |
203 return newValue; | 194 return newValue; |
204 } | 195 } |
205 | |
206 // NOTE: this is not exported publically. | |
207 final objectType = reflectClass(Object); | |
OLD | NEW |