Browse Source

Use lightweight observable for ExpressionObserver.

pull/1690/head
Steven Kirk 9 years ago
committed by Steven Kirk
parent
commit
dadc30d84e
  1. 105
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  2. 69
      src/Avalonia.Base/Reactive/LightweightObservableBase.cs
  3. 4
      src/Avalonia.Styling/Styling/ActivatedValue.cs

105
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@ -4,18 +4,19 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive; using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
using Avalonia.Reactive;
namespace Avalonia.Data.Core namespace Avalonia.Data.Core
{ {
/// <summary> /// <summary>
/// Observes and sets the value of an expression on an object. /// Observes and sets the value of an expression on an object.
/// </summary> /// </summary>
public class ExpressionObserver : ObservableBase<object>, IDescription public class ExpressionObserver : LightweightObservableBase<object>,
IDescription,
IObserver<object>
{ {
/// <summary> /// <summary>
/// An ordered collection of property accessor plugins that can be used to customize /// 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 static readonly object UninitializedValue = new object();
private readonly ExpressionNode _node; private readonly ExpressionNode _node;
private readonly Subject<Unit> _finished; private IDisposable _nodeSubscription;
private readonly object _root; private object _root;
private IObservable<object> _result; private IDisposable _rootSubscription;
private WeakReference<object> _value;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class. /// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
@ -107,7 +109,6 @@ namespace Avalonia.Data.Core
Expression = expression; Expression = expression;
Description = description ?? expression; Description = description ?? expression;
_node = Parse(expression, enableDataValidation); _node = Parse(expression, enableDataValidation);
_finished = new Subject<Unit>();
_root = rootObservable; _root = rootObservable;
} }
@ -135,8 +136,6 @@ namespace Avalonia.Data.Core
Expression = expression; Expression = expression;
Description = description ?? expression; Description = description ?? expression;
_node = Parse(expression, enableDataValidation); _node = Parse(expression, enableDataValidation);
_finished = new Subject<Unit>();
_node.Target = new WeakReference(rootGetter()); _node.Target = new WeakReference(rootGetter());
_root = update.Select(x => rootGetter()); _root = update.Select(x => rootGetter());
} }
@ -203,27 +202,42 @@ namespace Avalonia.Data.Core
} }
} }
/// <inheritdoc/> void IObserver<object>.OnNext(object value)
protected override IDisposable SubscribeCore(IObserver<object> observer)
{ {
if (_result == null) var broken = BindingNotification.ExtractError(value) as MarkupBindingChainException;
{ broken?.Commit(Description);
var source = (IObservable<object>)_node; _value = new WeakReference<object>(value);
PublishNext(value);
}
if (_finished != null) void IObserver<object>.OnCompleted()
{ {
source = source.TakeUntil(_finished); }
}
_result = Observable.Using(StartRoot, _ => source) void IObserver<object>.OnError(Exception error)
.Select(ToWeakReference) {
.Publish(UninitializedValue) }
.RefCount()
.Where(x => x != UninitializedValue) protected override void Initialize()
.Select(Translate); {
} _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<object> observer, bool first)
{
if (!first && _value != null && _value.TryGetTarget(out var value))
{
observer.OnNext(value);
}
} }
private static ExpressionNode Parse(string expression, bool enableDataValidation) 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); if (_root is IObservable<object> observable)
}
private object Translate(object o)
{
if (o is WeakReference weak)
{ {
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) else
{
broken.Commit(Description);
}
return o;
}
private IDisposable StartRoot()
{
switch (_root)
{ {
case IObservable<object> observable: _node.Target = (WeakReference)_root;
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.");
} }
return Disposable.Empty;
} }
} }
} }

69
src/Avalonia.Base/Reactive/LightweightObservableBase.cs

@ -39,26 +39,34 @@ namespace Avalonia.Reactive
return Disposable.Empty; return Disposable.Empty;
} }
var first = _observers.Count == 0;
lock (_observers) lock (_observers)
{ {
_observers.Add(observer); _observers.Add(observer);
} }
if (_observers.Count == 1) if (first)
{ {
Initialize(); Initialize();
} }
Subscribed(observer); Subscribed(observer, first);
return Disposable.Create(() => return Disposable.Create(() =>
{ {
_observers?.Remove(observer); if (_observers != null)
if (_observers?.Count == 0)
{ {
Deinitialize(); lock (_observers)
_observers.TrimExcess(); {
_observers?.Remove(observer);
if (_observers?.Count == 0)
{
Deinitialize();
_observers.TrimExcess();
}
}
} }
}); });
} }
@ -68,9 +76,16 @@ namespace Avalonia.Reactive
protected void PublishNext(T value) protected void PublishNext(T value)
{ {
lock (_observers) if (_observers != null)
{ {
foreach (var observer in _observers) IObserver<T>[] observers;
lock (_observers)
{
observers = _observers.ToArray();
}
foreach (var observer in observers)
{ {
observer.OnNext(value); observer.OnNext(value);
} }
@ -79,36 +94,48 @@ namespace Avalonia.Reactive
protected void PublishCompleted() protected void PublishCompleted()
{ {
lock (_observers) if (_observers != null)
{ {
foreach (var observer in _observers) IObserver<T>[] observers;
lock (_observers)
{
observers = _observers.ToArray();
_observers = null;
}
foreach (var observer in observers)
{ {
observer.OnCompleted(); observer.OnCompleted();
} }
_observers = null; Deinitialize();
} }
Deinitialize();
} }
protected void PublishError(Exception error) protected void PublishError(Exception error)
{ {
lock (_observers) if (_observers != null)
{ {
foreach (var observer in _observers) IObserver<T>[] observers;
lock (_observers)
{
observers = _observers.ToArray();
_observers = null;
}
foreach (var observer in observers)
{ {
observer.OnError(error); observer.OnError(error);
} }
_observers = null; _error = error;
Deinitialize();
} }
_error = error;
Deinitialize();
} }
protected virtual void Subscribed(IObserver<T> observer) protected virtual void Subscribed(IObserver<T> observer, bool first)
{ {
} }
} }

4
src/Avalonia.Styling/Styling/ActivatedValue.cs

@ -94,9 +94,9 @@ namespace Avalonia.Styling
_activatorSubscription = Activator.Subscribe(Listener); _activatorSubscription = Activator.Subscribe(Listener);
} }
protected override void Subscribed(IObserver<object> observer) protected override void Subscribed(IObserver<object> observer, bool first)
{ {
if (IsActive == true) if (IsActive == true && !first)
{ {
observer.OnNext(Value); observer.OnNext(Value);
} }

Loading…
Cancel
Save