From 640a54373be64c2708f88dfd75ff69e4f3ac7f8b Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Tue, 18 May 2021 22:47:28 -0400 Subject: [PATCH] perf: Improve DependencyPropertyDetails performance --- .../Given_DependencyProperty.cs | 40 +++ .../UI/Xaml/DependencyPropertyDetails.cs | 277 ++++++++++++++---- .../DependencyPropertyDetailsCollection.cs | 2 +- 3 files changed, 261 insertions(+), 58 deletions(-) diff --git a/src/Uno.UI.Tests/DependencyProperty/Given_DependencyProperty.cs b/src/Uno.UI.Tests/DependencyProperty/Given_DependencyProperty.cs index cf70655c69ba..338d7c3533c7 100644 --- a/src/Uno.UI.Tests/DependencyProperty/Given_DependencyProperty.cs +++ b/src/Uno.UI.Tests/DependencyProperty/Given_DependencyProperty.cs @@ -1644,6 +1644,13 @@ public void When_Set_With_Both_Style_And_LocalValue() #endif } + [TestMethod] + public void When_DefaultValueOverride() + { + var SUT = new MyDependencyObjectWithDefaultValueOverride(); + Assert.AreEqual(42, SUT.GetValue(MyDependencyObjectWithDefaultValueOverride.MyPropertyProperty)); + } + private class MyDependencyObject : FrameworkElement { internal static readonly DependencyProperty PropAProperty = DependencyProperty.Register( @@ -1790,5 +1797,38 @@ private void OnMyNullableChanged(DependencyPropertyChangedEventArgs e) } + partial class MyDependencyObjectWithDefaultValueOverride : DependencyObject + { + public MyDependencyObjectWithDefaultValueOverride() + { + this.RegisterDefaultValueProvider(OnProvideDefaultValue); + } + + private bool OnProvideDefaultValue(DependencyProperty property, out object defaultValue) + { + if(property == MyPropertyProperty) + { + defaultValue = 42; + + return true; + } + + defaultValue = null; + return false; + } + + public int MyProperty + { + get { return (int)GetValue(MyPropertyProperty); } + set { SetValue(MyPropertyProperty, value); } + } + + // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc... + public static readonly DependencyProperty MyPropertyProperty = + DependencyProperty.Register("MyProperty", typeof(int), typeof(MyDependencyObjectWithDefaultValueOverride), new PropertyMetadata(0)); + + } + + #endregion } diff --git a/src/Uno.UI/UI/Xaml/DependencyPropertyDetails.cs b/src/Uno.UI/UI/Xaml/DependencyPropertyDetails.cs index d4ace54cce2d..28b0d7652cd7 100644 --- a/src/Uno.UI/UI/Xaml/DependencyPropertyDetails.cs +++ b/src/Uno.UI/UI/Xaml/DependencyPropertyDetails.cs @@ -1,4 +1,6 @@ -using System; +#nullable enable + +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -13,14 +15,19 @@ namespace Windows.UI.Xaml /// /// Represents the stack of values used by the Dependency Property Value Precedence system /// - internal class DependencyPropertyDetails : IEnumerable, IEnumerable, IDisposable + internal class DependencyPropertyDetails : IEnumerable, IEnumerable, IDisposable { private DependencyPropertyValuePrecedences _highestPrecedence = DependencyPropertyValuePrecedences.DefaultValue; - private BindingExpression _lastBindings; - private static readonly ArrayPool _pool = ArrayPool.Create(100, 100); - private readonly object[] _stack; - private readonly bool _hasWeakStorage; - private readonly List _bindings = new List(); + private readonly Type _dependencyObjectType; + private object? _fastLocalValue; + private BindingExpression? _lastBindings; + private static readonly ArrayPool _pool = ArrayPool.Create(100, 100); + private object?[]? _stack; + private PropertyMetadata? _metadata; + private object? _defaultValue; + private List? _bindings; + private Flags _flags; + private DependencyPropertyCallbackManager? _callbackManager; private const int MaxIndex = (int)DependencyPropertyValuePrecedences.DefaultValue; private const int _stackLength = MaxIndex + 1; @@ -34,45 +41,57 @@ static DependencyPropertyDetails() } } - private DependencyPropertyDetails() - { - _stack = _pool.Rent(_stackLength); - } + private List Bindings => _bindings ??= new List(); + + private bool HasBindingsList => _bindings != null; public void Dispose() { CallbackManager.Dispose(); - _pool.Return(_stack, clearArray: true); + + if (_stack != null) + { + _pool.Return(_stack, clearArray: true); + } } - public DependencyPropertyCallbackManager CallbackManager { get; } = new DependencyPropertyCallbackManager(); + public DependencyPropertyCallbackManager CallbackManager => _callbackManager ??= new DependencyPropertyCallbackManager(); public DependencyProperty Property { get; } - public PropertyMetadata Metadata { get; } + public PropertyMetadata Metadata => _metadata ??= Property.GetMetadata(_dependencyObjectType); /// /// Constructor /// /// The default value of the Dependency Property - internal DependencyPropertyDetails(DependencyProperty property, Type dependencyObjectType) : this() + internal DependencyPropertyDetails(DependencyProperty property, Type dependencyObjectType) { - Property = property; - _hasWeakStorage = property.HasWeakStorage; - - Array.Copy(_unsetStack, _stack, _stackLength); - - var defaultValue = Property.GetMetadata(dependencyObjectType).DefaultValue; + Property = property; + _dependencyObjectType = dependencyObjectType; - // Ensures that the default value of non-nullable properties is not null - if (defaultValue == null && !Property.IsTypeNullable) + if (property.HasWeakStorage) { - defaultValue = Property.GetFallbackDefaultValue(); + _flags |= Flags.WeakStorage; } + } + + private object? GetDefaultValue() + { + if (!HasDefaultValueSet) + { + _defaultValue = Property.GetMetadata(_dependencyObjectType).DefaultValue; + + // Ensures that the default value of non-nullable properties is not null + if (_defaultValue == null && !Property.IsTypeNullable) + { + _defaultValue = Property.GetFallbackDefaultValue(); + } - _stack[MaxIndex] = defaultValue; + _flags |= Flags.DefaultValueSet; + } - Metadata = property.GetMetadata(dependencyObjectType); + return _defaultValue; } /// @@ -80,30 +99,49 @@ internal DependencyPropertyDetails(DependencyProperty property, Type dependencyO /// /// The value to set /// The precedence level to set the value at - internal void SetValue(object value, DependencyPropertyValuePrecedences precedence) + internal void SetValue(object? value, DependencyPropertyValuePrecedences precedence) { - if (_hasWeakStorage && _stack[(int)precedence] is ManagedWeakReference mwr) + if (!SetValueFast(value, precedence)) { - WeakReferencePool.ReturnWeakReference(this, mwr); + SetValueFull(value, precedence); } + } + + private void SetValueFull(object? value, DependencyPropertyValuePrecedences precedence) + { + var valueIsUnsetValue = value is UnsetValue; + + var stackAlias = Stack; + + if (HasWeakStorage) + { + if (stackAlias[(int)precedence] is ManagedWeakReference mwr) + { + WeakReferencePool.ReturnWeakReference(this, mwr); + } - _stack[(int)precedence] = Validate(value); + stackAlias[(int)precedence] = Validate(value); + } + else + { + stackAlias[(int)precedence] = ValidateNoWrap(value); + } // After setting the value, we need to update the current highest precedence if needed // If a higher value precedence was set, then this is the new highest - if (!(value is UnsetValue) && precedence < _highestPrecedence) + if (!valueIsUnsetValue && precedence < _highestPrecedence) { _highestPrecedence = precedence; return; } // If we were unsetting the current highest precedence value, we need to find the next highest - if (value is UnsetValue && precedence == _highestPrecedence) + if (valueIsUnsetValue && precedence == _highestPrecedence) { // Start from current precedence and find next highest for (int i = (int)precedence; i < (int)DependencyPropertyValuePrecedences.DefaultValue; i++) { - if (_stack[i] != DependencyProperty.UnsetValue) + if (stackAlias[i] != DependencyProperty.UnsetValue) { _highestPrecedence = (DependencyPropertyValuePrecedences)i; return; @@ -114,20 +152,59 @@ internal void SetValue(object value, DependencyPropertyValuePrecedences preceden } } - internal BindingExpression GetLastBinding() + private bool SetValueFast(object? value, DependencyPropertyValuePrecedences precedence) + { + if (_stack == null && precedence == DependencyPropertyValuePrecedences.Local) + { + var valueIsUnsetValue = value is UnsetValue; + + if (HasWeakStorage) + { + if (_fastLocalValue is ManagedWeakReference mwr2) + { + WeakReferencePool.ReturnWeakReference(this, mwr2); + } + + _fastLocalValue = Validate(value); + } + else + { + _fastLocalValue = ValidateNoWrap(value); + } + + _highestPrecedence = valueIsUnsetValue + ? DependencyPropertyValuePrecedences.DefaultValue + : DependencyPropertyValuePrecedences.Local; + + return true; + } + + return false; + } + + internal void SetDefaultValue(object defaultValue) + { + _defaultValue = defaultValue; + _flags |= Flags.DefaultValueSet; + } + + internal BindingExpression? GetLastBinding() => _lastBindings; internal void SetBinding(BindingExpression bindingExpression) { - _bindings.Add(bindingExpression); + Bindings.Add(bindingExpression); _lastBindings = bindingExpression; } internal void SetSourceValue(object value) { - for (int i = 0; i < _bindings.Count; i++) + if (HasBindingsList) { - _bindings[i].SetSourceValue(value); + for (int i = 0; i < Bindings.Count; i++) + { + Bindings[i].SetSourceValue(value); + } } } @@ -135,7 +212,7 @@ internal void SetSourceValue(object value) /// Gets the value at the current highest precedence level /// /// The value at the current highest precedence level - internal object GetValue() + internal object? GetValue() => GetValue(_highestPrecedence); /// @@ -144,8 +221,22 @@ internal object GetValue() /// The precedence level to get the value at /// The value at a given precedence level [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal object GetValue(DependencyPropertyValuePrecedences precedence) - => Unwrap(_stack[(int)precedence]); + internal object? GetValue(DependencyPropertyValuePrecedences precedence) + { + if (_stack == null) + { + return precedence switch + { + DependencyPropertyValuePrecedences.DefaultValue => GetDefaultValue(), + DependencyPropertyValuePrecedences.Local when _highestPrecedence == DependencyPropertyValuePrecedences.Local => Unwrap(_fastLocalValue), + _ => DependencyProperty.UnsetValue + }; + } + else + { + return Unwrap(Stack[(int)precedence]); + } + } /// /// Gets the value at the highest precedence level under the specified one @@ -154,20 +245,36 @@ internal object GetValue(DependencyPropertyValuePrecedences precedence) /// /// The value precedence under which to fetch a value /// The value at the highest precedence level under the specified one - internal (object value, DependencyPropertyValuePrecedences precedence) GetValueUnderPrecedence(DependencyPropertyValuePrecedences precedence) + internal (object? value, DependencyPropertyValuePrecedences precedence) GetValueUnderPrecedence(DependencyPropertyValuePrecedences precedence) { - // Start from current precedence and find next highest - for (int i = (int)precedence + 1; i < (int)DependencyPropertyValuePrecedences.DefaultValue; i++) + if (_stack == null) { - object value = Unwrap(_stack[i]); - - if (value != DependencyProperty.UnsetValue) + if(_highestPrecedence == DependencyPropertyValuePrecedences.Local) { - return (value, (DependencyPropertyValuePrecedences)i); + return (Unwrap(_fastLocalValue), DependencyPropertyValuePrecedences.Local); + } + else + { + return (GetDefaultValue(), DependencyPropertyValuePrecedences.DefaultValue); } } + else + { + var stackAlias = Stack; - return (_stack[(int)DependencyPropertyValuePrecedences.DefaultValue], DependencyPropertyValuePrecedences.DefaultValue); + // Start from current precedence and find next highest + for (int i = (int)precedence + 1; i < (int)DependencyPropertyValuePrecedences.DefaultValue; i++) + { + object? value = Unwrap(stackAlias[i]); + + if (value != DependencyProperty.UnsetValue) + { + return (value, (DependencyPropertyValuePrecedences)i); + } + } + + return (stackAlias[(int)DependencyPropertyValuePrecedences.DefaultValue], DependencyPropertyValuePrecedences.DefaultValue); + } } /// @@ -181,35 +288,77 @@ internal DependencyPropertyValuePrecedences CurrentHighestValuePrecedence /// /// value to validate /// The value if valid, otherwise the dependency property's default value. - private object Validate(object value) + private object? Validate(object? value) { return value == null && !Property.IsTypeNullable - ? _stack[(int)DependencyPropertyValuePrecedences.DefaultValue] + ? GetDefaultValue() : Wrap(value); } + /// + /// Validate the value to prevent setting null to non-nullable dependency properties. + /// + /// value to validate + /// The value if valid, otherwise the dependency property's default value. + private object? ValidateNoWrap(object? value) + { + return value == null && !Property.IsTypeNullable + ? GetDefaultValue() + : value; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] - private object Wrap(object value) - => _hasWeakStorage && value != null && value != DependencyProperty.UnsetValue + private object? Wrap(object? value) + => HasWeakStorage && value != null && value != DependencyProperty.UnsetValue ? WeakReferencePool.RentWeakReference(this, value) : value; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private object Unwrap(object value) - => _hasWeakStorage && value is ManagedWeakReference mwr + private object? Unwrap(object? value) + => HasWeakStorage && value is ManagedWeakReference mwr ? mwr.Target : value; + private bool HasWeakStorage + => (_flags & Flags.WeakStorage) != 0; + + private bool HasDefaultValueSet + => (_flags & Flags.DefaultValueSet) != 0; + + private object?[] Stack + { + get + { + if (_stack == null) + { + _stack = _pool.Rent(_stackLength); + + Array.Copy(_unsetStack, _stack, _stackLength); + + var defaultValue = GetDefaultValue(); + + _stack[MaxIndex] = defaultValue; + + if (_highestPrecedence == DependencyPropertyValuePrecedences.Local) + { + _stack[(int)DependencyPropertyValuePrecedences.Local] = _fastLocalValue; + } + } + + return _stack; + } + } + #region IEnumerable implementation - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { - return _stack.Cast().GetEnumerator(); + return _stack.Cast().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { - return _stack.GetEnumerator(); + return Stack.GetEnumerator(); } #endregion @@ -218,5 +367,19 @@ public override string ToString() { return $"DependencyPropertyDetails({Property.Name})"; } + + [Flags] + enum Flags + { + /// + /// This dependency property uses weak storage for its values + /// + WeakStorage = 1 << 0, + + /// + /// Determines if the default value has been populated + /// + DefaultValueSet = 1 << 1, + } } } diff --git a/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.cs b/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.cs index 10642d3355ff..9a0593660277 100644 --- a/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.cs +++ b/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.cs @@ -140,7 +140,7 @@ public DependencyPropertyDetails FindPropertyDetails(DependencyProperty property if(_defaultValueProvider != null && _defaultValueProvider(property, out var v)) { - propertyEntry.SetValue(v, DependencyPropertyValuePrecedences.DefaultValue); + propertyEntry.SetDefaultValue(v); } }