Browse Source

Don't use observables to detach styles.

Rather than adding a TakeUntil(control.StyleDetach) to each applied
style, keep track of the applied styles in a static list. This
dramatically reduces memory usage.
pull/471/head
Steven Kirk 10 years ago
parent
commit
fa9e7fbcd8
  1. 2
      src/Perspex.Base/Collections/PerspexList.cs
  2. 1
      src/Perspex.Base/Perspex.Base.csproj
  3. 41
      src/Perspex.Base/Reactive/ObservableEx.cs
  4. 6
      src/Perspex.Controls/Control.cs
  5. 2
      src/Perspex.Styling/Styling/ISetter.cs
  6. 5
      src/Perspex.Styling/Styling/IStyleable.cs
  7. 15
      src/Perspex.Styling/Styling/Setter.cs
  8. 46
      src/Perspex.Styling/Styling/Style.cs
  9. 4
      tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj
  10. 2
      tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs
  11. 19
      tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs
  12. 2
      tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs
  13. 2
      tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs
  14. 2
      tests/Perspex.Styling.UnitTests/TestControlBase.cs
  15. 2
      tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs

2
src/Perspex.Base/Collections/PerspexList.cs

@ -175,8 +175,6 @@ namespace Perspex.Collections
set { this[index] = (T)value; }
}
public int SubscriberCount => _collectionChanged?.GetInvocationList().Length ?? 0;
/// <summary>
/// Adds an item to the collection.
/// </summary>

1
src/Perspex.Base/Perspex.Base.csproj

@ -69,6 +69,7 @@
<Compile Include="Metadata\XmlnsDefinitionAttribute.cs" />
<Compile Include="PerspexObjectExtensions.cs" />
<Compile Include="PropertyMetadata.cs" />
<Compile Include="Reactive\ObservableEx.cs" />
<Compile Include="Reactive\WeakPropertyChangedObservable.cs" />
<Compile Include="StyledProperty.cs" />
<Compile Include="StyledPropertyBase.cs" />

41
src/Perspex.Base/Reactive/ObservableEx.cs

@ -0,0 +1,41 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Disposables;
namespace Perspex.Reactive
{
/// <summary>
/// Provides common observable methods not found in standard Rx framework.
/// </summary>
public static class ObservableEx
{
/// <summary>
/// Returns an observable that fires once with the specified value and never completes.
/// </summary>
/// <typeparam name="T">The type of the value.</typeparam>
/// <param name="value">The value.</param>
/// <returns>The observable.</returns>
public static IObservable<T> SingleValue<T>(T value)
{
return new SingleValueImpl<T>(value);
}
private class SingleValueImpl<T> : IObservable<T>
{
private T _value;
public SingleValueImpl(T value)
{
_value = value;
}
public IDisposable Subscribe(IObserver<T> observer)
{
observer.OnNext(_value);
return Disposable.Empty;
}
}
}
}

6
src/Perspex.Controls/Control.cs

@ -96,7 +96,7 @@ namespace Perspex.Controls
private INameScope _nameScope;
private Styles _styles;
private bool _styled;
private Subject<Unit> _styleDetach = new Subject<Unit>();
private Subject<IStyleable> _styleDetach = new Subject<IStyleable>();
/// <summary>
/// Initializes static members of the <see cref="Control"/> class.
@ -308,7 +308,7 @@ namespace Perspex.Controls
Type IStyleable.StyleKey => GetType();
/// <inheritdoc/>
IObservable<Unit> IStyleable.StyleDetach => _styleDetach;
IObservable<IStyleable> IStyleable.StyleDetach => _styleDetach;
/// <inheritdoc/>
IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent;
@ -534,7 +534,7 @@ namespace Perspex.Controls
}
_isAttachedToLogicalTree = false;
_styleDetach.OnNext(Unit.Default);
_styleDetach.OnNext(this);
this.TemplatedParent = null;
DetachedFromLogicalTree?.Invoke(this, e);

2
src/Perspex.Styling/Styling/ISetter.cs

