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 1230bea094e36970d402547ec6b386c868c33ec0..dbafc87d7f00b5721cc7cf98244906cd2a73ca4f 100644 |
| --- a/pkg/barback/lib/src/phase.dart |
| +++ b/pkg/barback/lib/src/phase.dart |
| @@ -11,11 +11,15 @@ import 'asset_cascade.dart'; |
| import 'asset_id.dart'; |
| import 'asset_node.dart'; |
| import 'asset_set.dart'; |
| +import 'cluster_runner.dart'; |
| import 'errors.dart'; |
| +import 'operator.dart'; |
| +import 'phase_forwarder.dart'; |
| import 'phase_input.dart'; |
| import 'phase_output.dart'; |
| import 'stream_pool.dart'; |
| import 'transformer.dart'; |
| +import 'transformer_cluster.dart'; |
| import 'utils.dart'; |
| /// One phase in the ordered series of transformations in an [AssetCascade]. |
| @@ -39,15 +43,38 @@ class Phase { |
| /// Their outputs will be available to the next phase. |
| final Set<Transformer> _transformers; |
| + /// The clusters for this phase. |
| + final _clusters = new Map<TransformerCluster, ClusterRunner>(); |
| + |
| /// The inputs for this phase. |
| /// |
| /// For the first phase, these will be the source assets. For all other |
| /// phases, they will be the outputs from the previous phase. |
| final _inputs = new Map<AssetId, PhaseInput>(); |
| + /// The forwarders for this phase. |
| + final _forwarders = new Map<AssetId, PhaseForwarder>(); |
| + |
| /// The outputs for this phase. |
| final _outputs = new Map<AssetId, PhaseOutput>(); |
| + // TODO(nweiz): don't re-calculate this on the fly all the time |
|
Bob Nystrom
2013/10/04 21:47:50
"." and capitalize "Don't".
nweiz
2013/10/07 23:21:31
Done.
|
| + /// The set of all [AssetNode.origin] properties of the input assets for this |
| + /// phase. |
| + /// |
| + /// This is used to determine which assets have been passed unmodified through |
| + /// [_inputs] or [_clusters]. Eeach input asset has a PhaseInput in [_inputs]. |
|
Bob Nystrom
2013/10/04 21:47:50
"Eeach" -> "Each".
nweiz
2013/10/07 23:21:31
Done.
|
| + /// If that input isn't consumed by any transformers, it will be forwarded |
| + /// through the PhaseInput. However, it's possible that it was consumed by a |
| + /// cluster, and so shouldn't be forwarded through the phase as a whole. |
| + /// |
| + /// In order to detect whether an output has been forwarded through a cluster |
| + /// or a PhaseInput, we must be able to distinguish it from other outputs with |
| + /// the same id. To do so, we check if its origin is in [_inputOrigins]. If |
| + /// so, it's been forwarded unmodified. |
| + Set<AssetNode> get _inputOrigins => |
| + _inputs.values.map((input) => input.input.origin).toSet(); |
| + |
| /// A stream that emits an event whenever this phase becomes dirty and needs |
| /// to be run. |
| /// |
| @@ -62,6 +89,10 @@ class Phase { |
| /// This is used whenever an input is added or transforms are changed. |
| final _onDirtyController = new StreamController.broadcast(sync: true); |
| + /// Whether this phase is dirty and needs to be run. |
| + bool get isDirty => _inputs.values.any((input) => input.isDirty) || |
| + _clusters.values.any((cluster) => cluster.isDirty); |
| + |
| /// The phase after this one. |
| /// |
| /// Outputs from this phase will be passed to it. |
| @@ -69,16 +100,21 @@ class Phase { |
| Phase _next; |
| /// Returns all currently-available output assets for this phase. |
| - AssetSet get availableOutputs { |
| - return new AssetSet.from(_outputs.values |
| + Set<AssetNode> get availableOutputs { |
| + return _outputs.values |
| .map((output) => output.output) |
| .where((node) => node.state.isAvailable) |
| - .map((node) => node.asset)); |
| + .toSet(); |
| } |
| - Phase(this.cascade, Iterable<Transformer> transformers) |
| - : _transformers = transformers.toSet() { |
| + Phase(this.cascade, Iterable<Operator> operators) |
|
Bob Nystrom
2013/10/04 21:47:50
We could keep this API typed if the transformers a
nweiz
2013/10/07 23:21:31
I think that would result in more code duplication
|
| + : _transformers = operators.where((op) => op is Transformer).toSet() { |
| _onDirtyPool.add(_onDirtyController.stream); |
| + |
| + for (var cluster in operators.where((op) => op is TransformerCluster)) { |
| + _clusters[cluster] = new ClusterRunner(cascade, cluster); |
| + _onDirtyPool.add(_clusters[cluster].onDirty); |
| + } |
| } |
| /// Adds a new asset as an input for this phase. |
| @@ -94,11 +130,27 @@ class Phase { |
| void addInput(AssetNode node) { |
| if (_inputs.containsKey(node.id)) _inputs[node.id].remove(); |
| + var forwarder = new PhaseForwarder(_clusters.length + 1); |
|
Bob Nystrom
2013/10/04 21:47:50
Explain what the .length + 1 represents.
nweiz
2013/10/07 23:21:31
Done.
|
| + _forwarders[node.id] = forwarder; |
| + forwarder.onForwarding.listen((asset) { |
| + _addOutput(asset); |
| + |
| + var exception = _outputs[asset.id].collisionException; |
| + if (exception != null) cascade.reportError(exception); |
| + }); |
| + |
| var input = new PhaseInput(this, node, _transformers); |
| _inputs[node.id] = input; |
| - input.input.whenRemoved.then((_) => _inputs.remove(node.id)); |
| + input.input.whenRemoved.then((_) { |
| + _inputs.remove(node.id); |
| + _forwarders.remove(node.id).remove(); |
| + }); |
| _onDirtyPool.add(input.onDirty); |
| _onDirtyController.add(null); |
| + |
| + for (var cluster in _clusters.values) { |
| + cluster.addInput(node); |
| + } |
| } |
| /// Gets the asset node for an input [id]. |
| @@ -123,22 +175,42 @@ class Phase { |
| }); |
| } |
| - /// Set this phase's transformers to [transformers]. |
| - void updateTransformers(Iterable<Transformer> transformers) { |
| + /// Set this phase's operators to [operators]. |
| + void updateOperators(Iterable<Operator> operators) { |
| _onDirtyController.add(null); |
| + |
| + var transformers = operators.where((op) => op is Transformer); |
| _transformers.clear(); |
| _transformers.addAll(transformers); |
| for (var input in _inputs.values) { |
| - input.updateTransformers(_transformers); |
| + input.updateTransformers(transformers); |
| + } |
| + |
| + var newClusters = operators.where((op) => op is TransformerCluster).toSet(); |
| + var oldClusters = _clusters.keys.toSet(); |
| + for (var removed in oldClusters.difference(newClusters)) { |
| + _clusters.remove(removed).remove(); |
| + } |
|
Bob Nystrom
2013/10/04 21:47:50
Nit but add blank line after "}".
nweiz
2013/10/07 23:21:31
Done.
|
| + for (var added in newClusters.difference(oldClusters)) { |
| + var runner = new ClusterRunner(cascade, added); |
| + _clusters[added] = runner; |
| + _onDirtyPool.add(runner.onDirty); |
| + for (var input in _inputs.values) { |
| + runner.addInput(input.input); |
| + } |
| + } |
| + |
| + for (var forwarder in _forwarders.values) { |
| + forwarder.channels = _clusters.length + 1; |
| } |
| } |
| - /// Add a new phase after this one with [transformers]. |
| + /// Add a new phase after this one with [operators]. |
| /// |
| /// This may only be called on a phase with no phase following it. |
| - Phase addPhase(Iterable<Transformer> transformers) { |
| + Phase addPhase(Iterable<Operators> operators) { |
| assert(_next == null); |
| - _next = new Phase(cascade, transformers); |
| + _next = new Phase(cascade, operators); |
| for (var output in _outputs.values.toList()) { |
| // Remove [output]'s listeners because now they should get the asset from |
| // [_next], rather than this phase. Any transforms consuming [output] will |
| @@ -162,6 +234,9 @@ class Phase { |
| for (var input in _inputs.values.toList()) { |
| input.remove(); |
| } |
| + for (var cluster in _clusters.values) { |
| + cluster.remove(); |
| + } |
| _onDirtyPool.close(); |
| } |
| @@ -177,26 +252,32 @@ class Phase { |
| /// Returns a future that completes when processing is done. If there is |
| /// nothing to process, returns `null`. |
| Future process() { |
| - if (!_inputs.values.any((input) => input.isDirty)) return null; |
| + if (!isDirty) return null; |
| var outputIds = new Set<AssetId>(); |
| - return Future.wait(_inputs.values.map((input) { |
| - if (!input.isDirty) return new Future.value(new Set()); |
| - return input.process().then((outputs) { |
| - for (var asset in outputs) { |
| - outputIds.add(asset.id); |
| - if (_outputs.containsKey(asset.id)) { |
| - _outputs[asset.id].add(asset); |
| - } else { |
| - _outputs[asset.id] = new PhaseOutput(this, asset); |
| - _outputs[asset.id].output.whenRemoved.then((_) { |
| - _outputs.remove(asset.id); |
| - }); |
| - if (_next != null) _next.addInput(_outputs[asset.id].output); |
| - } |
| + void _handleOutputs(Set<AssetNode> outputs) { |
| + for (var asset in outputs) { |
| + if (_inputOrigins.contains(asset.origin)) { |
| + _forwarders[asset.id].add(asset); |
| + continue; |
| } |
| - }); |
| - })).then((_) { |
| + |
| + outputIds.add(asset.id); |
| + _addOutput(asset); |
| + } |
| + } |
| + |
| + var outputFutures = []..addAll(_inputs.values.map((input) { |
| + if (!input.isDirty) return new Future.value(new Set()); |
| + return input.process().then(_handleOutputs); |
| + }))..addAll(_clusters.values.map((input) { |
|
Bob Nystrom
2013/10/04 21:47:50
Maybe it's just me, but using ".." here feels a bi
nweiz
2013/10/07 23:21:31
Done.
|
| + if (!input.isDirty) return new Future.value(new Set()); |
| + return input.process().then(_handleOutputs); |
| + })); |
| + |
| + // TODO(nweiz): handle pass-through. |
| + |
| + return Future.wait(outputFutures).then((_) { |
| // Report collisions in a deterministic order. |
| outputIds = outputIds.toList(); |
| outputIds.sort((a, b) => a.compareTo(b)); |
| @@ -209,4 +290,17 @@ class Phase { |
| } |
| }); |
| } |
| + |
| + /// Add [asset] as an output of this phase. |
| + void _addOutput(AssetNode asset) { |
| + if (_outputs.containsKey(asset.id)) { |
| + _outputs[asset.id].add(asset); |
| + } else { |
| + _outputs[asset.id] = new PhaseOutput(this, asset); |
| + _outputs[asset.id].output.whenRemoved.then((_) { |
| + _outputs.remove(asset.id); |
| + }); |
| + if (_next != null) _next.addInput(_outputs[asset.id].output); |
| + } |
| + } |
| } |