OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012, 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 /** |
| 6 * Runtime implementation for the observe library. |
| 7 * Note that this is a runtime library; it is not part of the compiler. |
| 8 */ |
| 9 library web_ui.src.observe.impl; |
| 10 |
| 11 import 'package:web_ui/observe/expression.dart'; |
| 12 import 'package:web_ui/observe/observable.dart'; |
| 13 |
| 14 ExpressionObserverImpl activeObserver; |
| 15 |
| 16 |
| 17 class ReadInfo { |
| 18 /** See [ChangeRecord.type]. */ |
| 19 final int type; |
| 20 |
| 21 /** See [ChangeRecord.name]. */ |
| 22 final name; |
| 23 |
| 24 ReadInfo(this.type, this.name); |
| 25 |
| 26 bool operator==(other) => |
| 27 other is ReadInfo && other.type == type && other.name == name; |
| 28 |
| 29 int get hashCode => type * 31 + name.hashCode; |
| 30 } |
| 31 |
| 32 class ExpressionObserverImpl { |
| 33 final ObservableExpression _expression; |
| 34 final ExpressionObserver _observer; |
| 35 |
| 36 // TODO(jmesserly): should we provide the old value? This will keep it |
| 37 // alive until we have a new value. Watchers needed to keep it alive anyway, |
| 38 // but we don't need it in observers, except to pass it to the |
| 39 // ExpressionObserver. |
| 40 Object _value; |
| 41 |
| 42 Observable _deliverHelper; |
| 43 |
| 44 final Map<Observable, Map<Object, int>> _reads = new Map(); |
| 45 final List<ChangeUnobserver> _unobservers = []; |
| 46 |
| 47 ExpressionObserverImpl(this._expression, this._observer); |
| 48 |
| 49 void observe() { |
| 50 // If an observe call starts another observation, we need to make sure that |
| 51 // the outer observe is tracked correctly. |
| 52 var parent = activeObserver; |
| 53 activeObserver = this; |
| 54 try { |
| 55 _value = _expression(); |
| 56 } catch (e, trace) { |
| 57 onObserveUnhandledError(e, trace, _expression); |
| 58 _value = null; |
| 59 } |
| 60 |
| 61 _reads.forEach(_watchForChange); |
| 62 _reads.clear(); |
| 63 |
| 64 // TODO(jmesserly): should we add our changes to the parent? |
| 65 assert(activeObserver == this); |
| 66 activeObserver = parent; |
| 67 |
| 68 _observeValue(); |
| 69 } |
| 70 |
| 71 void _observeValue() { |
| 72 if (_value is! Observable) return; |
| 73 |
| 74 _unobservers.add((_value as Observable).observe((_) { |
| 75 _observer(new ExpressionChange(_value, _value)); |
| 76 })); |
| 77 } |
| 78 |
| 79 void addRead(Observable target, int type, name) { |
| 80 var reads = _reads.putIfAbsent(target, () => new Map()); |
| 81 // We would like to easily match against the name and the type. So use a |
| 82 // mask. |
| 83 int mask = reads[name]; |
| 84 if (mask == null) mask = 0; |
| 85 reads[name] = mask | type; |
| 86 } |
| 87 |
| 88 void _watchForChange(Observable target, Map<Object, int> reads) { |
| 89 _unobservers.add(target.observe((changes) { |
| 90 if (_deliverHelper != null) return; |
| 91 for (var change in changes) { |
| 92 int mask = reads[change.name]; |
| 93 if (mask != null && (mask & change.type) != 0) { |
| 94 // Rather than hook directly into [deliverChangesSync], we use this |
| 95 // object to trigger a ChangeRecord that is handled in the same |
| 96 // deliverChangesSync batch. |
| 97 _deliverHelper = new Observable() |
| 98 ..observe(_deliver) |
| 99 ..notifyChange(ChangeRecord.FIELD, '', 0, 1); |
| 100 break; |
| 101 } |
| 102 } |
| 103 })); |
| 104 } |
| 105 |
| 106 void unobserve() { |
| 107 for (var unobserver in _unobservers) { |
| 108 unobserver(); |
| 109 } |
| 110 _unobservers.clear(); |
| 111 _deliverHelper = null; |
| 112 } |
| 113 |
| 114 // _deliver does two things: |
| 115 // 1. Evaluate the expression to compute the new value. |
| 116 // 2. Invoke observer for this expression. |
| 117 // |
| 118 // Note: if you mutate a shared value from one observer, future |
| 119 // observers will see the updated value. Essentially, we collapse |
| 120 // the two change notifications into one. |
| 121 // |
| 122 // We could try someting else, but the current order has benefits: |
| 123 // it preserves the invariant that ExpressionChange.newValue equals the |
| 124 // current value of the expression. |
| 125 void _deliver(_) { |
| 126 var oldValue = _value; |
| 127 |
| 128 // Call the expression again to compute the new value, and to get the new |
| 129 // list of dependencies. |
| 130 unobserve(); |
| 131 observe(); |
| 132 |
| 133 bool equal; |
| 134 try { |
| 135 equal = oldValue == _value; |
| 136 } catch (e, trace) { |
| 137 onObserveUnhandledError(e, trace, null); |
| 138 return; |
| 139 } |
| 140 |
| 141 if (!equal) { |
| 142 try { |
| 143 _observer(new ExpressionChange(oldValue, _value)); |
| 144 } catch (e, trace) { |
| 145 onObserveUnhandledError(e, trace, _observer); |
| 146 } |
| 147 } |
| 148 } |
| 149 } |
OLD | NEW |