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

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: 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
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 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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698