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

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

Issue 25376003: Add support for transformer clusters to barback. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Expose the Operator class. Created 7 years, 2 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 import 'dart:collection'; 8 import 'dart:collection';
9 9
10 import 'asset_cascade.dart'; 10 import 'asset_cascade.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 'cluster_runner.dart';
14 import 'errors.dart'; 15 import 'errors.dart';
16 import 'operator.dart';
17 import 'phase_forwarder.dart';
15 import 'phase_input.dart'; 18 import 'phase_input.dart';
16 import 'phase_output.dart'; 19 import 'phase_output.dart';
17 import 'stream_pool.dart'; 20 import 'stream_pool.dart';
18 import 'transformer.dart'; 21 import 'transformer.dart';
22 import 'transformer_cluster.dart';
19 import 'utils.dart'; 23 import 'utils.dart';
20 24
21 /// One phase in the ordered series of transformations in an [AssetCascade]. 25 /// One phase in the ordered series of transformations in an [AssetCascade].
22 /// 26 ///
23 /// Each phase can access outputs from previous phases and can in turn pass 27 /// Each phase can access outputs from previous phases and can in turn pass
24 /// outputs to later phases. Phases are processed strictly serially. All 28 /// outputs to later phases. Phases are processed strictly serially. All
25 /// transforms in a phase will be complete before moving on to the next phase. 29 /// transforms in a phase will be complete before moving on to the next phase.
26 /// Within a single phase, all transforms will be run in parallel. 30 /// Within a single phase, all transforms will be run in parallel.
27 /// 31 ///
28 /// Building can be interrupted between phases. For example, a source is added 32 /// Building can be interrupted between phases. For example, a source is added
29 /// which starts the background process. Sometime during, say, phase 2 (which 33 /// which starts the background process. Sometime during, say, phase 2 (which
30 /// is running asynchronously) that source is modified. When the process queue 34 /// is running asynchronously) that source is modified. When the process queue
31 /// goes to advance to phase 3, it will see that modification and start the 35 /// goes to advance to phase 3, it will see that modification and start the
32 /// waterfall from the beginning again. 36 /// waterfall from the beginning again.
33 class Phase { 37 class Phase {
34 /// The cascade that owns this phase. 38 /// The cascade that owns this phase.
35 final AssetCascade cascade; 39 final AssetCascade cascade;
36 40
37 /// The transformers that can access [inputs]. 41 /// The transformers that can access [inputs].
38 /// 42 ///
39 /// Their outputs will be available to the next phase. 43 /// Their outputs will be available to the next phase.
40 final Set<Transformer> _transformers; 44 final Set<Transformer> _transformers;
41 45
46 /// The clusters for this phase.
47 final _clusters = new Map<TransformerCluster, ClusterRunner>();
48
42 /// The inputs for this phase. 49 /// The inputs for this phase.
43 /// 50 ///
44 /// For the first phase, these will be the source assets. For all other 51 /// For the first phase, these will be the source assets. For all other
45 /// phases, they will be the outputs from the previous phase. 52 /// phases, they will be the outputs from the previous phase.
46 final _inputs = new Map<AssetId, PhaseInput>(); 53 final _inputs = new Map<AssetId, PhaseInput>();
47 54
55 /// The forwarders for this phase.
56 final _forwarders = new Map<AssetId, PhaseForwarder>();
57
48 /// The outputs for this phase. 58 /// The outputs for this phase.
49 final _outputs = new Map<AssetId, PhaseOutput>(); 59 final _outputs = new Map<AssetId, PhaseOutput>();
50 60
61 // TODO(nweiz): don't re-calculate this on the fly all the time
Bob Nystrom 2013/10/04 21:47:50 "." and capitalize "Don't".
nweiz 2013/10/07 23:21:31 Done.
62 /// The set of all [AssetNode.origin] properties of the input assets for this
63 /// phase.
64 ///
65 /// This is used to determine which assets have been passed unmodified through
66 /// [_inputs] or [_clusters]. Eeach input asset has a PhaseInput in [_inputs].
Bob Nystrom 2013/10/04 21:47:50 "Eeach" -> "Each".
nweiz 2013/10/07 23:21:31 Done.
67 /// If that input isn't consumed by any transformers, it will be forwarded
68 /// through the PhaseInput. However, it's possible that it was consumed by a
69 /// cluster, and so shouldn't be forwarded through the phase as a whole.
70 ///
71 /// In order to detect whether an output has been forwarded through a cluster
72 /// or a PhaseInput, we must be able to distinguish it from other outputs with
73 /// the same id. To do so, we check if its origin is in [_inputOrigins]. If
74 /// so, it's been forwarded unmodified.
75 Set<AssetNode> get _inputOrigins =>
76 _inputs.values.map((input) => input.input.origin).toSet();
77
51 /// A stream that emits an event whenever this phase becomes dirty and needs 78 /// A stream that emits an event whenever this phase becomes dirty and needs
52 /// to be run. 79 /// to be run.
53 /// 80 ///
54 /// This may emit events when the phase was already dirty or while processing 81 /// This may emit events when the phase was already dirty or while processing
55 /// transforms. Events are emitted synchronously to ensure that the dirty 82 /// transforms. Events are emitted synchronously to ensure that the dirty
56 /// state is thoroughly propagated as soon as any assets are changed. 83 /// state is thoroughly propagated as soon as any assets are changed.
57 Stream get onDirty => _onDirtyPool.stream; 84 Stream get onDirty => _onDirtyPool.stream;
58 final _onDirtyPool = new StreamPool.broadcast(); 85 final _onDirtyPool = new StreamPool.broadcast();
59 86
60 /// A controller whose stream feeds into [_onDirtyPool]. 87 /// A controller whose stream feeds into [_onDirtyPool].
61 /// 88 ///
62 /// This is used whenever an input is added or transforms are changed. 89 /// This is used whenever an input is added or transforms are changed.
63 final _onDirtyController = new StreamController.broadcast(sync: true); 90 final _onDirtyController = new StreamController.broadcast(sync: true);
64 91
92 /// Whether this phase is dirty and needs to be run.
93 bool get isDirty => _inputs.values.any((input) => input.isDirty) ||
94 _clusters.values.any((cluster) => cluster.isDirty);
95
65 /// The phase after this one. 96 /// The phase after this one.
66 /// 97 ///
67 /// Outputs from this phase will be passed to it. 98 /// Outputs from this phase will be passed to it.
68 Phase get next => _next; 99 Phase get next => _next;
69 Phase _next; 100 Phase _next;
70 101
71 /// Returns all currently-available output assets for this phase. 102 /// Returns all currently-available output assets for this phase.
72 AssetSet get availableOutputs { 103 Set<AssetNode> get availableOutputs {
73 return new AssetSet.from(_outputs.values 104 return _outputs.values
74 .map((output) => output.output) 105 .map((output) => output.output)
75 .where((node) => node.state.isAvailable) 106 .where((node) => node.state.isAvailable)
76 .map((node) => node.asset)); 107 .toSet();
77 } 108 }
78 109
79 Phase(this.cascade, Iterable<Transformer> transformers) 110 Phase(this.cascade, Iterable<Operator> operators)
Bob Nystrom 2013/10/04 21:47:50 We could keep this API typed if the transformers a
nweiz 2013/10/07 23:21:31 I think that would result in more code duplication
80 : _transformers = transformers.toSet() { 111 : _transformers = operators.where((op) => op is Transformer).toSet() {
81 _onDirtyPool.add(_onDirtyController.stream); 112 _onDirtyPool.add(_onDirtyController.stream);
113
114 for (var cluster in operators.where((op) => op is TransformerCluster)) {
115 _clusters[cluster] = new ClusterRunner(cascade, cluster);
116 _onDirtyPool.add(_clusters[cluster].onDirty);
117 }
82 } 118 }
83 119
84 /// Adds a new asset as an input for this phase. 120 /// Adds a new asset as an input for this phase.
85 /// 121 ///
86 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase 122 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase
87 /// will automatically begin determining which transforms can consume it as a 123 /// will automatically begin determining which transforms can consume it as a
88 /// primary input. The transforms themselves won't be applied until [process] 124 /// primary input. The transforms themselves won't be applied until [process]
89 /// is called, however. 125 /// is called, however.
90 /// 126 ///
91 /// This should only be used for brand-new assets or assets that have been 127 /// This should only be used for brand-new assets or assets that have been
92 /// removed and re-created. The phase will automatically handle updated assets 128 /// removed and re-created. The phase will automatically handle updated assets
93 /// using the [AssetNode.onStateChange] stream. 129 /// using the [AssetNode.onStateChange] stream.
94 void addInput(AssetNode node) { 130 void addInput(AssetNode node) {
95 if (_inputs.containsKey(node.id)) _inputs[node.id].remove(); 131 if (_inputs.containsKey(node.id)) _inputs[node.id].remove();
96 132
133 var forwarder = new PhaseForwarder(_clusters.length + 1);
Bob Nystrom 2013/10/04 21:47:50 Explain what the .length + 1 represents.
nweiz 2013/10/07 23:21:31 Done.
134 _forwarders[node.id] = forwarder;
135 forwarder.onForwarding.listen((asset) {
136 _addOutput(asset);
137
138 var exception = _outputs[asset.id].collisionException;
139 if (exception != null) cascade.reportError(exception);
140 });
141
97 var input = new PhaseInput(this, node, _transformers); 142 var input = new PhaseInput(this, node, _transformers);
98 _inputs[node.id] = input; 143 _inputs[node.id] = input;
99 input.input.whenRemoved.then((_) => _inputs.remove(node.id)); 144 input.input.whenRemoved.then((_) {
145 _inputs.remove(node.id);
146 _forwarders.remove(node.id).remove();
147 });
100 _onDirtyPool.add(input.onDirty); 148 _onDirtyPool.add(input.onDirty);
101 _onDirtyController.add(null); 149 _onDirtyController.add(null);
150
151 for (var cluster in _clusters.values) {
152 cluster.addInput(node);
153 }
102 } 154 }
103 155
104 /// Gets the asset node for an input [id]. 156 /// Gets the asset node for an input [id].
105 /// 157 ///
106 /// If an input with that ID cannot be found, returns null. 158 /// If an input with that ID cannot be found, returns null.
107 Future<AssetNode> getInput(AssetId id) { 159 Future<AssetNode> getInput(AssetId id) {
108 return newFuture(() { 160 return newFuture(() {
109 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); 161 if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
110 if (_inputs.containsKey(id)) return _inputs[id].input; 162 if (_inputs.containsKey(id)) return _inputs[id].input;
111 return null; 163 return null;
112 }); 164 });
113 } 165 }
114 166
115 /// Gets the asset node for an output [id]. 167 /// Gets the asset node for an output [id].
116 /// 168 ///
117 /// If an output with that ID cannot be found, returns null. 169 /// If an output with that ID cannot be found, returns null.
118 Future<AssetNode> getOutput(AssetId id) { 170 Future<AssetNode> getOutput(AssetId id) {
119 return newFuture(() { 171 return newFuture(() {
120 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); 172 if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
121 if (!_outputs.containsKey(id)) return null; 173 if (!_outputs.containsKey(id)) return null;
122 return _outputs[id].output; 174 return _outputs[id].output;
123 }); 175 });
124 } 176 }
125 177
126 /// Set this phase's transformers to [transformers]. 178 /// Set this phase's operators to [operators].
127 void updateTransformers(Iterable<Transformer> transformers) { 179 void updateOperators(Iterable<Operator> operators) {
128 _onDirtyController.add(null); 180 _onDirtyController.add(null);
181
182 var transformers = operators.where((op) => op is Transformer);
129 _transformers.clear(); 183 _transformers.clear();
130 _transformers.addAll(transformers); 184 _transformers.addAll(transformers);
131 for (var input in _inputs.values) { 185 for (var input in _inputs.values) {
132 input.updateTransformers(_transformers); 186 input.updateTransformers(transformers);
187 }
188
189 var newClusters = operators.where((op) => op is TransformerCluster).toSet();
190 var oldClusters = _clusters.keys.toSet();
191 for (var removed in oldClusters.difference(newClusters)) {
192 _clusters.remove(removed).remove();
193 }
Bob Nystrom 2013/10/04 21:47:50 Nit but add blank line after "}".
nweiz 2013/10/07 23:21:31 Done.
194 for (var added in newClusters.difference(oldClusters)) {
195 var runner = new ClusterRunner(cascade, added);
196 _clusters[added] = runner;
197 _onDirtyPool.add(runner.onDirty);
198 for (var input in _inputs.values) {
199 runner.addInput(input.input);
200 }
201 }
202
203 for (var forwarder in _forwarders.values) {
204 forwarder.channels = _clusters.length + 1;
133 } 205 }
134 } 206 }
135 207
136 /// Add a new phase after this one with [transformers]. 208 /// Add a new phase after this one with [operators].
137 /// 209 ///
138 /// This may only be called on a phase with no phase following it. 210 /// This may only be called on a phase with no phase following it.
139 Phase addPhase(Iterable<Transformer> transformers) { 211 Phase addPhase(Iterable<Operators> operators) {
140 assert(_next == null); 212 assert(_next == null);
141 _next = new Phase(cascade, transformers); 213 _next = new Phase(cascade, operators);
142 for (var output in _outputs.values.toList()) { 214 for (var output in _outputs.values.toList()) {
143 // Remove [output]'s listeners because now they should get the asset from 215 // Remove [output]'s listeners because now they should get the asset from
144 // [_next], rather than this phase. Any transforms consuming [output] will 216 // [_next], rather than this phase. Any transforms consuming [output] will
145 // be re-run and will consume the output from the new final phase. 217 // be re-run and will consume the output from the new final phase.
146 output.removeListeners(); 218 output.removeListeners();
147 219
148 // Removing [output]'s listeners will cause it to be removed from 220 // Removing [output]'s listeners will cause it to be removed from
149 // [_outputs], so we have to put it back. 221 // [_outputs], so we have to put it back.
150 _outputs[output.output.id] = output; 222 _outputs[output.output.id] = output;
151 output.output.whenRemoved.then((_) => _outputs.remove(output.output.id)); 223 output.output.whenRemoved.then((_) => _outputs.remove(output.output.id));
152 _next.addInput(output.output); 224 _next.addInput(output.output);
153 } 225 }
154 return _next; 226 return _next;
155 } 227 }
156 228
157 /// Mark this phase as removed. 229 /// Mark this phase as removed.
158 /// 230 ///
159 /// This will remove all the phase's outputs and all following phases. 231 /// This will remove all the phase's outputs and all following phases.
160 void remove() { 232 void remove() {
161 removeFollowing(); 233 removeFollowing();
162 for (var input in _inputs.values.toList()) { 234 for (var input in _inputs.values.toList()) {
163 input.remove(); 235 input.remove();
164 } 236 }
237 for (var cluster in _clusters.values) {
238 cluster.remove();
239 }
165 _onDirtyPool.close(); 240 _onDirtyPool.close();
166 } 241 }
167 242
168 /// Remove all phases after this one. 243 /// Remove all phases after this one.
169 void removeFollowing() { 244 void removeFollowing() {
170 if (_next == null) return; 245 if (_next == null) return;
171 _next.remove(); 246 _next.remove();
172 _next = null; 247 _next = null;
173 } 248 }
174 249
175 /// Processes this phase. 250 /// Processes this phase.
176 /// 251 ///
177 /// Returns a future that completes when processing is done. If there is 252 /// Returns a future that completes when processing is done. If there is
178 /// nothing to process, returns `null`. 253 /// nothing to process, returns `null`.
179 Future process() { 254 Future process() {
180 if (!_inputs.values.any((input) => input.isDirty)) return null; 255 if (!isDirty) return null;
181 256
182 var outputIds = new Set<AssetId>(); 257 var outputIds = new Set<AssetId>();
183 return Future.wait(_inputs.values.map((input) { 258 void _handleOutputs(Set<AssetNode> outputs) {
259 for (var asset in outputs) {
260 if (_inputOrigins.contains(asset.origin)) {
261 _forwarders[asset.id].add(asset);
262 continue;
263 }
264
265 outputIds.add(asset.id);
266 _addOutput(asset);
267 }
268 }
269
270 var outputFutures = []..addAll(_inputs.values.map((input) {
184 if (!input.isDirty) return new Future.value(new Set()); 271 if (!input.isDirty) return new Future.value(new Set());
185 return input.process().then((outputs) { 272 return input.process().then(_handleOutputs);
186 for (var asset in outputs) { 273 }))..addAll(_clusters.values.map((input) {
Bob Nystrom 2013/10/04 21:47:50 Maybe it's just me, but using ".." here feels a bi
nweiz 2013/10/07 23:21:31 Done.
187 outputIds.add(asset.id); 274 if (!input.isDirty) return new Future.value(new Set());
188 if (_outputs.containsKey(asset.id)) { 275 return input.process().then(_handleOutputs);
189 _outputs[asset.id].add(asset); 276 }));
190 } else { 277
191 _outputs[asset.id] = new PhaseOutput(this, asset); 278 // TODO(nweiz): handle pass-through.
192 _outputs[asset.id].output.whenRemoved.then((_) { 279
193 _outputs.remove(asset.id); 280 return Future.wait(outputFutures).then((_) {
194 });
195 if (_next != null) _next.addInput(_outputs[asset.id].output);
196 }
197 }
198 });
199 })).then((_) {
200 // Report collisions in a deterministic order. 281 // Report collisions in a deterministic order.
201 outputIds = outputIds.toList(); 282 outputIds = outputIds.toList();
202 outputIds.sort((a, b) => a.compareTo(b)); 283 outputIds.sort((a, b) => a.compareTo(b));
203 for (var id in outputIds) { 284 for (var id in outputIds) {
204 // It's possible the output was removed before other transforms in this 285 // It's possible the output was removed before other transforms in this
205 // phase finished. 286 // phase finished.
206 if (!_outputs.containsKey(id)) continue; 287 if (!_outputs.containsKey(id)) continue;
207 var exception = _outputs[id].collisionException; 288 var exception = _outputs[id].collisionException;
208 if (exception != null) cascade.reportError(exception); 289 if (exception != null) cascade.reportError(exception);
209 } 290 }
210 }); 291 });
211 } 292 }
293
294 /// Add [asset] as an output of this phase.
295 void _addOutput(AssetNode asset) {
296 if (_outputs.containsKey(asset.id)) {
297 _outputs[asset.id].add(asset);
298 } else {
299 _outputs[asset.id] = new PhaseOutput(this, asset);
300 _outputs[asset.id].output.whenRemoved.then((_) {
301 _outputs.remove(asset.id);
302 });
303 if (_next != null) _next.addInput(_outputs[asset.id].output);
304 }
305 }
212 } 306 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698