| OLD | NEW | 
|---|
| 1 // Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file | 
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a | 
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. | 
| 4 | 4 | 
| 5 library barback.graph.phase; | 5 library barback.graph.phase; | 
| 6 | 6 | 
| 7 import 'dart:async'; | 7 import 'dart:async'; | 
| 8 | 8 | 
| 9 import 'package:collection/collection.dart'; | 9 import 'package:collection/collection.dart'; | 
| 10 | 10 | 
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 85   final _streams = new NodeStreams(); | 85   final _streams = new NodeStreams(); | 
| 86   Stream<NodeStatus> get onStatusChange => _streams.onStatusChange; | 86   Stream<NodeStatus> get onStatusChange => _streams.onStatusChange; | 
| 87   Stream<AssetNode> get onAsset => _streams.onAsset; | 87   Stream<AssetNode> get onAsset => _streams.onAsset; | 
| 88   Stream<LogEntry> get onLog => _streams.onLog; | 88   Stream<LogEntry> get onLog => _streams.onLog; | 
| 89 | 89 | 
| 90   /// How far along [this] is in processing its assets. | 90   /// How far along [this] is in processing its assets. | 
| 91   NodeStatus get status { | 91   NodeStatus get status { | 
| 92     // Before any transformers are added, the phase should be dirty if and only | 92     // Before any transformers are added, the phase should be dirty if and only | 
| 93     // if any input is dirty. | 93     // if any input is dirty. | 
| 94     if (_classifiers.isEmpty && _groups.isEmpty && previous == null) { | 94     if (_classifiers.isEmpty && _groups.isEmpty && previous == null) { | 
| 95       return _inputs.any((input) => input.state.isDirty) ? | 95       return _inputs.any((input) => input.state.isDirty) | 
| 96           NodeStatus.RUNNING : NodeStatus.IDLE; | 96           ? NodeStatus.RUNNING | 
|  | 97           : NodeStatus.IDLE; | 
| 97     } | 98     } | 
| 98 | 99 | 
| 99     var classifierStatus = NodeStatus.dirtiest( | 100     var classifierStatus = NodeStatus | 
| 100         _classifiers.values.map((classifier) => classifier.status)); | 101         .dirtiest(_classifiers.values.map((classifier) => classifier.status)); | 
| 101     var groupStatus = NodeStatus.dirtiest( | 102     var groupStatus = | 
| 102         _groups.values.map((group) => group.status)); | 103         NodeStatus.dirtiest(_groups.values.map((group) => group.status)); | 
| 103     return (previous == null ? NodeStatus.IDLE : previous.status) | 104     return (previous == null ? NodeStatus.IDLE : previous.status) | 
| 104         .dirtier(classifierStatus) | 105         .dirtier(classifierStatus) | 
| 105         .dirtier(groupStatus); | 106         .dirtier(groupStatus); | 
| 106   } | 107   } | 
| 107 | 108 | 
| 108   /// The previous phase in the cascade, or null if this is the first phase. | 109   /// The previous phase in the cascade, or null if this is the first phase. | 
| 109   final Phase previous; | 110   final Phase previous; | 
| 110 | 111 | 
| 111   /// The subscription to [previous]'s [onStatusChange] stream. | 112   /// The subscription to [previous]'s [onStatusChange] stream. | 
| 112   StreamSubscription _previousStatusSubscription; | 113   StreamSubscription _previousStatusSubscription; | 
| (...skipping 14 matching lines...) Expand all  Loading... | 
| 127   Set<AssetNode> get availableOutputs { | 128   Set<AssetNode> get availableOutputs { | 
| 128     return _outputs.values | 129     return _outputs.values | 
| 129         .map((output) => output.output) | 130         .map((output) => output.output) | 
| 130         .where((node) => node.state.isAvailable) | 131         .where((node) => node.state.isAvailable) | 
| 131         .toSet(); | 132         .toSet(); | 
| 132   } | 133   } | 
| 133 | 134 | 
| 134   // TODO(nweiz): Rather than passing the cascade and the phase everywhere, | 135   // TODO(nweiz): Rather than passing the cascade and the phase everywhere, | 
| 135   // create an interface that just exposes [getInput]. Emit errors via | 136   // create an interface that just exposes [getInput]. Emit errors via | 
| 136   // [AssetNode]s. | 137   // [AssetNode]s. | 
| 137   Phase(AssetCascade cascade, String location) | 138   Phase(AssetCascade cascade, String location) : this._(cascade, location, 0); | 
| 138       : this._(cascade, location, 0); |  | 
| 139 | 139 | 
| 140   Phase._(this.cascade, this._location, this._index, [this.previous]) { | 140   Phase._(this.cascade, this._location, this._index, [this.previous]) { | 
| 141     if (previous != null) { | 141     if (previous != null) { | 
| 142       _previousOnAssetSubscription = previous.onAsset.listen(addInput); | 142       _previousOnAssetSubscription = previous.onAsset.listen(addInput); | 
| 143       _previousStatusSubscription = previous.onStatusChange | 143       _previousStatusSubscription = | 
| 144           .listen((_) => _streams.changeStatus(status)); | 144           previous.onStatusChange.listen((_) => _streams.changeStatus(status)); | 
| 145     } | 145     } | 
| 146 | 146 | 
| 147     onStatusChange.listen((status) { | 147     onStatusChange.listen((status) { | 
| 148       if (status == NodeStatus.RUNNING) return; | 148       if (status == NodeStatus.RUNNING) return; | 
| 149 | 149 | 
| 150       // All the previous phases have finished declaring or producing their | 150       // All the previous phases have finished declaring or producing their | 
| 151       // outputs. If anyone's still waiting for outputs, cut off the wait; we | 151       // outputs. If anyone's still waiting for outputs, cut off the wait; we | 
| 152       // won't be generating them, at least until a source asset changes. | 152       // won't be generating them, at least until a source asset changes. | 
| 153       for (var completer in _pendingOutputRequests.values) { | 153       for (var completer in _pendingOutputRequests.values) { | 
| 154         completer.complete(null); | 154         completer.complete(null); | 
| 155       } | 155       } | 
| 156       _pendingOutputRequests.clear(); | 156       _pendingOutputRequests.clear(); | 
| 157     }); | 157     }); | 
| 158   } | 158   } | 
| 159 | 159 | 
| 160   /// Adds a new asset as an input for this phase. | 160   /// Adds a new asset as an input for this phase. | 
| 161   /// | 161   /// | 
| 162   /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase | 162   /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase | 
| 163   /// will automatically begin determining which transforms can consume it as a | 163   /// will automatically begin determining which transforms can consume it as a | 
| 164   /// primary input. The transforms themselves won't be applied until [process] | 164   /// primary input. The transforms themselves won't be applied until [process] | 
| 165   /// is called, however. | 165   /// is called, however. | 
| 166   /// | 166   /// | 
| 167   /// This should only be used for brand-new assets or assets that have been | 167   /// This should only be used for brand-new assets or assets that have been | 
| 168   /// removed and re-created. The phase will automatically handle updated assets | 168   /// removed and re-created. The phase will automatically handle updated assets | 
| 169   /// using the [AssetNode.onStateChange] stream. | 169   /// using the [AssetNode.onStateChange] stream. | 
| 170   void addInput(AssetNode node) { | 170   void addInput(AssetNode node) { | 
| 171     // Each group is one channel along which an asset may be forwarded, as is | 171     // Each group is one channel along which an asset may be forwarded, as is | 
| 172     // each transformer. | 172     // each transformer. | 
| 173     var forwarder = new PhaseForwarder( | 173     var forwarder = | 
| 174         node, _classifiers.length, _groups.length); | 174         new PhaseForwarder(node, _classifiers.length, _groups.length); | 
| 175     _forwarders[node.id] = forwarder; | 175     _forwarders[node.id] = forwarder; | 
| 176     forwarder.onAsset.listen(_handleOutputWithoutForwarder); | 176     forwarder.onAsset.listen(_handleOutputWithoutForwarder); | 
| 177     if (forwarder.output != null) { | 177     if (forwarder.output != null) { | 
| 178       _handleOutputWithoutForwarder(forwarder.output); | 178       _handleOutputWithoutForwarder(forwarder.output); | 
| 179     } | 179     } | 
| 180 | 180 | 
| 181     _inputOrigins.add(node.origin); | 181     _inputOrigins.add(node.origin); | 
| 182     _inputs.add(node); | 182     _inputs.add(node); | 
| 183     _inputSubscriptions.add(node.onStateChange.listen((state) { | 183     _inputSubscriptions.add(node.onStateChange.listen((state) { | 
| 184       if (state.isRemoved) { | 184       if (state.isRemoved) { | 
| (...skipping 11 matching lines...) Expand all  Loading... | 
| 196   // TODO(nweiz): If the output is available when this is called, it's | 196   // TODO(nweiz): If the output is available when this is called, it's | 
| 197   // theoretically possible for it to become unavailable between the call and | 197   // theoretically possible for it to become unavailable between the call and | 
| 198   // the return. If it does so, it won't trigger the rebuilding process. To | 198   // the return. If it does so, it won't trigger the rebuilding process. To | 
| 199   // avoid this, we should have this and the methods it calls take explicit | 199   // avoid this, we should have this and the methods it calls take explicit | 
| 200   // callbacks, as in [AssetNode.whenAvailable]. | 200   // callbacks, as in [AssetNode.whenAvailable]. | 
| 201   /// Gets the asset node for an output [id]. | 201   /// Gets the asset node for an output [id]. | 
| 202   /// | 202   /// | 
| 203   /// If [id] is for a generated or transformed asset, this will wait until it | 203   /// If [id] is for a generated or transformed asset, this will wait until it | 
| 204   /// has been created and return it. This means that the returned asset will | 204   /// has been created and return it. This means that the returned asset will | 
| 205   /// always be [AssetState.AVAILABLE]. | 205   /// always be [AssetState.AVAILABLE]. | 
| 206   /// | 206   /// | 
| 207   /// If the output cannot be found, returns null. | 207   /// If the output cannot be found, returns null. | 
| 208   Future<AssetNode> getOutput(AssetId id) { | 208   Future<AssetNode> getOutput(AssetId id) { | 
| 209     return new Future.sync(() { | 209     return new Future.sync(() { | 
| 210       if (id.package != cascade.package) return cascade.graph.getAssetNode(id); | 210       if (id.package != cascade.package) return cascade.graph.getAssetNode(id); | 
| 211       if (_outputs.containsKey(id)) { | 211       if (_outputs.containsKey(id)) { | 
| 212         var output = _outputs[id].output; | 212         var output = _outputs[id].output; | 
| 213         // If the requested output is available, we can just return it. | 213         // If the requested output is available, we can just return it. | 
| 214         if (output.state.isAvailable) return output; | 214         if (output.state.isAvailable) return output; | 
| 215 | 215 | 
| 216         // If the requested output exists but isn't yet available, wait to see | 216         // If the requested output exists but isn't yet available, wait to see | 
| 217         // if it becomes available. If it's removed before becoming available, | 217         // if it becomes available. If it's removed before becoming available, | 
| 218         // try again, since it could be generated again. | 218         // try again, since it could be generated again. | 
| 219         output.force(); | 219         output.force(); | 
| 220         return output.whenAvailable((_) { | 220         return output.whenAvailable((_) { | 
| 221           return output; | 221           return output; | 
| 222         }).catchError((error) { | 222         }).catchError((error) { | 
| 223           if (error is! AssetNotFoundException) throw error; | 223           if (error is! AssetNotFoundException) throw error; | 
| 224           return getOutput(id); | 224           return getOutput(id); | 
| 225         }); | 225         }); | 
| 226       } | 226       } | 
| 227 | 227 | 
| 228       // If this phase and the previous phases are fully declared or done, the | 228       // If this phase and the previous phases are fully declared or done, the | 
| 229       // requested output won't be generated and we can safely return null. | 229       // requested output won't be generated and we can safely return null. | 
| 230       if (status != NodeStatus.RUNNING) return null; | 230       if (status != NodeStatus.RUNNING) return null; | 
| 231 | 231 | 
| 232       // Otherwise, store a completer for the asset node. If it's generated in | 232       // Otherwise, store a completer for the asset node. If it's generated in | 
| 233       // the future, we'll complete this completer. | 233       // the future, we'll complete this completer. | 
| 234       var completer = _pendingOutputRequests.putIfAbsent(id, | 234       var completer = | 
| 235           () => new Completer.sync()); | 235           _pendingOutputRequests.putIfAbsent(id, () => new Completer.sync()); | 
| 236       return completer.future; | 236       return completer.future; | 
| 237     }); | 237     }); | 
| 238   } | 238   } | 
| 239 | 239 | 
| 240   /// Set this phase's transformers to [transformers]. | 240   /// Set this phase's transformers to [transformers]. | 
| 241   void updateTransformers(Iterable transformers) { | 241   void updateTransformers(Iterable transformers) { | 
| 242     var newTransformers = transformers | 242     var newTransformers = transformers | 
| 243         .where((op) => op is Transformer || op is AggregateTransformer) | 243         .where((op) => op is Transformer || op is AggregateTransformer) | 
| 244         .toSet(); | 244         .toSet(); | 
| 245     var oldTransformers = _classifiers.keys.toSet(); | 245     var oldTransformers = _classifiers.keys.toSet(); | 
| 246     for (var removed in oldTransformers.difference(newTransformers)) { | 246     for (var removed in oldTransformers.difference(newTransformers)) { | 
| 247       _classifiers.remove(removed).remove(); | 247       _classifiers.remove(removed).remove(); | 
| 248     } | 248     } | 
| 249 | 249 | 
| 250     for (var transformer in newTransformers.difference(oldTransformers)) { | 250     for (var transformer in newTransformers.difference(oldTransformers)) { | 
| 251       var classifier = new TransformerClassifier( | 251       var classifier = | 
| 252           this, transformer, "$_location.$_index"); | 252           new TransformerClassifier(this, transformer, "$_location.$_index"); | 
| 253       _classifiers[transformer] = classifier; | 253       _classifiers[transformer] = classifier; | 
| 254       classifier.onAsset.listen(_handleOutput); | 254       classifier.onAsset.listen(_handleOutput); | 
| 255       _streams.onLogPool.add(classifier.onLog); | 255       _streams.onLogPool.add(classifier.onLog); | 
| 256       classifier.onStatusChange.listen((_) => _streams.changeStatus(status)); | 256       classifier.onStatusChange.listen((_) => _streams.changeStatus(status)); | 
| 257       for (var input in _inputs) { | 257       for (var input in _inputs) { | 
| 258         classifier.addInput(input); | 258         classifier.addInput(input); | 
| 259       } | 259       } | 
| 260     } | 260     } | 
| 261 | 261 | 
| 262     var newGroups = DelegatingSet.typed/*<TransformerGroup>*/( | 262     var newGroups = DelegatingSet.typed<TransformerGroup>( | 
| 263         transformers.where((op) => op is TransformerGroup).toSet()); | 263         transformers.where((op) => op is TransformerGroup).toSet()); | 
| 264     var oldGroups = _groups.keys.toSet(); | 264     var oldGroups = _groups.keys.toSet(); | 
| 265     for (var removed in oldGroups.difference(newGroups)) { | 265     for (var removed in oldGroups.difference(newGroups)) { | 
| 266       _groups.remove(removed).remove(); | 266       _groups.remove(removed).remove(); | 
| 267     } | 267     } | 
| 268 | 268 | 
| 269     for (var added in newGroups.difference(oldGroups)) { | 269     for (var added in newGroups.difference(oldGroups)) { | 
| 270       var runner = new GroupRunner(previous, added, "$_location.$_index"); | 270       var runner = new GroupRunner(previous, added, "$_location.$_index"); | 
| 271       _groups[added] = runner; | 271       _groups[added] = runner; | 
| 272       runner.onAsset.listen(_handleOutput); | 272       runner.onAsset.listen(_handleOutput); | 
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
| 347     } | 347     } | 
| 348   } | 348   } | 
| 349 | 349 | 
| 350   /// Add [asset] as an output of this phase without checking if it's a | 350   /// Add [asset] as an output of this phase without checking if it's a | 
| 351   /// forwarded asset. | 351   /// forwarded asset. | 
| 352   void _handleOutputWithoutForwarder(AssetNode asset) { | 352   void _handleOutputWithoutForwarder(AssetNode asset) { | 
| 353     if (_outputs.containsKey(asset.id)) { | 353     if (_outputs.containsKey(asset.id)) { | 
| 354       _outputs[asset.id].add(asset); | 354       _outputs[asset.id].add(asset); | 
| 355     } else { | 355     } else { | 
| 356       _outputs[asset.id] = new PhaseOutput(this, asset, "$_location.$_index"); | 356       _outputs[asset.id] = new PhaseOutput(this, asset, "$_location.$_index"); | 
| 357       _outputs[asset.id].onAsset.listen(_emit, | 357       _outputs[asset.id] | 
| 358           onDone: () => _outputs.remove(asset.id)); | 358           .onAsset | 
|  | 359           .listen(_emit, onDone: () => _outputs.remove(asset.id)); | 
| 359       _emit(_outputs[asset.id].output); | 360       _emit(_outputs[asset.id].output); | 
| 360     } | 361     } | 
| 361 | 362 | 
| 362     var exception = _outputs[asset.id].collisionException; | 363     var exception = _outputs[asset.id].collisionException; | 
| 363     if (exception != null) cascade.reportError(exception); | 364     if (exception != null) cascade.reportError(exception); | 
| 364   } | 365   } | 
| 365 | 366 | 
| 366   /// Emit [asset] as an output of this phase. | 367   /// Emit [asset] as an output of this phase. | 
| 367   /// | 368   /// | 
| 368   /// This should be called after [_handleOutput], so that collisions are | 369   /// This should be called after [_handleOutput], so that collisions are | 
| (...skipping 11 matching lines...) Expand all  Loading... | 
| 380 | 381 | 
| 381     if (asset.state.isAvailable) { | 382     if (asset.state.isAvailable) { | 
| 382       request.complete(asset); | 383       request.complete(asset); | 
| 383       return; | 384       return; | 
| 384     } | 385     } | 
| 385 | 386 | 
| 386     // A lazy asset may be emitted while still dirty. If so, we wait until it's | 387     // A lazy asset may be emitted while still dirty. If so, we wait until it's | 
| 387     // either available or removed before trying again to access it. | 388     // either available or removed before trying again to access it. | 
| 388     assert(asset.state.isDirty); | 389     assert(asset.state.isDirty); | 
| 389     asset.force(); | 390     asset.force(); | 
| 390     asset.whenStateChanges().then((state) { | 391     asset | 
| 391       if (state.isRemoved) return getOutput(asset.id); | 392         .whenStateChanges() | 
| 392       return asset; | 393         .then((state) { | 
| 393     }) | 394           if (state.isRemoved) return getOutput(asset.id); | 
| 394        .then((asset) => request.complete(asset)) | 395           return asset; | 
| 395        .catchError(request.completeError); | 396         }) | 
|  | 397         .then((asset) => request.complete(asset)) | 
|  | 398         .catchError(request.completeError); | 
| 396   } | 399   } | 
| 397 | 400 | 
| 398   String toString() => "phase $_location.$_index"; | 401   String toString() => "phase $_location.$_index"; | 
| 399 } | 402 } | 
| OLD | NEW | 
|---|