Browse Source

Clean up formatting. Fix bug in PriorityValue usage of DeferredSetter. Change DeferredSetter to use ConditionalWeakTable to not hold strong references to PriorityValue objects.

pull/856/head
Jeremy Koritzinsky 8 years ago
parent
commit
d8efff505d
  1. 15
      src/Avalonia.Base/AvaloniaObject.cs
  2. 85
      src/Avalonia.Base/PriorityValue.cs
  3. 47
      src/Avalonia.Base/Utilities/DeferredSetter.cs
  4. 2
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

15
src/Avalonia.Base/AvaloniaObject.cs

@ -594,8 +594,19 @@ namespace Avalonia
}
}
protected void SetAndRaise<T>(AvaloniaProperty<T> property, Action<T, Action<Action>> setterCallback, T value, Predicate<T> pendingSetCondition = null)
=> directDelayedSetter.SetAndNotify(property, (val, notify) => setterCallback((T)val, notify), value, o => pendingSetCondition?.Invoke((T)o) ?? true);
protected void SetAndRaise<T>(
AvaloniaProperty<T> property,
Action<T, Action<Action>> setterCallback,
T value,
Predicate<T> pendingSetCondition = null)
{
Contract.Requires<ArgumentNullException>(setterCallback != null);
directDelayedSetter.SetAndNotify(
property,
(val, notify) => setterCallback((T)val, notify),
value,
pendingSetCondition != null ? o => pendingSetCondition((T)o) : (Predicate<object>)null);
}
/// <summary>
/// Tries to cast a value to a type, taking into account that the value may be a

85
src/Avalonia.Base/PriorityValue.cs

@ -235,56 +235,61 @@ namespace Avalonia
/// <param name="priority">The priority level that the value came from.</param>
private void UpdateValue(object value, int priority)
{
delayedSetter.SetAndNotify(this, (update, notify) =>
delayedSetter.SetAndNotify(this,
UpdateCore,
(value, priority),
val => !object.Equals(val.value, _value));
}
private void UpdateCore((object value, int priority) update, Action<Action> notify)
{
var val = update.value;
var notification = val as BindingNotification;
object castValue;
if (notification != null)
{
var val = update.value;
var notification = val as BindingNotification;
object castValue;
val = (notification.HasValue) ? notification.Value : null;
}
if (notification != null)
{
val = (notification.HasValue) ? notification.Value : null;
}
if (TypeUtilities.TryConvertImplicit(_valueType, val, out castValue))
{
var old = _value;
if (TypeUtilities.TryConvertImplicit(_valueType, val, out castValue))
if (_validate != null && castValue != AvaloniaProperty.UnsetValue)
{
var old = _value;
if (_validate != null && castValue != AvaloniaProperty.UnsetValue)
{
castValue = _validate(castValue);
}
castValue = _validate(castValue);
}
ValuePriority = priority;
_value = castValue;
ValuePriority = update.priority;
_value = castValue;
if (notification?.HasValue == true)
{
notification.SetValue(castValue);
}
if (notification == null || notification.HasValue)
{
notify(() => Owner?.Changed(this, old, _value));
}
if (notification?.HasValue == true)
{
notification.SetValue(castValue);
}
if (notification != null)
{
Owner?.BindingNotificationReceived(this, notification);
}
if (notification == null || notification.HasValue)
{
notify(() => Owner?.Changed(this, old, _value));
}
else
if (notification != null)
{
Logger.Error(
LogArea.Binding,
Owner,
"Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
Property.Name,
_valueType,
val,
val?.GetType());
Owner?.BindingNotificationReceived(this, notification);
}
}, (value, priority), val => !object.Equals(val.value, _value));
}
else
{
Logger.Error(
LogArea.Binding,
Owner,
"Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
Property.Name,
_valueType,
val,
val?.GetType());
}
}
}
}

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

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Runtime.CompilerServices;
using System.Text;
namespace Avalonia.Utilities
@ -11,7 +12,24 @@ namespace Avalonia.Utilities
/// <typeparam name="TProperty">The type of the object that represents the property.</typeparam>
/// <typeparam name="TValue">The type of value with which to track the delayed assignment.</typeparam>
class DeferredSetter<TProperty, TValue>
where TProperty: class
{
private struct NotifyDisposable : IDisposable
{
private readonly SettingStatus status;
public NotifyDisposable(SettingStatus status)
{
this.status = status;
status.Notifying = true;
}
public void Dispose()
{
status.Notifying = false;
}
}
/// <summary>
/// Information on current setting/notification status of a property.
/// </summary>
@ -30,7 +48,7 @@ namespace Avalonia.Utilities
}
}
private readonly Dictionary<TProperty, SettingStatus> setRecords = new Dictionary<TProperty, SettingStatus>();
private readonly ConditionalWeakTable<TProperty, SettingStatus> setRecords = new ConditionalWeakTable<TProperty, SettingStatus>();
/// <summary>
/// Mark the property as currently notifying.
@ -40,14 +58,8 @@ namespace Avalonia.Utilities
internal IDisposable MarkNotifying(TProperty property)
{
Contract.Requires<InvalidOperationException>(!IsNotifying(property));
if (!setRecords.ContainsKey(property))
{
setRecords[property] = new SettingStatus();
}
setRecords[property].Notifying = true;
return Disposable.Create(() => setRecords[property].Notifying = false);
return new NotifyDisposable(setRecords.GetOrCreateValue(property));
}
/// <summary>
@ -66,11 +78,8 @@ namespace Avalonia.Utilities
internal void AddPendingSet(TProperty property, TValue value)
{
Contract.Requires<InvalidOperationException>(IsNotifying(property));
if (!setRecords.ContainsKey(property))
{
setRecords[property] = new SettingStatus();
}
setRecords[property].PendingValues.Enqueue(value);
setRecords.GetOrCreateValue(property).PendingValues.Enqueue(value);
}
/// <summary>
@ -80,7 +89,7 @@ namespace Avalonia.Utilities
/// <returns>If the property has any pending assignments.</returns>
internal bool HasPendingSet(TProperty property)
{
return setRecords.ContainsKey(property) && setRecords[property].PendingValues.Count != 0;
return setRecords.TryGetValue(property, out var status) && status.PendingValues.Count != 0;
}
/// <summary>
@ -90,7 +99,7 @@ namespace Avalonia.Utilities
/// <returns>The first pending assignment for the property.</returns>
internal TValue GetFirstPendingSet(TProperty property)
{
return setRecords[property].PendingValues.Dequeue();
return setRecords.GetOrCreateValue(property).PendingValues.Dequeue();
}
/// <summary>
@ -103,7 +112,11 @@ namespace Avalonia.Utilities
/// </param>
/// <param name="value">The value to try to set.</param>
/// <param name="pendingSetCondition">A predicate to filter what possible values should be added as pending sets (i.e. only values not equal to the current value).</param>
public void SetAndNotify(TProperty property, Action<TValue, Action<Action>> setterCallback, TValue value, Predicate<TValue> pendingSetCondition)
public void SetAndNotify(
TProperty property,
Action<TValue, Action<Action>> setterCallback,
TValue value,
Predicate<TValue> pendingSetCondition)
{
Contract.Requires<ArgumentNullException>(setterCallback != null);
if (!IsNotifying(property))

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

@ -406,7 +406,7 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(1, viewModel.SetterInvokedCount);
//here in real life stack overflow exception is thrown issue #855 and #824
// Issues #855 and #824 were causing a StackOverflowException at this point.
target.DoubleValue = 51.001;
Assert.Equal(2, viewModel.SetterInvokedCount);

Loading…
Cancel
Save