Browse Source

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

Jumar Macato 6 years ago
parent
commit
b017aee2d8

+ 10 - 2
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>

+ 14 - 3
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.");
             }

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

+ 17 - 3
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);
+            }
         }
     }
 }

+ 11 - 3
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)

+ 19 - 6
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);
+                    }
                 }
             }
         }

+ 1 - 1
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 - 0
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);
+            }
+        }
+    }
+}