| 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'; | |
| 11 import 'asset_cascade.dart'; | 10 import 'asset_cascade.dart'; |
| 12 import 'asset_id.dart'; | 11 import 'asset_id.dart'; |
| 13 import 'asset_node.dart'; | 12 import 'asset_node.dart'; |
| 14 import 'errors.dart'; | 13 import 'errors.dart'; |
| 14 import 'phase_input.dart'; |
| 15 import 'stream_pool.dart'; | 15 import 'stream_pool.dart'; |
| 16 import 'transform_node.dart'; | |
| 17 import 'transformer.dart'; | 16 import 'transformer.dart'; |
| 18 import 'utils.dart'; | 17 import 'utils.dart'; |
| 19 | 18 |
| 20 /// One phase in the ordered series of transformations in an [AssetCascade]. | 19 /// One phase in the ordered series of transformations in an [AssetCascade]. |
| 21 /// | 20 /// |
| 22 /// Each phase can access outputs from previous phases and can in turn pass | 21 /// Each phase can access outputs from previous phases and can in turn pass |
| 23 /// outputs to later phases. Phases are processed strictly serially. All | 22 /// outputs to later phases. Phases are processed strictly serially. All |
| 24 /// transforms in a phase will be complete before moving on to the next phase. | 23 /// transforms in a phase will be complete before moving on to the next phase. |
| 25 /// Within a single phase, all transforms will be run in parallel. | 24 /// Within a single phase, all transforms will be run in parallel. |
| 26 /// | 25 /// |
| 27 /// Building can be interrupted between phases. For example, a source is added | 26 /// Building can be interrupted between phases. For example, a source is added |
| 28 /// which starts the background process. Sometime during, say, phase 2 (which | 27 /// which starts the background process. Sometime during, say, phase 2 (which |
| 29 /// is running asynchronously) that source is modified. When the process queue | 28 /// 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 | 29 /// goes to advance to phase 3, it will see that modification and start the |
| 31 /// waterfall from the beginning again. | 30 /// waterfall from the beginning again. |
| 32 class Phase { | 31 class Phase { |
| 33 /// The cascade that owns this phase. | 32 /// The cascade that owns this phase. |
| 34 final AssetCascade cascade; | 33 final AssetCascade cascade; |
| 35 | 34 |
| 36 /// The transformers that can access [inputs]. | 35 /// The transformers that can access [inputs]. |
| 37 /// | 36 /// |
| 38 /// Their outputs will be available to the next phase. | 37 /// Their outputs will be available to the next phase. |
| 39 final Set<Transformer> _transformers; | 38 final Set<Transformer> _transformers; |
| 40 | 39 |
| 41 /// The inputs that are available for transforms in this phase to consume. | 40 /// The inputs for this phase. |
| 42 /// | 41 /// |
| 43 /// For the first phase, these will be the source assets. For all other | 42 /// For the first phase, these will be the source assets. For all other |
| 44 /// phases, they will be the outputs from the previous phase. | 43 /// phases, they will be the outputs from the previous phase. |
| 45 final _inputs = new Map<AssetId, AssetNode>(); | 44 final _inputs = new Map<AssetId, PhaseInput>(); |
| 46 | |
| 47 /// The transforms currently applicable to assets in [inputs], indexed by | |
| 48 /// the ids of their primary inputs. | |
| 49 /// | |
| 50 /// These are the transforms that have been "wired up": they represent a | |
| 51 /// repeatable transformation of a single concrete set of inputs. "dart2js" | |
| 52 /// is a transformer. "dart2js on web/main.dart" is a transform. | |
| 53 final _transforms = new Map<AssetId, Set<TransformNode>>(); | |
| 54 | |
| 55 /// Controllers for assets that aren't consumed by transforms in this phase. | |
| 56 /// | |
| 57 /// These assets are passed to the next phase unmodified. They need | |
| 58 /// intervening controllers to ensure that the outputs can be marked dirty | |
| 59 /// when determining whether transforms apply, and removed if they do. | |
| 60 final _passThroughControllers = new Map<AssetId, AssetNodeController>(); | |
| 61 | |
| 62 /// Futures that will complete once the transformers that can consume a given | |
| 63 /// asset are determined. | |
| 64 /// | |
| 65 /// Whenever an asset is added or modified, we need to asynchronously | |
| 66 /// determine which transformers can use it as their primary input. We can't | |
| 67 /// start processing until we know which transformers to run, and this allows | |
| 68 /// us to wait until we do. | |
| 69 var _adjustTransformersFutures = new Map<AssetId, Future>(); | |
| 70 | |
| 71 /// New asset nodes that were added while [_adjustTransformers] was still | |
| 72 /// being run on an old version of that asset. | |
| 73 var _pendingNewInputs = new Map<AssetId, AssetNode>(); | |
| 74 | 45 |
| 75 /// A map of output ids to the asset node outputs for those ids and the | 46 /// A map of output ids to the asset node outputs for those ids and the |
| 76 /// transforms that produced those asset nodes. | 47 /// transforms that produced those asset nodes. |
| 77 /// | 48 /// |
| 78 /// Usually there's only one node for a given output id. However, it's | 49 /// Usually there's only one node for a given output id. However, it's |
| 79 /// possible for multiple transformers to output an asset with the same id. In | 50 /// possible for multiple transformers to output an asset with the same id. In |
| 80 /// that case, the chronologically first output emitted is passed forward. We | 51 /// that case, the chronologically first output emitted is passed forward. We |
| 81 /// keep track of the other nodes so that if that output is removed, we know | 52 /// keep track of the other nodes so that if that output is removed, we know |
| 82 /// which asset to replace it with. | 53 /// which asset to replace it with. |
| 83 final _outputs = new Map<AssetId, Queue<AssetNode>>(); | 54 final _outputs = new Map<AssetId, Queue<AssetNode>>(); |
| 84 | 55 |
| 85 /// A stream that emits an event whenever this phase becomes dirty and needs | 56 /// A stream that emits an event whenever this phase becomes dirty and needs |
| 86 /// to be run. | 57 /// to be run. |
| 87 /// | 58 /// |
| 88 /// This may emit events when the phase was already dirty or while processing | 59 /// This may emit events when the phase was already dirty or while processing |
| 89 /// transforms. Events are emitted synchronously to ensure that the dirty | 60 /// transforms. Events are emitted synchronously to ensure that the dirty |
| 90 /// state is thoroughly propagated as soon as any assets are changed. | 61 /// state is thoroughly propagated as soon as any assets are changed. |
| 91 Stream get onDirty => _onDirtyPool.stream; | 62 Stream get onDirty => _onDirtyPool.stream; |
| 92 final _onDirtyPool = new StreamPool.broadcast(); | 63 final _onDirtyPool = new StreamPool.broadcast(); |
| 93 | 64 |
| 94 /// A controller whose stream feeds into [_onDirtyPool]. | 65 /// A controller whose stream feeds into [_onDirtyPool]. |
| 95 /// | 66 /// |
| 96 /// This is used whenever an input is added, changed, or removed. It's | 67 /// This is used whenever an input is added or transforms are changed. |
| 97 /// sometimes redundant with the events collected from [_transforms], but this | |
| 98 /// stream is necessary for new and removed inputs, and the transform stream | |
| 99 /// is necessary for modified secondary inputs. | |
| 100 final _onDirtyController = new StreamController.broadcast(sync: true); | 68 final _onDirtyController = new StreamController.broadcast(sync: true); |
| 101 | 69 |
| 102 /// The phase after this one. | 70 /// The phase after this one. |
| 103 /// | 71 /// |
| 104 /// Outputs from this phase will be passed to it. | 72 /// Outputs from this phase will be passed to it. |
| 105 Phase get next => _next; | 73 Phase get next => _next; |
| 106 Phase _next; | 74 Phase _next; |
| 107 | 75 |
| 108 /// Returns all currently-available output assets for this phase. | 76 /// Returns all currently-available output assets for this phase. |
| 109 AssetSet get availableOutputs { | 77 AssetSet get availableOutputs { |
| (...skipping 12 matching lines...) Expand all Loading... |
| 122 /// | 90 /// |
| 123 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase | 91 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase |
| 124 /// will automatically begin determining which transforms can consume it as a | 92 /// will automatically begin determining which transforms can consume it as a |
| 125 /// primary input. The transforms themselves won't be applied until [process] | 93 /// primary input. The transforms themselves won't be applied until [process] |
| 126 /// is called, however. | 94 /// is called, however. |
| 127 /// | 95 /// |
| 128 /// This should only be used for brand-new assets or assets that have been | 96 /// This should only be used for brand-new assets or assets that have been |
| 129 /// removed and re-created. The phase will automatically handle updated assets | 97 /// removed and re-created. The phase will automatically handle updated assets |
| 130 /// using the [AssetNode.onStateChange] stream. | 98 /// using the [AssetNode.onStateChange] stream. |
| 131 void addInput(AssetNode node) { | 99 void addInput(AssetNode node) { |
| 132 // We remove [node.id] from [inputs] as soon as the node is removed rather | 100 if (_inputs.containsKey(node.id)) _inputs[node.id].remove(); |
| 133 // than at the same time [node.id] is removed from [_transforms] so we don't | |
| 134 // have to wait on [_adjustTransformers]. It's important that [inputs] is | |
| 135 // always up-to-date so that the [AssetCascade] can look there for available | |
| 136 // assets. | |
| 137 _inputs[node.id] = node; | |
| 138 node.whenRemoved.then((_) => _inputs.remove(node.id)); | |
| 139 | 101 |
| 140 if (!_adjustTransformersFutures.containsKey(node.id)) { | 102 _inputs[node.id] = new PhaseInput(this, node, _transformers); |
| 141 _transforms[node.id] = new Set<TransformNode>(); | 103 _inputs[node.id].input.whenRemoved.then((_) => _inputs.remove(node.id)); |
| 142 _adjustTransformers(node); | 104 _onDirtyPool.add(_inputs[node.id].onDirty); |
| 143 return; | 105 _onDirtyController.add(null); |
| 144 } | |
| 145 | |
| 146 // If an input is added while the same input is still being processed, | |
| 147 // that means that the asset was removed and recreated while | |
| 148 // [_adjustTransformers] was being run on the old value. We have to wait | |
| 149 // until that finishes, then run it again on whatever the newest version | |
| 150 // of that asset is. | |
| 151 | |
| 152 // We may already be waiting for the existing [_adjustTransformers] call to | |
| 153 // finish. If so, all we need to do is change the node that will be loaded | |
| 154 // after it completes. | |
| 155 var containedKey = _pendingNewInputs.containsKey(node.id); | |
| 156 _pendingNewInputs[node.id] = node; | |
| 157 if (containedKey) return; | |
| 158 | |
| 159 // If we aren't already waiting, start doing so. | |
| 160 _adjustTransformersFutures[node.id].then((_) { | |
| 161 assert(!_adjustTransformersFutures.containsKey(node.id)); | |
| 162 assert(_pendingNewInputs.containsKey(node.id)); | |
| 163 _transforms[node.id] = new Set<TransformNode>(); | |
| 164 _adjustTransformers(_pendingNewInputs.remove(node.id)); | |
| 165 }, onError: (_) { | |
| 166 // If there was a programmatic error while processing the old input, | |
| 167 // we don't want to just ignore it; it may have left the system in an | |
| 168 // inconsistent state. We also don't want to top-level it, so we | |
| 169 // ignore it here but don't start processing the new input. That way | |
| 170 // when [process] is called, the error will be piped through its | |
| 171 // return value. | |
| 172 }).catchError((e) { | |
| 173 // If our code above has a programmatic error, ensure it will be piped | |
| 174 // through [process] by putting it into [_adjustTransformersFutures]. | |
| 175 _adjustTransformersFutures[node.id] = new Future.error(e); | |
| 176 }); | |
| 177 } | 106 } |
| 178 | 107 |
| 179 /// Gets the asset node for an input [id]. | 108 /// Gets the asset node for an input [id]. |
| 180 /// | 109 /// |
| 181 /// If an input with that ID cannot be found, returns null. | 110 /// If an input with that ID cannot be found, returns null. |
| 182 Future<AssetNode> getInput(AssetId id) { | 111 Future<AssetNode> getInput(AssetId id) { |
| 183 return newFuture(() { | 112 return newFuture(() { |
| 184 if (id.package == cascade.package) return _inputs[id]; | 113 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); |
| 185 return cascade.graph.getAssetNode(id); | 114 if (_inputs.containsKey(id)) return _inputs[id].input; |
| 115 return null; |
| 186 }); | 116 }); |
| 187 } | 117 } |
| 188 | 118 |
| 189 /// Gets the asset node for an output [id]. | 119 /// Gets the asset node for an output [id]. |
| 190 /// | 120 /// |
| 191 /// If an output with that ID cannot be found, returns null. | 121 /// If an output with that ID cannot be found, returns null. |
| 192 Future<AssetNode> getOutput(AssetId id) { | 122 Future<AssetNode> getOutput(AssetId id) { |
| 193 return newFuture(() { | 123 return newFuture(() { |
| 194 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); | 124 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); |
| 195 if (!_outputs.containsKey(id)) return null; | 125 if (!_outputs.containsKey(id)) return null; |
| 196 return _outputs[id].first; | 126 return _outputs[id].first; |
| 197 }); | 127 }); |
| 198 } | 128 } |
| 199 | 129 |
| 200 /// Set this phase's transformers to [transformers]. | 130 /// Set this phase's transformers to [transformers]. |
| 201 void updateTransformers(Iterable<Transformer> transformers) { | 131 void updateTransformers(Iterable<Transformer> transformers) { |
| 202 _onDirtyController.add(null); | 132 _onDirtyController.add(null); |
| 203 | 133 _transformers.clear(); |
| 204 var newTransformers = transformers.toSet(); | 134 _transformers.addAll(transformers); |
| 205 var oldTransformers = _transformers.toSet(); | 135 for (var input in _inputs.values) { |
| 206 for (var removedTransformer in | 136 input.updateTransformers(transformers); |
| 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 } | 137 } |
| 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 } | 138 } |
| 241 | 139 |
| 242 /// Add a new phase after this one with [transformers]. | 140 /// Add a new phase after this one with [transformers]. |
| 243 /// | 141 /// |
| 244 /// This may only be called on a phase with no phase following it. | 142 /// This may only be called on a phase with no phase following it. |
| 245 Phase addPhase(Iterable<Transformer> transformers) { | 143 Phase addPhase(Iterable<Transformer> transformers) { |
| 246 assert(_next == null); | 144 assert(_next == null); |
| 247 _next = new Phase(cascade, transformers); | 145 _next = new Phase(cascade, transformers); |
| 248 for (var outputs in _outputs.values) { | 146 for (var outputs in _outputs.values) { |
| 249 _next.addInput(outputs.first); | 147 _next.addInput(outputs.first); |
| 250 } | 148 } |
| 251 return _next; | 149 return _next; |
| 252 } | 150 } |
| 253 | 151 |
| 254 /// Asynchronously determines which transformers can consume [node] as a | |
| 255 /// primary input and creates transforms for them. | |
| 256 /// | |
| 257 /// This ensures that if [node] is modified or removed during or after the | |
| 258 /// time it takes to adjust its transformers, they're appropriately | |
| 259 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures]. | |
| 260 void _adjustTransformers(AssetNode node) { | |
| 261 // Mark the phase as dirty. This may not actually end up creating any new | |
| 262 // transforms, but we want adding or removing a source asset to consistently | |
| 263 // kick off a build, even if that build does nothing. | |
| 264 _onDirtyController.add(null); | |
| 265 | |
| 266 // If there's a pass-through for this node, mark it dirty while we figure | |
| 267 // out whether we need to add any transforms for it. | |
| 268 var controller = _passThroughControllers[node.id]; | |
| 269 if (controller != null) controller.setDirty(); | |
| 270 | |
| 271 // Once the input is available, hook up transformers for it. If it changes | |
| 272 // while that's happening, try again. | |
| 273 _adjustTransformersFutures[node.id] = _tryUntilStable(node, | |
| 274 (asset, transformers) { | |
| 275 var oldTransformers = _transforms[node.id] | |
| 276 .map((transform) => transform.transformer).toSet(); | |
| 277 | |
| 278 return _removeStaleTransforms(asset, transformers).then((_) => | |
| 279 _addFreshTransforms(node, transformers, oldTransformers)); | |
| 280 }).then((_) { | |
| 281 _adjustPassThrough(node); | |
| 282 | |
| 283 // Now all the transforms are set up correctly and the asset is available | |
| 284 // for the time being. Set up handlers for when the asset changes in the | |
| 285 // future. | |
| 286 node.onStateChange.first.then((state) { | |
| 287 if (state.isRemoved) { | |
| 288 _onDirtyController.add(null); | |
| 289 _transforms.remove(node.id); | |
| 290 var passThrough = _passThroughControllers.remove(node.id); | |
| 291 if (passThrough != null) passThrough.setRemoved(); | |
| 292 } else { | |
| 293 _adjustTransformers(node); | |
| 294 } | |
| 295 }).catchError((e) { | |
| 296 _adjustTransformersFutures[node.id] = new Future.error(e); | |
| 297 }); | |
| 298 }).catchError((error) { | |
| 299 if (error is! AssetNotFoundException || error.id != node.id) throw error; | |
| 300 | |
| 301 // If the asset is removed, [tryUntilStable] will throw an | |
| 302 // [AssetNotFoundException]. In that case, just remove all transforms for | |
| 303 // the node, and its pass-through. | |
| 304 _transforms.remove(node.id); | |
| 305 var passThrough = _passThroughControllers.remove(node.id); | |
| 306 if (passThrough != null) passThrough.setRemoved(); | |
| 307 }).whenComplete(() { | |
| 308 _adjustTransformersFutures.remove(node.id); | |
| 309 }); | |
| 310 | |
| 311 // Don't top-level errors coming from the input processing. Any errors will | |
| 312 // eventually be piped through [process]'s returned Future. | |
| 313 _adjustTransformersFutures[node.id].catchError((_) {}); | |
| 314 } | |
| 315 | |
| 316 // Remove any old transforms that used to have [asset] as a primary asset but | |
| 317 // no longer apply to its new contents. | |
| 318 Future _removeStaleTransforms(Asset asset, Set<Transformer> transformers) { | |
| 319 return Future.wait(_transforms[asset.id].map((transform) { | |
| 320 return newFuture(() { | |
| 321 if (!transformers.contains(transform.transformer)) return false; | |
| 322 | |
| 323 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to | |
| 324 // results. | |
| 325 return transform.transformer.isPrimary(asset); | |
| 326 }).then((isPrimary) { | |
| 327 if (isPrimary) return; | |
| 328 _transforms[asset.id].remove(transform); | |
| 329 _onDirtyPool.remove(transform.onDirty); | |
| 330 transform.remove(); | |
| 331 }); | |
| 332 })); | |
| 333 } | |
| 334 | |
| 335 // Add new transforms for transformers that consider [node]'s asset to be a | |
| 336 // primary input. | |
| 337 // | |
| 338 // [oldTransformers] is the set of transformers for which there were | |
| 339 // transforms that had [node] as a primary input prior to this. They don't | |
| 340 // need to be checked, since their transforms were removed or preserved in | |
| 341 // [_removeStaleTransforms]. | |
| 342 Future _addFreshTransforms(AssetNode node, Set<Transformer> transformers, | |
| 343 Set<Transformer> oldTransformers) { | |
| 344 return Future.wait(transformers.map((transformer) { | |
| 345 if (oldTransformers.contains(transformer)) return new Future.value(); | |
| 346 | |
| 347 // If the asset is unavailable, the results of this [_adjustTransformers] | |
| 348 // run will be discarded, so we can just short-circuit. | |
| 349 if (node.asset == null) return new Future.value(); | |
| 350 | |
| 351 // We can safely access [node.asset] here even though it might have | |
| 352 // changed since (as above) if it has, [_adjustTransformers] will just be | |
| 353 // re-run. | |
| 354 // TODO(rnystrom): Catch all errors from isPrimary() and redirect to | |
| 355 // results. | |
| 356 return transformer.isPrimary(node.asset).then((isPrimary) { | |
| 357 if (!isPrimary) return; | |
| 358 var transform = new TransformNode(this, transformer, node); | |
| 359 _transforms[node.id].add(transform); | |
| 360 _onDirtyPool.add(transform.onDirty); | |
| 361 }); | |
| 362 })); | |
| 363 } | |
| 364 | |
| 365 /// Adjust whether [node] is passed through the phase unmodified, based on | |
| 366 /// whether it's consumed by other transforms in this phase. | |
| 367 /// | |
| 368 /// If [node] was already passed-through, this will update the passed-through | |
| 369 /// value. | |
| 370 void _adjustPassThrough(AssetNode node) { | |
| 371 assert(node.state.isAvailable); | |
| 372 | |
| 373 if (_transforms[node.id].isEmpty) { | |
| 374 var controller = _passThroughControllers[node.id]; | |
| 375 if (controller != null) { | |
| 376 controller.setAvailable(node.asset); | |
| 377 } else { | |
| 378 _passThroughControllers[node.id] = | |
| 379 new AssetNodeController.available(node.asset, node.transform); | |
| 380 } | |
| 381 } else { | |
| 382 var controller = _passThroughControllers.remove(node.id); | |
| 383 if (controller != null) controller.setRemoved(); | |
| 384 } | |
| 385 } | |
| 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 | |
| 401 /// Processes this phase. | 152 /// Processes this phase. |
| 402 /// | 153 /// |
| 403 /// Returns a future that completes when processing is done. If there is | 154 /// Returns a future that completes when processing is done. If there is |
| 404 /// nothing to process, returns `null`. | 155 /// nothing to process, returns `null`. |
| 405 Future process() { | 156 Future process() { |
| 406 if (_adjustTransformersFutures.isEmpty) return _processTransforms(); | 157 if (!_inputs.values.any((input) => input.isDirty)) return null; |
| 407 return _waitForInputs().then((_) => _processTransforms()); | |
| 408 } | |
| 409 | 158 |
| 410 Future _waitForInputs() { | 159 return Future.wait(_inputs.values.map((input) { |
| 411 if (_adjustTransformersFutures.isEmpty) return new Future.value(); | 160 if (!input.isDirty) return new Future.value(new Set()); |
| 412 return Future.wait(_adjustTransformersFutures.values) | 161 return input.process().then((outputs) { |
| 413 .then((_) => _waitForInputs()); | 162 return outputs.where(_addOutput).map((output) => output.id).toSet(); |
| 414 } | |
| 415 | |
| 416 /// Applies all currently wired up and dirty transforms. | |
| 417 Future _processTransforms() { | |
| 418 var newPassThroughs = _passThroughControllers.values | |
| 419 .map((controller) => controller.node) | |
| 420 .where((output) { | |
| 421 return !_outputs.containsKey(output.id) || | |
| 422 !_outputs[output.id].contains(output); | |
| 423 }).toSet(); | |
| 424 | |
| 425 // Convert this to a list so we can safely modify _transforms while | |
| 426 // iterating over it. | |
| 427 var dirtyTransforms = | |
| 428 flatten(_transforms.values.map((transforms) => transforms.toList())) | |
| 429 .where((transform) => transform.isDirty).toList(); | |
| 430 | |
| 431 if (dirtyTransforms.isEmpty && newPassThroughs.isEmpty) return null; | |
| 432 | |
| 433 var collisions = new Set<AssetId>(); | |
| 434 for (var output in newPassThroughs) { | |
| 435 if (_addOutput(output)) collisions.add(output.id); | |
| 436 } | |
| 437 | |
| 438 return Future.wait(dirtyTransforms.map((transform) { | |
| 439 return transform.apply().then((outputs) { | |
| 440 for (var output in outputs) { | |
| 441 if (_addOutput(output)) collisions.add(output.id); | |
| 442 } | |
| 443 }); | 163 }); |
| 444 })).then((_) { | 164 })).then((collisionsList) { |
| 445 // Report collisions in a deterministic order. | 165 // Report collisions in a deterministic order. |
| 446 collisions = collisions.toList(); | 166 var collisions = unionAll(collisionsList).toList(); |
| 447 collisions.sort((a, b) => a.compareTo(b)); | 167 collisions.sort((a, b) => a.compareTo(b)); |
| 448 for (var collision in collisions) { | 168 for (var collision in collisions) { |
| 449 // Ensure that there's still a collision. It's possible it was resolved | 169 // Ensure that there's still a collision. It's possible it was resolved |
| 450 // while another transform was running. | 170 // while another transform was running. |
| 451 if (_outputs[collision].length <= 1) continue; | 171 if (_outputs[collision].length <= 1) continue; |
| 452 cascade.reportError(new AssetCollisionException( | 172 cascade.reportError(new AssetCollisionException( |
| 453 _outputs[collision].where((asset) => asset.transform != null) | 173 _outputs[collision].where((asset) => asset.transform != null) |
| 454 .map((asset) => asset.transform.info), | 174 .map((asset) => asset.transform.info), |
| 455 collision)); | 175 collision)); |
| 456 } | 176 } |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 503 // Pump the event queue to ensure that the removal of the input triggers | 223 // Pump the event queue to ensure that the removal of the input triggers |
| 504 // a new build to which we can attach the error. | 224 // a new build to which we can attach the error. |
| 505 newFuture(() => cascade.reportError(new AssetCollisionException( | 225 newFuture(() => cascade.reportError(new AssetCollisionException( |
| 506 assets.where((asset) => asset.transform != null) | 226 assets.where((asset) => asset.transform != null) |
| 507 .map((asset) => asset.transform.info), | 227 .map((asset) => asset.transform.info), |
| 508 output.id))); | 228 output.id))); |
| 509 } | 229 } |
| 510 }); | 230 }); |
| 511 } | 231 } |
| 512 } | 232 } |
| OLD | NEW |