Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(17)

Unified Diff: pkg/barback/lib/src/phase.dart

Issue 23363002: Factor out an input-handling class from Phase in barback. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fix a library name Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: pkg/barback/lib/src/phase.dart
diff --git a/pkg/barback/lib/src/phase.dart b/pkg/barback/lib/src/phase.dart
index 263741de931d6c3c3d0997b3b39b8a1d43c0a9e0..710317294cec2ca9da326b718dc941d95d8989c4 100644
--- a/pkg/barback/lib/src/phase.dart
+++ b/pkg/barback/lib/src/phase.dart
@@ -7,13 +7,12 @@ library barback.phase;
import 'dart:async';
import 'dart:collection';
-import 'asset.dart';
import 'asset_cascade.dart';
import 'asset_id.dart';
import 'asset_node.dart';
import 'errors.dart';
+import 'phase_input.dart';
import 'stream_pool.dart';
-import 'transform_node.dart';
import 'transformer.dart';
import 'utils.dart';
@@ -38,39 +37,11 @@ class Phase {
/// Their outputs will be available to the next phase.
final Set<Transformer> _transformers;
- /// The inputs that are available for transforms in this phase to consume.
+ /// 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, AssetNode>();
-
- /// The transforms currently applicable to assets in [inputs], indexed by
- /// the ids of their primary inputs.
- ///
- /// These are the transforms that have been "wired up": they represent a
- /// repeatable transformation of a single concrete set of inputs. "dart2js"
- /// is a transformer. "dart2js on web/main.dart" is a transform.
- final _transforms = new Map<AssetId, Set<TransformNode>>();
-
- /// Controllers for assets that aren't consumed by transforms in this phase.
- ///
- /// These assets are passed to the next phase unmodified. They need
- /// intervening controllers to ensure that the outputs can be marked dirty
- /// when determining whether transforms apply, and removed if they do.
- final _passThroughControllers = new Map<AssetId, AssetNodeController>();
-
- /// Futures that will complete once the transformers that can consume a given
- /// asset are determined.
- ///
- /// Whenever an asset is added or modified, we need to asynchronously
- /// determine which transformers can use it as their primary input. We can't
- /// start processing until we know which transformers to run, and this allows
- /// us to wait until we do.
- var _adjustTransformersFutures = new Map<AssetId, Future>();
-
- /// New asset nodes that were added while [_adjustTransformers] was still
- /// being run on an old version of that asset.
- var _pendingNewInputs = new Map<AssetId, AssetNode>();
+ final _inputs = new Map<AssetId, PhaseInput>();
/// A map of output ids to the asset node outputs for those ids and the
/// transforms that produced those asset nodes.
@@ -93,10 +64,7 @@ class Phase {
/// A controller whose stream feeds into [_onDirtyPool].
///
- /// This is used whenever an input is added, changed, or removed. It's
- /// sometimes redundant with the events collected from [_transforms], but this
- /// stream is necessary for new and removed inputs, and the transform stream
- /// is necessary for modified secondary inputs.
+ /// This is used whenever an input is added or transforms are changed.
final _onDirtyController = new StreamController.broadcast(sync: true);
/// The phase after this one.
@@ -129,51 +97,12 @@ class Phase {
/// removed and re-created. The phase will automatically handle updated assets
/// using the [AssetNode.onStateChange] stream.
void addInput(AssetNode node) {
- // We remove [node.id] from [inputs] as soon as the node is removed rather
- // than at the same time [node.id] is removed from [_transforms] so we don't
- // have to wait on [_adjustTransformers]. It's important that [inputs] is
- // always up-to-date so that the [AssetCascade] can look there for available
- // assets.
- _inputs[node.id] = node;
- node.whenRemoved.then((_) => _inputs.remove(node.id));
+ if (_inputs.containsKey(node.id)) _inputs[node.id].remove();
- if (!_adjustTransformersFutures.containsKey(node.id)) {
- _transforms[node.id] = new Set<TransformNode>();
- _adjustTransformers(node);
- return;
- }
-
- // If an input is added while the same input is still being processed,
- // that means that the asset was removed and recreated while
- // [_adjustTransformers] was being run on the old value. We have to wait
- // until that finishes, then run it again on whatever the newest version
- // of that asset is.
-
- // We may already be waiting for the existing [_adjustTransformers] call to
- // finish. If so, all we need to do is change the node that will be loaded
- // after it completes.
- var containedKey = _pendingNewInputs.containsKey(node.id);
- _pendingNewInputs[node.id] = node;
- if (containedKey) return;
-
- // If we aren't already waiting, start doing so.
- _adjustTransformersFutures[node.id].then((_) {
- assert(!_adjustTransformersFutures.containsKey(node.id));
- assert(_pendingNewInputs.containsKey(node.id));
- _transforms[node.id] = new Set<TransformNode>();
- _adjustTransformers(_pendingNewInputs.remove(node.id));
- }, onError: (_) {
- // If there was a programmatic error while processing the old input,
- // we don't want to just ignore it; it may have left the system in an
- // inconsistent state. We also don't want to top-level it, so we
- // ignore it here but don't start processing the new input. That way
- // when [process] is called, the error will be piped through its
- // return value.
- }).catchError((e) {
- // If our code above has a programmatic error, ensure it will be piped
- // through [process] by putting it into [_adjustTransformersFutures].
- _adjustTransformersFutures[node.id] = new Future.error(e);
- });
+ _inputs[node.id] = new PhaseInput(this, node, _transformers);
+ _inputs[node.id].input.whenRemoved.then((_) => _inputs.remove(node.id));
+ _onDirtyPool.add(_inputs[node.id].onDirty);
+ _onDirtyController.add(null);
}
/// Gets the asset node for an input [id].
@@ -181,8 +110,9 @@ class Phase {
/// If an input with that ID cannot be found, returns null.
Future<AssetNode> getInput(AssetId id) {
return newFuture(() {
- if (id.package == cascade.package) return _inputs[id];
- return cascade.graph.getAssetNode(id);
+ if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
+ if (_inputs.containsKey(id)) return _inputs[id].input;
+ return null;
});
}
@@ -200,43 +130,11 @@ class Phase {
/// Set this phase's transformers to [transformers].
void updateTransformers(Iterable<Transformer> transformers) {
_onDirtyController.add(null);
-
- var newTransformers = transformers.toSet();
- var oldTransformers = _transformers.toSet();
- for (var removedTransformer in
- oldTransformers.difference(newTransformers)) {
- _transformers.remove(removedTransformer);
-
- // Remove old transforms for which [removedTransformer] was a transformer.
- for (var id in _inputs.keys) {
- // If the transformers are being adjusted for [id], it will
- // automatically pick up on [removedTransformer] being gone.
- if (_adjustTransformersFutures.containsKey(id)) continue;
-
- _transforms[id].removeWhere((transform) {
- if (transform.transformer != removedTransformer) return false;
- transform.remove();
- return true;
- });
-
- if (!_transforms[id].isEmpty) continue;
- _passThroughControllers.putIfAbsent(id, () {
- return new AssetNodeController.available(
- _inputs[id].asset, _inputs[id].transform);
- });
- }
+ _transformers.clear();
+ _transformers.addAll(transformers);
+ for (var input in _inputs.values) {
+ input.updateTransformers(transformers);
}
-
- var brandNewTransformers = newTransformers.difference(oldTransformers);
- if (brandNewTransformers.isEmpty) return;
- brandNewTransformers.forEach(_transformers.add);
-
- // If there are any new transformers, start re-adjusting the transforms for
- // all inputs so we pick up which inputs the new transformers apply to.
- _inputs.forEach((id, node) {
- if (_adjustTransformersFutures.containsKey(id)) return;
- _adjustTransformers(node);
- });
}
/// Add a new phase after this one with [transformers].
@@ -251,199 +149,21 @@ class Phase {
return _next;
}
- /// Asynchronously determines which transformers can consume [node] as a
- /// primary input and creates transforms for them.
- ///
- /// This ensures that if [node] is modified or removed during or after the
- /// time it takes to adjust its transformers, they're appropriately
- /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures].
- void _adjustTransformers(AssetNode node) {
- // Mark the phase as dirty. This may not actually end up creating any new
- // transforms, but we want adding or removing a source asset to consistently
- // kick off a build, even if that build does nothing.
- _onDirtyController.add(null);
-
- // If there's a pass-through for this node, mark it dirty while we figure
- // out whether we need to add any transforms for it.
- var controller = _passThroughControllers[node.id];
- if (controller != null) controller.setDirty();
-
- // Once the input is available, hook up transformers for it. If it changes
- // while that's happening, try again.
- _adjustTransformersFutures[node.id] = _tryUntilStable(node,
- (asset, transformers) {
- var oldTransformers = _transforms[node.id]
- .map((transform) => transform.transformer).toSet();
-
- return _removeStaleTransforms(asset, transformers).then((_) =>
- _addFreshTransforms(node, transformers, oldTransformers));
- }).then((_) {
- _adjustPassThrough(node);
-
- // Now all the transforms are set up correctly and the asset is available
- // for the time being. Set up handlers for when the asset changes in the
- // future.
- node.onStateChange.first.then((state) {
- if (state.isRemoved) {
- _onDirtyController.add(null);
- _transforms.remove(node.id);
- var passThrough = _passThroughControllers.remove(node.id);
- if (passThrough != null) passThrough.setRemoved();
- } else {
- _adjustTransformers(node);
- }
- }).catchError((e) {
- _adjustTransformersFutures[node.id] = new Future.error(e);
- });
- }).catchError((error) {
- if (error is! AssetNotFoundException || error.id != node.id) throw error;
-
- // If the asset is removed, [tryUntilStable] will throw an
- // [AssetNotFoundException]. In that case, just remove all transforms for
- // the node, and its pass-through.
- _transforms.remove(node.id);
- var passThrough = _passThroughControllers.remove(node.id);
- if (passThrough != null) passThrough.setRemoved();
- }).whenComplete(() {
- _adjustTransformersFutures.remove(node.id);
- });
-
- // Don't top-level errors coming from the input processing. Any errors will
- // eventually be piped through [process]'s returned Future.
- _adjustTransformersFutures[node.id].catchError((_) {});
- }
-
- // Remove any old transforms that used to have [asset] as a primary asset but
- // no longer apply to its new contents.
- Future _removeStaleTransforms(Asset asset, Set<Transformer> transformers) {
- return Future.wait(_transforms[asset.id].map((transform) {
- return newFuture(() {
- if (!transformers.contains(transform.transformer)) return false;
-
- // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
- // results.
- return transform.transformer.isPrimary(asset);
- }).then((isPrimary) {
- if (isPrimary) return;
- _transforms[asset.id].remove(transform);
- _onDirtyPool.remove(transform.onDirty);
- transform.remove();
- });
- }));
- }
-
- // Add new transforms for transformers that consider [node]'s asset to be a
- // primary input.
- //
- // [oldTransformers] is the set of transformers for which there were
- // transforms that had [node] as a primary input prior to this. They don't
- // need to be checked, since their transforms were removed or preserved in
- // [_removeStaleTransforms].
- Future _addFreshTransforms(AssetNode node, Set<Transformer> transformers,
- Set<Transformer> oldTransformers) {
- return Future.wait(transformers.map((transformer) {
- if (oldTransformers.contains(transformer)) return new Future.value();
-
- // If the asset is unavailable, the results of this [_adjustTransformers]
- // run will be discarded, so we can just short-circuit.
- if (node.asset == null) return new Future.value();
-
- // We can safely access [node.asset] here even though it might have
- // changed since (as above) if it has, [_adjustTransformers] will just be
- // re-run.
- // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
- // results.
- return transformer.isPrimary(node.asset).then((isPrimary) {
- if (!isPrimary) return;
- var transform = new TransformNode(this, transformer, node);
- _transforms[node.id].add(transform);
- _onDirtyPool.add(transform.onDirty);
- });
- }));
- }
-
- /// Adjust whether [node] is passed through the phase unmodified, based on
- /// whether it's consumed by other transforms in this phase.
- ///
- /// If [node] was already passed-through, this will update the passed-through
- /// value.
- void _adjustPassThrough(AssetNode node) {
- assert(node.state.isAvailable);
-
- if (_transforms[node.id].isEmpty) {
- var controller = _passThroughControllers[node.id];
- if (controller != null) {
- controller.setAvailable(node.asset);
- } else {
- _passThroughControllers[node.id] =
- new AssetNodeController.available(node.asset, node.transform);
- }
- } else {
- var controller = _passThroughControllers.remove(node.id);
- if (controller != null) controller.setRemoved();
- }
- }
-
- /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this
- /// phase's transformers are modified.
- Future _tryUntilStable(AssetNode node,
- Future callback(Asset asset, Set<Transformer> transformers)) {
- var oldTransformers;
- return node.tryUntilStable((asset) {
- oldTransformers = _transformers.toSet();
- return callback(asset, _transformers);
- }).then((result) {
- if (setEquals(oldTransformers, _transformers)) return result;
- return _tryUntilStable(node, callback);
- });
- }
-
/// Processes this phase.
///
/// Returns a future that completes when processing is done. If there is
/// nothing to process, returns `null`.
Future process() {
- if (_adjustTransformersFutures.isEmpty) return _processTransforms();
- return _waitForInputs().then((_) => _processTransforms());
- }
-
- Future _waitForInputs() {
- if (_adjustTransformersFutures.isEmpty) return new Future.value();
- return Future.wait(_adjustTransformersFutures.values)
- .then((_) => _waitForInputs());
- }
-
- /// Applies all currently wired up and dirty transforms.
- Future _processTransforms() {
- var newPassThroughs = _passThroughControllers.values
- .map((controller) => controller.node)
- .where((output) {
- return !_outputs.containsKey(output.id) ||
- !_outputs[output.id].contains(output);
- }).toSet();
-
- // Convert this to a list so we can safely modify _transforms while
- // iterating over it.
- var dirtyTransforms =
- flatten(_transforms.values.map((transforms) => transforms.toList()))
- .where((transform) => transform.isDirty).toList();
-
- if (dirtyTransforms.isEmpty && newPassThroughs.isEmpty) return null;
-
- var collisions = new Set<AssetId>();
- for (var output in newPassThroughs) {
- if (_addOutput(output)) collisions.add(output.id);
- }
+ if (!_inputs.values.any((input) => input.isDirty)) return null;
- return Future.wait(dirtyTransforms.map((transform) {
- return transform.apply().then((outputs) {
- for (var output in outputs) {
- if (_addOutput(output)) collisions.add(output.id);
- }
+ return Future.wait(_inputs.values.map((input) {
+ if (!input.isDirty) return new Future.value(new Set());
+ return input.process().then((outputs) {
+ return outputs.where(_addOutput).map((output) => output.id).toSet();
});
- })).then((_) {
+ })).then((collisionsList) {
// Report collisions in a deterministic order.
- collisions = collisions.toList();
+ var collisions = unionAll(collisionsList).toList();
collisions.sort((a, b) => a.compareTo(b));
for (var collision in collisions) {
// Ensure that there's still a collision. It's possible it was resolved

Powered by Google App Engine
This is Rietveld 408576698