| 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.asset_cascade; | 5 library barback.asset_cascade; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | |
| 9 | 8 |
| 10 import 'asset.dart'; | 9 import 'asset.dart'; |
| 11 import 'asset_id.dart'; | 10 import 'asset_id.dart'; |
| 12 import 'asset_node.dart'; | 11 import 'asset_node.dart'; |
| 13 import 'asset_set.dart'; | 12 import 'asset_set.dart'; |
| 14 import 'log.dart'; | 13 import 'log.dart'; |
| 15 import 'cancelable_future.dart'; | 14 import 'cancelable_future.dart'; |
| 16 import 'errors.dart'; | 15 import 'errors.dart'; |
| 17 import 'package_graph.dart'; | 16 import 'package_graph.dart'; |
| 18 import 'phase.dart'; | 17 import 'phase.dart'; |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 71 /// | 70 /// |
| 72 /// This is synchronous in order to guarantee that it will emit an event as | 71 /// This is synchronous in order to guarantee that it will emit an event as |
| 73 /// soon as [isDirty] flips from `true` to `false`. | 72 /// soon as [isDirty] flips from `true` to `false`. |
| 74 Stream get onDone => _onDoneController.stream; | 73 Stream get onDone => _onDoneController.stream; |
| 75 final _onDoneController = new StreamController.broadcast(sync: true); | 74 final _onDoneController = new StreamController.broadcast(sync: true); |
| 76 | 75 |
| 77 /// Returns all currently-available output assets from this cascade. | 76 /// Returns all currently-available output assets from this cascade. |
| 78 AssetSet get availableOutputs => | 77 AssetSet get availableOutputs => |
| 79 new AssetSet.from(_phases.last.availableOutputs.map((node) => node.asset)); | 78 new AssetSet.from(_phases.last.availableOutputs.map((node) => node.asset)); |
| 80 | 79 |
| 81 /// A map of asset ids to completers for [getAssetNode] requests. | |
| 82 /// | |
| 83 /// If an asset node is requested before it's available, we put a completer in | |
| 84 /// this map to wait for the asset to be generated. If it's not generated, the | |
| 85 /// completer should complete to `null`. | |
| 86 final _pendingAssetRequests = new Map<AssetId, Completer<AssetNode>>(); | |
| 87 | |
| 88 /// Creates a new [AssetCascade]. | 80 /// Creates a new [AssetCascade]. |
| 89 /// | 81 /// |
| 90 /// It loads source assets within [package] using [provider]. | 82 /// It loads source assets within [package] using [provider]. |
| 91 AssetCascade(this.graph, this.package) { | 83 AssetCascade(this.graph, this.package) { |
| 92 _addPhase(new Phase(this, package)); | 84 _addPhase(new Phase(this, package)); |
| 93 } | 85 } |
| 94 | 86 |
| 95 /// Gets the asset identified by [id]. | 87 /// Gets the asset identified by [id]. |
| 96 /// | 88 /// |
| 97 /// If [id] is for a generated or transformed asset, this will wait until it | 89 /// If [id] is for a generated or transformed asset, this will wait until it |
| 98 /// has been created and return it. This means that the returned asset will | 90 /// has been created and return it. This means that the returned asset will |
| 99 /// always be [AssetState.AVAILABLE]. | 91 /// always be [AssetState.AVAILABLE]. |
| 100 /// | 92 /// |
| 101 /// If the asset cannot be found, returns null. | 93 /// If the asset cannot be found, returns null. |
| 102 Future<AssetNode> getAssetNode(AssetId id) { | 94 Future<AssetNode> getAssetNode(AssetId id) { |
| 103 assert(id.package == package); | 95 assert(id.package == package); |
| 104 | 96 |
| 97 var oldLastPhase = _phases.last; |
| 105 // TODO(rnystrom): Waiting for the entire build to complete is unnecessary | 98 // TODO(rnystrom): Waiting for the entire build to complete is unnecessary |
| 106 // in some cases. Should optimize: | 99 // in some cases. Should optimize: |
| 107 // * [id] may be generated before the compilation is finished. We should | 100 // * [id] may be generated before the compilation is finished. We should |
| 108 // be able to quickly check whether there are any more in-place | 101 // be able to quickly check whether there are any more in-place |
| 109 // transformations that can be run on it. If not, we can return it early. | 102 // transformations that can be run on it. If not, we can return it early. |
| 110 // * If [id] has never been generated and all active transformers provide | 103 // * If [id] has never been generated and all active transformers provide |
| 111 // metadata about the file names of assets it can emit, we can prove that | 104 // metadata about the file names of assets it can emit, we can prove that |
| 112 // none of them can emit [id] and fail early. | 105 // none of them can emit [id] and fail early. |
| 113 return _phases.last.getOutput(id).then((node) { | 106 return oldLastPhase.getOutput(id).then((node) { |
| 114 if (node != null) { | 107 // The last phase may have changed if [updateSources] was called after |
| 115 // If the requested asset is available, we can just return it. | 108 // requesting the output. In that case, we want the output from the new |
| 116 if (node.state.isAvailable) return node; | 109 // last phase. |
| 117 | 110 if (_phases.last == oldLastPhase) return node; |
| 118 // If the requested asset exists but isn't yet available, wait to see if | 111 return getAssetNode(id); |
| 119 // it becomes available. If it's removed before becoming available, try | |
| 120 // again, since it could be generated again. | |
| 121 node.force(); | |
| 122 return node.whenAvailable((_) => node).catchError((error) { | |
| 123 if (error is! AssetNotFoundException) throw error; | |
| 124 return getAssetNode(id); | |
| 125 }); | |
| 126 } | |
| 127 | |
| 128 // If the cascade isn't dirty, the phase won't generate the requested | |
| 129 // asset in the future. | |
| 130 if (!isDirty) return null; | |
| 131 | |
| 132 // If the cascade is dirty, store a completer for the asset node. If it's | |
| 133 // generated in the future, we'll complete this completer. | |
| 134 var completer = _pendingAssetRequests.putIfAbsent(id, | |
| 135 () => new Completer.sync()); | |
| 136 return completer.future; | |
| 137 }); | 112 }); |
| 138 } | 113 } |
| 139 | 114 |
| 140 /// Adds [sources] to the graph's known set of source assets. | 115 /// Adds [sources] to the graph's known set of source assets. |
| 141 /// | 116 /// |
| 142 /// Begins applying any transforms that can consume any of the sources. If a | 117 /// Begins applying any transforms that can consume any of the sources. If a |
| 143 /// given source is already known, it is considered modified and all | 118 /// given source is already known, it is considered modified and all |
| 144 /// transforms that use it will be re-applied. | 119 /// transforms that use it will be re-applied. |
| 145 void updateSources(Iterable<AssetId> sources) { | 120 void updateSources(Iterable<AssetId> sources) { |
| 146 for (var id in sources) { | 121 for (var id in sources) { |
| (...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 217 } | 192 } |
| 218 } | 193 } |
| 219 | 194 |
| 220 void reportError(BarbackException error) { | 195 void reportError(BarbackException error) { |
| 221 _errorsController.add(error); | 196 _errorsController.add(error); |
| 222 } | 197 } |
| 223 | 198 |
| 224 /// Add [phase] to the end of [_phases] and watch its streams. | 199 /// Add [phase] to the end of [_phases] and watch its streams. |
| 225 void _addPhase(Phase phase) { | 200 void _addPhase(Phase phase) { |
| 226 _onLogPool.add(phase.onLog); | 201 _onLogPool.add(phase.onLog); |
| 227 phase.onAsset.listen(_providePendingAsset); | |
| 228 | |
| 229 phase.onDone.listen((_) { | 202 phase.onDone.listen((_) { |
| 230 if (isDirty) return; | 203 if (!isDirty) _onDoneController.add(null); |
| 231 | |
| 232 // This cascade has finished building. If anyone's still waiting for | |
| 233 // assets, cut off the wait; we won't be generating them, at least until a | |
| 234 // source asset changes. | |
| 235 for (var completer in _pendingAssetRequests.values) { | |
| 236 completer.complete(null); | |
| 237 } | |
| 238 _pendingAssetRequests.clear(); | |
| 239 _onDoneController.add(null); | |
| 240 }); | 204 }); |
| 241 | 205 |
| 242 _phases.add(phase); | 206 _phases.add(phase); |
| 243 } | 207 } |
| 244 | 208 |
| 245 /// Provide an asset to a pending [getAssetNode] call. | |
| 246 void _providePendingAsset(AssetNode asset) { | |
| 247 // If anyone's waiting for this asset, provide it to them. | |
| 248 var request = _pendingAssetRequests.remove(asset.id); | |
| 249 if (request == null) return; | |
| 250 | |
| 251 if (asset.state.isAvailable) { | |
| 252 request.complete(asset); | |
| 253 return; | |
| 254 } | |
| 255 | |
| 256 // A lazy asset may be emitted while still dirty. If so, we wait until | |
| 257 // it's either available or removed before trying again to access it. We | |
| 258 // retry the entire [getAsset] process because the state of the graph may | |
| 259 // have changed dramatically by the time it's available. | |
| 260 assert(asset.state.isDirty); | |
| 261 asset.force(); | |
| 262 asset.whenStateChanges() | |
| 263 .then((_) => getAssetNode(asset.id)) | |
| 264 .then(request.complete) | |
| 265 .catchError(request.completeError); | |
| 266 } | |
| 267 | |
| 268 String toString() => "cascade for $package"; | 209 String toString() => "cascade for $package"; |
| 269 } | 210 } |
| OLD | NEW |