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; | 5 library barback.phase; |
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'; |
(...skipping 18 matching lines...) Expand all Loading... | |
29 /// is running asynchronously) that source is modified. When the process queue | 29 /// is running asynchronously) that source is modified. When the process queue |
30 /// goes to advance to phase 3, it will see that modification and start the | 30 /// goes to advance to phase 3, it will see that modification and start the |
31 /// waterfall from the beginning again. | 31 /// waterfall from the beginning again. |
32 class Phase { | 32 class Phase { |
33 /// The cascade that owns this phase. | 33 /// The cascade that owns this phase. |
34 final AssetCascade cascade; | 34 final AssetCascade cascade; |
35 | 35 |
36 /// The transformers that can access [inputs]. | 36 /// The transformers that can access [inputs]. |
37 /// | 37 /// |
38 /// Their outputs will be available to the next phase. | 38 /// Their outputs will be available to the next phase. |
39 final List<Transformer> _transformers; | 39 final Set<Transformer> _transformers; |
40 | 40 |
41 /// The inputs that are available for transforms in this phase to consume. | 41 /// The inputs that are available for transforms in this phase to consume. |
42 /// | 42 /// |
43 /// For the first phase, these will be the source assets. For all other | 43 /// For the first phase, these will be the source assets. For all other |
44 /// phases, they will be the outputs from the previous phase. | 44 /// phases, they will be the outputs from the previous phase. |
45 final _inputs = new Map<AssetId, AssetNode>(); | 45 final _inputs = new Map<AssetId, AssetNode>(); |
46 | 46 |
47 /// The transforms currently applicable to assets in [inputs], indexed by | 47 /// The transforms currently applicable to assets in [inputs], indexed by |
48 /// the ids of their primary inputs. | 48 /// the ids of their primary inputs. |
49 /// | 49 /// |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
95 /// | 95 /// |
96 /// This is used whenever an input is added, changed, or removed. It's | 96 /// This is used whenever an input is added, changed, or removed. It's |
97 /// sometimes redundant with the events collected from [_transforms], but this | 97 /// sometimes redundant with the events collected from [_transforms], but this |
98 /// stream is necessary for new and removed inputs, and the transform stream | 98 /// stream is necessary for new and removed inputs, and the transform stream |
99 /// is necessary for modified secondary inputs. | 99 /// is necessary for modified secondary inputs. |
100 final _onDirtyController = new StreamController.broadcast(sync: true); | 100 final _onDirtyController = new StreamController.broadcast(sync: true); |
101 | 101 |
102 /// The phase after this one. | 102 /// The phase after this one. |
103 /// | 103 /// |
104 /// Outputs from this phase will be passed to it. | 104 /// Outputs from this phase will be passed to it. |
105 final Phase _next; | 105 Phase get next => _next; |
106 Phase _next; | |
106 | 107 |
107 /// Returns all currently-available output assets for this phase. | 108 /// Returns all currently-available output assets for this phase. |
108 AssetSet get availableOutputs { | 109 AssetSet get availableOutputs { |
109 return new AssetSet.from(_outputs.values | 110 return new AssetSet.from(_outputs.values |
110 .map((queue) => queue.first) | 111 .map((queue) => queue.first) |
111 .where((node) => node.state.isAvailable) | 112 .where((node) => node.state.isAvailable) |
112 .map((node) => node.asset)); | 113 .map((node) => node.asset)); |
113 } | 114 } |
114 | 115 |
115 Phase(this.cascade, this._transformers, this._next) { | 116 Phase(this.cascade, Iterable<Transformer> transformers) |
117 : _transformers = transformers.toSet() { | |
116 _onDirtyPool.add(_onDirtyController.stream); | 118 _onDirtyPool.add(_onDirtyController.stream); |
117 } | 119 } |
118 | 120 |
119 /// Adds a new asset as an input for this phase. | 121 /// Adds a new asset as an input for this phase. |
120 /// | 122 /// |
121 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase | 123 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase |
122 /// will automatically begin determining which transforms can consume it as a | 124 /// will automatically begin determining which transforms can consume it as a |
123 /// primary input. The transforms themselves won't be applied until [process] | 125 /// primary input. The transforms themselves won't be applied until [process] |
124 /// is called, however. | 126 /// is called, however. |
125 /// | 127 /// |
(...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
188 /// | 190 /// |
189 /// If an output with that ID cannot be found, returns null. | 191 /// If an output with that ID cannot be found, returns null. |
190 Future<AssetNode> getOutput(AssetId id) { | 192 Future<AssetNode> getOutput(AssetId id) { |
191 return newFuture(() { | 193 return newFuture(() { |
192 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); | 194 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); |
193 if (!_outputs.containsKey(id)) return null; | 195 if (!_outputs.containsKey(id)) return null; |
194 return _outputs[id].first; | 196 return _outputs[id].first; |
195 }); | 197 }); |
196 } | 198 } |
197 | 199 |
200 /// Set this phase's transformers to [transformers]. | |
201 void updateTransformers(Iterable<Transformer> transformers) { | |
202 _onDirtyController.add(null); | |
203 | |
204 var newTransformers = transformers.toSet(); | |
205 var oldTransformers = _transformers.toSet(); | |
206 for (var removedTransformer in | |
207 oldTransformers.difference(newTransformers)) { | |
208 _transformers.remove(removedTransformer); | |
209 | |
210 // Remove old transforms for which [removedTransformer] was a transformer. | |
211 for (var id in _inputs.keys) { | |
212 // If the transformers are being adjusted for [id], it will | |
213 // automatically pick up on [removedTransformer] being gone. | |
214 if (_adjustTransformersFutures.containsKey(id)) continue; | |
215 | |
216 _transforms[id].removeWhere((transform) { | |
217 if (transform.transformer != removedTransformer) return false; | |
218 transform.remove(); | |
219 return true; | |
220 }); | |
221 | |
222 if (!_transforms[id].isEmpty) continue; | |
223 _passThroughControllers.putIfAbsent(id, () { | |
224 return new AssetNodeController.available( | |
225 _inputs[id].asset, _inputs[id].transform); | |
226 }); | |
227 } | |
228 } | |
229 | |
230 var brandNewTransformers = newTransformers.difference(oldTransformers); | |
231 if (brandNewTransformers.isEmpty) return; | |
232 brandNewTransformers.forEach(_transformers.add); | |
233 | |
234 // If there are any new transformers, start re-adjusting the transforms for | |
235 // all inputs so we pick up which inputs the new transformers apply to. | |
236 _inputs.forEach((id, node) { | |
237 if (_adjustTransformersFutures.containsKey(id)) return; | |
238 _adjustTransformers(node); | |
239 }); | |
240 } | |
241 | |
242 /// Add a new phase after this one with [transformers]. | |
243 /// | |
244 /// This may only be called on a phase with no phase following it. | |
245 Phase addPhase(Iterable<Transformer> transformers) { | |
246 assert(_next == null); | |
247 _next = new Phase(cascade, transformers); | |
248 for (var output in _outputs.values.map((queue) => queue.first)) { | |
Bob Nystrom
2013/08/20 19:08:54
The map here seems a bit gratuitous. How about jus
nweiz
2013/08/20 21:39:17
Done.
| |
249 _next.addInput(output); | |
250 } | |
251 return _next; | |
252 } | |
253 | |
198 /// Asynchronously determines which transformers can consume [node] as a | 254 /// Asynchronously determines which transformers can consume [node] as a |
199 /// primary input and creates transforms for them. | 255 /// primary input and creates transforms for them. |
200 /// | 256 /// |
201 /// This ensures that if [node] is modified or removed during or after the | 257 /// This ensures that if [node] is modified or removed during or after the |
202 /// time it takes to adjust its transformers, they're appropriately | 258 /// time it takes to adjust its transformers, they're appropriately |
203 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures]. | 259 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures]. |
204 void _adjustTransformers(AssetNode node) { | 260 void _adjustTransformers(AssetNode node) { |
205 // Mark the phase as dirty. This may not actually end up creating any new | 261 // Mark the phase as dirty. This may not actually end up creating any new |
206 // transforms, but we want adding or removing a source asset to consistently | 262 // transforms, but we want adding or removing a source asset to consistently |
207 // kick off a build, even if that build does nothing. | 263 // kick off a build, even if that build does nothing. |
208 _onDirtyController.add(null); | 264 _onDirtyController.add(null); |
209 | 265 |
210 // If there's a pass-through for this node, mark it dirty while we figure | 266 // If there's a pass-through for this node, mark it dirty while we figure |
211 // out whether we need to add any transforms for it. | 267 // out whether we need to add any transforms for it. |
212 var controller = _passThroughControllers[node.id]; | 268 var controller = _passThroughControllers[node.id]; |
213 if (controller != null) controller.setDirty(); | 269 if (controller != null) controller.setDirty(); |
214 | 270 |
215 // Once the input is available, hook up transformers for it. If it changes | 271 // Once the input is available, hook up transformers for it. If it changes |
216 // while that's happening, try again. | 272 // while that's happening, try again. |
217 _adjustTransformersFutures[node.id] = node.tryUntilStable((asset) { | 273 _adjustTransformersFutures[node.id] = _tryUntilStable(node, |
274 (asset, transformers) { | |
218 var oldTransformers = _transforms[node.id] | 275 var oldTransformers = _transforms[node.id] |
219 .map((transform) => transform.transformer).toSet(); | 276 .map((transform) => transform.transformer).toSet(); |
220 | 277 |
221 return _removeStaleTransforms(asset) | 278 return _removeStaleTransforms(asset, transformers).then((_) => |
222 .then((_) => _addFreshTransforms(node, oldTransformers)); | 279 _addFreshTransforms(node, transformers, oldTransformers)); |
223 }).then((_) { | 280 }).then((_) { |
224 _adjustPassThrough(node); | 281 _adjustPassThrough(node); |
225 | 282 |
226 // Now all the transforms are set up correctly and the asset is available | 283 // Now all the transforms are set up correctly and the asset is available |
227 // for the time being. Set up handlers for when the asset changes in the | 284 // for the time being. Set up handlers for when the asset changes in the |
228 // future. | 285 // future. |
229 node.onStateChange.first.then((state) { | 286 node.onStateChange.first.then((state) { |
230 if (state.isRemoved) { | 287 if (state.isRemoved) { |
231 _onDirtyController.add(null); | 288 _onDirtyController.add(null); |
232 _transforms.remove(node.id); | 289 _transforms.remove(node.id); |
(...skipping 18 matching lines...) Expand all Loading... | |
251 _adjustTransformersFutures.remove(node.id); | 308 _adjustTransformersFutures.remove(node.id); |
252 }); | 309 }); |
253 | 310 |
254 // Don't top-level errors coming from the input processing. Any errors will | 311 // Don't top-level errors coming from the input processing. Any errors will |
255 // eventually be piped through [process]'s returned Future. | 312 // eventually be piped through [process]'s returned Future. |
256 _adjustTransformersFutures[node.id].catchError((_) {}); | 313 _adjustTransformersFutures[node.id].catchError((_) {}); |
257 } | 314 } |
258 | 315 |
259 // Remove any old transforms that used to have [asset] as a primary asset but | 316 // Remove any old transforms that used to have [asset] as a primary asset but |
260 // no longer apply to its new contents. | 317 // no longer apply to its new contents. |
261 Future _removeStaleTransforms(Asset asset) { | 318 Future _removeStaleTransforms(Asset asset, Set<Transformer> transformers) { |
262 return Future.wait(_transforms[asset.id].map((transform) { | 319 return Future.wait(_transforms[asset.id].map((transform) { |
263 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to | 320 return newFuture(() { |
264 // results. | 321 if (!transformers.contains(transform.transformer)) return false; |
265 return transform.transformer.isPrimary(asset).then((isPrimary) { | 322 |
323 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to | |
324 // results. | |
325 return transform.transformer.isPrimary(asset); | |
326 }).then((isPrimary) { | |
266 if (isPrimary) return; | 327 if (isPrimary) return; |
267 _transforms[asset.id].remove(transform); | 328 _transforms[asset.id].remove(transform); |
268 _onDirtyPool.remove(transform.onDirty); | 329 _onDirtyPool.remove(transform.onDirty); |
269 transform.remove(); | 330 transform.remove(); |
270 }); | 331 }); |
271 })); | 332 })); |
272 } | 333 } |
273 | 334 |
274 // Add new transforms for transformers that consider [node]'s asset to be a | 335 // Add new transforms for transformers that consider [node]'s asset to be a |
275 // primary input. | 336 // primary input. |
276 // | 337 // |
277 // [oldTransformers] is the set of transformers that had [node] as a primary | 338 // [oldTransformers] is the set of transformers for which there were |
278 // input prior to this. They don't need to be checked, since they were removed | 339 // transforms that had [node] as a primary input prior to this. They don't |
279 // or preserved in [_removeStaleTransforms]. | 340 // need to be checked, since their transforms were removed or preserved in |
280 Future _addFreshTransforms(AssetNode node, Set<Transformer> oldTransformers) { | 341 // [_removeStaleTransforms]. |
281 return Future.wait(_transformers.map((transformer) { | 342 Future _addFreshTransforms(AssetNode node, Set<Transformer> transformers, |
343 Set<Transformer> oldTransformers) { | |
344 return Future.wait(transformers.map((transformer) { | |
282 if (oldTransformers.contains(transformer)) return new Future.value(); | 345 if (oldTransformers.contains(transformer)) return new Future.value(); |
283 | 346 |
284 // If the asset is unavailable, the results of this [_adjustTransformers] | 347 // If the asset is unavailable, the results of this [_adjustTransformers] |
285 // run will be discarded, so we can just short-circuit. | 348 // run will be discarded, so we can just short-circuit. |
286 if (node.asset == null) return new Future.value(); | 349 if (node.asset == null) return new Future.value(); |
287 | 350 |
288 // We can safely access [node.asset] here even though it might have | 351 // We can safely access [node.asset] here even though it might have |
289 // changed since (as above) if it has, [_adjustTransformers] will just be | 352 // changed since (as above) if it has, [_adjustTransformers] will just be |
290 // re-run. | 353 // re-run. |
291 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to | 354 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to |
(...skipping 22 matching lines...) Expand all Loading... | |
314 } else { | 377 } else { |
315 _passThroughControllers[node.id] = | 378 _passThroughControllers[node.id] = |
316 new AssetNodeController.available(node.asset, node.transform); | 379 new AssetNodeController.available(node.asset, node.transform); |
317 } | 380 } |
318 } else { | 381 } else { |
319 var controller = _passThroughControllers.remove(node.id); | 382 var controller = _passThroughControllers.remove(node.id); |
320 if (controller != null) controller.setRemoved(); | 383 if (controller != null) controller.setRemoved(); |
321 } | 384 } |
322 } | 385 } |
323 | 386 |
387 /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this | |
388 /// phase's transformers are modified. | |
389 Future _tryUntilStable(AssetNode node, | |
390 Future callback(Asset asset, Set<Transformer> transformers)) { | |
391 var oldTransformers; | |
392 return node.tryUntilStable((asset) { | |
393 oldTransformers = _transformers.toSet(); | |
394 return callback(asset, _transformers); | |
395 }).then((result) { | |
396 if (setEquals(oldTransformers, _transformers)) return result; | |
397 return _tryUntilStable(node, callback); | |
398 }); | |
399 } | |
400 | |
324 /// Processes this phase. | 401 /// Processes this phase. |
325 /// | 402 /// |
326 /// Returns a future that completes when processing is done. If there is | 403 /// Returns a future that completes when processing is done. If there is |
327 /// nothing to process, returns `null`. | 404 /// nothing to process, returns `null`. |
328 Future process() { | 405 Future process() { |
329 if (_adjustTransformersFutures.isEmpty) return _processTransforms(); | 406 if (_adjustTransformersFutures.isEmpty) return _processTransforms(); |
330 return _waitForInputs().then((_) => _processTransforms()); | 407 return _waitForInputs().then((_) => _processTransforms()); |
331 } | 408 } |
332 | 409 |
333 Future _waitForInputs() { | 410 Future _waitForInputs() { |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
426 // Pump the event queue to ensure that the removal of the input triggers | 503 // Pump the event queue to ensure that the removal of the input triggers |
427 // a new build to which we can attach the error. | 504 // a new build to which we can attach the error. |
428 newFuture(() => cascade.reportError(new AssetCollisionException( | 505 newFuture(() => cascade.reportError(new AssetCollisionException( |
429 assets.where((asset) => asset.transform != null) | 506 assets.where((asset) => asset.transform != null) |
430 .map((asset) => asset.transform.info), | 507 .map((asset) => asset.transform.info), |
431 output.id))); | 508 output.id))); |
432 } | 509 } |
433 }); | 510 }); |
434 } | 511 } |
435 } | 512 } |
OLD | NEW |