OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library barback.graph.asset_cascade; | |
6 | |
7 import 'dart:async'; | |
8 | |
9 import '../asset/asset.dart'; | |
10 import '../asset/asset_id.dart'; | |
11 import '../asset/asset_node.dart'; | |
12 import '../asset/asset_set.dart'; | |
13 import '../errors.dart'; | |
14 import '../log.dart'; | |
15 import '../transformer/transformer.dart'; | |
16 import '../utils.dart'; | |
17 import '../utils/cancelable_future.dart'; | |
18 import 'node_status.dart'; | |
19 import 'node_streams.dart'; | |
20 import 'package_graph.dart'; | |
21 import 'phase.dart'; | |
22 | |
23 /// The asset cascade for an individual package. | |
24 /// | |
25 /// This keeps track of which [Transformer]s are applied to which assets, and | |
26 /// re-runs those transformers when their dependencies change. The transformed | |
27 /// asset nodes are accessible via [getAssetNode]. | |
28 /// | |
29 /// A cascade consists of one or more [Phases], each of which has one or more | |
30 /// [Transformer]s that run in parallel, potentially on the same inputs. The | |
31 /// inputs of the first phase are the source assets for this cascade's package. | |
32 /// The inputs of each successive phase are the outputs of the previous phase, | |
33 /// as well as any assets that haven't yet been transformed. | |
34 class AssetCascade { | |
35 /// The name of the package whose assets are managed. | |
36 final String package; | |
37 | |
38 /// The [PackageGraph] that tracks all [AssetCascade]s for all dependencies of | |
39 /// the current app. | |
40 final PackageGraph graph; | |
41 | |
42 /// The controllers for the [AssetNode]s that provide information about this | |
43 /// cascade's package's source assets. | |
44 final _sourceControllerMap = new Map<AssetId, AssetNodeController>(); | |
45 | |
46 /// Futures for source assets that are currently being loaded. | |
47 /// | |
48 /// These futures are cancelable so that if an asset is updated after a load | |
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 | |
53 /// The list of phases in this cascade. | |
54 /// | |
55 /// This will always contain at least one phase, and the first phase will | |
56 /// never have any transformers. This ensures that every transformer can | |
57 /// request inputs from a previous phase. | |
58 final _phases = <Phase>[]; | |
59 | |
60 /// The subscription to the [Phase.onStatusChange] stream of the last [Phase] | |
61 /// in [_phases]. | |
62 StreamSubscription _phaseStatusSubscription; | |
63 | |
64 /// A stream that emits any errors from the cascade or the transformers. | |
65 /// | |
66 /// This emits errors as they're detected. If an error occurs in one part of | |
67 /// the cascade, unrelated parts will continue building. | |
68 Stream<BarbackException> get errors => _errorsController.stream; | |
69 final _errorsController = | |
70 new StreamController<BarbackException>.broadcast(sync: true); | |
71 | |
72 /// How far along [this] is in processing its assets. | |
73 NodeStatus get status { | |
74 // Just check the last phase, since it will check all the previous phases | |
75 // itself. | |
76 return _phases.last.status; | |
77 } | |
78 | |
79 /// The streams exposed by this cascade. | |
80 final _streams = new NodeStreams(); | |
81 Stream<LogEntry> get onLog => _streams.onLog; | |
82 Stream<NodeStatus> get onStatusChange => _streams.onStatusChange; | |
83 Stream<AssetNode> get onAsset => _streams.onAsset; | |
84 | |
85 /// Returns all currently-available output assets from this cascade. | |
86 Future<AssetSet> get availableOutputs => new Future.value(new AssetSet.from( | |
87 _phases.last.availableOutputs.map((node) => node.asset))); | |
88 | |
89 /// Creates a new [AssetCascade]. | |
90 /// | |
91 /// It loads source assets within [package] using [provider]. | |
92 AssetCascade(this.graph, this.package) { | |
93 _addPhase(new Phase(this, package)); | |
94 _streams.onAssetPool.add(_phases.last.onAsset); | |
95 } | |
96 | |
97 /// Gets the asset identified by [id]. | |
98 /// | |
99 /// If [id] is for a generated or transformed asset, this will wait until it | |
100 /// has been created and return it. This means that the returned asset will | |
101 /// always be [AssetState.AVAILABLE]. | |
102 /// | |
103 /// If the asset cannot be found, returns null. | |
104 Future<AssetNode> getAssetNode(AssetId id) { | |
105 assert(id.package == package); | |
106 | |
107 var oldLastPhase = _phases.last; | |
108 // TODO(rnystrom): Waiting for the entire build to complete is unnecessary | |
109 // in some cases. Should optimize: | |
110 // * [id] may be generated before the compilation is finished. We should | |
111 // be able to quickly check whether there are any more in-place | |
112 // transformations that can be run on it. If not, we can return it early. | |
113 // * If [id] has never been generated and all active transformers provide | |
114 // metadata about the file names of assets it can emit, we can prove that | |
115 // none of them can emit [id] and fail early. | |
116 return oldLastPhase.getOutput(id).then((node) { | |
117 // The last phase may have changed if [updateSources] was called after | |
118 // requesting the output. In that case, we want the output from the new | |
119 // last phase. | |
120 if (_phases.last == oldLastPhase) return node; | |
121 return getAssetNode(id); | |
122 }); | |
123 } | |
124 | |
125 /// Adds [sources] to the graph's known set of source assets. | |
126 /// | |
127 /// Begins applying any transforms that can consume any of the sources. If a | |
128 /// given source is already known, it is considered modified and all | |
129 /// transforms that use it will be re-applied. | |
130 void updateSources(Iterable<AssetId> sources) { | |
131 for (var id in sources) { | |
132 var controller = _sourceControllerMap[id]; | |
133 if (controller != null) { | |
134 controller.setDirty(); | |
135 } else { | |
136 _sourceControllerMap[id] = new AssetNodeController(id); | |
137 _phases.first.addInput(_sourceControllerMap[id].node); | |
138 } | |
139 | |
140 // If this source was already loading, cancel the old load, since it may | |
141 // return out-of-date contents for the asset. | |
142 if (_loadingSources.containsKey(id)) _loadingSources[id].cancel(); | |
143 | |
144 _loadingSources[id] = new CancelableFuture<Asset>( | |
145 syncFuture(() => graph.provider.getAsset(id))); | |
146 _loadingSources[id].whenComplete(() { | |
147 _loadingSources.remove(id); | |
148 }).then((asset) { | |
149 var controller = _sourceControllerMap[id].setAvailable(asset); | |
150 }).catchError((error, stack) { | |
151 reportError(new AssetLoadException(id, error, stack)); | |
152 | |
153 // TODO(nweiz): propagate error information through asset nodes. | |
154 _sourceControllerMap.remove(id).setRemoved(); | |
155 }); | |
156 } | |
157 } | |
158 | |
159 /// Removes [removed] from the graph's known set of source assets. | |
160 void removeSources(Iterable<AssetId> removed) { | |
161 removed.forEach((id) { | |
162 // If the source was being loaded, cancel that load. | |
163 if (_loadingSources.containsKey(id)) _loadingSources.remove(id).cancel(); | |
164 | |
165 var controller = _sourceControllerMap.remove(id); | |
166 // Don't choke if an id is double-removed for some reason. | |
167 if (controller != null) controller.setRemoved(); | |
168 }); | |
169 } | |
170 | |
171 /// Sets this cascade's transformer phases to [transformers]. | |
172 /// | |
173 /// Elements of the inner iterable of [transformers] must be [Transformer]s, | |
174 /// [TransformerGroup]s, or [AggregateTransformer]s. | |
175 void updateTransformers(Iterable<Iterable> transformersIterable) { | |
176 _streams.onAssetPool.remove(_phases.last.onAsset); | |
177 var transformers = transformersIterable.toList(); | |
178 | |
179 // Always preserve a single phase with no transformers at the beginning of | |
180 // the cascade so that [TransformNode]s in the first populated phase will | |
181 // have something to request assets from. | |
182 for (var i = 0; i < transformers.length; i++) { | |
183 if (_phases.length > i + 1) { | |
184 _phases[i + 1].updateTransformers(transformers[i]); | |
185 continue; | |
186 } | |
187 | |
188 var phase = _phases.last.addPhase(); | |
189 _addPhase(phase); | |
190 phase.updateTransformers(transformers[i]); | |
191 } | |
192 | |
193 for (var i = transformers.length + 1; i < _phases.length; i++) { | |
194 _phases[i].remove(); | |
195 } | |
196 _phases.removeRange(transformers.length + 1, _phases.length); | |
197 | |
198 _phaseStatusSubscription.cancel(); | |
199 _phaseStatusSubscription = _phases.last.onStatusChange | |
200 .listen(_streams.changeStatus); | |
201 | |
202 _streams.onAssetPool.add(_phases.last.onAsset); | |
203 } | |
204 | |
205 /// Force all [LazyTransformer]s' transforms in this cascade to begin | |
206 /// producing concrete assets. | |
207 void forceAllTransforms() { | |
208 for (var phase in _phases) { | |
209 phase.forceAllTransforms(); | |
210 } | |
211 } | |
212 | |
213 void reportError(BarbackException error) { | |
214 _errorsController.add(error); | |
215 } | |
216 | |
217 /// Add [phase] to the end of [_phases] and watch its streams. | |
218 void _addPhase(Phase phase) { | |
219 _streams.onLogPool.add(phase.onLog); | |
220 if (_phaseStatusSubscription != null) _phaseStatusSubscription.cancel(); | |
221 _phaseStatusSubscription = | |
222 phase.onStatusChange.listen(_streams.changeStatus); | |
223 | |
224 _phases.add(phase); | |
225 } | |
226 | |
227 String toString() => "cascade for $package"; | |
228 } | |
OLD | NEW |