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

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

Powered by Google App Engine
This is Rietveld 408576698