OLD | NEW |
---|---|
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_node; | 5 library barback.asset_node; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 | 8 |
9 import 'asset.dart'; | 9 import 'asset.dart'; |
10 import 'asset_id.dart'; | 10 import 'asset_id.dart'; |
11 import 'errors.dart'; | |
11 import 'phase.dart'; | 12 import 'phase.dart'; |
12 import 'transform_node.dart'; | 13 import 'transform_node.dart'; |
13 | 14 |
14 /// Describes an asset and its relationship to the build dependency graph. | 15 /// Describes the current state of an asset as part of a transformation graph. |
15 /// | 16 /// |
16 /// Keeps a cache of the last asset that was built for this node (i.e. for this | 17 /// An asset node can be in one of three states (see [AssetState]). It provides |
17 /// node's ID and phase) and tracks which transforms depend on it. | 18 /// an [onStateChange] stream that emits an event whenever it changes state. |
19 /// | |
20 /// Asset nodes are controlled using [AssetNodeController]s. | |
18 class AssetNode { | 21 class AssetNode { |
19 Asset asset; | 22 /// The id of the asset that this node represents. |
23 final AssetId id; | |
20 | 24 |
21 /// The [TransformNode]s that consume this node's asset as an input. | 25 /// The current state of the asset node. |
22 final consumers = new Set<TransformNode>(); | 26 AssetState get state => _state; |
27 AssetState _state; | |
23 | 28 |
24 AssetId get id => asset.id; | 29 /// The concrete asset that this node represents. |
30 /// | |
31 /// This is null unless [state] is [AssetState.AVAILABLE]. | |
32 Asset get asset => _asset; | |
Bob Nystrom
2013/07/31 20:05:41
Is it an error to access this when not available?
nweiz
2013/07/31 22:47:53
I think it's reasonable to access it and check for
| |
33 Asset _asset; | |
25 | 34 |
26 AssetNode(this.asset); | 35 /// A broadcast stream that emits an event whenever the node changes state. |
36 /// | |
37 /// This stream is synchronous to ensure that when a source asset is modified | |
38 /// or removed, the appropriate portion of the asset graph is dirtied before | |
39 /// any [Barback.getAssetById] calls emit newly-incorrect values. | |
40 Stream<AssetState> get onStateChange => _stateChangeController.stream; | |
27 | 41 |
28 /// Updates this node's generated asset value and marks all transforms that | 42 /// This is synchronous so that a source being updated will always be |
29 /// use this as dirty. | 43 /// propagated through the build graph before anything that depends on it is |
30 void updateAsset(Asset asset) { | 44 /// requested. |
31 // Cannot update an asset to one with a different ID. | 45 final _stateChangeController = |
32 assert(id == asset.id); | 46 new StreamController<AssetState>.broadcast(sync: true); |
Bob Nystrom
2013/07/31 20:05:41
Indent +2.
nweiz
2013/07/31 22:47:53
Done.
| |
33 | 47 |
34 this.asset = asset; | 48 /// Returns a Future that completes when the node's asset is available. |
35 consumers.forEach((consumer) => consumer.dirty()); | 49 /// |
50 /// If the asset is currently available, this will complete synchronously to | |
51 /// ensure that the asset is still available in the [Future.then] callback. | |
52 /// | |
53 /// If the asset is removed before becoming available, this will throw an | |
54 /// [AssetNotFoundException]. | |
55 Future<Asset> get whenAvailable { | |
56 return _waitForState((state) => state.isAvailable || state.isRemoved) | |
57 .then((state) { | |
58 if (state.isRemoved) throw new AssetNotFoundException(id); | |
59 return asset; | |
60 }); | |
61 } | |
62 | |
63 /// Returns a Future that completes when the node's asset is removed. | |
64 /// | |
65 /// If the asset is already removed when this is called, it will complete | |
Bob Nystrom
2013/07/31 20:05:41
"will complete" -> "completes"
nweiz
2013/07/31 22:47:53
Done.
| |
66 /// synchronously. | |
67 Future get whenRemoved => _waitForState((state) => state.isRemoved); | |
68 | |
69 /// Runs [callback] repeatedly until the node's asset has maintained the same | |
70 /// value for the duration. | |
71 /// | |
72 /// This will run [callback] as soon as the asset is available (synchronously | |
73 /// if it's available immediately). If the [state] changes at all while | |
74 /// waiting for the Future returned by [callback] to complete, it will be | |
75 /// re-run as soon as it completes and the asset is available again. This will | |
76 /// continue until [state] doesn't change at all. | |
77 /// | |
78 /// If this asset is removed, this will throw an [AssetNotFoundException] as | |
79 /// soon as [callback]'s Future is finished running. | |
80 Future tryUntilStable(Future callback(Asset asset)) { | |
81 return whenAvailable.then((asset) { | |
82 var modifiedDuringCallback = false; | |
83 var subscription; | |
84 subscription = onStateChange.listen((_) { | |
85 modifiedDuringCallback = true; | |
86 subscription.cancel(); | |
87 }); | |
88 | |
89 return callback(asset).then((result) { | |
90 // If the asset was modified at all while running the callback, the | |
91 // result was invalid and we should try again. | |
92 if (modifiedDuringCallback) return tryUntilStable(callback); | |
93 subscription.cancel(); | |
Bob Nystrom
2013/07/31 20:05:41
Move this above the if statement.
nweiz
2013/07/31 22:47:53
Done.
| |
94 return result; | |
95 }); | |
96 }); | |
97 } | |
98 | |
99 /// Returns a Future that completes as soon as the node is in a state that | |
100 /// matches [test]. | |
101 /// | |
102 /// The Future will complete synchronously if this is already in such a state. | |
103 Future<AssetState> _waitForState(bool test(AssetState state)) { | |
104 if (test(state)) return new Future.sync(() => state); | |
105 return onStateChange.firstWhere(test); | |
106 } | |
107 | |
108 AssetNode._(this.id) | |
109 : _state = AssetState.DIRTY; | |
110 | |
111 AssetNode._available(Asset asset) | |
112 : id = asset.id, | |
113 _asset = asset, | |
114 _state = AssetState.AVAILABLE; | |
115 } | |
116 | |
117 /// The controller for an [AssetNode]. | |
118 /// | |
119 /// This controls which state the node is in. | |
120 class AssetNodeController { | |
121 final AssetNode node; | |
122 | |
123 /// Creates a controller for a dirty node. | |
124 AssetNodeController(AssetId id) | |
125 : node = new AssetNode._(id); | |
126 | |
127 /// Creates a controller for an available node with the given concrete | |
128 /// [asset]. | |
129 AssetNodeController.available(Asset asset) | |
130 : node = new AssetNode._available(asset); | |
131 | |
132 /// Marks the node as [AssetState.DIRTY]. | |
133 void setDirty() { | |
134 assert(node._state != AssetState.REMOVED); | |
135 node._state = AssetState.DIRTY; | |
136 node._asset = null; | |
137 node._stateChangeController.add(AssetState.DIRTY); | |
138 } | |
139 | |
140 /// Marks the node as [AssetState.REMOVED]. | |
141 /// | |
142 /// Once a node is marked as removed, it can't be marked as any other state. | |
143 /// If a new asset is created with the same id, it should have a new node. | |
Bob Nystrom
2013/07/31 20:05:41
"should have" -> "will get"
nweiz
2013/07/31 22:47:53
Done.
| |
144 void setRemoved() { | |
145 assert(node._state != AssetState.REMOVED); | |
146 node._state = AssetState.REMOVED; | |
147 node._asset = null; | |
148 node._stateChangeController.add(AssetState.REMOVED); | |
149 } | |
150 | |
151 /// Marks the node as [AssetState.AVAILABLE] with the given concrete [asset]. | |
152 /// | |
153 /// It's an error to mark an already-available node as available. It should be | |
154 /// marked as dirty first. | |
155 void setAvailable(Asset asset) { | |
156 assert(asset.id == node.id); | |
157 assert(node._state != AssetState.REMOVED); | |
158 assert(node._state != AssetState.AVAILABLE); | |
Bob Nystrom
2013/07/31 20:05:41
assert(node._state == AssetState.DIRTY);
nweiz
2013/07/31 22:47:53
I intentionally avoided that because I'm planning
| |
159 node._state = AssetState.AVAILABLE; | |
160 node._asset = asset; | |
161 node._stateChangeController.add(AssetState.AVAILABLE); | |
36 } | 162 } |
37 } | 163 } |
164 | |
165 // TODO(nweiz): add an error state. | |
166 /// An enum of states that an [AssetNode] can be in. | |
167 class AssetState { | |
168 /// The state of having an asset available. | |
Bob Nystrom
2013/07/31 20:05:41
This sentence is strange. Just ditch it and use th
nweiz
2013/07/31 22:47:53
Done.
| |
169 /// | |
170 /// This state indicates that the node has a concrete asset loaded, available, | |
Bob Nystrom
2013/07/31 20:05:41
Remove "This state indicates that".
nweiz
2013/07/31 22:47:53
Done.
| |
171 /// and up-to-date. The asset is accessible via [AssetNode.asset]. An | |
172 /// available asset may be marked available again, but first it will be marked | |
173 /// [AssetState.DIRTY]. | |
Bob Nystrom
2013/07/31 20:05:41
This reads weird. Are you saying that an asset in
nweiz
2013/07/31 22:47:53
Done.
| |
174 static final AVAILABLE = new AssetState._("available"); | |
175 | |
176 /// The state of being removed. | |
177 /// | |
178 /// This state indicates that the asset is no longer available, possibly for | |
179 /// good. A removed asset will never enter another state. | |
Bob Nystrom
2013/07/31 20:05:41
Same as above. Remove first sentence, use this one
nweiz
2013/07/31 22:47:53
Done.
| |
180 static final REMOVED = new AssetState._("removed"); | |
181 | |
182 /// The state of waiting to be generated. | |
183 /// | |
184 /// This state indicates that the asset will exist in the future (unless it's | |
185 /// removed), but the concrete asset is not yet available. | |
Bob Nystrom
2013/07/31 20:05:41
Ditto.
nweiz
2013/07/31 22:47:53
Done.
| |
186 static final DIRTY = new AssetState._("dirty"); | |
Bob Nystrom
2013/07/31 20:05:41
Make these all constants instead of final, or name
nweiz
2013/07/31 22:47:53
Done.
| |
187 | |
188 /// Whether this state is [AssetState.AVAILABLE]. | |
189 bool get isAvailable => this == AssetState.AVAILABLE; | |
190 | |
191 /// Whether this state is [AssetState.REMOVED]. | |
192 bool get isRemoved => this == AssetState.REMOVED; | |
193 | |
194 /// Whether this state is [AssetState.DIRTY]. | |
195 bool get isDirty => this == AssetState.DIRTY; | |
196 | |
197 final String name; | |
198 | |
199 AssetState._(this.name); | |
Bob Nystrom
2013/07/31 20:05:41
Make this a const constructor.
nweiz
2013/07/31 22:47:53
Done.
| |
200 | |
201 String toString() => name; | |
202 } | |
OLD | NEW |