Browse Source

Added LightweightObservableBase class.

And use it for `ActivatedValue`.
pull/1690/head
Steven Kirk 9 years ago
committed by Steven Kirk
parent
commit
29eafc6be2
  1. 115
      src/Avalonia.Base/Reactive/LightweightObservableBase.cs
  2. 6
      src/Avalonia.Styling/Styling/ActivatedObservable.cs
  3. 36
      src/Avalonia.Styling/Styling/ActivatedSubject.cs
  4. 76
      src/Avalonia.Styling/Styling/ActivatedValue.cs

115
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)
{
}
}
}

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

36
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;
}
}

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

Loading…
Cancel
Save