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

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

Issue 21275003: Move barback to a more event-based model. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 4 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 | « no previous file | pkg/barback/lib/src/asset_node.dart » ('j') | pkg/barback/lib/src/asset_node.dart » ('J')
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.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 'package:stack_trace/stack_trace.dart'; 10 import 'package:stack_trace/stack_trace.dart';
11 11
12 import 'asset.dart'; 12 import 'asset.dart';
13 import 'asset_id.dart'; 13 import 'asset_id.dart';
14 import 'asset_set.dart'; 14 import 'asset_node.dart';
15 import 'cancelable_future.dart';
15 import 'errors.dart'; 16 import 'errors.dart';
16 import 'change_batch.dart'; 17 import 'change_batch.dart';
17 import 'package_graph.dart'; 18 import 'package_graph.dart';
18 import 'phase.dart'; 19 import 'phase.dart';
19 import 'transformer.dart'; 20 import 'transformer.dart';
20 import 'utils.dart'; 21 import 'utils.dart';
21 22
22 /// The asset cascade for an individual package. 23 /// The asset cascade for an individual package.
23 /// 24 ///
24 /// This keeps track of which [Transformer]s are applied to which assets, and 25 /// This keeps track of which [Transformer]s are applied to which assets, and
25 /// re-runs those transformers when their dependencies change. The transformed 26 /// re-runs those transformers when their dependencies change. The transformed
26 /// assets are accessible via [getAssetById]. 27 /// assets are accessible via [getAssetById].
27 /// 28 ///
28 /// A cascade consists of one or more [Phases], each of which has one or more 29 /// A cascade consists of one or more [Phases], each of which has one or more
29 /// [Transformer]s that run in parallel, potentially on the same inputs. The 30 /// [Transformer]s that run in parallel, potentially on the same inputs. The
30 /// inputs of the first phase are the source assets for this cascade's package. 31 /// inputs of the first phase are the source assets for this cascade's package.
31 /// The inputs of each successive phase are the outputs of the previous phase, 32 /// The inputs of each successive phase are the outputs of the previous phase,
32 /// as well as any assets that haven't yet been transformed. 33 /// as well as any assets that haven't yet been transformed.
33 class AssetCascade { 34 class AssetCascade {
34 /// The name of the package whose assets are managed. 35 /// The name of the package whose assets are managed.
35 final String package; 36 final String package;
36 37
37 /// The [PackageGraph] that tracks all [AssetCascade]s for all dependencies of 38 /// The [PackageGraph] that tracks all [AssetCascade]s for all dependencies of
38 /// the current app. 39 /// the current app.
39 final PackageGraph _graph; 40 final PackageGraph _graph;
40 41
42 /// The controllers for the [AssetNode]s that provide information about this
43 /// cascade's package's source assets, indexed by id.
Bob Nystrom 2013/07/31 20:05:41 Remove "indexed by id".
nweiz 2013/07/31 22:47:53 Done.
44 final _sourceControllerMap = new Map<AssetId, AssetNodeController>();
45
46 /// Futures for source assets that are currently being loaded, indexed by id.
47 ///
48 /// These futures are cancelable so that if an asset is updated after a load
Bob Nystrom 2013/07/31 20:05:41 This implies that a PackageProvider's implementati
nweiz 2013/07/31 22:47:53 Done.
49 /// has been kicked off, the previous load can be ignored in favor of a new
50 /// one.
51 final _loadingSources = new Map<AssetId, CancelableFuture<Asset>>();
52
41 final _phases = <Phase>[]; 53 final _phases = <Phase>[];
42 54
43 /// A stream that emits a [BuildResult] each time the build is completed, 55 /// A stream that emits a [BuildResult] each time the build is completed,
44 /// whether or not it succeeded. 56 /// whether or not it succeeded.
45 /// 57 ///
46 /// If an unexpected error in barback itself occurs, it will be emitted 58 /// If an unexpected error in barback itself occurs, it will be emitted
47 /// through this stream's error channel. 59 /// through this stream's error channel.
48 Stream<BuildResult> get results => _resultsController.stream; 60 Stream<BuildResult> get results => _resultsController.stream;
49 final _resultsController = new StreamController<BuildResult>.broadcast(); 61 final _resultsController = new StreamController<BuildResult>.broadcast();
50 62
(...skipping 10 matching lines...) Expand all
61 /// The errors that have occurred since the current build started. 73 /// The errors that have occurred since the current build started.
62 /// 74 ///
63 /// This will be empty if no build is occurring. 75 /// This will be empty if no build is occurring.
64 Queue _accumulatedErrors; 76 Queue _accumulatedErrors;
65 77
66 /// A future that completes when the currently running build process finishes. 78 /// A future that completes when the currently running build process finishes.
67 /// 79 ///
68 /// If no build it in progress, is `null`. 80 /// If no build it in progress, is `null`.
69 Future _processDone; 81 Future _processDone;
70 82
71 ChangeBatch _sourceChanges; 83 /// Whether any source assets have been updated or removed since processing
84 /// last began.
85 var _newChanges = false;
72 86
73 /// Creates a new [AssetCascade]. 87 /// Creates a new [AssetCascade].
74 /// 88 ///
75 /// It loads source assets within [package] using [provider] and then uses 89 /// It loads source assets within [package] using [provider] and then uses
76 /// [transformerPhases] to generate output files from them. 90 /// [transformerPhases] to generate output files from them.
77 //TODO(rnystrom): Better way of specifying transformers and their ordering. 91 //TODO(rnystrom): Better way of specifying transformers and their ordering.
78 AssetCascade(this._graph, this.package, 92 AssetCascade(this._graph, this.package,
79 Iterable<Iterable<Transformer>> transformerPhases) { 93 Iterable<Iterable<Transformer>> transformerPhases) {
80 // Flatten the phases to a list so we can traverse backwards to wire up 94 // Flatten the phases to a list so we can traverse backwards to wire up
81 // each phase to its next. 95 // each phase to its next.
(...skipping 17 matching lines...) Expand all
99 /// it has been created and return it. If the asset cannot be found, throws 113 /// it has been created and return it. If the asset cannot be found, throws
100 /// [AssetNotFoundException]. 114 /// [AssetNotFoundException].
101 Future<Asset> getAssetById(AssetId id) { 115 Future<Asset> getAssetById(AssetId id) {
102 assert(id.package == package); 116 assert(id.package == package);
103 117
104 // TODO(rnystrom): Waiting for the entire build to complete is unnecessary 118 // TODO(rnystrom): Waiting for the entire build to complete is unnecessary
105 // in some cases. Should optimize: 119 // in some cases. Should optimize:
106 // * [id] may be generated before the compilation is finished. We should 120 // * [id] may be generated before the compilation is finished. We should
107 // be able to quickly check whether there are any more in-place 121 // be able to quickly check whether there are any more in-place
108 // transformations that can be run on it. If not, we can return it early. 122 // transformations that can be run on it. If not, we can return it early.
109 // * If everything is compiled, something that didn't output [id] is
110 // dirtied, and then [id] is requested, we can return it immediately,
111 // since anything overwriting it at that point is an error.
112 // * If [id] has never been generated and all active transformers provide 123 // * If [id] has never been generated and all active transformers provide
113 // metadata about the file names of assets it can emit, we can prove that 124 // metadata about the file names of assets it can emit, we can prove that
114 // none of them can emit [id] and fail early. 125 // none of them can emit [id] and fail early.
115 return (_processDone == null ? new Future.value() : _processDone).then((_) { 126 return newFuture(() {
116 // Each phase's inputs are the outputs of the previous phase. Find the 127 var node = _getAssetNode(id);
117 // last phase that contains the asset. Since the last phase has no
118 // transformers, this will find the latest output for that id.
119 128
120 // TODO(rnystrom): Currently does not omit assets that are actually used 129 // If the requested asset is available, we can just return it.
121 // as inputs for transformers. This means you can request and get an 130 if (node != null) return node.asset;
122 // asset that should be "consumed" because it's used to generate the 131
123 // real asset you care about. Need to figure out how we want to handle 132 // If there's a build running, that build might generate the asset, so we
124 // that and what use cases there are related to it. 133 // wait for it to complete and then try again.
125 for (var i = _phases.length - 1; i >= 0; i--) { 134 if (_processDone != null) {
126 var node = _phases[i].inputs[id]; 135 return _processDone.then((_) => getAssetById(id));
127 if (node != null) {
128 // By the time we get here, the asset should have been built.
129 assert(node.asset != null);
130 return node.asset;
131 }
132 } 136 }
133 137
134 // Couldn't find it. 138 // If the asset hasn't been built and nothing is building now, the asset
139 // won't be generated, so we throw an error.
135 throw new AssetNotFoundException(id); 140 throw new AssetNotFoundException(id);
136 }); 141 });
137 } 142 }
138 143
144 // Returns the post-transformation asset node for [id], if one is available.
145 //
146 // This will only return a node that has an asset available, and only if that
147 // node is guaranteed not to be consumed by any transforms. If the phase is
148 // still working to figure out if a node will be consumed by a transformer,
149 // that node won't be returned.
150 AssetNode _getAssetNode(AssetId id) {
151 // Each phase's inputs are the outputs of the previous phase. Find the last
152 // phase that contains the asset. Since the last phase has no transformers,
153 // this will find the latest output for that id.
154 for (var i = _phases.length - 1; i >= 0; i--) {
155 var node = _phases[i].getUnconsumedInput(id);
156 if (node != null) return node;
157 }
158
159 return null;
160 }
161
139 /// Adds [sources] to the graph's known set of source assets. 162 /// Adds [sources] to the graph's known set of source assets.
140 /// 163 ///
141 /// Begins applying any transforms that can consume any of the sources. If a 164 /// Begins applying any transforms that can consume any of the sources. If a
142 /// given source is already known, it is considered modified and all 165 /// given source is already known, it is considered modified and all
143 /// transforms that use it will be re-applied. 166 /// transforms that use it will be re-applied.
144 void updateSources(Iterable<AssetId> sources) { 167 void updateSources(Iterable<AssetId> sources) {
145 if (_sourceChanges == null) _sourceChanges = new ChangeBatch(); 168 _newChanges = true;
146 assert(sources.every((id) => id.package == package)); 169
147 _sourceChanges.update(sources); 170 sources.forEach((id) {
Bob Nystrom 2013/07/31 20:05:41 Nit, but since this is imperative code, I think a
nweiz 2013/07/31 22:47:53 Done. Not sure why I wrote [forEach]. Too much Rub
171 var controller = _sourceControllerMap[id];
172 if (controller != null) {
173 controller.setDirty();
174 } else {
175 _sourceControllerMap[id] = new AssetNodeController(id);
176 _phases.first.addInput(_sourceControllerMap[id].node);
177 }
178
179 // If this source was already loading, cancel the old load, since it may
180 // return out-of-date contents for the asset.
181 if (_loadingSources.containsKey(id)) _loadingSources[id].cancel();
182
183 _loadingSources[id] =
184 new CancelableFuture<Asset>(_graph.provider.getAsset(id));
185 _loadingSources[id].whenComplete(() {
186 _loadingSources.remove(id);
187 }).then((asset) {
188 var controller = _sourceControllerMap[id].setAvailable(asset);
189 }).catchError((error) {
190 reportError(error);
191
192 // TODO(nweiz): propagate error information through asset nodes.
193 _sourceControllerMap.remove(id).setRemoved();
194 });
195 });
148 196
149 _waitForProcess(); 197 _waitForProcess();
150 } 198 }
151 199
152 /// Removes [removed] from the graph's known set of source assets. 200 /// Removes [removed] from the graph's known set of source assets.
153 void removeSources(Iterable<AssetId> removed) { 201 void removeSources(Iterable<AssetId> removed) {
154 if (_sourceChanges == null) _sourceChanges = new ChangeBatch(); 202 _newChanges = true;
155 assert(removed.every((id) => id.package == package)); 203
156 _sourceChanges.remove(removed); 204 removed.forEach((id) {
205 // If the source was being loaded, cancel that load.
206 if (_loadingSources.containsKey(id)) _loadingSources.remove(id).cancel();
207
208 var controller = _sourceControllerMap.remove(id);
209 // Don't choke if an id is double-removed for some reason.
210 if (controller != null) controller.setRemoved();
211 });
157 212
158 _waitForProcess(); 213 _waitForProcess();
159 } 214 }
160 215
161 void reportError(error) { 216 void reportError(error) {
162 _accumulatedErrors.add(error); 217 _accumulatedErrors.add(error);
163 _errorsController.add(error); 218 _errorsController.add(error);
164 } 219 }
165 220
166 /// Starts the build process asynchronously if there is work to be done. 221 /// Starts the build process asynchronously if there is work to be done.
(...skipping 24 matching lines...) Expand all
191 }).whenComplete(() { 246 }).whenComplete(() {
192 _processDone = null; 247 _processDone = null;
193 _accumulatedErrors = null; 248 _accumulatedErrors = null;
194 }); 249 });
195 } 250 }
196 251
197 /// Starts the background processing. 252 /// Starts the background processing.
198 /// 253 ///
199 /// Returns a future that completes when all assets have been processed. 254 /// Returns a future that completes when all assets have been processed.
200 Future _process() { 255 Future _process() {
201 return _processSourceChanges().then((_) { 256 _newChanges = false;
257 return newFuture(() {
202 // Find the first phase that has work to do and do it. 258 // Find the first phase that has work to do and do it.
203 var future; 259 var future;
204 for (var phase in _phases) { 260 for (var phase in _phases) {
205 future = phase.process(); 261 future = phase.process();
206 if (future != null) break; 262 if (future != null) break;
207 } 263 }
208 264
209 // If all phases are done and no new updates have come in, we're done. 265 // If all phases are done and no new updates have come in, we're done.
210 if (future == null) { 266 if (future == null) {
211 // If changes have come in, start over. 267 // If changes have come in, start over.
212 if (_sourceChanges != null) return _process(); 268 if (_newChanges) return _process();
213 269
214 // Otherwise, everything is done. 270 // Otherwise, everything is done.
215 return; 271 return;
216 } 272 }
217 273
218 // Process that phase and then loop onto the next. 274 // Process that phase and then loop onto the next.
219 return future.then((_) => _process()); 275 return future.then((_) => _process());
220 }); 276 });
221 } 277 }
222
223 /// Processes the current batch of changes to source assets.
224 Future _processSourceChanges() {
225 // Always pump the event loop. This ensures a bunch of synchronous source
226 // changes are processed in a single batch even when the first one starts
227 // the build process.
228 return newFuture(() {
229 if (_sourceChanges == null) return null;
230
231 // Take the current batch to ensure it doesn't get added to while we're
232 // processing it.
233 var changes = _sourceChanges;
234 _sourceChanges = null;
235
236 var updated = new AssetSet();
237 var futures = [];
238 for (var id in changes.updated) {
239 // TODO(rnystrom): Catch all errors from provider and route to results.
240 futures.add(_graph.provider.getAsset(id).then((asset) {
241 updated.add(asset);
242 }).catchError((error) {
243 if (error is AssetNotFoundException) {
244 // Handle missing asset errors like regular missing assets.
245 reportError(error);
246 } else {
247 // It's an unexpected error, so rethrow it.
248 throw error;
249 }
250 }));
251 }
252
253 return Future.wait(futures).then((_) {
254 _phases.first.updateInputs(updated, changes.removed);
255 });
256 });
257 }
258 } 278 }
259 279
260 /// An event indicating that the cascade has finished building all assets. 280 /// An event indicating that the cascade has finished building all assets.
261 /// 281 ///
262 /// A build can end either in success or failure. If there were no errors during 282 /// A build can end either in success or failure. If there were no errors during
263 /// the build, it's considered to be a success; any errors render it a failure, 283 /// the build, it's considered to be a success; any errors render it a failure,
264 /// although individual assets may still have built successfully. 284 /// although individual assets may still have built successfully.
265 class BuildResult { 285 class BuildResult {
266 /// All errors that occurred during the build. 286 /// All errors that occurred during the build.
267 final List errors; 287 final List errors;
(...skipping 21 matching lines...) Expand all
289 msg.write(prefixLines(error.toString())); 309 msg.write(prefixLines(error.toString()));
290 if (stackTrace != null) { 310 if (stackTrace != null) {
291 msg.write("\n\n"); 311 msg.write("\n\n");
292 msg.write("Stack trace:\n"); 312 msg.write("Stack trace:\n");
293 msg.write(prefixLines(stackTrace.toString())); 313 msg.write(prefixLines(stackTrace.toString()));
294 } 314 }
295 return msg.toString(); 315 return msg.toString();
296 }).join("\n\n"); 316 }).join("\n\n");
297 } 317 }
298 } 318 }
OLDNEW
« no previous file with comments | « no previous file | pkg/barback/lib/src/asset_node.dart » ('j') | pkg/barback/lib/src/asset_node.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698