Chromium Code Reviews| Index: pkg/barback/lib/src/phase.dart |
| diff --git a/pkg/barback/lib/src/phase.dart b/pkg/barback/lib/src/phase.dart |
| index 82ec5213ac1608e05cfe04e84de21e6b2b4e69d1..bdc2b5c97a214edb7216e1434da8a2f61ec450a0 100644 |
| --- a/pkg/barback/lib/src/phase.dart |
| +++ b/pkg/barback/lib/src/phase.dart |
| @@ -5,6 +5,7 @@ |
| library barback.phase; |
| import 'dart:async'; |
| +import 'dart:collection'; |
| import 'asset.dart'; |
| import 'asset_cascade.dart'; |
| @@ -68,11 +69,14 @@ class Phase { |
| /// being run on an old version of that asset. |
| var _pendingNewInputs = new Map<AssetId, AssetNode>(); |
| - /// The ids of assets that are emitted by transforms in this phase. |
| + /// A map of output ids to the asset node outputs for those ids. |
| /// |
| - /// This is used to detect collisions where multiple transforms emit the same |
| - /// output. |
| - final _outputs = new Set<AssetId>(); |
| + /// Usually there's only one node for a given output id. However, it's |
| + /// possible for multiple transformers to output an asset with the same id. In |
|
Bob Nystrom
2013/08/09 22:44:43
"transformers" -> "transformers in this phase"
nweiz
2013/08/12 21:15:44
Done.
|
| + /// that case, the first output emitted is passed forward. We keep track of |
|
Bob Nystrom
2013/08/09 22:44:43
"first" -> "chronologically first"
nweiz
2013/08/12 21:15:44
Done.
|
| + /// the other nodes so that if that output is removed, we know which asset to |
| + /// replace it with. |
| + final _outputs = new Map<AssetId, Queue<AssetNode>>(); |
| /// A stream that emits an event whenever this phase becomes dirty and needs |
| /// to be run. |
| @@ -310,27 +314,64 @@ class Phase { |
| .where((transform) => transform.isDirty).toList(); |
| if (dirtyTransforms.isEmpty) return null; |
| - return Future.wait(dirtyTransforms.map((transform) => transform.apply())) |
| - .then((allNewOutputs) { |
| - var newOutputs = allNewOutputs.reduce((set1, set2) => set1.union(set2)); |
| - |
| - var collisions = new Set<AssetId>(); |
| - for (var newOutput in newOutputs) { |
| - if (_outputs.contains(newOutput.id)) { |
| - collisions.add(newOutput.id); |
| - } else { |
| - _next.addInput(newOutput); |
| - _outputs.add(newOutput.id); |
| - newOutput.whenRemoved.then((_) => _outputs.remove(newOutput.id)); |
| + var collisions = new Set<AssetId>(); |
| + return Future.wait(dirtyTransforms.map((transform) { |
| + return transform.apply().then((outputs) { |
| + for (var output in outputs) { |
| + if (_outputs.containsKey(output.id)) { |
| + _outputs[output.id].add(output); |
| + collisions.add(output.id); |
| + } else { |
| + _outputs[output.id] = new Queue<AssetNode>.from([output]); |
| + _next.addInput(output); |
| + } |
| + |
| + _handleOutputRemoval(output); |
| } |
| - } |
| - |
| + }); |
| + })).then((_) { |
| // Report collisions in a deterministic order. |
| collisions = collisions.toList(); |
| collisions.sort((a, b) => a.toString().compareTo(b.toString())); |
|
Bob Nystrom
2013/08/09 22:44:43
This presumes AssetNode.toString() does something
nweiz
2013/08/12 21:15:44
I just went ahead and implemented comparable.
|
| for (var collision in collisions) { |
| + // Ensure that there's still a collision. It's possible it was resolved |
| + // while another transform was running. |
| + if (_outputs[collision].length < 2) return; |
|
Bob Nystrom
2013/08/09 22:44:43
"< 2" -> "<= 1"
"return" -> "continue"
nweiz
2013/08/12 21:15:44
Done.
|
| cascade.reportError(new AssetCollisionException(collision)); |
| - // TODO(rnystrom): Define what happens after a collision occurs. |
| + } |
| + }); |
| + } |
| + |
| + /// Properly resolve collisions when [output] is removed. |
| + void _handleOutputRemoval(AssetNode output) { |
| + output.whenRemoved.then((_) { |
| + var assets = _outputs[output.id]; |
| + var wasFirst = assets.first == output; |
| + assets.remove(output); |
| + |
| + // If this wasn't the first asset, then a collision was resolved, |
| + // possibly partially. |
| + if (wasFirst) { |
| + // If this is the only asset with this id, we're no longer |
| + // producing that output. Otherwise, there was a collision, and we |
| + // should return whatever asset was emitted after the one that was |
| + // just removed. |
| + if (assets.isEmpty) { |
| + _outputs.remove(output.id); |
|
Bob Nystrom
2013/08/09 22:44:43
How about handling this case first and then exitin
Bob Nystrom
2013/08/09 22:44:43
Also, do we need to tell the next phase the input
nweiz
2013/08/12 21:15:44
Done.
|
| + } else { |
| + // Pump the event queue to give [_next] a chance to handle the removal |
| + // of its input before getting a new input. |
| + newFuture(() => _next.addInput(assets.first)); |
| + } |
| + } |
| + |
| + // If there's still a collision, report it. This lets the user know |
| + // if they've successfully resolved the collision or not. |
| + if (assets.length > 1) { |
| + // Pump the event queue to ensure that the removal of the input triggers |
| + // a new build to which we can attach the error. |
| + newFuture(() => |
| + cascade.reportError(new AssetCollisionException(output.id))); |
| } |
| }); |
| } |