Chromium Code Reviews| 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 16 matching lines...) Expand all Loading... | |
| 27 /// | 27 /// |
| 28 /// Building can be interrupted between phases. For example, a source is added | 28 /// Building can be interrupted between phases. For example, a source is added |
| 29 /// which starts the background process. Sometime during, say, phase 2 (which | 29 /// which starts the background process. Sometime during, say, phase 2 (which |
| 30 /// is running asynchronously) that source is modified. When the process queue | 30 /// 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 | 31 /// goes to advance to phase 3, it will see that modification and start the |
| 32 /// waterfall from the beginning again. | 32 /// waterfall from the beginning again. |
| 33 class Phase { | 33 class Phase { |
| 34 /// The cascade that owns this phase. | 34 /// The cascade that owns this phase. |
| 35 final AssetCascade cascade; | 35 final AssetCascade cascade; |
| 36 | 36 |
| 37 /// This phase's position relative to the other phases. Zero-based. | |
| 38 final int _index; | |
| 39 | |
| 40 /// The transformers that can access [inputs]. | 37 /// The transformers that can access [inputs]. |
| 41 /// | 38 /// |
| 42 /// Their outputs will be available to the next phase. | 39 /// Their outputs will be available to the next phase. |
| 43 final List<Transformer> _transformers; | 40 final List<Transformer> _transformers; |
| 44 | 41 |
| 45 /// The inputs that are available for transforms in this phase to consume. | 42 /// The inputs that are available for transforms in this phase to consume. |
| 46 /// | 43 /// |
| 47 /// For the first phase, these will be the source assets. For all other | 44 /// For the first phase, these will be the source assets. For all other |
| 48 /// phases, they will be the outputs from the previous phase. | 45 /// phases, they will be the outputs from the previous phase. |
| 49 final _inputs = new Map<AssetId, AssetNode>(); | 46 final _inputs = new Map<AssetId, AssetNode>(); |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 101 /// sometimes redundant with the events collected from [_transforms], but this | 98 /// sometimes redundant with the events collected from [_transforms], but this |
| 102 /// stream is necessary for new and removed inputs, and the transform stream | 99 /// stream is necessary for new and removed inputs, and the transform stream |
| 103 /// is necessary for modified secondary inputs. | 100 /// is necessary for modified secondary inputs. |
| 104 final _onDirtyController = new StreamController.broadcast(sync: true); | 101 final _onDirtyController = new StreamController.broadcast(sync: true); |
| 105 | 102 |
| 106 /// The phase after this one. | 103 /// The phase after this one. |
| 107 /// | 104 /// |
| 108 /// Outputs from this phase will be passed to it. | 105 /// Outputs from this phase will be passed to it. |
| 109 final Phase _next; | 106 final Phase _next; |
| 110 | 107 |
| 111 Phase(this.cascade, this._index, this._transformers, this._next) { | 108 Phase(this.cascade, this._transformers, this._next) { |
| 112 _onDirtyPool.add(_onDirtyController.stream); | 109 _onDirtyPool.add(_onDirtyController.stream); |
| 113 } | 110 } |
| 114 | 111 |
| 115 /// Adds a new asset as an input for this phase. | 112 /// Adds a new asset as an input for this phase. |
| 116 /// | 113 /// |
| 117 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase | 114 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase |
| 118 /// will automatically begin determining which transforms can consume it as a | 115 /// will automatically begin determining which transforms can consume it as a |
| 119 /// primary input. The transforms themselves won't be applied until [process] | 116 /// primary input. The transforms themselves won't be applied until [process] |
| 120 /// is called, however. | 117 /// is called, however. |
| 121 /// | 118 /// |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 173 /// Gets the asset node for an input [id]. | 170 /// Gets the asset node for an input [id]. |
| 174 /// | 171 /// |
| 175 /// If an input with that ID cannot be found, returns null. | 172 /// If an input with that ID cannot be found, returns null. |
| 176 Future<AssetNode> getInput(AssetId id) { | 173 Future<AssetNode> getInput(AssetId id) { |
| 177 return newFuture(() { | 174 return newFuture(() { |
| 178 if (id.package == cascade.package) return _inputs[id]; | 175 if (id.package == cascade.package) return _inputs[id]; |
| 179 return cascade.graph.getAssetNode(id); | 176 return cascade.graph.getAssetNode(id); |
| 180 }); | 177 }); |
| 181 } | 178 } |
| 182 | 179 |
| 180 /// Gets the asset node for an output [id]. | |
| 181 /// | |
| 182 /// If an output with that ID cannot be found, returns null. | |
| 183 Future<AssetNode> getOutput(AssetId id) { | |
| 184 return newFuture(() { | |
| 185 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); | |
| 186 if (!_outputs.containsKey(id)) return null; | |
| 187 return _outputs[id].first; | |
| 188 }); | |
| 189 } | |
| 190 | |
| 191 /// Returns all currently-available output assets for this phase. | |
| 192 AssetSet get availableOutputs { | |
| 193 return new AssetSet.from(_outputs.values | |
| 194 .map((queue) => queue.first) | |
| 195 .where((node) => node.state.isAvailable) | |
| 196 .map((node) => node.asset)); | |
| 197 } | |
|
Bob Nystrom
2013/08/19 20:46:36
Nit, but I think we generally put getters above th
nweiz
2013/08/20 00:26:26
Done.
| |
| 198 | |
| 183 /// Asynchronously determines which transformers can consume [node] as a | 199 /// Asynchronously determines which transformers can consume [node] as a |
| 184 /// primary input and creates transforms for them. | 200 /// primary input and creates transforms for them. |
| 185 /// | 201 /// |
| 186 /// This ensures that if [node] is modified or removed during or after the | 202 /// This ensures that if [node] is modified or removed during or after the |
| 187 /// time it takes to adjust its transformers, they're appropriately | 203 /// time it takes to adjust its transformers, they're appropriately |
| 188 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures]. | 204 /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures]. |
| 189 void _adjustTransformers(AssetNode node) { | 205 void _adjustTransformers(AssetNode node) { |
| 190 // Mark the phase as dirty. This may not actually end up creating any new | 206 // Mark the phase as dirty. This may not actually end up creating any new |
| 191 // transforms, but we want adding or removing a source asset to consistently | 207 // transforms, but we want adding or removing a source asset to consistently |
| 192 // kick off a build, even if that build does nothing. | 208 // kick off a build, even if that build does nothing. |
| (...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 316 } | 332 } |
| 317 | 333 |
| 318 Future _waitForInputs() { | 334 Future _waitForInputs() { |
| 319 if (_adjustTransformersFutures.isEmpty) return new Future.value(); | 335 if (_adjustTransformersFutures.isEmpty) return new Future.value(); |
| 320 return Future.wait(_adjustTransformersFutures.values) | 336 return Future.wait(_adjustTransformersFutures.values) |
| 321 .then((_) => _waitForInputs()); | 337 .then((_) => _waitForInputs()); |
| 322 } | 338 } |
| 323 | 339 |
| 324 /// Applies all currently wired up and dirty transforms. | 340 /// Applies all currently wired up and dirty transforms. |
| 325 Future _processTransforms() { | 341 Future _processTransforms() { |
| 326 if (_next == null) return; | |
| 327 | |
| 328 var newPassThroughs = _passThroughControllers.values | 342 var newPassThroughs = _passThroughControllers.values |
| 329 .map((controller) => controller.node) | 343 .map((controller) => controller.node) |
| 330 .where((output) { | 344 .where((output) { |
| 331 return !_outputs.containsKey(output.id) || | 345 return !_outputs.containsKey(output.id) || |
| 332 !_outputs[output.id].contains(output); | 346 !_outputs[output.id].contains(output); |
| 333 }).toSet(); | 347 }).toSet(); |
| 334 | 348 |
| 335 // Convert this to a list so we can safely modify _transforms while | 349 // Convert this to a list so we can safely modify _transforms while |
| 336 // iterating over it. | 350 // iterating over it. |
| 337 var dirtyTransforms = | 351 var dirtyTransforms = |
| 338 flatten(_transforms.values.map((transforms) => transforms.toList())) | 352 flatten(_transforms.values.map((transforms) => transforms.toList())) |
| 339 .where((transform) => transform.isDirty).toList(); | 353 .where((transform) => transform.isDirty).toList(); |
| 340 | 354 |
| 341 if (dirtyTransforms.isEmpty && newPassThroughs.isEmpty) return null; | 355 if (dirtyTransforms.isEmpty && newPassThroughs.isEmpty) return null; |
| 342 | 356 |
| 343 var collisions = _passAssetsThrough(newPassThroughs); | 357 var collisions = new Set<AssetId>(); |
| 358 for (var output in newPassThroughs) { | |
| 359 if (_addOutput(output)) collisions.add(output.id); | |
| 360 } | |
| 361 | |
| 344 return Future.wait(dirtyTransforms.map((transform) { | 362 return Future.wait(dirtyTransforms.map((transform) { |
| 345 return transform.apply().then((outputs) { | 363 return transform.apply().then((outputs) { |
| 346 for (var output in outputs) { | 364 for (var output in outputs) { |
| 347 if (_outputs.containsKey(output.id)) { | 365 if (_addOutput(output)) collisions.add(output.id); |
| 348 _outputs[output.id].add(output); | |
| 349 collisions.add(output.id); | |
| 350 } else { | |
| 351 _outputs[output.id] = new Queue<AssetNode>.from([output]); | |
| 352 _next.addInput(output); | |
| 353 } | |
| 354 | |
| 355 _handleOutputRemoval(output); | |
| 356 } | 366 } |
| 357 }); | 367 }); |
| 358 })).then((_) { | 368 })).then((_) { |
| 359 // Report collisions in a deterministic order. | 369 // Report collisions in a deterministic order. |
| 360 collisions = collisions.toList(); | 370 collisions = collisions.toList(); |
| 361 collisions.sort((a, b) => a.compareTo(b)); | 371 collisions.sort((a, b) => a.compareTo(b)); |
| 362 for (var collision in collisions) { | 372 for (var collision in collisions) { |
| 363 // Ensure that there's still a collision. It's possible it was resolved | 373 // Ensure that there's still a collision. It's possible it was resolved |
| 364 // while another transform was running. | 374 // while another transform was running. |
| 365 if (_outputs[collision].length <= 1) continue; | 375 if (_outputs[collision].length <= 1) continue; |
| 366 cascade.reportError(new AssetCollisionException( | 376 cascade.reportError(new AssetCollisionException( |
| 367 _outputs[collision].where((asset) => asset.transform != null) | 377 _outputs[collision].where((asset) => asset.transform != null) |
| 368 .map((asset) => asset.transform.info), | 378 .map((asset) => asset.transform.info), |
| 369 collision)); | 379 collision)); |
| 370 } | 380 } |
| 371 }); | 381 }); |
| 372 } | 382 } |
| 373 | 383 |
| 374 /// Pass all new assets that aren't consumed by transforms through to the next | 384 /// Add [output] as an output of this phase, forwarding it to the next phase |
| 375 /// phase. | 385 /// if necessary. |
| 376 /// | 386 /// |
| 377 /// Returns a set of asset ids that have collisions between new passed-through | 387 /// Returns whether or not [output] collides with another pre-existing output. |
| 378 /// assets and pre-existing transform outputs. | 388 bool _addOutput(AssetNode output) { |
| 379 Set<AssetId> _passAssetsThrough(Set<AssetId> newPassThroughs) { | 389 _handleOutputRemoval(output); |
| 380 var collisions = new Set<AssetId>(); | |
| 381 for (var output in newPassThroughs) { | |
| 382 if (_outputs.containsKey(output.id)) { | |
| 383 // There shouldn't be another pass-through asset with the same id. | |
| 384 assert(!_outputs[output.id].any((asset) => asset.transform == null)); | |
| 385 | 390 |
| 386 _outputs[output.id].add(output); | 391 if (_outputs.containsKey(output.id)) { |
| 387 collisions.add(output.id); | 392 _outputs[output.id].add(output); |
| 388 } else { | 393 return true; |
| 389 _outputs[output.id] = new Queue<AssetNode>.from([output]); | 394 } |
| 390 _next.addInput(output); | |
| 391 } | |
| 392 | 395 |
| 393 _handleOutputRemoval(output); | 396 _outputs[output.id] = new Queue<AssetNode>.from([output]); |
| 394 } | 397 if (_next != null) _next.addInput(output); |
| 395 return collisions; | 398 return false; |
| 396 } | 399 } |
| 397 | 400 |
| 398 /// Properly resolve collisions when [output] is removed. | 401 /// Properly resolve collisions when [output] is removed. |
| 399 void _handleOutputRemoval(AssetNode output) { | 402 void _handleOutputRemoval(AssetNode output) { |
| 400 output.whenRemoved.then((_) { | 403 output.whenRemoved.then((_) { |
| 401 var assets = _outputs[output.id]; | 404 var assets = _outputs[output.id]; |
| 402 if (assets.length == 1) { | 405 if (assets.length == 1) { |
| 403 assert(assets.single == output); | 406 assert(assets.single == output); |
| 404 _outputs.remove(output.id); | 407 _outputs.remove(output.id); |
| 405 return; | 408 return; |
| 406 } | 409 } |
| 407 | 410 |
| 408 // If there was more than one asset, we're resolving a collision -- | 411 // If there was more than one asset, we're resolving a collision -- |
| 409 // possibly partially. | 412 // possibly partially. |
| 410 var wasFirst = assets.first == output; | 413 var wasFirst = assets.first == output; |
| 411 assets.remove(output); | 414 assets.remove(output); |
| 412 | 415 |
| 413 // If this was the first asset, we need to pass the next asset | 416 // If this was the first asset, we need to pass the next asset |
| 414 // (chronologically) to the next phase. Pump the event queue first to give | 417 // (chronologically) to the next phase. Pump the event queue first to give |
| 415 // [_next] a chance to handle the removal of its input before getting a | 418 // [_next] a chance to handle the removal of its input before getting a |
| 416 // new input. | 419 // new input. |
| 417 if (wasFirst) { | 420 if (wasFirst && _next != null) { |
| 418 newFuture(() => _next.addInput(assets.first)); | 421 newFuture(() => _next.addInput(assets.first)); |
| 419 } | 422 } |
| 420 | 423 |
| 421 // If there's still a collision, report it. This lets the user know | 424 // If there's still a collision, report it. This lets the user know |
| 422 // if they've successfully resolved the collision or not. | 425 // if they've successfully resolved the collision or not. |
| 423 if (assets.length > 1) { | 426 if (assets.length > 1) { |
| 424 // Pump the event queue to ensure that the removal of the input triggers | 427 // Pump the event queue to ensure that the removal of the input triggers |
| 425 // a new build to which we can attach the error. | 428 // a new build to which we can attach the error. |
| 426 newFuture(() => cascade.reportError(new AssetCollisionException( | 429 newFuture(() => cascade.reportError(new AssetCollisionException( |
| 427 assets.where((asset) => asset.transform != null) | 430 assets.where((asset) => asset.transform != null) |
| 428 .map((asset) => asset.transform.info), | 431 .map((asset) => asset.transform.info), |
| 429 output.id))); | 432 output.id))); |
| 430 } | 433 } |
| 431 }); | 434 }); |
| 432 } | 435 } |
| 433 } | 436 } |
| OLD | NEW |