@ -16,6 +16,6 @@ namespace Perspex.Styling
/// <param name="style">The style that is being applied.</param>
/// <param name="control">The control.</param>
/// <param name="activator">An optional activator.</param>
void Apply(IStyle style, IStyleable control, IObservable<bool> activator);
IDisposable Apply(IStyle style, IStyleable control, IObservable<bool> activator);
}
}

5
src/Perspex.Styling/Styling/IStyleable.cs

@ -3,7 +3,6 @@
using System;
using Perspex.Collections;
using System.Reactive;
namespace Perspex.Styling
{
@ -13,9 +12,9 @@ namespace Perspex.Styling
public interface IStyleable : IPerspexObject, INamed
{
/// <summary>
/// Raised when the control's style should be removed.
/// Signalled when the control's style should be removed.
/// </summary>
IObservable<Unit> StyleDetach { get; }
IObservable<IStyleable> StyleDetach { get; }
/// <summary>
/// Gets the list of classes for the control.

15
src/Perspex.Styling/Styling/Setter.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using Perspex.Data;
using Perspex.Metadata;
@ -63,7 +64,7 @@ namespace Perspex.Styling
/// <param name="style">The style that is being applied.</param>
/// <param name="control">The control.</param>
/// <param name="activator">An optional activator.</param>
public void Apply(IStyle style, IStyleable control, IObservable<bool> activator)
public IDisposable Apply(IStyle style, IStyleable control, IObservable<bool> activator)
{
Contract.Requires<ArgumentNullException>(control != null);
@ -80,16 +81,12 @@ namespace Perspex.Styling
{
if (activator == null)
{
control.SetValue(Property, Value, BindingPriority.Style);
return control.Bind(Property, ObservableEx.SingleValue(Value), BindingPriority.Style);
}
else
{
var activated = new ActivatedValue(activator, Value, description);
var instanced = new InstancedBinding(
activated,
BindingMode.OneWay,
BindingPriority.StyleTrigger);
BindingOperations.Apply(control, Property, instanced, null);
return control.Bind(Property, activated, BindingPriority.StyleTrigger);
}
}
else
@ -99,9 +96,11 @@ namespace Perspex.Styling
if (source != null)
{
var cloned = Clone(source, style, activator);
BindingOperations.Apply(control, Property, cloned, null);
return BindingOperations.Apply(control, Property, cloned, null);
}
}
return Disposable.Empty;
}
private InstancedBinding Clone(InstancedBinding sourceInstance, IStyle style, IObservable<bool> activator)

46
src/Perspex.Styling/Styling/Style.cs

@ -13,7 +13,9 @@ namespace Perspex.Styling
/// </summary>
public class Style : IStyle
{
private static readonly IObservable<bool> True = Observable.Never<bool>().StartWith(true);
private static Dictionary<IStyleable, List<IDisposable>> _applied =
new Dictionary<IStyleable, List<IDisposable>>();
private Dictionary<string, object> _resources;
/// <summary>
@ -85,20 +87,23 @@ namespace Perspex.Styling
if (match.ImmediateResult != false)
{
var activator = (match.ObservableResult ?? True)
.TakeUntil(control.StyleDetach);
var subs = GetSubscriptions(control);
foreach (var setter in Setters)
{
setter.Apply(this, control, activator);
var sub = setter.Apply(this, control, match.ObservableResult);
subs.Add(sub);
}
}
}
else if (control == container)
{
var subs = GetSubscriptions(control);
foreach (var setter in Setters)
{
setter.Apply(this, control, null);
var sub = setter.Apply(this, control, null);
subs.Add(sub);
}
}
}
@ -139,5 +144,36 @@ namespace Perspex.Styling
return "Style";
}
}
private static List<IDisposable> GetSubscriptions(IStyleable control)
{
List<IDisposable> subscriptions;
if (!_applied.TryGetValue(control, out subscriptions))
{
subscriptions = new List<IDisposable>(2);
subscriptions.Add(control.StyleDetach.Subscribe(ControlDetach));
_applied.Add(control, subscriptions);
}
return subscriptions;
}
/// <summary>
/// Called when a control's <see cref="IStyleable.StyleDetach"/> is signalled to remove
/// all applied styles.
/// </summary>
/// <param name="control">The control.</param>
private static void ControlDetach(IStyleable control)
{
var subscriptions = _applied[control];
foreach (var subscription in subscriptions)
{
subscription.Dispose();
}
_applied.Remove(control);
}
}
}

