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

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

Issue 187263003: Move Barback to a more thoroughly push-based 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.asset_cascade; 5 library barback.asset_cascade;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:collection'; 8 import 'dart:collection';
9 9
10 import 'asset.dart'; 10 import 'asset.dart';
11 import 'asset_id.dart'; 11 import 'asset_id.dart';
12 import 'asset_node.dart'; 12 import 'asset_node.dart';
13 import 'asset_set.dart'; 13 import 'asset_set.dart';
14 import 'log.dart'; 14 import 'log.dart';
15 import 'build_result.dart';
16 import 'cancelable_future.dart'; 15 import 'cancelable_future.dart';
17 import 'errors.dart'; 16 import 'errors.dart';
18 import 'package_graph.dart'; 17 import 'package_graph.dart';
19 import 'phase.dart'; 18 import 'phase.dart';
20 import 'stream_pool.dart'; 19 import 'stream_pool.dart';
21 import 'transformer.dart'; 20 import 'transformer.dart';
22 import 'utils.dart';
23 21
24 /// The asset cascade for an individual package. 22 /// The asset cascade for an individual package.
25 /// 23 ///
26 /// This keeps track of which [Transformer]s are applied to which assets, and 24 /// This keeps track of which [Transformer]s are applied to which assets, and
27 /// re-runs those transformers when their dependencies change. The transformed 25 /// re-runs those transformers when their dependencies change. The transformed
28 /// asset nodes are accessible via [getAssetNode]. 26 /// asset nodes are accessible via [getAssetNode].
29 /// 27 ///
30 /// A cascade consists of one or more [Phases], each of which has one or more 28 /// A cascade consists of one or more [Phases], each of which has one or more
31 /// [Transformer]s that run in parallel, potentially on the same inputs. The 29 /// [Transformer]s that run in parallel, potentially on the same inputs. The
32 /// inputs of the first phase are the source assets for this cascade's package. 30 /// inputs of the first phase are the source assets for this cascade's package.
(...skipping 13 matching lines...) Expand all
46 44
47 /// Futures for source assets that are currently being loaded. 45 /// Futures for source assets that are currently being loaded.
48 /// 46 ///
49 /// These futures are cancelable so that if an asset is updated after a load 47 /// These futures are cancelable so that if an asset is updated after a load
50 /// has been kicked off, the previous load can be ignored in favor of a new 48 /// has been kicked off, the previous load can be ignored in favor of a new
51 /// one. 49 /// one.
52 final _loadingSources = new Map<AssetId, CancelableFuture<Asset>>(); 50 final _loadingSources = new Map<AssetId, CancelableFuture<Asset>>();
53 51
54 final _phases = <Phase>[]; 52 final _phases = <Phase>[];
55 53
56 /// A stream that emits a [BuildResult] each time the build is completed,
57 /// whether or not it succeeded.
58 ///
59 /// If an unexpected error in barback itself occurs, it will be emitted
60 /// through this stream's error channel.
61 Stream<BuildResult> get results => _resultsController.stream;
62 final _resultsController = new StreamController<BuildResult>.broadcast();
63
64 /// A stream that emits any errors from the cascade or the transformers. 54 /// A stream that emits any errors from the cascade or the transformers.
65 /// 55 ///
66 /// This emits errors as they're detected. If an error occurs in one part of 56 /// This emits errors as they're detected. If an error occurs in one part of
67 /// the cascade, unrelated parts will continue building. 57 /// the cascade, unrelated parts will continue building.
68 ///
69 /// This will not emit programming errors from barback itself. Those will be
70 /// emitted through the [results] stream's error channel.
71 Stream<BarbackException> get errors => _errorsController.stream; 58 Stream<BarbackException> get errors => _errorsController.stream;
72 final _errorsController = new StreamController<BarbackException>.broadcast(); 59 final _errorsController =
73 60 new StreamController<BarbackException>.broadcast(sync: true);
74 /// A stream that emits an event whenever this cascade becomes dirty.
75 ///
76 /// After this stream emits an event, [results] will emit an event once the
77 /// cascade is no longer dirty.
78 ///
79 /// This may emit events when the cascade was already dirty. Events are
80 /// emitted synchronously to ensure that the dirty state is thoroughly
81 /// propagated as soon as any assets are changed.
82 Stream get onDirty => _onDirtyPool.stream;
83 final _onDirtyPool = new StreamPool.broadcast();
84
85 /// A controller whose stream feeds into [_onDirtyPool].
86 final _onDirtyController = new StreamController.broadcast(sync: true);
87 61
88 /// A stream that emits an event whenever any transforms in this cascade logs 62 /// A stream that emits an event whenever any transforms in this cascade logs
89 /// an entry. 63 /// an entry.
90 Stream<LogEntry> get onLog => _onLogPool.stream; 64 Stream<LogEntry> get onLog => _onLogPool.stream;
91 final _onLogPool = new StreamPool<LogEntry>.broadcast(); 65 final _onLogPool = new StreamPool<LogEntry>.broadcast();
92 66
93 /// The errors that have occurred since the current build started. 67 /// Whether [this] is dirty and still has more processing to do.
68 bool get isDirty => _phases.any((phase) => phase.isDirty);
69
70 /// A stream that emits an event whenever [this] is no longer dirty.
94 /// 71 ///
95 /// This will be empty if no build is occurring. 72 /// This is synchronous in order to guarantee that it will emit an event as
96 Queue<BarbackException> _accumulatedErrors; 73 /// soon as [isDirty] flips from `true` to `false`.
97 74 Stream get onDone => _onDoneController.stream;
98 /// The number of errors that have been logged since the current build 75 final _onDoneController = new StreamController<bool>.broadcast(sync: true);
Bob Nystrom 2014/03/05 22:13:25 Ditch the <bool>.
nweiz 2014/03/06 00:29:08 Done.
99 /// started.
100 int _numLogErrors;
101
102 /// A future that completes when the currently running build process finishes.
103 ///
104 /// If no build it in progress, is `null`.
105 Future _processDone;
106
107 /// Whether any source assets have been updated or removed since processing
108 /// last began.
109 var _newChanges = false;
110 76
111 /// Returns all currently-available output assets from this cascade. 77 /// Returns all currently-available output assets from this cascade.
112 AssetSet get availableOutputs => 78 AssetSet get availableOutputs =>
113 new AssetSet.from(_phases.last.availableOutputs.map((node) => node.asset)); 79 new AssetSet.from(_phases.last.availableOutputs.map((node) => node.asset));
114 80
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, Queue<Completer<AssetNode>>>();
87
115 /// Creates a new [AssetCascade]. 88 /// Creates a new [AssetCascade].
116 /// 89 ///
117 /// It loads source assets within [package] using [provider]. 90 /// It loads source assets within [package] using [provider].
118 AssetCascade(this.graph, this.package) { 91 AssetCascade(this.graph, this.package) {
119 _onDirtyPool.add(_onDirtyController.stream); 92 _addPhase(new Phase(this, package));
120 _addPhase(new Phase(this, [], package));
121
122 // Keep track of logged errors so we can know that the build failed.
123 onLog.listen((entry) {
124 if (entry.level == LogLevel.ERROR) {
125 // TODO(nweiz): keep track of stack chain.
126 _accumulatedErrors.add(
127 new TransformerException(entry.transform, entry.message, null));
128 }
129 });
130 } 93 }
131 94
132 /// Gets the asset identified by [id]. 95 /// Gets the asset identified by [id].
133 /// 96 ///
134 /// If [id] is for a generated or transformed asset, this will wait until it 97 /// If [id] is for a generated or transformed asset, this will wait until it
135 /// has been created and return it. This means that the returned asset will 98 /// has been created and return it. This means that the returned asset will
136 /// always be [AssetState.AVAILABLE]. 99 /// always be [AssetState.AVAILABLE].
137 /// 100 ///
138 /// If the asset cannot be found, returns null. 101 /// If the asset cannot be found, returns null.
139 Future<AssetNode> getAssetNode(AssetId id) { 102 Future<AssetNode> getAssetNode(AssetId id) {
140 assert(id.package == package); 103 assert(id.package == package);
141 104
142 // TODO(rnystrom): Waiting for the entire build to complete is unnecessary 105 // TODO(rnystrom): Waiting for the entire build to complete is unnecessary
143 // in some cases. Should optimize: 106 // in some cases. Should optimize:
144 // * [id] may be generated before the compilation is finished. We should 107 // * [id] may be generated before the compilation is finished. We should
145 // be able to quickly check whether there are any more in-place 108 // be able to quickly check whether there are any more in-place
146 // transformations that can be run on it. If not, we can return it early. 109 // transformations that can be run on it. If not, we can return it early.
147 // * If [id] has never been generated and all active transformers provide 110 // * If [id] has never been generated and all active transformers provide
148 // metadata about the file names of assets it can emit, we can prove that 111 // metadata about the file names of assets it can emit, we can prove that
149 // none of them can emit [id] and fail early. 112 // none of them can emit [id] and fail early.
150 return _phases.last.getOutput(id).then((node) { 113 return _phases.last.getOutput(id).then((node) {
151 // If the requested asset is available, we can just return it. 114 if (node != null) {
152 if (node != null && node.state.isAvailable) return node; 115 // If the requested asset is available, we can just return it.
116 if (node.state.isAvailable) return node;
153 117
154 if (_processDone != null) { 118 // If the requested asset exists but isn't yet available, wait to see if
155 // If there's a build running, that build might generate the asset, so 119 // it becomes available. If it's removed before becoming available, try
156 // we wait for it to complete and then try again. 120 // again, since it could be generated again.
157 return _processDone.then((_) => getAssetNode(id)); 121 node.force();
122 return node.whenAvailable((_) => node).catchError((error) {
123 if (error is! AssetNotFoundException) throw error;
124 return getAssetNode(id);
125 });
158 } 126 }
159 127
160 // If the asset hasn't been built and nothing is building now, the asset 128 // If the cascade isn't dirty, the phase won't generate the requested
161 // won't be generated, so we return null. 129 // asset in the future.
162 return null; 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 completers = _pendingAssetRequests.putIfAbsent(id, () => new Queue());
Bob Nystrom 2014/03/05 22:13:25 Why do you need a queue here? Can you just reuse t
nweiz 2014/03/06 00:29:08 Done.
135 completers.add(new Completer.sync());
136 return completers.last.future;
163 }); 137 });
164 } 138 }
165 139
166 /// Adds [sources] to the graph's known set of source assets. 140 /// Adds [sources] to the graph's known set of source assets.
167 /// 141 ///
168 /// Begins applying any transforms that can consume any of the sources. If a 142 /// Begins applying any transforms that can consume any of the sources. If a
169 /// given source is already known, it is considered modified and all 143 /// given source is already known, it is considered modified and all
170 /// transforms that use it will be re-applied. 144 /// transforms that use it will be re-applied.
171 void updateSources(Iterable<AssetId> sources) { 145 void updateSources(Iterable<AssetId> sources) {
172 for (var id in sources) { 146 for (var id in sources) {
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
215 /// [Transformer]s or [TransformerGroup]s. 189 /// [Transformer]s or [TransformerGroup]s.
216 void updateTransformers(Iterable<Iterable> transformersIterable) { 190 void updateTransformers(Iterable<Iterable> transformersIterable) {
217 var transformers = transformersIterable.toList(); 191 var transformers = transformersIterable.toList();
218 192
219 for (var i = 0; i < transformers.length; i++) { 193 for (var i = 0; i < transformers.length; i++) {
220 if (_phases.length > i) { 194 if (_phases.length > i) {
221 _phases[i].updateTransformers(transformers[i]); 195 _phases[i].updateTransformers(transformers[i]);
222 continue; 196 continue;
223 } 197 }
224 198
225 _addPhase(_phases.last.addPhase(transformers[i])); 199 var phase = _phases.last.addPhase();
200 _addPhase(phase);
201 phase.updateTransformers(transformers[i]);
226 } 202 }
227 203
228 if (transformers.length == 0) { 204 if (transformers.length == 0) {
229 _phases.last.updateTransformers([]); 205 _phases.last.updateTransformers([]);
230 } else if (transformers.length < _phases.length) { 206 } else if (transformers.length < _phases.length) {
231 _phases[transformers.length - 1].removeFollowing(); 207 _phases[transformers.length - 1].removeFollowing();
232 _phases.removeRange(transformers.length, _phases.length); 208 _phases.removeRange(transformers.length, _phases.length);
233 } 209 }
234 } 210 }
235 211
236 /// Force all [LazyTransformer]s' transforms in this cascade to begin 212 /// Force all [LazyTransformer]s' transforms in this cascade to begin
237 /// producing concrete assets. 213 /// producing concrete assets.
238 void forceAllTransforms() { 214 void forceAllTransforms() {
239 for (var phase in _phases) { 215 for (var phase in _phases) {
240 phase.forceAllTransforms(); 216 phase.forceAllTransforms();
241 } 217 }
242 } 218 }
243 219
244 void reportError(BarbackException error) { 220 void reportError(BarbackException error) {
245 _accumulatedErrors.add(error);
246 _errorsController.add(error); 221 _errorsController.add(error);
247 } 222 }
248 223
249 /// Add [phase] to the end of [_phases] and watch its [onDirty] stream. 224 /// Add [phase] to the end of [_phases] and watch its streams.
250 void _addPhase(Phase phase) { 225 void _addPhase(Phase phase) {
251 _onDirtyPool.add(phase.onDirty);
252 _onLogPool.add(phase.onLog); 226 _onLogPool.add(phase.onLog);
253 phase.onDirty.listen((_) { 227 phase.onAsset.listen(_providePendingAsset);
254 _newChanges = true; 228
255 _waitForProcess(); 229 phase.onDone.listen((_) {
230 if (isDirty) return;
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 _pendingAssetRequests.forEach((_, completers) {
236 for (var completer in completers) {
237 completer.complete(null);
238 }
239 });
240 _pendingAssetRequests.clear();
241 _onDoneController.add(null);
256 }); 242 });
243
257 _phases.add(phase); 244 _phases.add(phase);
258 } 245 }
259 246
260 /// Starts the build process asynchronously if there is work to be done. 247 /// Provide an asset to a pending [getAssetNode] call.
261 /// 248 void _providePendingAsset(AssetNode asset) {
262 /// Returns a future that completes with the background processing is done. 249 // If anyone's waiting for this asset, provide it to them.
263 /// If there is no work to do, returns a future that completes immediately. 250 var requests = _pendingAssetRequests.remove(asset.id);
264 /// All errors that occur during processing will be caught (and routed to the 251 if (requests == null) return;
265 /// [results] stream) before they get to the returned future, so it is safe
266 /// to discard it.
267 Future _waitForProcess() {
268 if (_processDone != null) return _processDone;
269 252
270 _accumulatedErrors = new Queue(); 253 for (var request in requests) {
271 _numLogErrors = 0; 254 if (asset.state.isAvailable) {
Bob Nystrom 2014/03/05 22:13:25 This seems weird to me. Doesn't it make more sense
nweiz 2014/03/06 00:29:08 N/A since I got rid of the queue.
272 return _processDone = _process().then((_) { 255 request.complete(asset);
273 // Report the build completion. 256 continue;
274 // TODO(rnystrom): Put some useful data in here.
275 _resultsController.add(
276 new BuildResult(_accumulatedErrors));
277 _processDone = null;
278 _accumulatedErrors = null;
279 });
280 }
281
282 /// Starts the background processing.
283 ///
284 /// Returns a future that completes when all assets have been processed.
285 Future _process() {
286 _newChanges = false;
287 return newFuture(() {
288 // Find the first phase that has work to do and do it.
289 var future;
290 for (var phase in _phases) {
291 future = phase.process();
292 if (future != null) break;
293 } 257 }
294 258
295 // If all phases are done and no new updates have come in, we're done. 259 // A lazy asset may be emitted while still dirty. If so, we wait until
296 if (future == null) { 260 // it's either available or removed before trying again to access it. We
297 // If changes have come in, start over. 261 // retry the entire [getAsset] process because the state of the graph may
298 if (_newChanges) return _process(); 262 // have changed dramatically by the time it's available.
299 263 assert(asset.state.isDirty);
300 // Otherwise, everything is done. 264 asset.force();
301 return null; 265 asset.whenStateChanges()
302 } 266 .then((_) => getAssetNode(asset.id))
303 267 .then(request.complete)
304 // Process that phase and then loop onto the next. 268 .catchError(request.completeError);
305 return future.then((_) => _process()); 269 }
306 });
307 } 270 }
308 271
309 String toString() => "cascade for $package"; 272 String toString() => "cascade for $package";
310 } 273 }
OLDNEW
« no previous file with comments | « no previous file | pkg/barback/lib/src/asset_node.dart » ('j') | pkg/barback/lib/src/group_runner.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698