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

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

Issue 16854005: First pass at build dependency graph for barback. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Revise. Created 7 years, 6 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
new file mode 100644
index 0000000000000000000000000000000000000000..d52ed7578597c19bde4f1392e3305af4958ba0f9
--- /dev/null
+++ b/pkg/barback/lib/src/phase.dart
@@ -0,0 +1,184 @@
+// 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;
+
+import 'dart:async';
+
+import 'asset.dart';
+import 'asset_graph.dart';
+import 'asset_id.dart';
+import 'asset_node.dart';
+import 'errors.dart';
+import 'transform_node.dart';
+import 'transformer.dart';
+
+/// One phase in the ordered series of transformations in an [AssetGraph].
+///
+/// Each phase can access outputs from previous phases and can in turn pass
+/// outputs to later phases. Phases are processed strictly serially. All
+/// transforms in a phase will be complete before moving on to the next phase.
+/// Within a single phase, all transforms will be run in parallel.
+///
+/// Building can be interrupted between phases. For example, a source is added
+/// which starts the background process. Sometime during, say, phase 2 (which
+/// is running asynchronously) that source is modified. When the process queue
+/// goes to advance to phase 3, it will see that modification and start the
+/// waterfall from the beginning again.
+class Phase {
+ /// The graph that owns this phase.
+ final AssetGraph graph;
+
+ /// This phase's position relative to the other phases. Zero-based.
+ final int _index;
+
+ /// The transformers that can access [inputs].
+ ///
+ /// Their outputs will be available to the next phase.
+ final List<Transformer> _transformers;
+
+ /// The inputs that are available for transforms in this phase to consume.
+ ///
+ /// 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].
+ ///
+ /// 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>();
+
+ /// The nodes that are new in this phase since the last time [process] was
+ /// called.
+ ///
+ /// When we process, we'll check these to see if we can hang new transforms
+ /// off them.
+ final _newInputs = new Set<AssetNode>();
+
+ /// The phase after this one.
+ ///
+ /// Outputs from this phase will be passed to it.
+ final Phase _next;
+
+ Phase(this.graph, this._index, this._transformers, this._next);
+
+ /// Updates the phase's inputs with [updated] and removes [removed].
+ ///
+ /// This marks any affected [transforms] as dirty or discards them if their
+ /// inputs are removed.
+ void updateInputs(Map<AssetId, Asset> updated, Set<AssetId> removed) {
+ // Remove any nodes that are no longer being output. Handle removals first
+ // in case there are assets that were removed by one transform but updated
+ // by another. In that case, the update should win.
+ for (var id in removed) {
+ var node = inputs.remove(id);
+
+ // Every transform that was using it is dirty now.
+ if (node != null) {
+ node.consumers.forEach((consumer) => consumer.dirty());
+ }
+ }
+
+ // Update and new or modified assets.
+ updated.forEach((id, asset) {
+ var node = inputs.putIfAbsent(id, () => new AssetNode(id));
+
+ // If it's a new node, remember that so we can see if any new transforms
+ // will consume it.
+ if (node.asset == null) _newInputs.add(node);
+
+ node.updateAsset(asset);
+ });
+ }
+
+ /// Processes this phase.
+ ///
+ /// For all new inputs, it tries to see if there are transformers that can
+ /// consume them. Then all applicable transforms are applied.
+ ///
+ /// Returns a future that completes when processing is done. If there is
+ /// nothing to process, returns `null`.
+ Future process() {
+ var future = _processNewInputs();
+ if (future == null) {
+ return _processTransforms();
+ }
+
+ return future.then((_) => _processTransforms());
+ }
+
+ /// Creates new transforms for any new inputs that are applicable.
+ Future _processNewInputs() {
+ if (_newInputs.isEmpty) return null;
+
+ var futures = [];
+ for (var node in _newInputs) {
+ for (var transformer in _transformers) {
+ // TODO(rnystrom): Catch all errors from isPrimary() and redirect
+ // to results.
+ futures.add(transformer.isPrimary(node.id).then((isPrimary) {
+ if (!isPrimary) return;
+ var transform = new TransformNode(this, transformer, node);
+ node.consumers.add(transform);
+ _transforms.add(transform);
+ }));
+ }
+ }
+
+ _newInputs.clear();
+
+ return Future.wait(futures);
+ }
+
+ /// Applies all currently wired up and dirty transforms.
+ ///
+ /// Passes their outputs to the next phase.
+ Future _processTransforms() {
+ var dirtyTransforms = _transforms.where((transform) => transform.isDirty);
+ if (dirtyTransforms.isEmpty) return null;
+
+ return Future.wait(dirtyTransforms.map((transform) => transform.apply()))
+ .then((transformOutputs) {
+ // Collect all of the outputs. Since the transforms are run in parallel,
+ // we have to be careful here to ensure that the result is deterministic
+ // and not influenced by the order that transforms complete.
+ var updated = new Map<AssetId, Asset>();
+ var removed = new Set<AssetId>();
+ var collisions = new Set<AssetId>();
+
+ // Handle the generated outputs of all transforms first.
+ for (var outputs in transformOutputs) {
+ // Collect the outputs of all transformers together.
+ outputs.updated.forEach((id, asset) {
+ if (updated.containsKey(id)) {
+ // Report a collision.
+ collisions.add(id);
+ } else {
+ // TODO(rnystrom): In the case of a collision, the asset that
+ // "wins" is chosen non-deterministically. Do something better.
+ updated[id] = asset;
+ }
+ });
+
+ // Track any assets no longer output by this transform. We don't
+ // handle the case where *another* transform generates the asset
+ // no longer generated by this one. updateInputs() handles that.
+ removed.addAll(outputs.removed);
+ }
+
+ // Report any collisions in deterministic order.
+ collisions = collisions.toList();
+ collisions.sort((a, b) => a.toString().compareTo(b.toString()));
+ for (var collision in collisions) {
+ graph.reportError(new AssetCollisionException(collision));
+ // TODO(rnystrom): Define what happens after a collision occurs.
+ }
+
+ // Pass the outputs to the next phase.
+ _next.updateInputs(updated, removed);
+ });
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698