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

Unified Diff: pkg/barback/lib/src/phase_input.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
« no previous file with comments | « pkg/barback/lib/src/phase.dart ('k') | pkg/barback/lib/src/transform_node.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/barback/lib/src/phase_input.dart
diff --git a/pkg/barback/lib/src/phase_input.dart b/pkg/barback/lib/src/phase_input.dart
new file mode 100644
index 0000000000000000000000000000000000000000..e32581d95adc8bbcc3174beaf9613c40fdd8b19b
--- /dev/null
+++ b/pkg/barback/lib/src/phase_input.dart
@@ -0,0 +1,297 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library barback.phase_input;
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'asset.dart';
+import 'asset_forwarder.dart';
+import 'asset_node.dart';
+import 'errors.dart';
+import 'stream_pool.dart';
+import 'transform_node.dart';
+import 'transformer.dart';
+import 'utils.dart';
+
+/// A class for watching a single [AssetNode] and running any necessary
+/// transforms on that node.
Bob Nystrom 2013/08/21 19:47:38 Can you clarify here whether it runs transforms wh
nweiz 2013/08/21 20:33:51 Done.
+class PhaseInput {
+ /// The phase for which this is an input.
+ final Phase _phase;
+
+ /// The transformers to (potentially) run against [input].
+ final Set<Transformer> _transformers;
+
+ /// The transforms currently applicable to [input].
+ ///
+ /// 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 Set<TransformNode>();
+
+ /// A forwarder for the input [AssetNode] for this phase.
+ ///
+ /// This is used to mark the node as removed should the input ever be removed.
+ final AssetForwarder _inputForwarder;
+
+ /// The asset node for this input.
+ AssetNode get input => _inputForwarder.node;
+
+ /// The controller that's used for the output node if [input] isn't consumed
+ /// by any transformers.
+ ///
+ /// Theis needs an intervening controller to ensure that the output can be
+ /// marked dirty when determining whether transforms apply, and removed if
+ /// they do. It's null if the asset is not being passed through.
+ AssetNodeController _passThroughController;
+
+ /// Whether [_passThroughController] has been newly created since [process]
+ /// last completed.
+ bool _newPassThrough = false;
+
+ /// A Future that will complete once the transformers that consume [input] are
+ /// determined.
+ Future _adjustTransformersFuture;
+
+ /// A stream that emits an event whenever this input becomes dirty and needs
+ /// [process] to be called.
+ ///
+ /// This may emit events when the input was already dirty or while processing
+ /// transforms. Events are emitted synchronously to ensure that the dirty
+ /// state is thoroughly propagated as soon as any assets are changed.
+ Stream get onDirty => _onDirtyPool.stream;
+ final _onDirtyPool = new StreamPool.broadcast();
+
+ /// A controller whose stream feeds into [_onDirtyPool].
+ ///
+ /// This is used whenever the input is changed or removed. It's sometimes
+ /// redundant with the events collected from [_transforms], but this stream is
+ /// necessary for removed inputs, and the transform stream is necessary for
+ /// modified secondary inputs.
+ final _onDirtyController = new StreamController.broadcast(sync: true);
+
+ /// Whether this input is dirty and needs [process] to be called.
+ bool get isDirty => _adjustTransformersFuture != null ||
+ _newPassThrough || _transforms.any((transform) => transform.isDirty);
+
+ PhaseInput(this._phase, AssetNode input, Iterable<Transformer> transformers)
+ : _transformers = transformers.toSet(),
+ _inputForwarder = new AssetForwarder(input) {
+ _onDirtyPool.add(_onDirtyController.stream);
+
+ input.onStateChange.listen((state) {
+ if (state.isRemoved) {
+ remove();
+ } else if (_adjustTransformersFuture == null) {
+ _adjustTransformers();
+ }
+ });
+
+ _adjustTransformers();
+ }
+
+ /// Removes this input.
+ ///
+ /// This marks all outputs of the input as removed.
+ void remove() {
+ _onDirtyController.add(null);
+ _onDirtyPool.close();
+ _inputForwarder.close();
+ if (_passThroughController != null) {
+ _passThroughController.setRemoved();
+ _passThroughController = null;
+ }
+ }
+
+ /// Set this input's transformers to [transformers].
+ void updateTransformers(Iterable<Transformer> transformers) {
+ var newTransformers = transformers.toSet();
+ var oldTransformers = _transformers.toSet();
+ for (var removedTransformer in
+ oldTransformers.difference(newTransformers)) {
+ _transformers.remove(removedTransformer);
+
+ // If the transformers are being adjusted for [id], it will
+ // automatically pick up on [removedTransformer] being gone.
+ if (_adjustTransformersFuture != null) continue;
+
+ _transforms.removeWhere((transform) {
+ if (transform.transformer != removedTransformer) return false;
+ transform.remove();
+ return true;
+ });
+ }
+
+ if (_transforms.isEmpty && _adjustTransformersFuture == null &&
+ _passThroughController == null) {
+ _passThroughController =
+ new AssetNodeController.available(input.asset, input.transform);
+ _newPassThrough = true;
+ }
+
+ var brandNewTransformers = newTransformers.difference(oldTransformers);
+ if (brandNewTransformers.isEmpty) return;
+
+ brandNewTransformers.forEach(_transformers.add);
+ _adjustTransformers();
+ }
+
+ /// Asynchronously determines which transformers can consume [input] as a
+ /// primary input and creates transforms for them.
+ ///
+ /// This ensures that if [input] 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 [_adjustTransformersFuture].
+ void _adjustTransformers() {
+ // Mark the input 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 input, mark it dirty while we figure
+ // out whether we need to add any transforms for it.
+ if (_passThroughController != null) _passThroughController.setDirty();
+
+ // Once the input is available, hook up transformers for it. If it changes
+ // while that's happening, try again.
+ _adjustTransformersFuture = _tryUntilStable((asset, transformers) {
+ var oldTransformers =
+ _transforms.map((transform) => transform.transformer).toSet();
+
+ return _removeStaleTransforms(asset, transformers).then((_) =>
+ _addFreshTransforms(transformers, oldTransformers));
+ }).then((_) => _adjustPassThrough()).catchError((error) {
+ if (error is! AssetNotFoundException || error.id != input.id) {
+ throw error;
+ }
+
+ // If the asset is removed, [_tryUntilStable] will throw an
+ // [AssetNotFoundException]. In that case, just remove it.
+ remove();
+ }).whenComplete(() {
+ _adjustTransformersFuture = null;
+ });
+
+ // Don't top-level errors coming from the input processing. Any errors will
+ // eventually be piped through [process]'s returned Future.
+ _adjustTransformersFuture.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.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.remove(transform);
+ transform.remove();
+ });
+ }));
+ }
+
+ // Add new transforms for transformers that consider [input]'s asset to be a
+ // primary input.
+ //
+ // [oldTransformers] is the set of transformers for which there were
+ // transforms that had [input] 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(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 (input.asset == null) return new Future.value();
+
+ // We can safely access [input.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(input.asset).then((isPrimary) {
+ if (!isPrimary) return;
+ var transform = new TransformNode(_phase, transformer, input);
+ _transforms.add(transform);
+ _onDirtyPool.add(transform.onDirty);
+ });
+ }));
+ }
+
+ /// Adjust whether [input] is passed through the phase unmodified, based on
+ /// whether it's consumed by other transforms in this phase.
+ ///
+ /// If [input] was already passed-through, this will update the passed-through
+ /// value.
+ void _adjustPassThrough() {
+ assert(input.state.isAvailable);
+
+ if (_transforms.isEmpty) {
+ if (_passThroughController != null) {
+ _passThroughController.setAvailable(input.asset);
+ } else {
+ _passThroughController =
+ new AssetNodeController.available(input.asset, input.transform);
+ _newPassThrough = true;
+ }
+ } else if (_passThroughController != null) {
+ _passThroughController.setRemoved();
+ _passThroughController = null;
+ _newPassThrough = false;
+ }
+ }
+
+ /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this
+ /// phase's transformers are modified.
+ Future _tryUntilStable(
+ Future callback(Asset asset, Set<Transformer> transformers)) {
+ var oldTransformers;
+ return input.tryUntilStable((asset) {
+ oldTransformers = _transformers.toSet();
+ return callback(asset, _transformers);
+ }).then((result) {
+ if (setEquals(oldTransformers, _transformers)) return result;
+ return _tryUntilStable(callback);
+ });
+ }
+
+ /// Processes the transforms for this input.
+ Future<Set<AssetNode>> process() {
+ if (_adjustTransformersFuture == null) return _processTransforms();
+ return _waitForInputs().then((_) => _processTransforms());
+ }
+
+ Future _waitForInputs() {
+ // Return a synchronous future so we can be sure [_adjustTransformers] isn't
+ // called between now and when the Future completes.
+ if (_adjustTransformersFuture == null) return new Future.sync(() {});
+ return _adjustTransformersFuture.then((_) => _waitForInputs());
+ }
+
+ /// Applies all currently wired up and dirty transforms.
+ Future<Set<AssetNode>> _processTransforms() {
+ if (input.state.isRemoved) return new Future.value(new Set());
+
+ if (_passThroughController != null) {
+ if (!_newPassThrough) return new Future.value(new Set());
+ _newPassThrough = false;
+ return new Future.value(
+ new Set<AssetNode>.from([_passThroughController.node]));
+ }
+
+ return Future.wait(_transforms.map((transform) {
+ if (!transform.isDirty) return new Future.value(new Set());
+ return transform.apply();
+ })).then((outputs) => unionAll(outputs));
+ }
+}
« no previous file with comments | « pkg/barback/lib/src/phase.dart ('k') | pkg/barback/lib/src/transform_node.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698