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

Side by Side Diff: pkg/barback/lib/src/transform_node.dart

Issue 187263003: Move Barback to a more thoroughly push-based model. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 9 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
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 barback.transform_node; 5 library barback.transform_node;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 8
9 import 'package:source_maps/span.dart'; 9 import 'package:source_maps/span.dart';
10 10
(...skipping 24 matching lines...) Expand all
35 35
36 /// The node for the primary asset this transform depends on. 36 /// The node for the primary asset this transform depends on.
37 final AssetNode primary; 37 final AssetNode primary;
38 38
39 /// A string describing the location of [this] in the transformer graph. 39 /// A string describing the location of [this] in the transformer graph.
40 final String _location; 40 final String _location;
41 41
42 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. 42 /// The subscription to [primary]'s [AssetNode.onStateChange] stream.
43 StreamSubscription _primarySubscription; 43 StreamSubscription _primarySubscription;
44 44
45 /// True if an input has been modified since the last time this transform 45 // TODO(nweiz): remove this and move isPrimary computation into TransformNode.
Bob Nystrom 2014/03/05 22:13:25 "remove" -> "Remove".
nweiz 2014/03/06 00:29:08 Done.
46 /// began running. 46 /// Whether the parent [PhaseInput] is currently computing whether its input
47 bool get isDirty => _isDirty; 47 /// is primary for [this].
48 var _isDirty = true; 48 bool _pendingIsPrimary = false;
49
50 /// Whether [this] is dirty and still has more processing to do.
51 bool get isDirty => _pendingIsPrimary || _isApplying;
52
53 /// Whether any input has become dirty since [_apply] last started running.
54 var _hasBecomeDirty = false;
55
56 /// Whether [_apply] is currently running.
57 var _isApplying = false;
Bob Nystrom 2014/03/05 22:13:25 I think a state enum would be a better fit than a
nweiz 2014/03/06 00:29:08 See previous comment.
49 58
50 /// Whether [transformer] is lazy and this transform has yet to be forced. 59 /// Whether [transformer] is lazy and this transform has yet to be forced.
51 bool _isLazy; 60 bool _isLazy;
52 61
53 /// The subscriptions to each input's [AssetNode.onStateChange] stream. 62 /// The subscriptions to each input's [AssetNode.onStateChange] stream.
54 var _inputSubscriptions = new Map<AssetId, StreamSubscription>(); 63 var _inputSubscriptions = new Map<AssetId, StreamSubscription>();
55 64
56 /// The controllers for the asset nodes emitted by this node. 65 /// The controllers for the asset nodes emitted by this node.
57 var _outputControllers = new Map<AssetId, AssetNodeController>(); 66 var _outputControllers = new Map<AssetId, AssetNodeController>();
58 67
59 /// A stream that emits an event whenever this transform becomes dirty and 68 /// A stream that emits an event whenever [this] is no longer dirty.
60 /// needs to be re-run.
61 /// 69 ///
62 /// This may emit events when the transform was already dirty or while 70 /// This is synchronous in order to guarantee that it will emit an event as
63 /// processing transforms. Events are emitted synchronously to ensure that the 71 /// soon as [isDirty] flips from `true` to `false`.
64 /// dirty state is thoroughly propagated as soon as any assets are changed. 72 Stream get onDone => _onDoneController.stream;
65 Stream get onDirty => _onDirtyController.stream; 73 final _onDoneController = new StreamController.broadcast(sync: true);
66 final _onDirtyController = new StreamController.broadcast(sync: true); 74
75 /// A stream that emits any new assets emitted by [this].
76 ///
77 /// Assets are emitted synchronously to ensure that any changes are thoroughly
78 /// propagated as soon as they occur.
79 Stream<AssetNode> get onAsset => _onAssetController.stream;
80 final _onAssetController = new StreamController<AssetNode>(sync: true);
67 81
68 /// A stream that emits an event whenever this transform logs an entry. 82 /// A stream that emits an event whenever this transform logs an entry.
69 /// 83 ///
70 /// This is synchronous because error logs can cause the transform to fail, so 84 /// This is synchronous because error logs can cause the transform to fail, so
71 /// we need to ensure that their processing isn't delayed until after the 85 /// we need to ensure that their processing isn't delayed until after the
72 /// transform or build has finished. 86 /// transform or build has finished.
73 Stream<LogEntry> get onLog => _onLogController.stream; 87 Stream<LogEntry> get onLog => _onLogController.stream;
74 final _onLogController = new StreamController<LogEntry>.broadcast(sync: true); 88 final _onLogController = new StreamController<LogEntry>.broadcast(sync: true);
75 89
76 TransformNode(this.phase, Transformer transformer, this.primary, 90 TransformNode(this.phase, Transformer transformer, this.primary,
77 this._location) 91 this._location)
78 : transformer = transformer, 92 : transformer = transformer,
79 _isLazy = transformer is LazyTransformer { 93 _isLazy = transformer is LazyTransformer {
80 _primarySubscription = primary.onStateChange.listen((state) { 94 _primarySubscription = primary.onStateChange.listen((state) {
81 if (state.isRemoved) { 95 if (state.isRemoved) {
82 remove(); 96 remove();
83 } else { 97 } else {
98 if (state.isDirty) _pendingIsPrimary = true;
84 _dirty(); 99 _dirty();
85 } 100 }
86 }); 101 });
102
103 _apply();
87 } 104 }
88 105
89 /// The [TransformInfo] describing this node. 106 /// The [TransformInfo] describing this node.
90 /// 107 ///
91 /// [TransformInfo] is the publicly-visible representation of a transform 108 /// [TransformInfo] is the publicly-visible representation of a transform
92 /// node. 109 /// node.
93 TransformInfo get info => new TransformInfo(transformer, primary.id); 110 TransformInfo get info => new TransformInfo(transformer, primary.id);
94 111
95 /// Marks this transform as removed. 112 /// Marks this transform as removed.
96 /// 113 ///
97 /// This causes all of the transform's outputs to be marked as removed as 114 /// This causes all of the transform's outputs to be marked as removed as
98 /// well. Normally this will be automatically done internally based on events 115 /// well. Normally this will be automatically done internally based on events
99 /// from the primary input, but it's possible for a transform to no longer be 116 /// from the primary input, but it's possible for a transform to no longer be
100 /// valid even if its primary input still exists. 117 /// valid even if its primary input still exists.
101 void remove() { 118 void remove() {
102 _isDirty = true; 119 _hasBecomeDirty = false;
103 _onDirtyController.close(); 120 _onAssetController.close();
121 _onDoneController.close();
104 _primarySubscription.cancel(); 122 _primarySubscription.cancel();
105 for (var subscription in _inputSubscriptions.values) { 123 for (var subscription in _inputSubscriptions.values) {
106 subscription.cancel(); 124 subscription.cancel();
107 } 125 }
108 for (var controller in _outputControllers.values) { 126 for (var controller in _outputControllers.values) {
109 controller.setRemoved(); 127 controller.setRemoved();
110 } 128 }
111 } 129 }
112 130
113 /// If [transformer] is lazy, ensures that its concrete outputs will be 131 /// If [transformer] is lazy, ensures that its concrete outputs will be
114 /// generated. 132 /// generated.
115 void force() { 133 void force() {
116 // TODO(nweiz): we might want to have a timeout after which, if the 134 // TODO(nweiz): we might want to have a timeout after which, if the
117 // transform's outputs have gone unused, we switch it back to lazy mode. 135 // transform's outputs have gone unused, we switch it back to lazy mode.
118 if (!_isLazy) return; 136 if (!_isLazy) return;
119 _isLazy = false; 137 _isLazy = false;
120 _dirty(); 138 _dirty();
121 } 139 }
122 140
141 // TODO(nweiz): remove this and move isPrimary computation into TransformNode.
142 /// Mark that the parent [PhaseInput] has determined that its input is indeed
143 /// primary for [this].
144 void markPrimary() {
145 if (!_pendingIsPrimary) return;
146 _pendingIsPrimary = false;
147 if (!_isApplying) _apply();
148 }
149
123 /// Marks this transform as dirty. 150 /// Marks this transform as dirty.
124 /// 151 ///
125 /// This causes all of the transform's outputs to be marked as dirty as well. 152 /// This causes all of the transform's outputs to be marked as dirty as well.
126 void _dirty() { 153 void _dirty() {
127 _isDirty = true;
128 for (var controller in _outputControllers.values) { 154 for (var controller in _outputControllers.values) {
129 controller.setDirty(); 155 controller.setDirty();
130 } 156 }
131 _onDirtyController.add(null); 157
158 _hasBecomeDirty = true;
159 if (!_isApplying && !_pendingIsPrimary) _apply();
132 } 160 }
133 161
134 /// Applies this transform. 162 /// Applies this transform.
135 /// 163 void _apply() {
136 /// Returns a set of asset nodes representing the outputs from this transform 164 assert(!_onAssetController.isClosed);
137 /// that weren't emitted last time it was run.
138 Future<Set<AssetNode>> apply() {
139 assert(!_onDirtyController.isClosed);
140 165
141 // Clear all the old input subscriptions. If an input is re-used, we'll 166 // Clear all the old input subscriptions. If an input is re-used, we'll
142 // re-subscribe. 167 // re-subscribe.
143 for (var subscription in _inputSubscriptions.values) { 168 for (var subscription in _inputSubscriptions.values) {
144 subscription.cancel(); 169 subscription.cancel();
145 } 170 }
146 _inputSubscriptions.clear(); 171 _inputSubscriptions.clear();
147 172
148 _isDirty = false; 173 _isApplying = true;
174 primary.whenAvailable((_) {
175 _hasBecomeDirty = false;
149 176
150 return syncFuture(() {
151 // TODO(nweiz): If [transformer] is a [DeclaringTransformer] but not a 177 // TODO(nweiz): If [transformer] is a [DeclaringTransformer] but not a
152 // [LazyTransformer], we can get some mileage out of doing a declarative 178 // [LazyTransformer], we can get some mileage out of doing a declarative
153 // first so we know how to hook up the assets. 179 // first so we know how to hook up the assets.
154 if (_isLazy) return _declareLazy(); 180 if (_isLazy) return _declareLazy();
155 return _applyImmediate(); 181 return _applyImmediate();
156 }).catchError((error, stackTrace) { 182 }).catchError((error, stackTrace) {
157 // If the transform became dirty while processing, ignore any errors from 183 // If the transform became dirty while processing, ignore any errors from
158 // it. 184 // it.
159 if (_isDirty) return new Set(); 185 if (_hasBecomeDirty) return;
160 186
161 if (error is! MissingInputException) { 187 if (error is! MissingInputException) {
162 error = new TransformerException(info, error, stackTrace); 188 error = new TransformerException(info, error, stackTrace);
163 } 189 }
164 190
165 // Catch all transformer errors and pipe them to the results stream. This 191 // Catch all transformer errors and pipe them to the results stream. This
166 // is so a broken transformer doesn't take down the whole graph. 192 // is so a broken transformer doesn't take down the whole graph.
167 phase.cascade.reportError(error); 193 phase.cascade.reportError(error);
168 194
169 return new Set(); 195 // Remove all the previously-emitted assets.
196 for (var controller in _outputControllers.values) {
197 controller.setRemoved();
198 }
199 _outputControllers.clear();
200 }).then((_) {
201 if (_onAssetController.isClosed) return;
202
203 _isApplying = false;
204 if (_hasBecomeDirty) {
205 // Re-apply the transform if it became dirty while applying.
206 if (!_pendingIsPrimary) _apply();
207 } else {
208 assert(!isDirty);
209 // Otherwise, notify the parent nodes that it's no longer dirty.
210 _onDoneController.add(null);
211 }
170 }); 212 });
171 } 213 }
172 214
173 /// Gets the asset for an input [id]. 215 /// Gets the asset for an input [id].
174 /// 216 ///
175 /// If an input with that ID cannot be found, throws an 217 /// If an input with that ID cannot be found, throws an
176 /// [AssetNotFoundException]. 218 /// [AssetNotFoundException].
177 Future<Asset> getInput(AssetId id) { 219 Future<Asset> getInput(AssetId id) {
178 return phase.getInput(id).then((node) { 220 return phase.getInput(id).then((node) {
179 // Throw if the input isn't found. This ensures the transformer's apply 221 // Throw if the input isn't found. This ensures the transformer's apply
(...skipping 12 matching lines...) Expand all
192 if (error is! AssetNotFoundException || error.id != id) throw error; 234 if (error is! AssetNotFoundException || error.id != id) throw error;
193 // If the node was removed before it could be loaded, treat it as though 235 // If the node was removed before it could be loaded, treat it as though
194 // it never existed and throw a MissingInputException. 236 // it never existed and throw a MissingInputException.
195 throw new MissingInputException(info, id); 237 throw new MissingInputException(info, id);
196 }); 238 });
197 }); 239 });
198 } 240 }
199 241
200 /// Applies the transform so that it produces concrete (as opposed to lazy) 242 /// Applies the transform so that it produces concrete (as opposed to lazy)
201 /// outputs. 243 /// outputs.
202 Future<Set<AssetNode>> _applyImmediate() { 244 Future _applyImmediate() {
203 var newOutputs = new AssetSet(); 245 var newOutputs = new AssetSet();
204 var transform = new Transform(this, newOutputs, _log); 246 var transform = new Transform(this, newOutputs, _log);
205 247
206 return syncFuture(() => transformer.apply(transform)).then((_) { 248 return syncFuture(() => transformer.apply(transform)).then((_) {
207 if (_isDirty) return new Set(); 249 if (_hasBecomeDirty) return;
208 250
209 // Any ids that are for a different package are invalid. 251 // Any ids that are for a different package are invalid.
210 var invalidIds = newOutputs 252 var invalidIds = newOutputs
211 .map((asset) => asset.id) 253 .map((asset) => asset.id)
212 .where((id) => id.package != phase.cascade.package) 254 .where((id) => id.package != phase.cascade.package)
213 .toSet(); 255 .toSet();
214 for (var id in invalidIds) { 256 for (var id in invalidIds) {
215 newOutputs.removeId(id); 257 newOutputs.removeId(id);
216 // TODO(nweiz): report this as a warning rather than a failing error. 258 // TODO(nweiz): report this as a warning rather than a failing error.
217 phase.cascade.reportError(new InvalidOutputException(info, id)); 259 phase.cascade.reportError(new InvalidOutputException(info, id));
218 } 260 }
219 261
220 // Remove outputs that used to exist but don't anymore. 262 // Remove outputs that used to exist but don't anymore.
221 for (var id in _outputControllers.keys.toList()) { 263 for (var id in _outputControllers.keys.toList()) {
222 if (newOutputs.containsId(id)) continue; 264 if (newOutputs.containsId(id)) continue;
223 _outputControllers.remove(id).setRemoved(); 265 _outputControllers.remove(id).setRemoved();
224 } 266 }
225 267
226 var brandNewOutputs = new Set<AssetNode>();
227 // Store any new outputs or new contents for existing outputs. 268 // Store any new outputs or new contents for existing outputs.
228 for (var asset in newOutputs) { 269 for (var asset in newOutputs) {
229 var controller = _outputControllers[asset.id]; 270 var controller = _outputControllers[asset.id];
230 if (controller != null) { 271 if (controller != null) {
231 controller.setAvailable(asset); 272 controller.setAvailable(asset);
232 } else { 273 } else {
233 var controller = new AssetNodeController.available(asset, this); 274 var controller = new AssetNodeController.available(asset, this);
234 _outputControllers[asset.id] = controller; 275 _outputControllers[asset.id] = controller;
235 brandNewOutputs.add(controller.node); 276 _onAssetController.add(controller.node);
236 } 277 }
237 } 278 }
238
239 return brandNewOutputs;
240 }); 279 });
241 } 280 }
242 281
243 /// Applies the transform in declarative mode so that it produces lazy 282 /// Applies the transform in declarative mode so that it produces lazy
244 /// outputs. 283 /// outputs.
245 Future<Set<AssetNode>> _declareLazy() { 284 Future _declareLazy() {
246 var newIds = new Set(); 285 var newIds = new Set();
247 var transform = new DeclaringTransform(this, newIds, _log); 286 var transform = new DeclaringTransform(this, newIds, _log);
248 287
249 return syncFuture(() { 288 return syncFuture(() {
250 return (transformer as LazyTransformer).declareOutputs(transform); 289 return (transformer as LazyTransformer).declareOutputs(transform);
251 }).then((_) { 290 }).then((_) {
252 if (_isDirty) return new Set(); 291 if (_hasBecomeDirty) return;
253 292
254 var invalidIds = 293 var invalidIds =
255 newIds.where((id) => id.package != phase.cascade.package).toSet(); 294 newIds.where((id) => id.package != phase.cascade.package).toSet();
256 for (var id in invalidIds) { 295 for (var id in invalidIds) {
257 newIds.remove(id); 296 newIds.remove(id);
258 // TODO(nweiz): report this as a warning rather than a failing error. 297 // TODO(nweiz): report this as a warning rather than a failing error.
259 phase.cascade.reportError(new InvalidOutputException(info, id)); 298 phase.cascade.reportError(new InvalidOutputException(info, id));
260 } 299 }
261 300
262 // Remove outputs that used to exist but don't anymore. 301 // Remove outputs that used to exist but don't anymore.
263 for (var id in _outputControllers.keys.toList()) { 302 for (var id in _outputControllers.keys.toList()) {
264 if (newIds.contains(id)) continue; 303 if (newIds.contains(id)) continue;
265 _outputControllers.remove(id).setRemoved(); 304 _outputControllers.remove(id).setRemoved();
266 } 305 }
267 306
268 var brandNewOutputs = new Set<AssetNode>();
269 for (var id in newIds) { 307 for (var id in newIds) {
270 var controller = _outputControllers[id]; 308 var controller = _outputControllers[id];
271 if (controller != null) { 309 if (controller != null) {
272 controller.setLazy(force); 310 controller.setLazy(force);
273 } else { 311 } else {
274 var controller = new AssetNodeController.lazy(id, force, this); 312 var controller = new AssetNodeController.lazy(id, force, this);
275 _outputControllers[id] = controller; 313 _outputControllers[id] = controller;
276 brandNewOutputs.add(controller.node); 314 _onAssetController.add(controller.node);
277 } 315 }
278 } 316 }
279
280 return brandNewOutputs;
281 }); 317 });
282 } 318 }
283 319
284 void _log(AssetId asset, LogLevel level, String message, Span span) { 320 void _log(AssetId asset, LogLevel level, String message, Span span) {
285 // If the log isn't already associated with an asset, use the primary. 321 // If the log isn't already associated with an asset, use the primary.
286 if (asset == null) asset = primary.id; 322 if (asset == null) asset = primary.id;
287 var entry = new LogEntry(info, asset, level, message, span); 323 var entry = new LogEntry(info, asset, level, message, span);
288 _onLogController.add(entry); 324 _onLogController.add(entry);
289 } 325 }
290 326
291 String toString() => 327 String toString() =>
292 "transform node in $_location for $transformer on $primary"; 328 "transform node in $_location for $transformer on $primary";
293 } 329 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698