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