Browse Source

Added LightweightObservableBase class.

And use it for `ActivatedValue`.
Steven Kirk 8 years ago
parent
commit
29eafc6be2

+ 115 - 0
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
+{
+    /// <summary>
+    /// Lightweight base class for observable implementations.
+    /// </summary>
+    /// <typeparam name="T">The observable type.</typeparam>
+    /// <remarks>
+    /// <see cref="ObservableBase{T}"/> 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.
+    /// </remarks>
+    public abstract class LightweightObservableBase<T> : IObservable<T>
+    {
+        private Exception _error;
+        private List<IObserver<T>> _observers = new List<IObserver<T>>();
+
+        public IDisposable Subscribe(IObserver<T> observer)
+        {
+            Contract.Requires<ArgumentNullException>(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<T> observer)
+        {
+        }
+    }
+}

+ 3 - 3
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.
     /// </summary>
     /// <remarks>
-    /// An <see cref="ActivatedObservable"/> has two inputs: an activator observable a 
+    /// An <see cref="ActivatedObservable"/> has two inputs: an activator observable and a 
     /// <see cref="Source"/> observable which produces the activated value. When the activator 
     /// produces true, the <see cref="ActivatedObservable"/> 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<object>.OnCompleted() => Parent.NotifyCompleted();
-            void IObserver<object>.OnError(Exception error) => Parent.NotifyError(error);
+            void IObserver<object>.OnCompleted() => Parent.CompletedReceived();
+            void IObserver<object>.OnError(Exception error) => Parent.ErrorReceived(error);
             void IObserver<object>.OnNext(object value) => Parent.NotifyValue(value);
         }
     }

+ 17 - 19
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.
     /// </summary>
     /// <remarks>
-    /// An <see cref="ActivatedSubject"/> has two inputs: an activator observable and either an
-    /// <see cref="ActivatedValue"/> or a <see cref="Source"/> observable which produces the
-    /// activated value. When the activator produces true, the <see cref="ActivatedObservable"/> will
-    /// produce the current activated value. When the activator produces false it will produce
-    /// <see cref="AvaloniaProperty.UnsetValue"/>.
+    /// An <see cref="ActivatedSubject"/> extends <see cref="ActivatedObservable"/> to
+    /// be an <see cref="ISubject{Object}"/>. When the object is active then values
+    /// received via <see cref="OnNext(object)"/> will be passed to the source subject.
     /// </remarks>
     internal class ActivatedSubject : ActivatedObservable, ISubject<object>, 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;
             }
         }
 

+ 18 - 58
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
     /// <see cref="ActivatedValue"/> will produce the current value. When the activator 
     /// produces false it will produce <see cref="AvaloniaProperty.UnsetValue"/>.
     /// </remarks>
-    internal class ActivatedValue : IObservable<object>, IDescription
+    internal class ActivatedValue : LightweightObservableBase<object>, IDescription
     {
         private static readonly object NotSent = new object();
-        private List<IObserver<object>> _observers = new List<IObserver<object>>();
         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<object> observer)
+        protected virtual void ActiveChanged(bool active)
         {
-            Contract.Requires<ArgumentNullException>(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<object> 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<bool>.OnCompleted() => Parent.NotifyCompleted();
-            void IObserver<bool>.OnError(Exception error) => Parent.NotifyError(error);
-            void IObserver<bool>.OnNext(bool value) => Parent.NotifyActive(value);
+            void IObserver<bool>.OnCompleted() => Parent.CompletedReceived();
+            void IObserver<bool>.OnError(Exception error) => Parent.ErrorReceived(error);
+            void IObserver<bool>.OnNext(bool value) => Parent.ActiveChanged(value);
         }
     }
 }