Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(785)

Side by Side Diff: pkg/barback/lib/src/phase.dart

Issue 189263002: Make Phase.getInput in barback play nicely with the push model. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: code review Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/barback/lib/src/asset_cascade.dart ('k') | pkg/barback/lib/src/transform_node.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « pkg/barback/lib/src/asset_cascade.dart ('k') | pkg/barback/lib/src/transform_node.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698