4
tests/Perspex.Base.UnitTests/Perspex.Base.UnitTests.csproj

@ -69,6 +69,10 @@
<HintPath>..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="xunit.abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c">
<HintPath>..\..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll</HintPath>
</Reference>

2
tests/Perspex.Markup.Xaml.UnitTests/Converters/PerspexPropertyConverterTest.cs

@ -97,7 +97,7 @@ namespace Perspex.Markup.Xaml.UnitTests.Converters
get { throw new NotImplementedException(); }
}
IObservable<Unit> IStyleable.StyleDetach { get; }
IObservable<IStyleable> IStyleable.StyleDetach { get; }
}
private class AttachedOwner

19
tests/Perspex.Styling.UnitTests/ActivatedValueTests.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Microsoft.Reactive.Testing;
using Xunit;
namespace Perspex.Styling.UnitTests
@ -38,5 +39,23 @@ namespace Perspex.Styling.UnitTests
Assert.True(completed);
}
[Fact]
public void Should_Unsubscribe_From_Activator_When_All_Subscriptions_Disposed()
{
var scheduler = new TestScheduler();
var activator1 = scheduler.CreateColdObservable<bool>();
var activator2 = scheduler.CreateColdObservable<bool>();
var activator = StyleActivator.And(new[] { activator1, activator2 });
var target = new ActivatedValue(activator, 1, string.Empty);
var subscription = target.Subscribe(_ => { });
Assert.Equal(1, activator1.Subscriptions.Count);
Assert.Equal(Subscription.Infinite, activator1.Subscriptions[0].Unsubscribe);
subscription.Dispose();
Assert.Equal(1, activator1.Subscriptions.Count);
Assert.Equal(0, activator1.Subscriptions[0].Unsubscribe);
}
}
}

2
tests/Perspex.Styling.UnitTests/SelectorTests_Child.cs

@ -105,7 +105,7 @@ namespace Perspex.Styling.UnitTests
public ITemplatedControl TemplatedParent { get; }
IObservable<Unit> IStyleable.StyleDetach { get; }
IObservable<IStyleable> IStyleable.StyleDetach { get; }
IPerspexReadOnlyList<string> IStyleable.Classes => Classes;

2
tests/Perspex.Styling.UnitTests/SelectorTests_Descendent.cs

@ -138,7 +138,7 @@ namespace Perspex.Styling.UnitTests
IPerspexReadOnlyList<string> IStyleable.Classes => Classes;
IObservable<Unit> IStyleable.StyleDetach { get; }
IObservable<IStyleable> IStyleable.StyleDetach { get; }
public object GetValue(PerspexProperty property)
{

2
tests/Perspex.Styling.UnitTests/TestControlBase.cs

@ -35,7 +35,7 @@ namespace Perspex.Styling.UnitTests
IPerspexReadOnlyList<string> IStyleable.Classes => Classes;
IObservable<Unit> IStyleable.StyleDetach { get; }
IObservable<IStyleable> IStyleable.StyleDetach { get; }
public object GetValue(PerspexProperty property)
{

2
tests/Perspex.Styling.UnitTests/TestTemplatedControl.cs

@ -35,7 +35,7 @@ namespace Perspex.Styling.UnitTests
IPerspexReadOnlyList<string> IStyleable.Classes => Classes;
IObservable<Unit> IStyleable.StyleDetach { get; }
IObservable<IStyleable> IStyleable.StyleDetach { get; }
public object GetValue(PerspexProperty property)
{

Loading…
Cancel
Save