Browse Source

Reduce memory usage of binding operations.

pull/3265/head
Dariusz Komosinski 6 years ago
parent
commit
8e60e83d4c
  1. 12
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  2. 16
      src/Avalonia.Base/Data/BindingOperations.cs
  3. 6
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  4. 19
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  5. 12
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  6. 2
      src/Avalonia.Base/Utilities/IdentifierParser.cs
  7. 43
      tests/Avalonia.Benchmarks/Data/BindingOperations.cs

12
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -173,12 +173,20 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(name != null);
if (name.Contains('.'))
if (name.Contains("."))
{
throw new InvalidOperationException("Attached properties not supported.");
}
return GetRegistered(type).FirstOrDefault(x => x.Name == name);
foreach (AvaloniaProperty x in GetRegistered(type))
{
if (x.Name == name)
{
return x;
}
}
return null;
}
/// <summary>

16
src/Avalonia.Base/Data/BindingOperations.cs

@ -56,22 +56,34 @@ namespace Avalonia.Data
if (source != null)
{
// Perf: Avoid allocating closure in the outer scope.
var targetCopy = target;
var propertyCopy = property;
var bindingCopy = binding;
return source
.Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue)
.Take(1)
.Subscribe(x => target.SetValue(property, x, binding.Priority));
.Subscribe(x => targetCopy.SetValue(propertyCopy, x, bindingCopy.Priority));
}
else
{
target.SetValue(property, binding.Value, binding.Priority);
return Disposable.Empty;
}
case BindingMode.OneWayToSource:
{
// Perf: Avoid allocating closure in the outer scope.
var bindingCopy = binding;
return Observable.CombineLatest(
binding.Observable,
target.GetObservable(property),
(_, v) => v)
.Subscribe(x => binding.Subject.OnNext(x));
.Subscribe(x => bindingCopy.Subject.OnNext(x));
}
default:
throw new ArgumentException("Invalid binding mode.");
}

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

@ -21,7 +21,7 @@ namespace Avalonia.Data.Core
/// An ordered collection of property accessor plugins that can be used to customize
/// the reading and subscription of property values on a type.
/// </summary>
public static readonly IList<IPropertyAccessorPlugin> PropertyAccessors =
public static readonly List<IPropertyAccessorPlugin> PropertyAccessors =
new List<IPropertyAccessorPlugin>
{
new AvaloniaPropertyAccessorPlugin(),
@ -33,7 +33,7 @@ namespace Avalonia.Data.Core
/// An ordered collection of validation checker plugins that can be used to customize
/// the validation of view model and model data.
/// </summary>
public static readonly IList<IDataValidationPlugin> DataValidators =
public static readonly List<IDataValidationPlugin> DataValidators =
new List<IDataValidationPlugin>
{
new DataAnnotationsValidationPlugin(),
@ -45,7 +45,7 @@ namespace Avalonia.Data.Core
/// An ordered collection of stream plugins that can be used to customize the behavior
/// of the '^' stream binding operator.
/// </summary>
public static readonly IList<IStreamPlugin> StreamHandlers =
public static readonly List<IStreamPlugin> StreamHandlers =
new List<IStreamPlugin>
{
new TaskStreamPlugin(),

19
src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs

@ -3,6 +3,7 @@
using System;
using System.Reactive.Linq;
using System.Runtime.ExceptionServices;
namespace Avalonia.Data.Core.Plugins
{
@ -76,7 +77,7 @@ namespace Avalonia.Data.Core.Plugins
return false;
}
private class Accessor : PropertyAccessorBase
private class Accessor : PropertyAccessorBase, IObserver<object>
{
private readonly WeakReference<AvaloniaObject> _reference;
private readonly AvaloniaProperty _property;
@ -117,7 +118,7 @@ namespace Avalonia.Data.Core.Plugins
protected override void SubscribeCore()
{
_subscription = Instance?.GetObservable(_property).Subscribe(PublishValue);
_subscription = Instance?.GetObservable(_property).Subscribe(this);
}
protected override void UnsubscribeCore()
@ -125,6 +126,20 @@ namespace Avalonia.Data.Core.Plugins
_subscription?.Dispose();
_subscription = null;
}
void IObserver<object>.OnCompleted()
{
}
void IObserver<object>.OnError(Exception error)
{
ExceptionDispatchInfo.Capture(error).Throw();
}
void IObserver<object>.OnNext(object value)
{
PublishValue(value);
}
}
}
}

12
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@ -41,7 +41,17 @@ namespace Avalonia.Data.Core
{
reference.TryGetTarget(out object target);
var plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(target, PropertyName));
IPropertyAccessorPlugin plugin = null;
foreach (IPropertyAccessorPlugin x in ExpressionObserver.PropertyAccessors)
{
if (x.Match(target, PropertyName))
{
plugin = x;
break;
}
}
var accessor = plugin?.Start(reference, PropertyName);
if (_enableValidation && Next == null)

2
src/Avalonia.Base/Utilities/IdentifierParser.cs

@ -15,7 +15,7 @@ namespace Avalonia.Utilities
{
if (IsValidIdentifierStart(r.Peek))
{
return r.TakeWhile(IsValidIdentifierChar);
return r.TakeWhile(c => IsValidIdentifierChar(c));
}
else
{

43
tests/Avalonia.Benchmarks/Data/BindingOperations.cs

@ -0,0 +1,43 @@
using Avalonia.Data;
using BenchmarkDotNet.Attributes;
namespace Avalonia.Benchmarks.Data
{
[MemoryDiagnoser, InProcess]
public class BindingsBenchmark
{
[Benchmark]
public void TwoWayBinding_Via_Binding()
{
var instance = new TestClass();
var binding = new Binding(nameof(TestClass.BoundValue), BindingMode.TwoWay)
{
Source = instance
};
instance.Bind(TestClass.IntValueProperty, binding);
}
private class TestClass : AvaloniaObject
{
public static readonly StyledProperty<int> IntValueProperty =
AvaloniaProperty.Register<TestClass, int>(nameof(IntValue));
public static readonly StyledProperty<int> BoundValueProperty =
AvaloniaProperty.Register<TestClass, int>(nameof(BoundValue));
public int IntValue
{
get => GetValue(IntValueProperty);
set => SetValue(IntValueProperty, value);
}
public int BoundValue
{
get => GetValue(BoundValueProperty);
set => SetValue(BoundValueProperty, value);
}
}
}
}
Loading…
Cancel
Save