Index: mojo/public/dart/third_party/barback/lib/src/graph/phase_output.dart |
diff --git a/mojo/public/dart/third_party/barback/lib/src/graph/phase_output.dart b/mojo/public/dart/third_party/barback/lib/src/graph/phase_output.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3d6460b657e3d9edcb90f4e82bdf8d22f6acb448 |
--- /dev/null |
+++ b/mojo/public/dart/third_party/barback/lib/src/graph/phase_output.dart |
@@ -0,0 +1,114 @@ |
+// 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.graph.phase_output; |
+ |
+import 'dart:async'; |
+import 'dart:collection'; |
+ |
+import '../asset/asset_forwarder.dart'; |
+import '../asset/asset_node.dart'; |
+import '../errors.dart'; |
+import 'phase.dart'; |
+ |
+/// A class that handles a single output of a phase. |
+/// |
+/// Normally there's only a single [AssetNode] for a phase's output, but it's |
+/// possible that multiple transformers in the same phase emit assets with the |
+/// same id, causing collisions. This handles those collisions by forwarding the |
+/// chronologically first asset. |
+/// |
+/// When the asset being forwarding changes, the old value of [output] will be |
+/// marked as removed and a new value will replace it. Users of this class can |
+/// be notified of this using [onAsset]. |
+class PhaseOutput { |
+ /// The phase for which this is an output. |
+ final Phase _phase; |
+ |
+ /// A string describing the location of [this] in the transformer graph. |
+ final String _location; |
+ |
+ /// The asset node for this output. |
+ AssetNode get output => _outputForwarder.node; |
+ AssetForwarder _outputForwarder; |
+ |
+ /// A stream that emits an [AssetNode] each time this output starts forwarding |
+ /// a new asset. |
+ Stream<AssetNode> get onAsset => _onAssetController.stream; |
+ final _onAssetController = |
+ new StreamController<AssetNode>.broadcast(sync: true); |
+ |
+ /// The assets for this output. |
+ /// |
+ /// If there's no collision, this will only have one element. Otherwise, it |
+ /// will be ordered by which asset was added first. |
+ final _assets = new Queue<AssetNode>(); |
+ |
+ /// The [AssetCollisionException] for this output, or null if there is no |
+ /// collision currently. |
+ AssetCollisionException get collisionException { |
+ if (_assets.length == 1) return null; |
+ return new AssetCollisionException( |
+ _assets.where((asset) => asset.transform != null) |
+ .map((asset) => asset.transform.info), |
+ output.id); |
+ } |
+ |
+ PhaseOutput(this._phase, AssetNode output, this._location) |
+ : _outputForwarder = new AssetForwarder(output) { |
+ assert(!output.state.isRemoved); |
+ add(output); |
+ } |
+ |
+ /// Adds an asset node as an output with this id. |
+ void add(AssetNode node) { |
+ assert(node.id == output.id); |
+ assert(!output.state.isRemoved); |
+ _assets.add(node); |
+ _watchAsset(node); |
+ } |
+ |
+ /// Removes all existing listeners on [output] without actually closing |
+ /// [this]. |
+ /// |
+ /// This marks [output] as removed, but immediately replaces it with a new |
+ /// [AssetNode] in the same state as the old output. This is used when adding |
+ /// a new [Phase] to cause consumers of the prior phase's outputs to be to |
+ /// start consuming the new phase's outputs instead. |
+ void removeListeners() { |
+ _outputForwarder.close(); |
+ _outputForwarder = new AssetForwarder(_assets.first); |
+ _onAssetController.add(output); |
+ } |
+ |
+ /// Watches [node] to adjust [_assets] and [output] when it's removed. |
+ void _watchAsset(AssetNode node) { |
+ node.whenRemoved(() { |
+ if (_assets.length == 1) { |
+ assert(_assets.single == node); |
+ _outputForwarder.close(); |
+ _onAssetController.close(); |
+ return; |
+ } |
+ |
+ // If there was more than one asset, we're resolving a collision -- |
+ // possibly partially. |
+ var wasFirst = _assets.first == node; |
+ _assets.remove(node); |
+ |
+ // If this was the first asset, we replace it with the next asset |
+ // (chronologically). |
+ if (wasFirst) removeListeners(); |
+ |
+ // 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) { |
+ // TODO(nweiz): report this through the output asset. |
+ _phase.cascade.reportError(collisionException); |
+ } |
+ }); |
+ } |
+ |
+ String toString() => "phase output in $_location for $output"; |
+} |