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