| 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);
|
| + }
|
| + }
|
| + }
|
| +}
|
|
|