Chromium Code Reviews| 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 for (var phase = this; phase != null; phase = phase._previous) { | |
| 141 phase.onDone.listen((_) { | |
| 142 if (_isTransitivelyDirty) return; | |
|
Bob Nystrom
2014/03/06 23:59:22
If we're listening on every phase, why do we need
nweiz
2014/03/07 00:20:19
This listener only fires when one phase finishes i
Bob Nystrom
2014/03/07 00:54:15
Say there are phases A, B, C, and D. A's onDone fi
nweiz
2014/03/07 01:08:27
Ah, I see. I'll add a TODO to find a better way to
| |
| 143 | |
| 144 // All the previous phases have finished building. If anyone's still | |
| 145 // waiting for outputs, cut off the wait; we won't be generating them, | |
| 146 // at least until a source asset changes. | |
| 147 for (var completer in _pendingOutputRequests.values) { | |
| 148 completer.complete(null); | |
| 149 } | |
| 150 _pendingOutputRequests.clear(); | |
| 151 }); | |
| 152 } | |
| 153 } | |
| 125 | 154 |
| 126 /// Adds a new asset as an input for this phase. | 155 /// Adds a new asset as an input for this phase. |
| 127 /// | 156 /// |
| 128 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase | 157 /// [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 | 158 /// will automatically begin determining which transforms can consume it as a |
| 130 /// primary input. The transforms themselves won't be applied until [process] | 159 /// primary input. The transforms themselves won't be applied until [process] |
| 131 /// is called, however. | 160 /// is called, however. |
| 132 /// | 161 /// |
| 133 /// This should only be used for brand-new assets or assets that have been | 162 /// 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 | 163 /// removed and re-created. The phase will automatically handle updated assets |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 157 _onLogPool.add(input.onLog); | 186 _onLogPool.add(input.onLog); |
| 158 input.onDone.listen((_) { | 187 input.onDone.listen((_) { |
| 159 if (!isDirty) _onDoneController.add(null); | 188 if (!isDirty) _onDoneController.add(null); |
| 160 }); | 189 }); |
| 161 | 190 |
| 162 for (var group in _groups.values) { | 191 for (var group in _groups.values) { |
| 163 group.addInput(node); | 192 group.addInput(node); |
| 164 } | 193 } |
| 165 } | 194 } |
| 166 | 195 |
| 196 // TODO(nweiz): If the input is available when this is called, it's | |
| 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 | |
| 199 // avoid this, we should have this and the methods it calls take explicit | |
| 200 // callbacks, as in [AssetNode.whenAvailable]. | |
| 167 /// Gets the asset node for an input [id]. | 201 /// Gets the asset node for an input [id]. |
| 168 /// | 202 /// |
| 169 /// If an input with that ID cannot be found, returns null. | 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 | |
| 205 /// always be [AssetState.AVAILABLE]. | |
| 206 /// | |
| 207 /// If the input cannot be found, returns null. | |
| 170 Future<AssetNode> getInput(AssetId id) { | 208 Future<AssetNode> getInput(AssetId id) { |
| 171 return newFuture(() { | 209 return syncFuture(() { |
| 172 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); | 210 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); |
| 173 if (_inputs.containsKey(id)) return _inputs[id].input; | 211 if (_previous != null) return _previous.getOutput(id); |
| 174 return null; | 212 if (!_inputs.containsKey(id)) return null; |
| 213 | |
| 214 var input = _inputs[id].input; | |
| 215 return input.whenAvailable((_) => input).catchError((error) { | |
| 216 if (error is! AssetNotFoundException || error.id != id) throw error; | |
| 217 // Retry in case the input was replaced. | |
| 218 return getInput(id); | |
| 219 }); | |
| 175 }); | 220 }); |
| 176 } | 221 } |
| 177 | 222 |
| 178 /// Gets the asset node for an output [id]. | 223 /// Gets the asset node for an output [id]. |
| 179 /// | 224 /// |
| 180 /// If an output with that ID cannot be found, returns null. | 225 /// If [id] is for a generated or transformed asset, this will wait until it |
| 226 /// has been created and return it. This means that the returned asset will | |
| 227 /// always be [AssetState.AVAILABLE]. | |
| 228 /// | |
| 229 /// If the output cannot be found, returns null. | |
| 181 Future<AssetNode> getOutput(AssetId id) { | 230 Future<AssetNode> getOutput(AssetId id) { |
| 182 return newFuture(() { | 231 return syncFuture(() { |
| 183 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); | 232 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); |
| 184 if (!_outputs.containsKey(id)) return null; | 233 if (_outputs.containsKey(id)) { |
| 185 var output = _outputs[id].output; | 234 var output = _outputs[id].output; |
| 186 output.force(); | 235 // If the requested output is available, we can just return it. |
| 187 return output; | 236 if (output.state.isAvailable) return output; |
| 237 | |
| 238 // If the requested output exists but isn't yet available, wait to see i f | |
|
Bob Nystrom
2014/03/06 23:59:22
Long line.
nweiz
2014/03/07 00:20:19
Done.
| |
| 239 // it becomes available. If it's removed before becoming available, try | |
| 240 // again, since it could be generated again. | |
| 241 output.force(); | |
| 242 return output.whenAvailable((_) => output).catchError((error) { | |
| 243 if (error is! AssetNotFoundException) throw error; | |
| 244 return getOutput(id); | |
| 245 }); | |
| 246 } | |
| 247 | |
| 248 // If neither this phase nor the previous phases are dirty, the requested | |
| 249 // output won't be generated and we can safely return null. | |
| 250 if (!_isTransitivelyDirty) return null; | |
| 251 | |
| 252 // Otherwise, store a completer for the asset node. If it's generated in | |
| 253 // the future, we'll complete this completer. | |
| 254 var completer = _pendingOutputRequests.putIfAbsent(id, | |
| 255 () => new Completer.sync()); | |
| 256 return completer.future; | |
| 188 }); | 257 }); |
| 189 } | 258 } |
| 190 | 259 |
| 191 /// Set this phase's transformers to [transformers]. | 260 /// Set this phase's transformers to [transformers]. |
| 192 void updateTransformers(Iterable transformers) { | 261 void updateTransformers(Iterable transformers) { |
| 193 var actualTransformers = transformers.where((op) => op is Transformer); | 262 var actualTransformers = transformers.where((op) => op is Transformer); |
| 194 _transformers.clear(); | 263 _transformers.clear(); |
| 195 _transformers.addAll(actualTransformers); | 264 _transformers.addAll(actualTransformers); |
| 196 for (var input in _inputs.values) { | 265 for (var input in _inputs.values) { |
| 197 input.updateTransformers(actualTransformers); | 266 input.updateTransformers(actualTransformers); |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 232 for (var input in _inputs.values) { | 301 for (var input in _inputs.values) { |
| 233 input.forceAllTransforms(); | 302 input.forceAllTransforms(); |
| 234 } | 303 } |
| 235 } | 304 } |
| 236 | 305 |
| 237 /// Add a new phase after this one. | 306 /// Add a new phase after this one. |
| 238 /// | 307 /// |
| 239 /// This may only be called on a phase with no phase following it. | 308 /// This may only be called on a phase with no phase following it. |
| 240 Phase addPhase() { | 309 Phase addPhase() { |
| 241 assert(_next == null); | 310 assert(_next == null); |
| 242 _next = new Phase._(cascade, _location, _index + 1); | 311 _next = new Phase._(cascade, _location, _index + 1, this); |
| 243 for (var output in _outputs.values.toList()) { | 312 for (var output in _outputs.values.toList()) { |
| 244 // Remove [output]'s listeners because now they should get the asset from | 313 // Remove [output]'s listeners because now they should get the asset from |
| 245 // [_next], rather than this phase. Any transforms consuming [output] will | 314 // [_next], rather than this phase. Any transforms consuming [output] will |
| 246 // be re-run and will consume the output from the new final phase. | 315 // be re-run and will consume the output from the new final phase. |
| 247 output.removeListeners(); | 316 output.removeListeners(); |
| 248 } | 317 } |
| 249 return _next; | 318 return _next; |
| 250 } | 319 } |
| 251 | 320 |
| 252 /// Mark this phase as removed. | 321 /// Mark this phase as removed. |
| 253 /// | 322 /// |
| 254 /// This will remove all the phase's outputs and all following phases. | 323 /// This will remove all the phase's outputs and all following phases. |
| 255 void remove() { | 324 void remove() { |
| 325 _previous._next = null; | |
| 256 removeFollowing(); | 326 removeFollowing(); |
| 257 for (var input in _inputs.values.toList()) { | 327 for (var input in _inputs.values.toList()) { |
| 258 input.remove(); | 328 input.remove(); |
| 259 } | 329 } |
| 260 for (var group in _groups.values) { | 330 for (var group in _groups.values) { |
| 261 group.remove(); | 331 group.remove(); |
| 262 } | 332 } |
| 263 _onAssetController.close(); | 333 _onAssetController.close(); |
| 264 _onLogPool.close(); | 334 _onLogPool.close(); |
| 265 } | 335 } |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 299 /// Emit [asset] as an output of this phase. | 369 /// Emit [asset] as an output of this phase. |
| 300 /// | 370 /// |
| 301 /// This should be called after [_handleOutput], so that collisions are | 371 /// This should be called after [_handleOutput], so that collisions are |
| 302 /// resolved. | 372 /// resolved. |
| 303 void _emit(AssetNode asset) { | 373 void _emit(AssetNode asset) { |
| 304 if (_next != null) { | 374 if (_next != null) { |
| 305 _next.addInput(asset); | 375 _next.addInput(asset); |
| 306 } else { | 376 } else { |
| 307 _onAssetController.add(asset); | 377 _onAssetController.add(asset); |
| 308 } | 378 } |
| 379 _providePendingAsset(asset); | |
| 380 } | |
| 381 | |
| 382 /// Provide an asset to a pending [getOutput] call. | |
| 383 void _providePendingAsset(AssetNode asset) { | |
| 384 // If anyone's waiting for this asset, provide it to them. | |
| 385 var request = _pendingOutputRequests.remove(asset.id); | |
| 386 if (request == null) return; | |
| 387 | |
| 388 if (asset.state.isAvailable) { | |
| 389 request.complete(asset); | |
| 390 return; | |
| 391 } | |
| 392 | |
| 393 // A lazy asset may be emitted while still dirty. If so, we wait until it's | |
| 394 // either available or removed before trying again to access it. | |
| 395 assert(asset.state.isDirty); | |
| 396 asset.force(); | |
| 397 asset.whenStateChanges().then((state) { | |
| 398 if (state.isRemoved) return getOutput(asset.id); | |
| 399 return asset; | |
| 400 }).then(request.complete).catchError(request.completeError); | |
| 309 } | 401 } |
| 310 | 402 |
| 311 String toString() => "phase $_location.$_index"; | 403 String toString() => "phase $_location.$_index"; |
| 312 } | 404 } |
| OLD | NEW |