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

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

Powered by Google App Engine
This is Rietveld 408576698