| 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; | 
 |   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); | 
|   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 completes 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 completes | 
 |   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         subscription.cancel(); | 
 |   91  | 
 |   92         // If the asset was modified at all while running the callback, the | 
 |   93         // result was invalid and we should try again. | 
 |   94         if (modifiedDuringCallback) return tryUntilStable(callback); | 
 |   95         return result; | 
 |   96       }); | 
 |   97     }); | 
 |   98   } | 
 |   99  | 
 |  100   /// Returns a Future that completes as soon as the node is in a state that | 
 |  101   /// matches [test]. | 
 |  102   /// | 
 |  103   /// The Future completes synchronously if this is already in such a state. | 
 |  104   Future<AssetState> _waitForState(bool test(AssetState state)) { | 
 |  105     if (test(state)) return new Future.sync(() => state); | 
 |  106     return onStateChange.firstWhere(test); | 
 |  107   } | 
 |  108  | 
 |  109   AssetNode._(this.id) | 
 |  110       : _state = AssetState.DIRTY; | 
 |  111  | 
 |  112   AssetNode._available(Asset asset) | 
 |  113       : id = asset.id, | 
 |  114         _asset = asset, | 
 |  115         _state = AssetState.AVAILABLE; | 
 |  116 } | 
 |  117  | 
 |  118 /// The controller for an [AssetNode]. | 
 |  119 /// | 
 |  120 /// This controls which state the node is in. | 
 |  121 class AssetNodeController { | 
 |  122   final AssetNode node; | 
 |  123  | 
 |  124   /// Creates a controller for a dirty node. | 
 |  125   AssetNodeController(AssetId id) | 
 |  126       : node = new AssetNode._(id); | 
 |  127  | 
 |  128   /// Creates a controller for an available node with the given concrete | 
 |  129   /// [asset]. | 
 |  130   AssetNodeController.available(Asset asset) | 
 |  131       : node = new AssetNode._available(asset); | 
 |  132  | 
 |  133   /// Marks the node as [AssetState.DIRTY]. | 
 |  134   void setDirty() { | 
 |  135     assert(node._state != AssetState.REMOVED); | 
 |  136     node._state = AssetState.DIRTY; | 
 |  137     node._asset = null; | 
 |  138     node._stateChangeController.add(AssetState.DIRTY); | 
 |  139   } | 
 |  140  | 
 |  141   /// Marks the node as [AssetState.REMOVED]. | 
 |  142   /// | 
 |  143   /// Once a node is marked as removed, it can't be marked as any other state. | 
 |  144   /// If a new asset is created with the same id, it will get a new node. | 
 |  145   void setRemoved() { | 
 |  146     assert(node._state != AssetState.REMOVED); | 
 |  147     node._state = AssetState.REMOVED; | 
 |  148     node._asset = null; | 
 |  149     node._stateChangeController.add(AssetState.REMOVED); | 
 |  150   } | 
 |  151  | 
 |  152   /// Marks the node as [AssetState.AVAILABLE] with the given concrete [asset]. | 
 |  153   /// | 
 |  154   /// It's an error to mark an already-available node as available. It should be | 
 |  155   /// marked as dirty first. | 
 |  156   void setAvailable(Asset asset) { | 
 |  157     assert(asset.id == node.id); | 
 |  158     assert(node._state != AssetState.REMOVED); | 
 |  159     assert(node._state != AssetState.AVAILABLE); | 
 |  160     node._state = AssetState.AVAILABLE; | 
 |  161     node._asset = asset; | 
 |  162     node._stateChangeController.add(AssetState.AVAILABLE); | 
|   36   } |  163   } | 
|   37 } |  164 } | 
 |  165  | 
 |  166 // TODO(nweiz): add an error state. | 
 |  167 /// An enum of states that an [AssetNode] can be in. | 
 |  168 class AssetState { | 
 |  169   /// The node has a concrete asset loaded, available, and up-to-date. The asset | 
 |  170   /// is accessible via [AssetNode.asset]. An asset can only be marked available | 
 |  171   /// again from the [AssetState.DIRTY] state. | 
 |  172   static final AVAILABLE = const AssetState._("available"); | 
 |  173  | 
 |  174   /// The asset is no longer available, possibly for good. A removed asset will | 
 |  175   /// never enter another state. | 
 |  176   static final REMOVED = const AssetState._("removed"); | 
 |  177  | 
 |  178   /// The asset will exist in the future (unless it's removed), but the concrete | 
 |  179   /// asset is not yet available. | 
 |  180   static final DIRTY = const AssetState._("dirty"); | 
 |  181  | 
 |  182   /// Whether this state is [AssetState.AVAILABLE]. | 
 |  183   bool get isAvailable => this == AssetState.AVAILABLE; | 
 |  184  | 
 |  185   /// Whether this state is [AssetState.REMOVED]. | 
 |  186   bool get isRemoved => this == AssetState.REMOVED; | 
 |  187  | 
 |  188   /// Whether this state is [AssetState.DIRTY]. | 
 |  189   bool get isDirty => this == AssetState.DIRTY; | 
 |  190  | 
 |  191   final String name; | 
 |  192  | 
 |  193   const AssetState._(this.name); | 
 |  194  | 
 |  195   String toString() => name; | 
 |  196 } | 
| OLD | NEW |