From dadc30d84ebfbb257c0a2625937efc781b525b49 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 21 Dec 2017 00:26:36 +0000 Subject: [PATCH] Use lightweight observable for ExpressionObserver. --- .../Data/Core/ExpressionObserver.cs | 105 ++++++++---------- .../Reactive/LightweightObservableBase.cs | 69 ++++++++---- .../Styling/ActivatedValue.cs | 4 +- 3 files changed, 98 insertions(+), 80 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs index 14bc09f5b7..3a25407133 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs @@ -4,18 +4,19 @@ using System; using System.Collections.Generic; using System.Reactive; -using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Reactive.Subjects; using Avalonia.Data; using Avalonia.Data.Core.Plugins; +using Avalonia.Reactive; namespace Avalonia.Data.Core { /// /// Observes and sets the value of an expression on an object. /// - public class ExpressionObserver : ObservableBase, IDescription + public class ExpressionObserver : LightweightObservableBase, + IDescription, + IObserver { /// /// An ordered collection of property accessor plugins that can be used to customize @@ -54,9 +55,10 @@ namespace Avalonia.Data.Core private static readonly object UninitializedValue = new object(); private readonly ExpressionNode _node; - private readonly Subject _finished; - private readonly object _root; - private IObservable _result; + private IDisposable _nodeSubscription; + private object _root; + private IDisposable _rootSubscription; + private WeakReference _value; /// /// Initializes a new instance of the class. @@ -107,7 +109,6 @@ namespace Avalonia.Data.Core Expression = expression; Description = description ?? expression; _node = Parse(expression, enableDataValidation); - _finished = new Subject(); _root = rootObservable; } @@ -135,8 +136,6 @@ namespace Avalonia.Data.Core Expression = expression; Description = description ?? expression; _node = Parse(expression, enableDataValidation); - _finished = new Subject(); - _node.Target = new WeakReference(rootGetter()); _root = update.Select(x => rootGetter()); } @@ -203,27 +202,42 @@ namespace Avalonia.Data.Core } } - /// - protected override IDisposable SubscribeCore(IObserver observer) + void IObserver.OnNext(object value) { - if (_result == null) - { - var source = (IObservable)_node; + var broken = BindingNotification.ExtractError(value) as MarkupBindingChainException; + broken?.Commit(Description); + _value = new WeakReference(value); + PublishNext(value); + } - if (_finished != null) - { - source = source.TakeUntil(_finished); - } + void IObserver.OnCompleted() + { + } - _result = Observable.Using(StartRoot, _ => source) - .Select(ToWeakReference) - .Publish(UninitializedValue) - .RefCount() - .Where(x => x != UninitializedValue) - .Select(Translate); - } + void IObserver.OnError(Exception error) + { + } + + protected override void Initialize() + { + _value = null; + _nodeSubscription = _node.Subscribe(this); + StartRoot(); + } + + protected override void Deinitialize() + { + _rootSubscription?.Dispose(); + _nodeSubscription?.Dispose(); + _rootSubscription = _nodeSubscription = null; + } - return _result.Subscribe(observer); + protected override void Subscribed(IObserver observer, bool first) + { + if (!first && _value != null && _value.TryGetTarget(out var value)) + { + observer.OnNext(value); + } } private static ExpressionNode Parse(string expression, bool enableDataValidation) @@ -238,42 +252,19 @@ namespace Avalonia.Data.Core } } - private static object ToWeakReference(object o) + private void StartRoot() { - return o is BindingNotification ? o : new WeakReference(o); - } - - private object Translate(object o) - { - if (o is WeakReference weak) + if (_root is IObservable observable) { - return weak.Target; + _rootSubscription = observable.Subscribe( + x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null), + x => PublishCompleted(), + () => PublishCompleted()); } - else if (BindingNotification.ExtractError(o) is MarkupBindingChainException broken) - { - broken.Commit(Description); - } - - return o; - } - - private IDisposable StartRoot() - { - switch (_root) + else { - case IObservable observable: - return observable.Subscribe( - x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null), - _ => _finished.OnNext(Unit.Default), - () => _finished.OnNext(Unit.Default)); - case WeakReference weak: - _node.Target = weak; - break; - default: - throw new AvaloniaInternalException("The ExpressionObserver._root member should only be either an observable or WeakReference."); + _node.Target = (WeakReference)_root; } - - return Disposable.Empty; } } } diff --git a/src/Avalonia.Base/Reactive/LightweightObservableBase.cs b/src/Avalonia.Base/Reactive/LightweightObservableBase.cs index 3ae6e5916b..ced2c03b01 100644 --- a/src/Avalonia.Base/Reactive/LightweightObservableBase.cs +++ b/src/Avalonia.Base/Reactive/LightweightObservableBase.cs @@ -39,26 +39,34 @@ namespace Avalonia.Reactive return Disposable.Empty; } + var first = _observers.Count == 0; + lock (_observers) { _observers.Add(observer); } - if (_observers.Count == 1) + if (first) { Initialize(); } - Subscribed(observer); + Subscribed(observer, first); return Disposable.Create(() => { - _observers?.Remove(observer); - - if (_observers?.Count == 0) + if (_observers != null) { - Deinitialize(); - _observers.TrimExcess(); + lock (_observers) + { + _observers?.Remove(observer); + + if (_observers?.Count == 0) + { + Deinitialize(); + _observers.TrimExcess(); + } + } } }); } @@ -68,9 +76,16 @@ namespace Avalonia.Reactive protected void PublishNext(T value) { - lock (_observers) + if (_observers != null) { - foreach (var observer in _observers) + IObserver[] observers; + + lock (_observers) + { + observers = _observers.ToArray(); + } + + foreach (var observer in observers) { observer.OnNext(value); } @@ -79,36 +94,48 @@ namespace Avalonia.Reactive protected void PublishCompleted() { - lock (_observers) + if (_observers != null) { - foreach (var observer in _observers) + IObserver[] observers; + + lock (_observers) + { + observers = _observers.ToArray(); + _observers = null; + } + + foreach (var observer in observers) { observer.OnCompleted(); } - _observers = null; + Deinitialize(); } - - Deinitialize(); } protected void PublishError(Exception error) { - lock (_observers) + if (_observers != null) { - foreach (var observer in _observers) + IObserver[] observers; + + lock (_observers) + { + observers = _observers.ToArray(); + _observers = null; + } + + foreach (var observer in observers) { observer.OnError(error); } - _observers = null; + _error = error; + Deinitialize(); } - - _error = error; - Deinitialize(); } - protected virtual void Subscribed(IObserver observer) + protected virtual void Subscribed(IObserver observer, bool first) { } } diff --git a/src/Avalonia.Styling/Styling/ActivatedValue.cs b/src/Avalonia.Styling/Styling/ActivatedValue.cs index 5c69c2c9aa..908d89b751 100644 --- a/src/Avalonia.Styling/Styling/ActivatedValue.cs +++ b/src/Avalonia.Styling/Styling/ActivatedValue.cs @@ -94,9 +94,9 @@ namespace Avalonia.Styling _activatorSubscription = Activator.Subscribe(Listener); } - protected override void Subscribed(IObserver observer) + protected override void Subscribed(IObserver observer, bool first) { - if (IsActive == true) + if (IsActive == true && !first) { observer.OnNext(Value); }