Index: lib/src/observe/impl.dart |
diff --git a/lib/src/observe/impl.dart b/lib/src/observe/impl.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..753fe352cacad3db409691ae6cda1d498598fe60 |
--- /dev/null |
+++ b/lib/src/observe/impl.dart |
@@ -0,0 +1,149 @@ |
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+/** |
+ * Runtime implementation for the observe library. |
+ * Note that this is a runtime library; it is not part of the compiler. |
+ */ |
+library web_ui.src.observe.impl; |
+ |
+import 'package:web_ui/observe/expression.dart'; |
+import 'package:web_ui/observe/observable.dart'; |
+ |
+ExpressionObserverImpl activeObserver; |
+ |
+ |
+class ReadInfo { |
+ /** See [ChangeRecord.type]. */ |
+ final int type; |
+ |
+ /** See [ChangeRecord.name]. */ |
+ final name; |
+ |
+ ReadInfo(this.type, this.name); |
+ |
+ bool operator==(other) => |
+ other is ReadInfo && other.type == type && other.name == name; |
+ |
+ int get hashCode => type * 31 + name.hashCode; |
+} |
+ |
+class ExpressionObserverImpl { |
+ final ObservableExpression _expression; |
+ final ExpressionObserver _observer; |
+ |
+ // TODO(jmesserly): should we provide the old value? This will keep it |
+ // alive until we have a new value. Watchers needed to keep it alive anyway, |
+ // but we don't need it in observers, except to pass it to the |
+ // ExpressionObserver. |
+ Object _value; |
+ |
+ Observable _deliverHelper; |
+ |
+ final Map<Observable, Map<Object, int>> _reads = new Map(); |
+ final List<ChangeUnobserver> _unobservers = []; |
+ |
+ ExpressionObserverImpl(this._expression, this._observer); |
+ |
+ void observe() { |
+ // If an observe call starts another observation, we need to make sure that |
+ // the outer observe is tracked correctly. |
+ var parent = activeObserver; |
+ activeObserver = this; |
+ try { |
+ _value = _expression(); |
+ } catch (e, trace) { |
+ onObserveUnhandledError(e, trace, _expression); |
+ _value = null; |
+ } |
+ |
+ _reads.forEach(_watchForChange); |
+ _reads.clear(); |
+ |
+ // TODO(jmesserly): should we add our changes to the parent? |
+ assert(activeObserver == this); |
+ activeObserver = parent; |
+ |
+ _observeValue(); |
+ } |
+ |
+ void _observeValue() { |
+ if (_value is! Observable) return; |
+ |
+ _unobservers.add((_value as Observable).observe((_) { |
+ _observer(new ExpressionChange(_value, _value)); |
+ })); |
+ } |
+ |
+ void addRead(Observable target, int type, name) { |
+ var reads = _reads.putIfAbsent(target, () => new Map()); |
+ // We would like to easily match against the name and the type. So use a |
+ // mask. |
+ int mask = reads[name]; |
+ if (mask == null) mask = 0; |
+ reads[name] = mask | type; |
+ } |
+ |
+ void _watchForChange(Observable target, Map<Object, int> reads) { |
+ _unobservers.add(target.observe((changes) { |
+ if (_deliverHelper != null) return; |
+ for (var change in changes) { |
+ int mask = reads[change.name]; |
+ if (mask != null && (mask & change.type) != 0) { |
+ // Rather than hook directly into [deliverChangesSync], we use this |
+ // object to trigger a ChangeRecord that is handled in the same |
+ // deliverChangesSync batch. |
+ _deliverHelper = new Observable() |
+ ..observe(_deliver) |
+ ..notifyChange(ChangeRecord.FIELD, '', 0, 1); |
+ break; |
+ } |
+ } |
+ })); |
+ } |
+ |
+ void unobserve() { |
+ for (var unobserver in _unobservers) { |
+ unobserver(); |
+ } |
+ _unobservers.clear(); |
+ _deliverHelper = null; |
+ } |
+ |
+ // _deliver does two things: |
+ // 1. Evaluate the expression to compute the new value. |
+ // 2. Invoke observer for this expression. |
+ // |
+ // Note: if you mutate a shared value from one observer, future |
+ // observers will see the updated value. Essentially, we collapse |
+ // the two change notifications into one. |
+ // |
+ // We could try someting else, but the current order has benefits: |
+ // it preserves the invariant that ExpressionChange.newValue equals the |
+ // current value of the expression. |
+ void _deliver(_) { |
+ var oldValue = _value; |
+ |
+ // Call the expression again to compute the new value, and to get the new |
+ // list of dependencies. |
+ unobserve(); |
+ observe(); |
+ |
+ bool equal; |
+ try { |
+ equal = oldValue == _value; |
+ } catch (e, trace) { |
+ onObserveUnhandledError(e, trace, null); |
+ return; |
+ } |
+ |
+ if (!equal) { |
+ try { |
+ _observer(new ExpressionChange(oldValue, _value)); |
+ } catch (e, trace) { |
+ onObserveUnhandledError(e, trace, _observer); |
+ } |
+ } |
+ } |
+} |