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

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

Issue 149243009: Add support for lazy transformers. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: name changes Created 6 years, 10 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
11 import 'asset.dart'; 11 import 'asset.dart';
12 import 'asset_id.dart'; 12 import 'asset_id.dart';
13 import 'asset_node.dart'; 13 import 'asset_node.dart';
14 import 'asset_set.dart'; 14 import 'asset_set.dart';
15 import 'declaring_transform.dart';
15 import 'errors.dart'; 16 import 'errors.dart';
17 import 'lazy_transformer.dart';
16 import 'log.dart'; 18 import 'log.dart';
17 import 'phase.dart'; 19 import 'phase.dart';
18 import 'transform.dart'; 20 import 'transform.dart';
19 import 'transformer.dart'; 21 import 'transformer.dart';
22 import 'utils.dart';
20 23
21 /// Describes a transform on a set of assets and its relationship to the build 24 /// Describes a transform on a set of assets and its relationship to the build
22 /// dependency graph. 25 /// dependency graph.
23 /// 26 ///
24 /// Keeps track of whether it's dirty and needs to be run and which assets it 27 /// Keeps track of whether it's dirty and needs to be run and which assets it
25 /// depends on. 28 /// depends on.
26 class TransformNode { 29 class TransformNode {
27 /// The [Phase] that this transform runs in. 30 /// The [Phase] that this transform runs in.
28 final Phase phase; 31 final Phase phase;
29 32
30 /// The [Transformer] to apply to this node's inputs. 33 /// The [Transformer] to apply to this node's inputs.
31 final Transformer transformer; 34 final Transformer transformer;
32 35
33 /// The node for the primary asset this transform depends on. 36 /// The node for the primary asset this transform depends on.
34 final AssetNode primary; 37 final AssetNode primary;
35 38
36 /// The subscription to [primary]'s [AssetNode.onStateChange] stream. 39 /// The subscription to [primary]'s [AssetNode.onStateChange] stream.
37 StreamSubscription _primarySubscription; 40 StreamSubscription _primarySubscription;
38 41
39 /// True if an input has been modified since the last time this transform 42 /// True if an input has been modified since the last time this transform
40 /// began running. 43 /// began running.
41 bool get isDirty => _isDirty; 44 bool get isDirty => _isDirty;
42 var _isDirty = true; 45 var _isDirty = true;
43 46
47 /// Whether [transformer] is lazy and this transform has yet to be forced.
48 bool _isLazy;
49
44 /// The subscriptions to each input's [AssetNode.onStateChange] stream. 50 /// The subscriptions to each input's [AssetNode.onStateChange] stream.
45 var _inputSubscriptions = new Map<AssetId, StreamSubscription>(); 51 var _inputSubscriptions = new Map<AssetId, StreamSubscription>();
46 52
47 /// The controllers for the asset nodes emitted by this node. 53 /// The controllers for the asset nodes emitted by this node.
48 var _outputControllers = new Map<AssetId, AssetNodeController>(); 54 var _outputControllers = new Map<AssetId, AssetNodeController>();
49 55
50 /// A stream that emits an event whenever this transform becomes dirty and 56 /// A stream that emits an event whenever this transform becomes dirty and
51 /// needs to be re-run. 57 /// needs to be re-run.
52 /// 58 ///
53 /// This may emit events when the transform was already dirty or while 59 /// This may emit events when the transform was already dirty or while
54 /// processing transforms. Events are emitted synchronously to ensure that the 60 /// processing transforms. Events are emitted synchronously to ensure that the
55 /// dirty state is thoroughly propagated as soon as any assets are changed. 61 /// dirty state is thoroughly propagated as soon as any assets are changed.
56 Stream get onDirty => _onDirtyController.stream; 62 Stream get onDirty => _onDirtyController.stream;
57 final _onDirtyController = new StreamController.broadcast(sync: true); 63 final _onDirtyController = new StreamController.broadcast(sync: true);
58 64
59 /// A stream that emits an event whenever this transform logs an entry. 65 /// A stream that emits an event whenever this transform logs an entry.
60 /// 66 ///
61 /// This is synchronous because error logs can cause the transform to fail, so 67 /// This is synchronous because error logs can cause the transform to fail, so
62 /// we need to ensure that their processing isn't delayed until after the 68 /// we need to ensure that their processing isn't delayed until after the
63 /// transform or build has finished. 69 /// transform or build has finished.
64 Stream<LogEntry> get onLog => _onLogController.stream; 70 Stream<LogEntry> get onLog => _onLogController.stream;
65 final _onLogController = new StreamController<LogEntry>.broadcast(sync: true); 71 final _onLogController = new StreamController<LogEntry>.broadcast(sync: true);
66 72
67 TransformNode(this.phase, this.transformer, this.primary) { 73 TransformNode(this.phase, Transformer transformer, this.primary)
74 : transformer = transformer,
75 _isLazy = transformer is LazyTransformer {
68 _primarySubscription = primary.onStateChange.listen((state) { 76 _primarySubscription = primary.onStateChange.listen((state) {
69 if (state.isRemoved) { 77 if (state.isRemoved) {
70 remove(); 78 remove();
71 } else { 79 } else {
72 _dirty(); 80 _dirty();
73 } 81 }
74 }); 82 });
75 } 83 }
76 84
77 /// The [TransformInfo] describing this node. 85 /// The [TransformInfo] describing this node.
(...skipping 13 matching lines...) Expand all
91 _onDirtyController.close(); 99 _onDirtyController.close();
92 _primarySubscription.cancel(); 100 _primarySubscription.cancel();
93 for (var subscription in _inputSubscriptions.values) { 101 for (var subscription in _inputSubscriptions.values) {
94 subscription.cancel(); 102 subscription.cancel();
95 } 103 }
96 for (var controller in _outputControllers.values) { 104 for (var controller in _outputControllers.values) {
97 controller.setRemoved(); 105 controller.setRemoved();
98 } 106 }
99 } 107 }
100 108
109 /// If [transformer] is lazy, ensures that its concrete outputs will be
110 /// generated.
111 void force() {
112 // TODO(nweiz): we might want to have a timeout after which, if the
113 // transform's outputs have gone unused, we switch it back to lazy mode.
114 if (!_isLazy) return;
115 _isLazy = false;
116 _dirty();
117 }
118
101 /// Marks this transform as dirty. 119 /// Marks this transform as dirty.
102 /// 120 ///
103 /// This causes all of the transform's outputs to be marked as dirty as well. 121 /// This causes all of the transform's outputs to be marked as dirty as well.
104 void _dirty() { 122 void _dirty() {
105 _isDirty = true; 123 _isDirty = true;
106 for (var controller in _outputControllers.values) { 124 for (var controller in _outputControllers.values) {
107 controller.setDirty(); 125 controller.setDirty();
108 } 126 }
109 _onDirtyController.add(null); 127 _onDirtyController.add(null);
110 } 128 }
111 129
112 /// Applies this transform. 130 /// Applies this transform.
113 /// 131 ///
114 /// Returns a set of asset nodes representing the outputs from this transform 132 /// Returns a set of asset nodes representing the outputs from this transform
115 /// that weren't emitted last time it was run. 133 /// that weren't emitted last time it was run.
116 Future<Set<AssetNode>> apply() { 134 Future<Set<AssetNode>> apply() {
117 assert(!_onDirtyController.isClosed); 135 assert(!_onDirtyController.isClosed);
118 136
119 var newOutputs = new AssetSet();
120 var transform = createTransform(this, newOutputs, _log);
121
122 // Clear all the old input subscriptions. If an input is re-used, we'll 137 // Clear all the old input subscriptions. If an input is re-used, we'll
123 // re-subscribe. 138 // re-subscribe.
124 for (var subscription in _inputSubscriptions.values) { 139 for (var subscription in _inputSubscriptions.values) {
125 subscription.cancel(); 140 subscription.cancel();
126 } 141 }
127 _inputSubscriptions.clear(); 142 _inputSubscriptions.clear();
128 143
129 _isDirty = false; 144 _isDirty = false;
130 145
131 return transformer.apply(transform).catchError((error, stack) { 146 return syncFuture(() {
147 // TODO(nweiz): If [transformer] is a [DeclaringTransformer] but not a
148 // [LazyTransformer], we can get some mileage out of doing a declarative
149 // first so we know how to hook up the assets.
150 if (_isLazy) return _declareLazy();
151 return _applyImmediate();
152 }).catchError((error, stackTrace) {
132 // If the transform became dirty while processing, ignore any errors from 153 // If the transform became dirty while processing, ignore any errors from
133 // it. 154 // it.
134 if (_isDirty) return; 155 if (_isDirty) return new Set();
135 156
136 if (error is! MissingInputException) { 157 if (error is! MissingInputException) {
137 error = new TransformerException(info, error, stack); 158 error = new TransformerException(info, error, stackTrace);
138 } 159 }
139 160
140 // Catch all transformer errors and pipe them to the results stream. 161 // Catch all transformer errors and pipe them to the results stream. This
141 // This is so a broken transformer doesn't take down the whole graph. 162 // is so a broken transformer doesn't take down the whole graph.
142 phase.cascade.reportError(error); 163 phase.cascade.reportError(error);
143 164
144 // Don't allow partial results from a failed transform. 165 return new Set();
145 newOutputs.clear();
146 }).then((_) {
147 if (_isDirty) return new Set();
148
149 return _adjustOutputs(newOutputs);
150 }); 166 });
151 } 167 }
152 168
153 /// Gets the asset for an input [id]. 169 /// Gets the asset for an input [id].
154 /// 170 ///
155 /// If an input with that ID cannot be found, throws an 171 /// If an input with that ID cannot be found, throws an
156 /// [AssetNotFoundException]. 172 /// [AssetNotFoundException].
157 Future<Asset> getInput(AssetId id) { 173 Future<Asset> getInput(AssetId id) {
158 return phase.getInput(id).then((node) { 174 return phase.getInput(id).then((node) {
159 // Throw if the input isn't found. This ensures the transformer's apply 175 // Throw if the input isn't found. This ensures the transformer's apply
(...skipping 10 matching lines...) Expand all
170 return asset; 186 return asset;
171 }).catchError((error) { 187 }).catchError((error) {
172 if (error is! AssetNotFoundException || error.id != id) throw error; 188 if (error is! AssetNotFoundException || error.id != id) throw error;
173 // If the node was removed before it could be loaded, treat it as though 189 // If the node was removed before it could be loaded, treat it as though
174 // it never existed and throw a MissingInputException. 190 // it never existed and throw a MissingInputException.
175 throw new MissingInputException(info, id); 191 throw new MissingInputException(info, id);
176 }); 192 });
177 }); 193 });
178 } 194 }
179 195
180 /// Adjusts the outputs of the transform to reflect the outputs emitted on its 196 /// Applies the transform so that it produces concrete (as opposed to lazy)
181 /// most recent run. 197 /// outputs.
182 Set<AssetNode> _adjustOutputs(AssetSet newOutputs) { 198 Future<Set<AssetNode>> _applyImmediate() {
183 // Any ids that are for a different package are invalid. 199 var newOutputs = new AssetSet();
184 var invalidIds = newOutputs 200 var transform = new Transform(this, newOutputs, _log);
185 .map((asset) => asset.id)
186 .where((id) => id.package != phase.cascade.package)
187 .toSet();
188 for (var id in invalidIds) {
189 newOutputs.removeId(id);
190 // TODO(nweiz): report this as a warning rather than a failing error.
191 phase.cascade.reportError(new InvalidOutputException(info, id));
192 }
193 201
194 // Remove outputs that used to exist but don't anymore. 202 return syncFuture(() => transformer.apply(transform)).then((_) {
195 for (var id in _outputControllers.keys.toList()) { 203 if (_isDirty) return new Set();
196 if (newOutputs.containsId(id)) continue;
197 _outputControllers.remove(id).setRemoved();
198 }
199 204
200 var brandNewOutputs = new Set<AssetNode>(); 205 // Any ids that are for a different package are invalid.
201 // Store any new outputs or new contents for existing outputs. 206 var invalidIds = newOutputs
202 for (var asset in newOutputs) { 207 .map((asset) => asset.id)
203 var controller = _outputControllers[asset.id]; 208 .where((id) => id.package != phase.cascade.package)
204 if (controller != null) { 209 .toSet();
205 controller.setAvailable(asset); 210 for (var id in invalidIds) {
206 } else { 211 newOutputs.removeId(id);
207 var controller = new AssetNodeController.available(asset, this); 212 // TODO(nweiz): report this as a warning rather than a failing error.
208 _outputControllers[asset.id] = controller; 213 phase.cascade.reportError(new InvalidOutputException(info, id));
209 brandNewOutputs.add(controller.node);
210 } 214 }
211 }
212 215
213 return brandNewOutputs; 216 // Remove outputs that used to exist but don't anymore.
217 for (var id in _outputControllers.keys.toList()) {
218 if (newOutputs.containsId(id)) continue;
219 _outputControllers.remove(id).setRemoved();
220 }
221
222 var brandNewOutputs = new Set<AssetNode>();
223 // Store any new outputs or new contents for existing outputs.
224 for (var asset in newOutputs) {
225 var controller = _outputControllers[asset.id];
226 if (controller != null) {
227 controller.setAvailable(asset);
228 } else {
229 var controller = new AssetNodeController.available(asset, this);
230 _outputControllers[asset.id] = controller;
231 brandNewOutputs.add(controller.node);
232 }
233 }
234
235 return brandNewOutputs;
236 });
237 }
238
239 /// Applies the transform in declarative mode so that it produces lazy outputs .
Bob Nystrom 2014/02/07 21:02:28 Long line.
nweiz 2014/02/10 20:14:04 Done.
240 Future<Set<AssetNode>> _declareLazy() {
241 var newIds = new Set();
242 var transform = new DeclaringTransform(this, newIds, _log);
243
244 return syncFuture(() {
245 return (transformer as LazyTransformer).declareOutputs(transform);
246 }).then((_) {
247 if (_isDirty) return new Set();
248
249 var invalidIds =
250 newIds.where((id) => id.package != phase.cascade.package).toSet();
251 for (var id in invalidIds) {
252 newIds.remove(id);
253 // TODO(nweiz): report this as a warning rather than a failing error.
254 phase.cascade.reportError(new InvalidOutputException(info, id));
255 }
256
257 // Remove outputs that used to exist but don't anymore.
258 for (var id in _outputControllers.keys.toList()) {
259 if (newIds.contains(id)) continue;
260 _outputControllers.remove(id).setRemoved();
261 }
262
263 var brandNewOutputs = new Set<AssetNode>();
264 for (var id in newIds) {
265 var controller = _outputControllers[id];
266 if (controller != null) {
267 controller.setLazy(force);
268 } else {
269 var controller = new AssetNodeController.lazy(id, force, this);
270 _outputControllers[id] = controller;
271 brandNewOutputs.add(controller.node);
272 }
273 }
274
275 return brandNewOutputs;
276 });
214 } 277 }
215 278
216 void _log(AssetId asset, LogLevel level, String message, Span span) { 279 void _log(AssetId asset, LogLevel level, String message, Span span) {
217 // If the log isn't already associated with an asset, use the primary. 280 // If the log isn't already associated with an asset, use the primary.
218 if (asset == null) asset = primary.id; 281 if (asset == null) asset = primary.id;
219 var entry = new LogEntry(info, asset, level, message, span); 282 var entry = new LogEntry(info, asset, level, message, span);
220 _onLogController.add(entry); 283 _onLogController.add(entry);
221 } 284 }
222 } 285 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698