Browse Source

Merge branch 'master' into visual-tree-traversal-v2

pull/3253/head
Jumar Macato 6 years ago
committed by GitHub
parent
commit
b017aee2d8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  2. 17
      src/Avalonia.Base/Data/BindingOperations.cs
  3. 6
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  4. 20
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  5. 14
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  6. 25
      src/Avalonia.Base/Reactive/LightweightObservableBase.cs
  7. 2
      src/Avalonia.Base/Utilities/IdentifierParser.cs
  8. 59
      tests/Avalonia.Benchmarks/Data/BindingsBenchmark.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>

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

@ -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.");
}

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(),

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

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

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

@ -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)

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

@ -116,20 +116,33 @@ namespace Avalonia.Reactive
{
if (Volatile.Read(ref _observers) != null)
{
IObserver<T>[] observers;
IObserver<T>[] observers = null;
IObserver<T> singleObserver = null;
lock (this)
{
if (_observers == null)
{
return;
}
observers = _observers.ToArray();
if (_observers.Count == 1)
{
singleObserver = _observers[0];
}
else
{
observers = _observers.ToArray();
}
}
foreach (var observer in observers)
if (singleObserver != null)
{
observer.OnNext(value);
singleObserver.OnNext(value);
}
else
{
foreach (var observer in observers)
{
observer.OnNext(value);
}
}
}
}

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
{

59
tests/Avalonia.Benchmarks/Data/BindingsBenchmark.cs

@ -0,0 +1,59 @@
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);
}
[Benchmark]
public void UpdateTwoWayBinding_Via_Binding()
{
var instance = new TestClass();
var binding = new Binding(nameof(TestClass.BoundValue), BindingMode.TwoWay)
{
Source = instance
};
instance.Bind(TestClass.IntValueProperty, binding);
for (int i = 0; i < 60; i++)
{
instance.IntValue = i;
}
}
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