| 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.phase; | 5 library barback.phase; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 | 8 |
| 9 import 'asset_cascade.dart'; | 9 import 'asset_cascade.dart'; |
| 10 import 'asset_id.dart'; | 10 import 'asset_id.dart'; |
| 11 import 'asset_node.dart'; | 11 import 'asset_node.dart'; |
| 12 import 'errors.dart'; |
| 12 import 'group_runner.dart'; | 13 import 'group_runner.dart'; |
| 13 import 'log.dart'; | 14 import 'log.dart'; |
| 14 import 'multiset.dart'; | 15 import 'multiset.dart'; |
| 15 import 'phase_forwarder.dart'; | 16 import 'phase_forwarder.dart'; |
| 16 import 'phase_input.dart'; | 17 import 'phase_input.dart'; |
| 17 import 'phase_output.dart'; | 18 import 'phase_output.dart'; |
| 18 import 'stream_pool.dart'; | 19 import 'stream_pool.dart'; |
| 19 import 'transformer.dart'; | 20 import 'transformer.dart'; |
| 20 import 'transformer_group.dart'; | 21 import 'transformer_group.dart'; |
| 21 import 'utils.dart'; | 22 import 'utils.dart'; |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 89 /// Assets are emitted synchronously to ensure that any changes are thoroughly | 90 /// Assets are emitted synchronously to ensure that any changes are thoroughly |
| 90 /// propagated as soon as they occur. Only a phase with no [next] phase will | 91 /// propagated as soon as they occur. Only a phase with no [next] phase will |
| 91 /// emit assets. | 92 /// emit assets. |
| 92 Stream<AssetNode> get onAsset => _onAssetController.stream; | 93 Stream<AssetNode> get onAsset => _onAssetController.stream; |
| 93 final _onAssetController = new StreamController<AssetNode>(sync: true); | 94 final _onAssetController = new StreamController<AssetNode>(sync: true); |
| 94 | 95 |
| 95 /// Whether [this] is dirty and still has more processing to do. | 96 /// Whether [this] is dirty and still has more processing to do. |
| 96 bool get isDirty => _inputs.values.any((input) => input.isDirty) || | 97 bool get isDirty => _inputs.values.any((input) => input.isDirty) || |
| 97 _groups.values.any((group) => group.isDirty); | 98 _groups.values.any((group) => group.isDirty); |
| 98 | 99 |
| 100 /// Whether [this] or any previous phase is dirty. |
| 101 bool get _isTransitivelyDirty => isDirty || |
| 102 (_previous != null && _previous._isTransitivelyDirty); |
| 103 |
| 99 /// A stream that emits an event whenever any transforms in this phase logs | 104 /// A stream that emits an event whenever any transforms in this phase logs |
| 100 /// an entry. | 105 /// an entry. |
| 101 Stream<LogEntry> get onLog => _onLogPool.stream; | 106 Stream<LogEntry> get onLog => _onLogPool.stream; |
| 102 final _onLogPool = new StreamPool<LogEntry>.broadcast(); | 107 final _onLogPool = new StreamPool<LogEntry>.broadcast(); |
| 103 | 108 |
| 109 /// The previous phase in the cascade, or null if this is the first phase. |
| 110 final Phase _previous; |
| 111 |
| 104 /// The phase after this one. | 112 /// The phase after this one. |
| 105 /// | 113 /// |
| 106 /// Outputs from this phase will be passed to it. | 114 /// Outputs from this phase will be passed to it. |
| 107 Phase get next => _next; | 115 Phase get next => _next; |
| 108 Phase _next; | 116 Phase _next; |
| 109 | 117 |
| 118 /// A map of asset ids to completers for [getInput] requests. |
| 119 /// |
| 120 /// If an asset node is requested before it's available, we put a completer in |
| 121 /// this map to wait for the asset to be generated. If it's not generated, the |
| 122 /// completer should complete to `null`. |
| 123 final _pendingOutputRequests = new Map<AssetId, Completer<AssetNode>>(); |
| 124 |
| 110 /// Returns all currently-available output assets for this phase. | 125 /// Returns all currently-available output assets for this phase. |
| 111 Set<AssetNode> get availableOutputs { | 126 Set<AssetNode> get availableOutputs { |
| 112 return _outputs.values | 127 return _outputs.values |
| 113 .map((output) => output.output) | 128 .map((output) => output.output) |
| 114 .where((node) => node.state.isAvailable) | 129 .where((node) => node.state.isAvailable) |
| 115 .toSet(); | 130 .toSet(); |
| 116 } | 131 } |
| 117 | 132 |
| 118 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, | 133 // TODO(nweiz): Rather than passing the cascade and the phase everywhere, |
| 119 // create an interface that just exposes [getInput]. Emit errors via | 134 // create an interface that just exposes [getInput]. Emit errors via |
| 120 // [AssetNode]s. | 135 // [AssetNode]s. |
| 121 Phase(AssetCascade cascade, String location) | 136 Phase(AssetCascade cascade, String location) |
| 122 : this._(cascade, location, 0); | 137 : this._(cascade, location, 0); |
| 123 | 138 |
| 124 Phase._(this.cascade, this._location, this._index); | 139 Phase._(this.cascade, this._location, this._index, [this._previous]) { |
| 140 // TODO(nweiz): This does O(n^2) work whenever a phase emits an [onDone] |
| 141 // event, since each phase after it has to check each phase before. Find a |
| 142 // better way to do this. |
| 143 for (var phase = this; phase != null; phase = phase._previous) { |
| 144 phase.onDone.listen((_) { |
| 145 if (_isTransitivelyDirty) return; |
| 146 |
| 147 // All the previous phases have finished building. If anyone's still |
| 148 // waiting for outputs, cut off the wait; we won't be generating them, |
| 149 // at least until a source asset changes. |
| 150 for (var completer in _pendingOutputRequests.values) { |
| 151 completer.complete(null); |
| 152 } |
| 153 _pendingOutputRequests.clear(); |
| 154 }); |
| 155 } |
| 156 } |
| 125 | 157 |
| 126 /// Adds a new asset as an input for this phase. | 158 /// Adds a new asset as an input for this phase. |
| 127 /// | 159 /// |
| 128 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase | 160 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase |
| 129 /// will automatically begin determining which transforms can consume it as a | 161 /// will automatically begin determining which transforms can consume it as a |
| 130 /// primary input. The transforms themselves won't be applied until [process] | 162 /// primary input. The transforms themselves won't be applied until [process] |
| 131 /// is called, however. | 163 /// is called, however. |
| 132 /// | 164 /// |
| 133 /// This should only be used for brand-new assets or assets that have been | 165 /// This should only be used for brand-new assets or assets that have been |
| 134 /// removed and re-created. The phase will automatically handle updated assets | 166 /// removed and re-created. The phase will automatically handle updated assets |
| (...skipping 22 matching lines...) Expand all Loading... |
| 157 _onLogPool.add(input.onLog); | 189 _onLogPool.add(input.onLog); |
| 158 input.onDone.listen((_) { | 190 input.onDone.listen((_) { |
| 159 if (!isDirty) _onDoneController.add(null); | 191 if (!isDirty) _onDoneController.add(null); |
| 160 }); | 192 }); |
| 161 | 193 |
| 162 for (var group in _groups.values) { | 194 for (var group in _groups.values) { |
| 163 group.addInput(node); | 195 group.addInput(node); |
| 164 } | 196 } |
| 165 } | 197 } |
| 166 | 198 |
| 199 // TODO(nweiz): If the input is available when this is called, it's |
| 200 // theoretically possible for it to become unavailable between the call and |
| 201 // the return. If it does so, it won't trigger the rebuilding process. To |
| 202 // avoid this, we should have this and the methods it calls take explicit |
| 203 // callbacks, as in [AssetNode.whenAvailable]. |
| 167 /// Gets the asset node for an input [id]. | 204 /// Gets the asset node for an input [id]. |
| 168 /// | 205 /// |
| 169 /// If an input with that ID cannot be found, returns null. | 206 /// If [id] is for a generated or transformed asset, this will wait until it |
| 207 /// has been created and return it. This means that the returned asset will |
| 208 /// always be [AssetState.AVAILABLE]. |
| 209 /// |
| 210 /// If the input cannot be found, returns null. |
| 170 Future<AssetNode> getInput(AssetId id) { | 211 Future<AssetNode> getInput(AssetId id) { |
| 171 return newFuture(() { | 212 return syncFuture(() { |
| 172 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); | 213 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); |
| 173 if (_inputs.containsKey(id)) return _inputs[id].input; | 214 if (_previous != null) return _previous.getOutput(id); |
| 174 return null; | 215 if (!_inputs.containsKey(id)) return null; |
| 216 |
| 217 var input = _inputs[id].input; |
| 218 return input.whenAvailable((_) => input).catchError((error) { |
| 219 if (error is! AssetNotFoundException || error.id != id) throw error; |
| 220 // Retry in case the input was replaced. |
| 221 return getInput(id); |
| 222 }); |
| 175 }); | 223 }); |
| 176 } | 224 } |
| 177 | 225 |
| 178 /// Gets the asset node for an output [id]. | 226 /// Gets the asset node for an output [id]. |
| 179 /// | 227 /// |
| 180 /// If an output with that ID cannot be found, returns null. | 228 /// If [id] is for a generated or transformed asset, this will wait until it |
| 229 /// has been created and return it. This means that the returned asset will |
| 230 /// always be [AssetState.AVAILABLE]. |
| 231 /// |
| 232 /// If the output cannot be found, returns null. |
| 181 Future<AssetNode> getOutput(AssetId id) { | 233 Future<AssetNode> getOutput(AssetId id) { |
| 182 return newFuture(() { | 234 return syncFuture(() { |
| 183 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); | 235 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); |
| 184 if (!_outputs.containsKey(id)) return null; | 236 if (_outputs.containsKey(id)) { |
| 185 var output = _outputs[id].output; | 237 var output = _outputs[id].output; |
| 186 output.force(); | 238 // If the requested output is available, we can just return it. |
| 187 return output; | 239 if (output.state.isAvailable) return output; |
| 240 |
| 241 // If the requested output exists but isn't yet available, wait to see |
| 242 // if it becomes available. If it's removed before becoming available, |
| 243 // try again, since it could be generated again. |
| 244 output.force(); |
| 245 return output.whenAvailable((_) => output).catchError((error) { |
| 246 if (error is! AssetNotFoundException) throw error; |
| 247 return getOutput(id); |
| 248 }); |
| 249 } |
| 250 |
| 251 // If neither this phase nor the previous phases are dirty, the requested |
| 252 // output won't be generated and we can safely return null. |
| 253 if (!_isTransitivelyDirty) return null; |
| 254 |
| 255 // Otherwise, store a completer for the asset node. If it's generated in |
| 256 // the future, we'll complete this completer. |
| 257 var completer = _pendingOutputRequests.putIfAbsent(id, |
| 258 () => new Completer.sync()); |
| 259 return completer.future; |
| 188 }); | 260 }); |
| 189 } | 261 } |
| 190 | 262 |
| 191 /// Set this phase's transformers to [transformers]. | 263 /// Set this phase's transformers to [transformers]. |
| 192 void updateTransformers(Iterable transformers) { | 264 void updateTransformers(Iterable transformers) { |
| 193 var actualTransformers = transformers.where((op) => op is Transformer); | 265 var actualTransformers = transformers.where((op) => op is Transformer); |
| 194 _transformers.clear(); | 266 _transformers.clear(); |
| 195 _transformers.addAll(actualTransformers); | 267 _transformers.addAll(actualTransformers); |
| 196 for (var input in _inputs.values) { | 268 for (var input in _inputs.values) { |
| 197 input.updateTransformers(actualTransformers); | 269 input.updateTransformers(actualTransformers); |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 232 for (var input in _inputs.values) { | 304 for (var input in _inputs.values) { |
| 233 input.forceAllTransforms(); | 305 input.forceAllTransforms(); |
| 234 } | 306 } |
| 235 } | 307 } |
| 236 | 308 |
| 237 /// Add a new phase after this one. | 309 /// Add a new phase after this one. |
| 238 /// | 310 /// |
| 239 /// This may only be called on a phase with no phase following it. | 311 /// This may only be called on a phase with no phase following it. |
| 240 Phase addPhase() { | 312 Phase addPhase() { |
| 241 assert(_next == null); | 313 assert(_next == null); |
| 242 _next = new Phase._(cascade, _location, _index + 1); | 314 _next = new Phase._(cascade, _location, _index + 1, this); |
| 243 for (var output in _outputs.values.toList()) { | 315 for (var output in _outputs.values.toList()) { |
| 244 // Remove [output]'s listeners because now they should get the asset from | 316 // Remove [output]'s listeners because now they should get the asset from |
| 245 // [_next], rather than this phase. Any transforms consuming [output] will | 317 // [_next], rather than this phase. Any transforms consuming [output] will |
| 246 // be re-run and will consume the output from the new final phase. | 318 // be re-run and will consume the output from the new final phase. |
| 247 output.removeListeners(); | 319 output.removeListeners(); |
| 248 } | 320 } |
| 249 return _next; | 321 return _next; |
| 250 } | 322 } |
| 251 | 323 |
| 252 /// Mark this phase as removed. | 324 /// Mark this phase as removed. |
| 253 /// | 325 /// |
| 254 /// This will remove all the phase's outputs and all following phases. | 326 /// This will remove all the phase's outputs and all following phases. |
| 255 void remove() { | 327 void remove() { |
| 328 _previous._next = null; |
| 256 removeFollowing(); | 329 removeFollowing(); |
| 257 for (var input in _inputs.values.toList()) { | 330 for (var input in _inputs.values.toList()) { |
| 258 input.remove(); | 331 input.remove(); |
| 259 } | 332 } |
| 260 for (var group in _groups.values) { | 333 for (var group in _groups.values) { |
| 261 group.remove(); | 334 group.remove(); |
| 262 } | 335 } |
| 263 _onAssetController.close(); | 336 _onAssetController.close(); |
| 264 _onLogPool.close(); | 337 _onLogPool.close(); |
| 265 } | 338 } |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 299 /// Emit [asset] as an output of this phase. | 372 /// Emit [asset] as an output of this phase. |
| 300 /// | 373 /// |
| 301 /// This should be called after [_handleOutput], so that collisions are | 374 /// This should be called after [_handleOutput], so that collisions are |
| 302 /// resolved. | 375 /// resolved. |
| 303 void _emit(AssetNode asset) { | 376 void _emit(AssetNode asset) { |
| 304 if (_next != null) { | 377 if (_next != null) { |
| 305 _next.addInput(asset); | 378 _next.addInput(asset); |
| 306 } else { | 379 } else { |
| 307 _onAssetController.add(asset); | 380 _onAssetController.add(asset); |
| 308 } | 381 } |
| 382 _providePendingAsset(asset); |
| 383 } |
| 384 |
| 385 /// Provide an asset to a pending [getOutput] call. |
| 386 void _providePendingAsset(AssetNode asset) { |
| 387 // If anyone's waiting for this asset, provide it to them. |
| 388 var request = _pendingOutputRequests.remove(asset.id); |
| 389 if (request == null) return; |
| 390 |
| 391 if (asset.state.isAvailable) { |
| 392 request.complete(asset); |
| 393 return; |
| 394 } |
| 395 |
| 396 // A lazy asset may be emitted while still dirty. If so, we wait until it's |
| 397 // either available or removed before trying again to access it. |
| 398 assert(asset.state.isDirty); |
| 399 asset.force(); |
| 400 asset.whenStateChanges().then((state) { |
| 401 if (state.isRemoved) return getOutput(asset.id); |
| 402 return asset; |
| 403 }).then(request.complete).catchError(request.completeError); |
| 309 } | 404 } |
| 310 | 405 |
| 311 String toString() => "phase $_location.$_index"; | 406 String toString() => "phase $_location.$_index"; |
| 312 } | 407 } |
| OLD | NEW |