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