OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library barback.graph.asset_cascade; |
| 6 |
| 7 import 'dart:async'; |
| 8 |
| 9 import '../asset/asset.dart'; |
| 10 import '../asset/asset_id.dart'; |
| 11 import '../asset/asset_node.dart'; |
| 12 import '../asset/asset_set.dart'; |
| 13 import '../errors.dart'; |
| 14 import '../log.dart'; |
| 15 import '../transformer/transformer.dart'; |
| 16 import '../utils.dart'; |
| 17 import '../utils/cancelable_future.dart'; |
| 18 import 'node_status.dart'; |
| 19 import 'node_streams.dart'; |
| 20 import 'package_graph.dart'; |
| 21 import 'phase.dart'; |
| 22 |
| 23 /// The asset cascade for an individual package. |
| 24 /// |
| 25 /// This keeps track of which [Transformer]s are applied to which assets, and |
| 26 /// re-runs those transformers when their dependencies change. The transformed |
| 27 /// asset nodes are accessible via [getAssetNode]. |
| 28 /// |
| 29 /// A cascade consists of one or more [Phases], each of which has one or more |
| 30 /// [Transformer]s that run in parallel, potentially on the same inputs. The |
| 31 /// inputs of the first phase are the source assets for this cascade's package. |
| 32 /// The inputs of each successive phase are the outputs of the previous phase, |
| 33 /// as well as any assets that haven't yet been transformed. |
| 34 class AssetCascade { |
| 35 /// The name of the package whose assets are managed. |
| 36 final String package; |
| 37 |
| 38 /// The [PackageGraph] that tracks all [AssetCascade]s for all dependencies of |
| 39 /// the current app. |
| 40 final PackageGraph graph; |
| 41 |
| 42 /// The controllers for the [AssetNode]s that provide information about this |
| 43 /// cascade's package's source assets. |
| 44 final _sourceControllerMap = new Map<AssetId, AssetNodeController>(); |
| 45 |
| 46 /// Futures for source assets that are currently being loaded. |
| 47 /// |
| 48 /// These futures are cancelable so that if an asset is updated after a load |
| 49 /// has been kicked off, the previous load can be ignored in favor of a new |
| 50 /// one. |
| 51 final _loadingSources = new Map<AssetId, CancelableFuture<Asset>>(); |
| 52 |
| 53 /// The list of phases in this cascade. |
| 54 /// |
| 55 /// This will always contain at least one phase, and the first phase will |
| 56 /// never have any transformers. This ensures that every transformer can |
| 57 /// request inputs from a previous phase. |
| 58 final _phases = <Phase>[]; |
| 59 |
| 60 /// The subscription to the [Phase.onStatusChange] stream of the last [Phase] |
| 61 /// in [_phases]. |
| 62 StreamSubscription _phaseStatusSubscription; |
| 63 |
| 64 /// A stream that emits any errors from the cascade or the transformers. |
| 65 /// |
| 66 /// This emits errors as they're detected. If an error occurs in one part of |
| 67 /// the cascade, unrelated parts will continue building. |
| 68 Stream<BarbackException> get errors => _errorsController.stream; |
| 69 final _errorsController = |
| 70 new StreamController<BarbackException>.broadcast(sync: true); |
| 71 |
| 72 /// How far along [this] is in processing its assets. |
| 73 NodeStatus get status { |
| 74 // Just check the last phase, since it will check all the previous phases |
| 75 // itself. |
| 76 return _phases.last.status; |
| 77 } |
| 78 |
| 79 /// The streams exposed by this cascade. |
| 80 final _streams = new NodeStreams(); |
| 81 Stream<LogEntry> get onLog => _streams.onLog; |
| 82 Stream<NodeStatus> get onStatusChange => _streams.onStatusChange; |
| 83 Stream<AssetNode> get onAsset => _streams.onAsset; |
| 84 |
| 85 /// Returns all currently-available output assets from this cascade. |
| 86 Future<AssetSet> get availableOutputs => new Future.value(new AssetSet.from( |
| 87 _phases.last.availableOutputs.map((node) => node.asset))); |
| 88 |
| 89 /// Creates a new [AssetCascade]. |
| 90 /// |
| 91 /// It loads source assets within [package] using [provider]. |
| 92 AssetCascade(this.graph, this.package) { |
| 93 _addPhase(new Phase(this, package)); |
| 94 _streams.onAssetPool.add(_phases.last.onAsset); |
| 95 } |
| 96 |
| 97 /// Gets the asset identified by [id]. |
| 98 /// |
| 99 /// If [id] is for a generated or transformed asset, this will wait until it |
| 100 /// has been created and return it. This means that the returned asset will |
| 101 /// always be [AssetState.AVAILABLE]. |
| 102 /// |
| 103 /// If the asset cannot be found, returns null. |
| 104 Future<AssetNode> getAssetNode(AssetId id) { |
| 105 assert(id.package == package); |
| 106 |
| 107 var oldLastPhase = _phases.last; |
| 108 // TODO(rnystrom): Waiting for the entire build to complete is unnecessary |
| 109 // in some cases. Should optimize: |
| 110 // * [id] may be generated before the compilation is finished. We should |
| 111 // be able to quickly check whether there are any more in-place |
| 112 // transformations that can be run on it. If not, we can return it early. |
| 113 // * If [id] has never been generated and all active transformers provide |
| 114 // metadata about the file names of assets it can emit, we can prove that |
| 115 // none of them can emit [id] and fail early. |
| 116 return oldLastPhase.getOutput(id).then((node) { |
| 117 // The last phase may have changed if [updateSources] was called after |
| 118 // requesting the output. In that case, we want the output from the new |
| 119 // last phase. |
| 120 if (_phases.last == oldLastPhase) return node; |
| 121 return getAssetNode(id); |
| 122 }); |
| 123 } |
| 124 |
| 125 /// Adds [sources] to the graph's known set of source assets. |
| 126 /// |
| 127 /// Begins applying any transforms that can consume any of the sources. If a |
| 128 /// given source is already known, it is considered modified and all |
| 129 /// transforms that use it will be re-applied. |
| 130 void updateSources(Iterable<AssetId> sources) { |
| 131 for (var id in sources) { |
| 132 var controller = _sourceControllerMap[id]; |
| 133 if (controller != null) { |
| 134 controller.setDirty(); |
| 135 } else { |
| 136 _sourceControllerMap[id] = new AssetNodeController(id); |
| 137 _phases.first.addInput(_sourceControllerMap[id].node); |
| 138 } |
| 139 |
| 140 // If this source was already loading, cancel the old load, since it may |
| 141 // return out-of-date contents for the asset. |
| 142 if (_loadingSources.containsKey(id)) _loadingSources[id].cancel(); |
| 143 |
| 144 _loadingSources[id] = new CancelableFuture<Asset>( |
| 145 syncFuture(() => graph.provider.getAsset(id))); |
| 146 _loadingSources[id].whenComplete(() { |
| 147 _loadingSources.remove(id); |
| 148 }).then((asset) { |
| 149 var controller = _sourceControllerMap[id].setAvailable(asset); |
| 150 }).catchError((error, stack) { |
| 151 reportError(new AssetLoadException(id, error, stack)); |
| 152 |
| 153 // TODO(nweiz): propagate error information through asset nodes. |
| 154 _sourceControllerMap.remove(id).setRemoved(); |
| 155 }); |
| 156 } |
| 157 } |
| 158 |
| 159 /// Removes [removed] from the graph's known set of source assets. |
| 160 void removeSources(Iterable<AssetId> removed) { |
| 161 removed.forEach((id) { |
| 162 // If the source was being loaded, cancel that load. |
| 163 if (_loadingSources.containsKey(id)) _loadingSources.remove(id).cancel(); |
| 164 |
| 165 var controller = _sourceControllerMap.remove(id); |
| 166 // Don't choke if an id is double-removed for some reason. |
| 167 if (controller != null) controller.setRemoved(); |
| 168 }); |
| 169 } |
| 170 |
| 171 /// Sets this cascade's transformer phases to [transformers]. |
| 172 /// |
| 173 /// Elements of the inner iterable of [transformers] must be [Transformer]s, |
| 174 /// [TransformerGroup]s, or [AggregateTransformer]s. |
| 175 void updateTransformers(Iterable<Iterable> transformersIterable) { |
| 176 _streams.onAssetPool.remove(_phases.last.onAsset); |
| 177 var transformers = transformersIterable.toList(); |
| 178 |
| 179 // Always preserve a single phase with no transformers at the beginning of |
| 180 // the cascade so that [TransformNode]s in the first populated phase will |
| 181 // have something to request assets from. |
| 182 for (var i = 0; i < transformers.length; i++) { |
| 183 if (_phases.length > i + 1) { |
| 184 _phases[i + 1].updateTransformers(transformers[i]); |
| 185 continue; |
| 186 } |
| 187 |
| 188 var phase = _phases.last.addPhase(); |
| 189 _addPhase(phase); |
| 190 phase.updateTransformers(transformers[i]); |
| 191 } |
| 192 |
| 193 for (var i = transformers.length + 1; i < _phases.length; i++) { |
| 194 _phases[i].remove(); |
| 195 } |
| 196 _phases.removeRange(transformers.length + 1, _phases.length); |
| 197 |
| 198 _phaseStatusSubscription.cancel(); |
| 199 _phaseStatusSubscription = _phases.last.onStatusChange |
| 200 .listen(_streams.changeStatus); |
| 201 |
| 202 _streams.onAssetPool.add(_phases.last.onAsset); |
| 203 } |
| 204 |
| 205 /// Force all [LazyTransformer]s' transforms in this cascade to begin |
| 206 /// producing concrete assets. |
| 207 void forceAllTransforms() { |
| 208 for (var phase in _phases) { |
| 209 phase.forceAllTransforms(); |
| 210 } |
| 211 } |
| 212 |
| 213 void reportError(BarbackException error) { |
| 214 _errorsController.add(error); |
| 215 } |
| 216 |
| 217 /// Add [phase] to the end of [_phases] and watch its streams. |
| 218 void _addPhase(Phase phase) { |
| 219 _streams.onLogPool.add(phase.onLog); |
| 220 if (_phaseStatusSubscription != null) _phaseStatusSubscription.cancel(); |
| 221 _phaseStatusSubscription = |
| 222 phase.onStatusChange.listen(_streams.changeStatus); |
| 223 |
| 224 _phases.add(phase); |
| 225 } |
| 226 |
| 227 String toString() => "cascade for $package"; |
| 228 } |
OLD | NEW |