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; return false;
} }
DeferredSetter<T> setter = Values.GetDeferredSetter(property); DeferredSetter<T> setter = Values.GetDirectDeferredSetter(property);
return setter.SetAndNotify(this, property, ref field, value); 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> /// <param name="notification">The notification.</param>
void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification); 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> /// <summary>
/// Logs a binding error. /// Logs a binding error.
/// </summary> /// </summary>
@ -40,7 +47,5 @@ namespace Avalonia
/// Ensures that the current thread is the UI thread. /// Ensures that the current thread is the UI thread.
/// </summary> /// </summary>
void VerifyAccess(); 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 SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>();
private readonly Func<object, object> _validate; private readonly Func<object, object> _validate;
private readonly SetAndNotifyCallback<(object, int)> _setAndNotifyCallback;
private (object value, int priority) _value; private (object value, int priority) _value;
private DeferredSetter<object> _setter;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PriorityValue"/> class. /// Initializes a new instance of the <see cref="PriorityValue"/> class.
@ -50,6 +52,7 @@ namespace Avalonia
_valueType = valueType; _valueType = valueType;
_value = (AvaloniaProperty.UnsetValue, int.MaxValue); _value = (AvaloniaProperty.UnsetValue, int.MaxValue);
_validate = validate; _validate = validate;
_setAndNotifyCallback = SetAndNotify;
} }
/// <summary> /// <summary>
@ -242,22 +245,22 @@ namespace Avalonia
/// <param name="priority">The priority level that the value came from.</param> /// <param name="priority">The priority level that the value came from.</param>
private void UpdateValue(object value, int priority) private void UpdateValue(object value, int priority)
{ {
Owner.Setter.SetAndNotify(Property, var newValue = (value, priority);
ref _value,
UpdateCore, if (newValue == _value)
(value, priority)); {
} return;
}
private bool UpdateCore( if (_setter == null)
object update, {
ref (object value, int priority) backing, _setter = Owner.GetNonDirectDeferredSetter(Property);
Action<Action> notify) }
=> UpdateCore(((object, int))update, ref backing, notify);
_setter.SetAndNotifyCallback(Property, _setAndNotifyCallback, ref _value, newValue);
}
private bool UpdateCore( private void SetAndNotify(AvaloniaProperty property, ref (object value, int priority) backing, (object value, int priority) update)
(object value, int priority) update,
ref (object value, int priority) backing,
Action<Action> notify)
{ {
var val = update.value; var val = update.value;
var notification = val as BindingNotification; var notification = val as BindingNotification;
@ -286,7 +289,7 @@ namespace Avalonia
if (notification == null || notification.HasValue) if (notification == null || notification.HasValue)
{ {
notify(() => Owner?.Changed(Property, ValuePriority, old, Value)); Owner?.Changed(Property, ValuePriority, old, Value);
} }
if (notification != null) if (notification != null)
@ -305,7 +308,6 @@ namespace Avalonia
val, val,
val?.GetType()); val?.GetType());
} }
return true;
} }
} }
} }

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

@ -5,6 +5,15 @@ using System;
namespace Avalonia.Utilities 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> /// <summary>
/// A utility class to enable deferring assignment until after property-changed notifications are sent. /// A utility class to enable deferring assignment until after property-changed notifications are sent.
/// Used to fix #855. /// Used to fix #855.
@ -61,6 +70,35 @@ namespace Avalonia.Utilities
return false; 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> /// <summary>
/// Disposable that marks the property as currently notifying. /// Disposable that marks the property as currently notifying.
/// When disposed, marks the property as done notifying. /// When disposed, marks the property as done notifying.

15
src/Avalonia.Base/ValueStore.cs

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

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

@ -307,7 +307,7 @@ namespace Avalonia.Base.UnitTests
private static Mock<IPriorityValueOwner> GetMockOwner() private static Mock<IPriorityValueOwner> GetMockOwner()
{ {
var owner = new Mock<IPriorityValueOwner>(); 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; return owner;
} }
} }

Loading…
Cancel
Save