From 29eafc6be226ab7932fd5abe616b9f43ba48c4ff Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 20 Dec 2017 22:15:35 +0000 Subject: [PATCH] Added LightweightObservableBase class. And use it for `ActivatedValue`. --- .../Reactive/LightweightObservableBase.cs | 115 ++++++++++++++++++ .../Styling/ActivatedObservable.cs | 6 +- .../Styling/ActivatedSubject.cs | 36 +++--- .../Styling/ActivatedValue.cs | 76 +++--------- 4 files changed, 153 insertions(+), 80 deletions(-) create mode 100644 src/Avalonia.Base/Reactive/LightweightObservableBase.cs diff --git a/src/Avalonia.Base/Reactive/LightweightObservableBase.cs b/src/Avalonia.Base/Reactive/LightweightObservableBase.cs new file mode 100644 index 0000000000..3ae6e5916b --- /dev/null +++ b/src/Avalonia.Base/Reactive/LightweightObservableBase.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Reactive.Disposables; +using Avalonia.Threading; + +namespace Avalonia.Reactive +{ + /// + /// Lightweight base class for observable implementations. + /// + /// The observable type. + /// + /// is rather heavyweight in terms of allocations and memory + /// usage. This class provides a more lightweight base for some internal observable types + /// in the Avalonia framework. + /// + public abstract class LightweightObservableBase : IObservable + { + private Exception _error; + private List> _observers = new List>(); + + public IDisposable Subscribe(IObserver observer) + { + Contract.Requires(observer != null); + Dispatcher.UIThread.VerifyAccess(); + + if (_observers == null) + { + if (_error != null) + { + observer.OnError(_error); + } + else + { + observer.OnCompleted(); + } + + return Disposable.Empty; + } + + lock (_observers) + { + _observers.Add(observer); + } + + if (_observers.Count == 1) + { + Initialize(); + } + + Subscribed(observer); + + return Disposable.Create(() => + { + _observers?.Remove(observer); + + if (_observers?.Count == 0) + { + Deinitialize(); + _observers.TrimExcess(); + } + }); + } + + protected abstract void Initialize(); + protected abstract void Deinitialize(); + + protected void PublishNext(T value) + { + lock (_observers) + { + foreach (var observer in _observers) + { + observer.OnNext(value); + } + } + } + + protected void PublishCompleted() + { + lock (_observers) + { + foreach (var observer in _observers) + { + observer.OnCompleted(); + } + + _observers = null; + } + + Deinitialize(); + } + + protected void PublishError(Exception error) + { + lock (_observers) + { + foreach (var observer in _observers) + { + observer.OnError(error); + } + + _observers = null; + } + + _error = error; + Deinitialize(); + } + + protected virtual void Subscribed(IObserver observer) + { + } + } +} diff --git a/src/Avalonia.Styling/Styling/ActivatedObservable.cs b/src/Avalonia.Styling/Styling/ActivatedObservable.cs index 4933daed31..5b2774943a 100644 --- a/src/Avalonia.Styling/Styling/ActivatedObservable.cs +++ b/src/Avalonia.Styling/Styling/ActivatedObservable.cs @@ -9,7 +9,7 @@ namespace Avalonia.Styling /// An observable which is switched on or off according to an activator observable. /// /// - /// An has two inputs: an activator observable a + /// An has two inputs: an activator observable and a /// observable which produces the activated value. When the activator /// produces true, the will produce the current activated /// value. When the activator produces false it will produce @@ -69,8 +69,8 @@ namespace Avalonia.Styling } protected new ActivatedObservable Parent => (ActivatedObservable)base.Parent; - void IObserver.OnCompleted() => Parent.NotifyCompleted(); - void IObserver.OnError(Exception error) => Parent.NotifyError(error); + void IObserver.OnCompleted() => Parent.CompletedReceived(); + void IObserver.OnError(Exception error) => Parent.ErrorReceived(error); void IObserver.OnNext(object value) => Parent.NotifyValue(value); } } diff --git a/src/Avalonia.Styling/Styling/ActivatedSubject.cs b/src/Avalonia.Styling/Styling/ActivatedSubject.cs index bcbf30649a..a8446c4bfb 100644 --- a/src/Avalonia.Styling/Styling/ActivatedSubject.cs +++ b/src/Avalonia.Styling/Styling/ActivatedSubject.cs @@ -10,11 +10,9 @@ namespace Avalonia.Styling /// A subject which is switched on or off according to an activator observable. /// /// - /// An has two inputs: an activator observable and either an - /// or a observable which produces the - /// activated value. When the activator produces true, the will - /// produce the current activated value. When the activator produces false it will produce - /// . + /// An extends to + /// be an . When the object is active then values + /// received via will be passed to the source subject. /// internal class ActivatedSubject : ActivatedObservable, ISubject, IDescription { @@ -63,37 +61,37 @@ namespace Avalonia.Styling } } - protected override void NotifyCompleted() + protected override void ActiveChanged(bool active) { - base.NotifyCompleted(); + bool first = !IsActive.HasValue; - if (!_completed) + base.ActiveChanged(active); + + if (!first) { - Source.OnCompleted(); - _completed = true; + Source.OnNext(active ? _pushValue : AvaloniaProperty.UnsetValue); } } - protected override void NotifyError(Exception error) + protected override void CompletedReceived() { - base.NotifyError(error); + base.CompletedReceived(); if (!_completed) { - Source.OnError(error); + Source.OnCompleted(); _completed = true; } } - protected override void NotifyActive(bool active) + protected override void ErrorReceived(Exception error) { - bool first = !IsActive.HasValue; + base.ErrorReceived(error); - base.NotifyActive(active); - - if (!first) + if (!_completed) { - Source.OnNext(active ? _pushValue : AvaloniaProperty.UnsetValue); + Source.OnError(error); + _completed = true; } } diff --git a/src/Avalonia.Styling/Styling/ActivatedValue.cs b/src/Avalonia.Styling/Styling/ActivatedValue.cs index a68ae1e435..5c69c2c9aa 100644 --- a/src/Avalonia.Styling/Styling/ActivatedValue.cs +++ b/src/Avalonia.Styling/Styling/ActivatedValue.cs @@ -2,9 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; -using System.Collections.Generic; -using System.Reactive.Disposables; -using Avalonia.Threading; +using Avalonia.Reactive; namespace Avalonia.Styling { @@ -17,10 +15,9 @@ namespace Avalonia.Styling /// will produce the current value. When the activator /// produces false it will produce . /// - internal class ActivatedValue : IObservable, IDescription + internal class ActivatedValue : LightweightObservableBase, IDescription { private static readonly object NotSent = new object(); - private List> _observers = new List>(); private IDisposable _activatorSubscription; private object _value; private object _last = NotSent; @@ -74,68 +71,35 @@ namespace Avalonia.Styling protected ActivatorListener Listener { get; } - public virtual IDisposable Subscribe(IObserver observer) + protected virtual void ActiveChanged(bool active) { - Contract.Requires(observer != null); - Dispatcher.UIThread.VerifyAccess(); - - _observers.Add(observer); - - if (_observers.Count == 1) - { - Initialize(); - } - - return Disposable.Create(() => - { - _observers.Remove(observer); - - if (_observers.Count == 0) - { - Deinitialize(); - } - }); + IsActive = active; + PublishValue(); } + protected virtual void CompletedReceived() => PublishCompleted(); + protected virtual ActivatorListener CreateListener() => new ActivatorListener(this); - protected virtual void Deinitialize() + protected override void Deinitialize() { _activatorSubscription.Dispose(); _activatorSubscription = null; } - protected virtual void Initialize() - { - _activatorSubscription = Activator.Subscribe(Listener); - } + protected virtual void ErrorReceived(Exception error) => PublishError(error); - protected virtual void NotifyCompleted() + protected override void Initialize() { - foreach (var observer in _observers) - { - observer.OnCompleted(); - } - - Deinitialize(); - _observers = null; + _activatorSubscription = Activator.Subscribe(Listener); } - protected virtual void NotifyError(Exception error) + protected override void Subscribed(IObserver observer) { - foreach (var observer in _observers) + if (IsActive == true) { - observer.OnError(error); + observer.OnNext(Value); } - - Deinitialize(); - _observers = null; - } - - protected virtual void NotifyActive(bool active) - { - IsActive = active; - PublishValue(); } private void PublishValue() @@ -146,11 +110,7 @@ namespace Avalonia.Styling if (!Equals(v, _last)) { - foreach (var observer in _observers) - { - observer.OnNext(v); - } - + PublishNext(v); _last = v; } } @@ -165,9 +125,9 @@ namespace Avalonia.Styling protected ActivatedValue Parent { get; } - void IObserver.OnCompleted() => Parent.NotifyCompleted(); - void IObserver.OnError(Exception error) => Parent.NotifyError(error); - void IObserver.OnNext(bool value) => Parent.NotifyActive(value); + void IObserver.OnCompleted() => Parent.CompletedReceived(); + void IObserver.OnError(Exception error) => Parent.ErrorReceived(error); + void IObserver.OnNext(bool value) => Parent.ActiveChanged(value); } } }