Browse Source

Fix PriorityValue usage of DeferredSetter. Implement slower path in DeferredSetter that deals with callbacks.

pull/2362/head
Dariusz Komosinski 7 years ago
parent
commit
ad994a685c
  1. 2
      src/Avalonia.Base/AvaloniaObject.cs
  2. 9
      src/Avalonia.Base/IPriorityValueOwner.cs
  3. 34
      src/Avalonia.Base/PriorityValue.cs
  4. 38
      src/Avalonia.Base/Utilities/DeferredSetter.cs
  5. 15
      src/Avalonia.Base/ValueStore.cs
  6. 2
      tests/Avalonia.Base.UnitTests/PriorityValueTests.cs

2
src/Avalonia.Base/AvaloniaObject.cs

@ -528,7 +528,7 @@ namespace Avalonia
return false;
}
DeferredSetter<T> setter = Values.GetDeferredSetter(property);
DeferredSetter<T> setter = Values.GetDirectDeferredSetter(property);
return setter.SetAndNotify(this, property, ref field, value);
}

9
src/Avalonia.Base/IPriorityValueOwner.cs

@ -29,6 +29,13 @@ namespace Avalonia
/// <param name="notification">The notification.</param>
void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification);
/// <summary>
/// Returns deferred setter for given non-direct property.
/// </summary>
/// <param name="property">Property.</param>
/// <returns>Deferred setter for given property.</returns>
DeferredSetter<object> GetNonDirectDeferredSetter(AvaloniaProperty property);
/// <summary>
/// Logs a binding error.
/// </summary>
@ -40,7 +47,5 @@ namespace Avalonia
/// Ensures that the current thread is the UI thread.
/// </summary>
void VerifyAccess();
DeferredSetter<object> Setter { get; }
}
}

34
src/Avalonia.Base/PriorityValue.cs

@ -30,7 +30,9 @@ namespace Avalonia
private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>();
private readonly Func<object, object> _validate;
private readonly SetAndNotifyCallback<(object, int)> _setAndNotifyCallback;
private (object value, int priority) _value;
private DeferredSetter<object> _setter;
/// <summary>
/// Initializes a new instance of the <see cref="PriorityValue"/> class.
@ -50,6 +52,7 @@ namespace Avalonia
_valueType = valueType;
_value = (AvaloniaProperty.UnsetValue, int.MaxValue);
_validate = validate;
_setAndNotifyCallback = SetAndNotify;
}
/// <summary>
@ -242,22 +245,22 @@ namespace Avalonia
/// <param name="priority">The priority level that the value came from.</param>
private void UpdateValue(object value, int priority)
{
Owner.Setter.SetAndNotify(Property,
ref _value,
UpdateCore,
(value, priority));
}
var newValue = (value, priority);
if (newValue == _value)
{
return;
}
private bool UpdateCore(
object update,
ref (object value, int priority) backing,
Action<Action> notify)
=> UpdateCore(((object, int))update, ref backing, notify);
if (_setter == null)
{
_setter = Owner.GetNonDirectDeferredSetter(Property);
}
_setter.SetAndNotifyCallback(Property, _setAndNotifyCallback, ref _value, newValue);
}
private bool UpdateCore(
(object value, int priority) update,
ref (object value, int priority) backing,
Action<Action> notify)
private void SetAndNotify(AvaloniaProperty property, ref (object value, int priority) backing, (object value, int priority) update)
{
var val = update.value;
var notification = val as BindingNotification;
@ -286,7 +289,7 @@ namespace Avalonia
if (notification == null || notification.HasValue)
{
notify(() => Owner?.Changed(Property, ValuePriority, old, Value));
Owner?.Changed(Property, ValuePriority, old, Value);
}
if (notification != null)
@ -305,7 +308,6 @@ namespace Avalonia
val,
val?.GetType());
}
return true;
}
}
}

38
src/Avalonia.Base/Utilities/DeferredSetter.cs

@ -5,6 +5,15 @@ using System;
namespace Avalonia.Utilities
{
/// <summary>
/// Callback invoked when deferred setter wants to set a value.
/// </summary>
/// <typeparam name="TValue">Value type.</typeparam>
/// <param name="property">Property being set.</param>
/// <param name="backing">Backing field reference.</param>
/// <param name="value">New value.</param>
internal delegate void SetAndNotifyCallback<TValue>(AvaloniaProperty property, ref TValue backing, TValue value);
/// <summary>
/// A utility class to enable deferring assignment until after property-changed notifications are sent.
/// Used to fix #855.
@ -61,6 +70,35 @@ namespace Avalonia.Utilities
return false;
}
public bool SetAndNotifyCallback<TValue>(AvaloniaProperty property, SetAndNotifyCallback<TValue> setAndNotifyCallback, ref TValue backing, TValue value)
where TValue : TSetRecord
{
if (!_isNotifying)
{
using (new NotifyDisposable(this))
{
setAndNotifyCallback(property, ref backing, value);
}
if (!_pendingValues.Empty)
{
using (new NotifyDisposable(this))
{
while (!_pendingValues.Empty)
{
setAndNotifyCallback(property, ref backing, (TValue) _pendingValues.Dequeue());
}
}
}
return true;
}
_pendingValues.Enqueue(value);
return false;
}
/// <summary>
/// Disposable that marks the property as currently notifying.
/// When disposed, marks the property as done notifying.

15
src/Avalonia.Base/ValueStore.cs

@ -178,7 +178,7 @@ namespace Avalonia
return value;
}
public DeferredSetter<T> GetDeferredSetter<T>(AvaloniaProperty<T> property)
private DeferredSetter<T> GetDeferredSetter<T>(AvaloniaProperty property)
{
if (_deferredSetters.TryGetValue(property, out var deferredSetter))
{
@ -192,15 +192,14 @@ namespace Avalonia
return newDeferredSetter;
}
private DeferredSetter<object> _deferredSetter;
public DeferredSetter<object> GetNonDirectDeferredSetter(AvaloniaProperty property)
{
return GetDeferredSetter<object>(property);
}
public DeferredSetter<object> Setter
public DeferredSetter<T> GetDirectDeferredSetter<T>(AvaloniaProperty<T> property)
{
get
{
return _deferredSetter ??
(_deferredSetter = new DeferredSetter<object>());
}
return GetDeferredSetter<T>(property);
}
}
}

2
tests/Avalonia.Base.UnitTests/PriorityValueTests.cs

@ -307,7 +307,7 @@ namespace Avalonia.Base.UnitTests
private static Mock<IPriorityValueOwner> GetMockOwner()
{
var owner = new Mock<IPriorityValueOwner>();
owner.SetupGet(o => o.Setter).Returns(new DeferredSetter<object>());
owner.Setup(o => o.GetNonDirectDeferredSetter(It.IsAny<AvaloniaProperty>())).Returns(new DeferredSetter<object>());
return owner;
}
}

Loading…
Cancel
Save