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

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: More code reivew 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
« no previous file with comments | « pkg/barback/lib/src/group_runner.dart ('k') | pkg/barback/lib/src/phase_forwarder.dart » ('j') | no next file with comments »
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.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 // TODO(nweiz): Rather than passing the cascade and the phase everywhere,
80 : _transformers = transformers.toSet() { 110 // create an interface that just exposes [getInput]. Emit errors via
111 // [AssetNode]s.
112 Phase(this.cascade, Iterable transformers)
113 : _transformers = transformers.where((op) => op is Transformer).toSet() {
81 _onDirtyPool.add(_onDirtyController.stream); 114 _onDirtyPool.add(_onDirtyController.stream);
115
116 for (var group in transformers.where((op) => op is TransformerGroup)) {
117 _groups[group] = new GroupRunner(cascade, group);
118 _onDirtyPool.add(_groups[group].onDirty);
119 }
82 } 120 }
83 121
84 /// Adds a new asset as an input for this phase. 122 /// Adds a new asset as an input for this phase.
85 /// 123 ///
86 /// [node] doesn't have to be [AssetState.AVAILABLE]. Once it is, the phase 124 /// [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 125 /// will automatically begin determining which transforms can consume it as a
88 /// primary input. The transforms themselves won't be applied until [process] 126 /// primary input. The transforms themselves won't be applied until [process]
89 /// is called, however. 127 /// is called, however.
90 /// 128 ///
91 /// This should only be used for brand-new assets or assets that have been 129 /// 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 130 /// removed and re-created. The phase will automatically handle updated assets
93 /// using the [AssetNode.onStateChange] stream. 131 /// using the [AssetNode.onStateChange] stream.
94 void addInput(AssetNode node) { 132 void addInput(AssetNode node) {
95 if (_inputs.containsKey(node.id)) _inputs[node.id].remove(); 133 if (_inputs.containsKey(node.id)) _inputs[node.id].remove();
96 134
135 // Each group is one channel along which an asset may be forwarded. Then
136 // there's one additional channel for the non-grouped transformers.
137 var forwarder = new PhaseForwarder(_groups.length + 1);
138 _forwarders[node.id] = forwarder;
139 forwarder.onForwarding.listen((asset) {
140 _addOutput(asset);
141
142 var exception = _outputs[asset.id].collisionException;
143 if (exception != null) cascade.reportError(exception);
144 });
145
97 var input = new PhaseInput(this, node, _transformers); 146 var input = new PhaseInput(this, node, _transformers);
98 _inputs[node.id] = input; 147 _inputs[node.id] = input;
99 input.input.whenRemoved.then((_) => _inputs.remove(node.id)); 148 input.input.whenRemoved.then((_) {
149 _inputs.remove(node.id);
150 _forwarders.remove(node.id).remove();
151 });
100 _onDirtyPool.add(input.onDirty); 152 _onDirtyPool.add(input.onDirty);
101 _onDirtyController.add(null); 153 _onDirtyController.add(null);
154
155 for (var group in _groups.values) {
156 group.addInput(node);
157 }
102 } 158 }
103 159
104 /// Gets the asset node for an input [id]. 160 /// Gets the asset node for an input [id].
105 /// 161 ///
106 /// If an input with that ID cannot be found, returns null. 162 /// If an input with that ID cannot be found, returns null.
107 Future<AssetNode> getInput(AssetId id) { 163 Future<AssetNode> getInput(AssetId id) {
108 return newFuture(() { 164 return newFuture(() {
109 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); 165 if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
110 if (_inputs.containsKey(id)) return _inputs[id].input; 166 if (_inputs.containsKey(id)) return _inputs[id].input;
111 return null; 167 return null;
112 }); 168 });
113 } 169 }
114 170
115 /// Gets the asset node for an output [id]. 171 /// Gets the asset node for an output [id].
116 /// 172 ///
117 /// If an output with that ID cannot be found, returns null. 173 /// If an output with that ID cannot be found, returns null.
118 Future<AssetNode> getOutput(AssetId id) { 174 Future<AssetNode> getOutput(AssetId id) {
119 return newFuture(() { 175 return newFuture(() {
120 if (id.package != cascade.package) return cascade.graph.getAssetNode(id); 176 if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
121 if (!_outputs.containsKey(id)) return null; 177 if (!_outputs.containsKey(id)) return null;
122 return _outputs[id].output; 178 return _outputs[id].output;
123 }); 179 });
124 } 180 }
125 181
126 /// Set this phase's transformers to [transformers]. 182 /// Set this phase's transformers to [transformers].
127 void updateTransformers(Iterable<Transformer> transformers) { 183 void updateTransformers(Iterable transformers) {
128 _onDirtyController.add(null); 184 _onDirtyController.add(null);
185
186 var actualTransformers = transformers.where((op) => op is Transformer);
129 _transformers.clear(); 187 _transformers.clear();
130 _transformers.addAll(transformers); 188 _transformers.addAll(actualTransformers);
131 for (var input in _inputs.values) { 189 for (var input in _inputs.values) {
132 input.updateTransformers(_transformers); 190 input.updateTransformers(actualTransformers);
191 }
192
193 var newGroups = transformers.where((op) => op is TransformerGroup)
194 .toSet();
195 var oldGroups = _groups.keys.toSet();
196 for (var removed in oldGroups.difference(newGroups)) {
197 _groups.remove(removed).remove();
198 }
199
200 for (var added in newGroups.difference(oldGroups)) {
201 var runner = new GroupRunner(cascade, added);
202 _groups[added] = runner;
203 _onDirtyPool.add(runner.onDirty);
204 for (var input in _inputs.values) {
205 runner.addInput(input.input);
206 }
207 }
208
209 for (var forwarder in _forwarders.values) {
210 forwarder.numChannels = _groups.length + 1;
133 } 211 }
134 } 212 }
135 213
136 /// Add a new phase after this one with [transformers]. 214 /// Add a new phase after this one with [transformers].
137 /// 215 ///
138 /// This may only be called on a phase with no phase following it. 216 /// This may only be called on a phase with no phase following it.
139 Phase addPhase(Iterable<Transformer> transformers) { 217 Phase addPhase(Iterable transformers) {
140 assert(_next == null); 218 assert(_next == null);
141 _next = new Phase(cascade, transformers); 219 _next = new Phase(cascade, transformers);
142 for (var output in _outputs.values.toList()) { 220 for (var output in _outputs.values.toList()) {
143 // Remove [output]'s listeners because now they should get the asset from 221 // Remove [output]'s listeners because now they should get the asset from
144 // [_next], rather than this phase. Any transforms consuming [output] will 222 // [_next], rather than this phase. Any transforms consuming [output] will
145 // be re-run and will consume the output from the new final phase. 223 // be re-run and will consume the output from the new final phase.
146 output.removeListeners(); 224 output.removeListeners();
147 225
148 // Removing [output]'s listeners will cause it to be removed from 226 // Removing [output]'s listeners will cause it to be removed from
149 // [_outputs], so we have to put it back. 227 // [_outputs], so we have to put it back.
150 _outputs[output.output.id] = output; 228 _outputs[output.output.id] = output;
151 output.output.whenRemoved.then((_) => _outputs.remove(output.output.id)); 229 output.output.whenRemoved.then((_) => _outputs.remove(output.output.id));
152 _next.addInput(output.output); 230 _next.addInput(output.output);
153 } 231 }
154 return _next; 232 return _next;
155 } 233 }
156 234
157 /// Mark this phase as removed. 235 /// Mark this phase as removed.
158 /// 236 ///
159 /// This will remove all the phase's outputs and all following phases. 237 /// This will remove all the phase's outputs and all following phases.
160 void remove() { 238 void remove() {
161 removeFollowing(); 239 removeFollowing();
162 for (var input in _inputs.values.toList()) { 240 for (var input in _inputs.values.toList()) {
163 input.remove(); 241 input.remove();
164 } 242 }
243 for (var group in _groups.values) {
244 group.remove();
245 }
165 _onDirtyPool.close(); 246 _onDirtyPool.close();
166 } 247 }
167 248
168 /// Remove all phases after this one. 249 /// Remove all phases after this one.
169 void removeFollowing() { 250 void removeFollowing() {
170 if (_next == null) return; 251 if (_next == null) return;
171 _next.remove(); 252 _next.remove();
172 _next = null; 253 _next = null;
173 } 254 }
174 255
175 /// Processes this phase. 256 /// Processes this phase.
176 /// 257 ///
177 /// Returns a future that completes when processing is done. If there is 258 /// Returns a future that completes when processing is done. If there is
178 /// nothing to process, returns `null`. 259 /// nothing to process, returns `null`.
179 Future process() { 260 Future process() {
180 if (!_inputs.values.any((input) => input.isDirty)) return null; 261 if (!isDirty) return null;
181 262
182 var outputIds = new Set<AssetId>(); 263 var outputIds = new Set<AssetId>();
183 return Future.wait(_inputs.values.map((input) { 264 void _handleOutputs(Set<AssetNode> outputs) {
265 for (var asset in outputs) {
266 if (_inputOrigins.contains(asset.origin)) {
267 _forwarders[asset.id].addIntermediateAsset(asset);
268 continue;
269 }
270
271 outputIds.add(asset.id);
272 _addOutput(asset);
273 }
274 }
275
276 var outputFutures = [];
277 outputFutures.addAll(_inputs.values.map((input) {
184 if (!input.isDirty) return new Future.value(new Set()); 278 if (!input.isDirty) return new Future.value(new Set());
185 return input.process().then((outputs) { 279 return input.process().then(_handleOutputs);
186 for (var asset in outputs) { 280 }));
187 outputIds.add(asset.id); 281 outputFutures.addAll(_groups.values.map((input) {
188 if (_outputs.containsKey(asset.id)) { 282 if (!input.isDirty) return new Future.value(new Set());
189 _outputs[asset.id].add(asset); 283 return input.process().then(_handleOutputs);
190 } else { 284 }));
191 _outputs[asset.id] = new PhaseOutput(this, asset); 285
192 _outputs[asset.id].output.whenRemoved.then((_) { 286 // TODO(nweiz): handle pass-through.
193 _outputs.remove(asset.id); 287
194 }); 288 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. 289 // Report collisions in a deterministic order.
201 outputIds = outputIds.toList(); 290 outputIds = outputIds.toList();
202 outputIds.sort((a, b) => a.compareTo(b)); 291 outputIds.sort((a, b) => a.compareTo(b));
203 for (var id in outputIds) { 292 for (var id in outputIds) {
204 // It's possible the output was removed before other transforms in this 293 // It's possible the output was removed before other transforms in this
205 // phase finished. 294 // phase finished.
206 if (!_outputs.containsKey(id)) continue; 295 if (!_outputs.containsKey(id)) continue;
207 var exception = _outputs[id].collisionException; 296 var exception = _outputs[id].collisionException;
208 if (exception != null) cascade.reportError(exception); 297 if (exception != null) cascade.reportError(exception);
209 } 298 }
210 }); 299 });
211 } 300 }
301
302 /// Add [asset] as an output of this phase.
303 void _addOutput(AssetNode asset) {
304 if (_outputs.containsKey(asset.id)) {
305 _outputs[asset.id].add(asset);
306 } else {
307 _outputs[asset.id] = new PhaseOutput(this, asset);
308 _outputs[asset.id].output.whenRemoved.then((_) {
309 _outputs.remove(asset.id);
310 });
311 if (_next != null) _next.addInput(_outputs[asset.id].output);
312 }
313 }
212 } 314 }
OLDNEW
« no previous file with comments | « pkg/barback/lib/src/group_runner.dart ('k') | pkg/barback/lib/src/phase_forwarder.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698