From 112484d7e40c1b85ab41d77ae21d866d2dfdac7c Mon Sep 17 00:00:00 2001 From: ahopper Date: Mon, 2 Sep 2019 11:45:15 +0100 Subject: [PATCH 1/3] reduce allocation, cache Inherited props,remove duplicate events --- src/Avalonia.Base/AvaloniaObject.cs | 37 ++++++++++------ src/Avalonia.Base/AvaloniaPropertyRegistry.cs | 44 +++++++++++++++++++ 2 files changed, 67 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index c619d80e23..34b209473b 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -82,6 +82,7 @@ namespace Avalonia set { + VerifyAccess(); if (_inheritanceParent != value) { if (_inheritanceParent != null) @@ -89,25 +90,33 @@ namespace Avalonia _inheritanceParent.InheritablePropertyChanged -= ParentPropertyChanged; } - var properties = AvaloniaPropertyRegistry.Instance.GetRegistered(this) - .Concat(AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(this.GetType())); - var inherited = (from property in properties - where property.Inherits - select new - { - Property = property, - Value = GetValue(property), - }).ToList(); - + var oldInheritanceParent = _inheritanceParent; _inheritanceParent = value; + var valuestore = _values; - foreach (var i in inherited) + foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegisteredInherited(GetType())) { - object newValue = GetValue(i.Property); + if (valuestore != null && valuestore.GetValue(property) != AvaloniaProperty.UnsetValue) + { + // if local value set there can be no change + continue; + } + // get the value as it would have been with the previous InheritanceParent + object oldValue; + if (oldInheritanceParent is AvaloniaObject aobj) + { + oldValue = aobj.GetValueOrDefaultUnchecked(property); + } + else + { + oldValue = ((IStyledPropertyAccessor)property).GetDefaultValue(GetType()); + } + + object newValue = GetDefaultValue(property); - if (!Equals(i.Value, newValue)) + if (!Equals(oldValue, newValue)) { - RaisePropertyChanged(i.Property, i.Value, newValue, BindingPriority.LocalValue); + RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue); } } diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index 88b0201fcb..d718f5917c 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -26,6 +26,8 @@ namespace Avalonia new Dictionary>(); private readonly Dictionary> _initializedCache = new Dictionary>(); + private readonly Dictionary> _inheritedCache = + new Dictionary>(); /// /// Gets the instance @@ -103,6 +105,46 @@ namespace Avalonia return result; } + /// + /// Gets all inherited s registered on a type. + /// + /// The type. + /// A collection of definitions. + public IEnumerable GetRegisteredInherited(Type type) + { + Contract.Requires(type != null); + + if (_inheritedCache.TryGetValue(type, out var result)) + { + return result; + } + + result = new List(); + var visited = new HashSet(); + + foreach (var property in GetRegistered(type)) + { + if (property.Inherits) + { + result.Add(property); + visited.Add(property); + } + } + foreach (var property in GetRegisteredAttached(type)) + { + if (property.Inherits) + { + if (!visited.Contains(property)) + { + result.Add(property); + } + } + } + + _inheritedCache.Add(type, result); + return result; + } + /// /// Gets all s registered on a object. /// @@ -230,6 +272,7 @@ namespace Avalonia _registeredCache.Clear(); _initializedCache.Clear(); + _inheritedCache.Clear(); } /// @@ -266,6 +309,7 @@ namespace Avalonia _attachedCache.Clear(); _initializedCache.Clear(); + _inheritedCache.Clear(); } internal void NotifyInitialized(AvaloniaObject o) From 11298df527718bc4466e08d51b3b70e4a2c10e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20Komosi=C5=84ski?= Date: Fri, 6 Sep 2019 14:26:19 +0200 Subject: [PATCH 2/3] Allow for setting up deferred setter notify handlers without extra allocations. --- src/Avalonia.Base/PriorityValue.cs | 14 +++++---- src/Avalonia.Base/Utilities/DeferredSetter.cs | 30 +++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs index 4996420fe7..2871271062 100644 --- a/src/Avalonia.Base/PriorityValue.cs +++ b/src/Avalonia.Base/PriorityValue.cs @@ -24,13 +24,11 @@ namespace Avalonia /// method on the /// owner object is fired with the old and new values. /// - internal class PriorityValue + internal sealed class PriorityValue : ISetAndNotifyHandler<(object,int)> { private readonly Type _valueType; private readonly SingleOrDictionary _levels = new SingleOrDictionary(); - private readonly Func _validate; - private readonly SetAndNotifyCallback<(object, int)> _setAndNotifyCallback; private (object value, int priority) _value; private DeferredSetter _setter; @@ -52,7 +50,6 @@ namespace Avalonia _valueType = valueType; _value = (AvaloniaProperty.UnsetValue, int.MaxValue); _validate = validate; - _setAndNotifyCallback = SetAndNotify; } /// @@ -257,10 +254,15 @@ namespace Avalonia _setter = Owner.GetNonDirectDeferredSetter(Property); } - _setter.SetAndNotifyCallback(Property, _setAndNotifyCallback, ref _value, newValue); + _setter.SetAndNotifyCallback(Property, this, ref _value, newValue); + } + + void ISetAndNotifyHandler<(object, int)>.HandleSetAndNotify(AvaloniaProperty property, ref (object, int) backing, (object, int) value) + { + SetAndNotify(ref backing, value); } - private void SetAndNotify(AvaloniaProperty property, ref (object value, int priority) backing, (object value, int priority) update) + private void SetAndNotify(ref (object value, int priority) backing, (object value, int priority) update) { var val = update.value; var notification = val as BindingNotification; diff --git a/src/Avalonia.Base/Utilities/DeferredSetter.cs b/src/Avalonia.Base/Utilities/DeferredSetter.cs index fd7a66fb52..fe9b0e58a0 100644 --- a/src/Avalonia.Base/Utilities/DeferredSetter.cs +++ b/src/Avalonia.Base/Utilities/DeferredSetter.cs @@ -5,15 +5,6 @@ using System; namespace Avalonia.Utilities { - /// - /// Callback invoked when deferred setter wants to set a value. - /// - /// Value type. - /// Property being set. - /// Backing field reference. - /// New value. - internal delegate void SetAndNotifyCallback(AvaloniaProperty property, ref TValue backing, TValue value); - /// /// A utility class to enable deferring assignment until after property-changed notifications are sent. /// Used to fix #855. @@ -70,14 +61,14 @@ namespace Avalonia.Utilities return false; } - public bool SetAndNotifyCallback(AvaloniaProperty property, SetAndNotifyCallback setAndNotifyCallback, ref TValue backing, TValue value) + public bool SetAndNotifyCallback(AvaloniaProperty property, ISetAndNotifyHandler setAndNotifyHandler, ref TValue backing, TValue value) where TValue : TSetRecord { if (!_isNotifying) { using (new NotifyDisposable(this)) { - setAndNotifyCallback(property, ref backing, value); + setAndNotifyHandler.HandleSetAndNotify(property, ref backing, value); } if (!_pendingValues.Empty) @@ -86,7 +77,7 @@ namespace Avalonia.Utilities { while (!_pendingValues.Empty) { - setAndNotifyCallback(property, ref backing, (TValue) _pendingValues.Dequeue()); + setAndNotifyHandler.HandleSetAndNotify(property, ref backing, (TValue)_pendingValues.Dequeue()); } } } @@ -119,4 +110,19 @@ namespace Avalonia.Utilities } } } + + /// + /// Handler for set and notify requests. + /// + /// Value type. + internal interface ISetAndNotifyHandler + { + /// + /// Handles deferred setter requests to set a value. + /// + /// Property being set. + /// Backing field reference. + /// New value. + void HandleSetAndNotify(AvaloniaProperty property, ref TValue backing, TValue value); + } } From 0eb7b2d25e458b2df2172b43fe3102119e03993a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 9 Sep 2019 18:01:43 +0200 Subject: [PATCH 3/3] Enable adding event handlers to Gestures events. Added the `Add*Handler` and `Remove*Handler` events required by XAML in order to use attached event syntax. Added unit test to ensure this works. --- src/Avalonia.Input/Gestures.cs | 30 ++++++++++++ .../Xaml/EventTests.cs | 46 +++++++++++-------- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs index ea4892ebfc..6b06151773 100644 --- a/src/Avalonia.Input/Gestures.cs +++ b/src/Avalonia.Input/Gestures.cs @@ -39,6 +39,36 @@ namespace Avalonia.Input InputElement.PointerReleasedEvent.RouteFinished.Subscribe(PointerReleased); } + public static void AddTappedHandler(IInteractive element, EventHandler handler) + { + element.AddHandler(TappedEvent, handler); + } + + public static void AddDoubleTappedHandler(IInteractive element, EventHandler handler) + { + element.AddHandler(DoubleTappedEvent, handler); + } + + public static void AddRightTappedHandler(IInteractive element, EventHandler handler) + { + element.AddHandler(RightTappedEvent, handler); + } + + public static void RemoveTappedHandler(IInteractive element, EventHandler handler) + { + element.RemoveHandler(TappedEvent, handler); + } + + public static void RemoveDoubleTappedHandler(IInteractive element, EventHandler handler) + { + element.RemoveHandler(DoubleTappedEvent, handler); + } + + public static void RemoveRightTappedHandler(IInteractive element, EventHandler handler) + { + element.RemoveHandler(RightTappedEvent, handler); + } + private static void PointerPressed(RoutedEventArgs ev) { if (ev.Route == RoutingStrategies.Bubble) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs index dcb6533b5e..bde4e34643 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/EventTests.cs @@ -1,7 +1,6 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Interactivity; @@ -12,45 +11,56 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml public class EventTests : XamlTestBase { [Fact] - public void Event_Is_Attached() + public void Event_Is_Assigned() { var xaml = @"