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 barback.phase_input; | 5 library barback.phase_input; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:collection'; | 8 import 'dart:collection'; |
9 | 9 |
10 import 'asset.dart'; | 10 import 'asset.dart'; |
11 import 'asset_forwarder.dart'; | 11 import 'asset_forwarder.dart'; |
12 import 'asset_node.dart'; | 12 import 'asset_node.dart'; |
13 import 'asset_node_set.dart'; | |
14 import 'errors.dart'; | 13 import 'errors.dart'; |
15 import 'log.dart'; | 14 import 'log.dart'; |
16 import 'phase.dart'; | 15 import 'phase.dart'; |
17 import 'stream_pool.dart'; | 16 import 'stream_pool.dart'; |
18 import 'transform_node.dart'; | 17 import 'transform_node.dart'; |
19 import 'transformer.dart'; | 18 import 'transformer.dart'; |
20 import 'utils.dart'; | 19 import 'utils.dart'; |
21 | 20 |
22 /// A class for watching a single [AssetNode] and running any transforms that | 21 /// A class for watching a single [AssetNode] and running any transforms that |
23 /// take that node as a primary input. | 22 /// take that node as a primary input. |
(...skipping 15 matching lines...) Expand all Loading... |
39 final _transforms = new Set<TransformNode>(); | 38 final _transforms = new Set<TransformNode>(); |
40 | 39 |
41 /// A forwarder for the input [AssetNode] for this phase. | 40 /// A forwarder for the input [AssetNode] for this phase. |
42 /// | 41 /// |
43 /// This is used to mark the node as removed should the input ever be removed. | 42 /// This is used to mark the node as removed should the input ever be removed. |
44 final AssetForwarder _inputForwarder; | 43 final AssetForwarder _inputForwarder; |
45 | 44 |
46 /// The asset node for this input. | 45 /// The asset node for this input. |
47 AssetNode get input => _inputForwarder.node; | 46 AssetNode get input => _inputForwarder.node; |
48 | 47 |
49 /// The controller that's used for the output node if [input] isn't | 48 /// The controller that's used for the output node if [input] isn't consumed |
50 /// overwritten by any transformers. | 49 /// by any transformers. |
51 /// | 50 /// |
52 /// This needs an intervening controller to ensure that the output can be | 51 /// This needs an intervening controller to ensure that the output can be |
53 /// marked dirty when determining whether transforms will overwrite it, and be | 52 /// marked dirty when determining whether transforms apply, and removed if |
54 /// marked removed if they do. It's null if the asset is not being passed | 53 /// they do. It's null if the asset is not being passed through. |
55 /// through. | |
56 AssetNodeController _passThroughController; | 54 AssetNodeController _passThroughController; |
57 | 55 |
58 /// Whether [_passThroughController] has been newly created since [process] | 56 /// Whether [_passThroughController] has been newly created since [process] |
59 /// last completed. | 57 /// last completed. |
60 bool _newPassThrough = false; | 58 bool _newPassThrough = false; |
61 | 59 |
62 final _outputs = new AssetNodeSet(); | |
63 | |
64 /// A Future that will complete once the transformers that consume [input] are | 60 /// A Future that will complete once the transformers that consume [input] are |
65 /// determined. | 61 /// determined. |
66 Future _adjustTransformersFuture; | 62 Future _adjustTransformersFuture; |
67 | 63 |
68 /// A stream that emits an event whenever this input becomes dirty and needs | 64 /// A stream that emits an event whenever this input becomes dirty and needs |
69 /// [process] to be called. | 65 /// [process] to be called. |
70 /// | 66 /// |
71 /// This may emit events when the input was already dirty or while processing | 67 /// This may emit events when the input was already dirty or while processing |
72 /// transforms. Events are emitted synchronously to ensure that the dirty | 68 /// transforms. Events are emitted synchronously to ensure that the dirty |
73 /// state is thoroughly propagated as soon as any assets are changed. | 69 /// state is thoroughly propagated as soon as any assets are changed. |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
134 // automatically pick up on [removedTransformer] being gone. | 130 // automatically pick up on [removedTransformer] being gone. |
135 if (_adjustTransformersFuture != null) continue; | 131 if (_adjustTransformersFuture != null) continue; |
136 | 132 |
137 _transforms.removeWhere((transform) { | 133 _transforms.removeWhere((transform) { |
138 if (transform.transformer != removedTransformer) return false; | 134 if (transform.transformer != removedTransformer) return false; |
139 transform.remove(); | 135 transform.remove(); |
140 return true; | 136 return true; |
141 }); | 137 }); |
142 } | 138 } |
143 | 139 |
| 140 if (_transforms.isEmpty && _adjustTransformersFuture == null && |
| 141 _passThroughController == null) { |
| 142 _passThroughController = new AssetNodeController.from(input); |
| 143 _newPassThrough = true; |
| 144 } |
| 145 |
144 var brandNewTransformers = newTransformers.difference(oldTransformers); | 146 var brandNewTransformers = newTransformers.difference(oldTransformers); |
145 if (brandNewTransformers.isEmpty) return; | 147 if (brandNewTransformers.isEmpty) return; |
146 | 148 |
147 brandNewTransformers.forEach(_transformers.add); | 149 brandNewTransformers.forEach(_transformers.add); |
148 if (_adjustTransformersFuture == null) _adjustTransformers(); | 150 if (_adjustTransformersFuture == null) _adjustTransformers(); |
149 } | 151 } |
150 | 152 |
151 /// Force all [LazyTransformer]s' transforms in this input to begin producing | 153 /// Force all [LazyTransformer]s' transforms in this input to begin producing |
152 /// concrete assets. | 154 /// concrete assets. |
153 void forceAllTransforms() { | 155 void forceAllTransforms() { |
154 for (var transform in _transforms) { | 156 for (var transform in _transforms) { |
155 transform.force(); | 157 transform.force(); |
156 } | 158 } |
157 } | 159 } |
158 | 160 |
159 /// Asynchronously determines which transformers can consume [input] as a | 161 /// Asynchronously determines which transformers can consume [input] as a |
160 /// primary input and creates transforms for them. | 162 /// primary input and creates transforms for them. |
161 /// | 163 /// |
162 /// This ensures that if [input] is modified or removed during or after the | 164 /// This ensures that if [input] is modified or removed during or after the |
163 /// time it takes to adjust its transformers, they're appropriately | 165 /// time it takes to adjust its transformers, they're appropriately |
164 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFuture]. | 166 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFuture]. |
165 void _adjustTransformers() { | 167 void _adjustTransformers() { |
166 // Mark the input as dirty. This may not actually end up creating any new | 168 // Mark the input as dirty. This may not actually end up creating any new |
167 // transforms, but we want adding or removing a source asset to consistently | 169 // transforms, but we want adding or removing a source asset to consistently |
168 // kick off a build, even if that build does nothing. | 170 // kick off a build, even if that build does nothing. |
169 _onDirtyController.add(null); | 171 _onDirtyController.add(null); |
170 | 172 |
171 // If there's a pass-through for this input, mark it dirty until we figure | 173 // If there's a pass-through for this input, mark it dirty while we figure |
172 // out if a transformer will emit an asset with that id. | 174 // out whether we need to add any transforms for it. |
173 if (_passThroughController != null) _passThroughController.setDirty(); | 175 if (_passThroughController != null) _passThroughController.setDirty(); |
174 | 176 |
175 // Once the input is available, hook up transformers for it. If it changes | 177 // Once the input is available, hook up transformers for it. If it changes |
176 // while that's happening, try again. | 178 // while that's happening, try again. |
177 _adjustTransformersFuture = _tryUntilStable((asset, transformers) { | 179 _adjustTransformersFuture = _tryUntilStable((asset, transformers) { |
178 var oldTransformers = | 180 var oldTransformers = |
179 _transforms.map((transform) => transform.transformer).toSet(); | 181 _transforms.map((transform) => transform.transformer).toSet(); |
180 | 182 |
181 return _removeStaleTransforms(asset, transformers).then((_) => | 183 return _removeStaleTransforms(asset, transformers).then((_) => |
182 _addFreshTransforms(transformers, oldTransformers)); | 184 _addFreshTransforms(transformers, oldTransformers)); |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
234 // We can safely access [input.asset] here even though it might have | 236 // We can safely access [input.asset] here even though it might have |
235 // changed since (as above) if it has, [_adjustTransformers] will just be | 237 // changed since (as above) if it has, [_adjustTransformers] will just be |
236 // re-run. | 238 // re-run. |
237 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to | 239 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to |
238 // results. | 240 // results. |
239 return transformer.isPrimary(input.asset).then((isPrimary) { | 241 return transformer.isPrimary(input.asset).then((isPrimary) { |
240 if (!isPrimary) return; | 242 if (!isPrimary) return; |
241 var transform = new TransformNode( | 243 var transform = new TransformNode( |
242 _phase, transformer, input, _location); | 244 _phase, transformer, input, _location); |
243 _transforms.add(transform); | 245 _transforms.add(transform); |
244 transform.onDirty.listen((_) { | |
245 if (_passThroughController != null) _passThroughController.setDirty(); | |
246 }); | |
247 _onDirtyPool.add(transform.onDirty); | 246 _onDirtyPool.add(transform.onDirty); |
248 _onLogPool.add(transform.onLog); | 247 _onLogPool.add(transform.onLog); |
249 }); | 248 }); |
250 })); | 249 })); |
251 } | 250 } |
252 | 251 |
| 252 /// Adjust whether [input] is passed through the phase unmodified, based on |
| 253 /// whether it's consumed by other transforms in this phase. |
| 254 /// |
| 255 /// If [input] was already passed-through, this will update the passed-through |
| 256 /// value. |
| 257 void _adjustPassThrough() { |
| 258 assert(input.state.isAvailable); |
| 259 |
| 260 if (_transforms.isEmpty) { |
| 261 if (_passThroughController != null) { |
| 262 _passThroughController.setAvailable(input.asset); |
| 263 } else { |
| 264 _passThroughController = new AssetNodeController.from(input); |
| 265 _newPassThrough = true; |
| 266 } |
| 267 } else if (_passThroughController != null) { |
| 268 _passThroughController.setRemoved(); |
| 269 _passThroughController = null; |
| 270 _newPassThrough = false; |
| 271 } |
| 272 } |
| 273 |
253 /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this | 274 /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this |
254 /// phase's transformers are modified. | 275 /// phase's transformers are modified. |
255 Future _tryUntilStable( | 276 Future _tryUntilStable( |
256 Future callback(Asset asset, Set<Transformer> transformers)) { | 277 Future callback(Asset asset, Set<Transformer> transformers)) { |
257 var oldTransformers; | 278 var oldTransformers; |
258 return input.tryUntilStable((asset) { | 279 return input.tryUntilStable((asset) { |
259 oldTransformers = _transformers.toSet(); | 280 oldTransformers = _transformers.toSet(); |
260 return callback(asset, _transformers); | 281 return callback(asset, _transformers); |
261 }).then((result) { | 282 }).then((result) { |
262 if (setEquals(oldTransformers, _transformers)) return result; | 283 if (setEquals(oldTransformers, _transformers)) return result; |
263 return _tryUntilStable(callback); | 284 return _tryUntilStable(callback); |
264 }); | 285 }); |
265 } | 286 } |
266 | 287 |
267 /// Processes the transforms for this input. | 288 /// Processes the transforms for this input. |
268 /// | 289 /// |
269 /// Returns the set of newly-created asset nodes that transforms have emitted | 290 /// Returns the set of newly-created asset nodes that transforms have emitted |
270 /// for this input. The assets returned this way are guaranteed not to be | 291 /// for this input. The assets returned this way are guaranteed not to be |
271 /// [AssetState.REMOVED]. | 292 /// [AssetState.REMOVED]. |
272 Future<Set<AssetNode>> process() { | 293 Future<Set<AssetNode>> process() { |
273 return _waitForTransformers(() { | 294 return _waitForTransformers(() => _processTransforms()).then((outputs) { |
274 if (input.state.isRemoved) return new Future.value(new Set()); | |
275 | |
276 return Future.wait(_transforms.map((transform) { | |
277 if (!transform.isDirty) return new Future.value(new Set()); | |
278 return transform.apply(); | |
279 })).then((outputs) => unionAll(outputs)); | |
280 }).then((outputs) { | |
281 if (input.state.isRemoved) return new Set(); | 295 if (input.state.isRemoved) return new Set(); |
282 | |
283 for (var output in outputs) { | |
284 assert(!output.state.isRemoved); | |
285 _outputs.add(output); | |
286 output.whenRemoved(_adjustPassThrough); | |
287 } | |
288 | |
289 _adjustPassThrough(); | |
290 if (_newPassThrough) { | |
291 outputs.add(_passThroughController.node); | |
292 _newPassThrough = false; | |
293 } | |
294 return outputs; | 296 return outputs; |
295 }); | 297 }); |
296 } | 298 } |
297 | 299 |
298 /// Runs [callback] once all the transformers are adjusted correctly and the | 300 /// Runs [callback] once all the transformers are adjusted correctly and the |
299 /// input is ready to be processed. | 301 /// input is ready to be processed. |
300 /// | 302 /// |
301 /// If the transformers are already properly adjusted, [callback] is called | 303 /// If the transformers are already properly adjusted, [callback] is called |
302 /// synchronously to ensure that [_adjustTransformers] isn't called before the | 304 /// synchronously to ensure that [_adjustTransformers] isn't called before the |
303 /// callback. | 305 /// callback. |
304 Future _waitForTransformers(callback()) { | 306 Future _waitForTransformers(callback()) { |
305 if (_adjustTransformersFuture == null) return syncFuture(callback); | 307 if (_adjustTransformersFuture == null) return syncFuture(callback); |
306 return _adjustTransformersFuture.then( | 308 return _adjustTransformersFuture.then( |
307 (_) => _waitForTransformers(callback)); | 309 (_) => _waitForTransformers(callback)); |
308 } | 310 } |
309 | 311 |
310 /// Adjust whether [input] is passed through the phase unmodified, based on | 312 /// Applies all currently wired up and dirty transforms. |
311 /// whether it's overwritten by other transforms in this phase. | 313 Future<Set<AssetNode>> _processTransforms() { |
312 /// | 314 if (input.state.isRemoved) return new Future.value(new Set()); |
313 /// If [input] was already passed-through, this will update the passed-through | |
314 /// value. | |
315 void _adjustPassThrough() { | |
316 if (!input.state.isAvailable) return; | |
317 | 315 |
318 // If there's an output with the same id as the primary input, that | 316 if (_passThroughController != null) { |
319 // overwrites the input so it doesn't get passed through. Otherwise, | 317 if (!_newPassThrough) return new Future.value(new Set()); |
320 // create a pass-through controller if none exists, or set the existing | 318 _newPassThrough = false; |
321 // one available. | 319 return new Future.value( |
322 if (_outputs.any((output) => output.id == input.id)) { | 320 new Set<AssetNode>.from([_passThroughController.node])); |
323 if (_passThroughController != null) { | |
324 _passThroughController.setRemoved(); | |
325 _passThroughController = null; | |
326 _newPassThrough = false; | |
327 } | |
328 } else if (_passThroughController == null) { | |
329 _passThroughController = new AssetNodeController.from(input); | |
330 _newPassThrough = true; | |
331 } else if (_passThroughController.node.state.isDirty) { | |
332 _passThroughController.setAvailable(input.asset); | |
333 } | 321 } |
| 322 |
| 323 return Future.wait(_transforms.map((transform) { |
| 324 if (!transform.isDirty) return new Future.value(new Set()); |
| 325 return transform.apply(); |
| 326 })).then((outputs) => unionAll(outputs)); |
334 } | 327 } |
335 | 328 |
336 String toString() => "phase input in $_location for $input"; | 329 String toString() => "phase input in $_location for $input"; |
337 } | 330 } |
OLD | NEW |