Browse Source
Merge pull request #3265 from MarchingCube/perf-bindingoperations-closures
Reduce memory usage of binding operations.
pull/3279/head
Dariusz Komosiński
7 years ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with
99 additions and
15 deletions
-
src/Avalonia.Base/AvaloniaPropertyRegistry.cs
-
src/Avalonia.Base/Data/BindingOperations.cs
-
src/Avalonia.Base/Data/Core/ExpressionObserver.cs
-
src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
-
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
-
src/Avalonia.Base/Utilities/IdentifierParser.cs
-
tests/Avalonia.Benchmarks/Data/BindingsBenchmark.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>
|
|
|
|
|
|
|
|
@ -2,7 +2,6 @@ |
|
|
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
|
|
|
|
|
|
using System; |
|
|
|
using System.Linq; |
|
|
|
using System.Reactive.Disposables; |
|
|
|
using System.Reactive.Linq; |
|
|
|
|
|
|
|
@ -56,22 +55,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."); |
|
|
|
} |
|
|
|
|
|
|
|
@ -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(), |
|
|
|
|
|
|
|
@ -2,7 +2,7 @@ |
|
|
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
|
|
|
|
|
|
using System; |
|
|
|
using System.Reactive.Linq; |
|
|
|
using System.Runtime.ExceptionServices; |
|
|
|
|
|
|
|
namespace Avalonia.Data.Core.Plugins |
|
|
|
{ |
|
|
|
@ -76,7 +76,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 +117,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 +125,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); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -2,8 +2,6 @@ |
|
|
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|
|
|
|
|
|
|
using System; |
|
|
|
using System.Linq; |
|
|
|
using System.Reactive.Linq; |
|
|
|
using Avalonia.Data.Core.Plugins; |
|
|
|
|
|
|
|
namespace Avalonia.Data.Core |
|
|
|
@ -41,7 +39,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) |
|
|
|
|
|
|
|
@ -15,7 +15,7 @@ namespace Avalonia.Utilities |
|
|
|
{ |
|
|
|
if (IsValidIdentifierStart(r.Peek)) |
|
|
|
{ |
|
|
|
return r.TakeWhile(IsValidIdentifierChar); |
|
|
|
return r.TakeWhile(c => IsValidIdentifierChar(c)); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
|
|
|
|
@ -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); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |