Browse Source

Remove old DeferredSetter implementation. Cleanup code, add license info.

pull/2362/head
Dariusz Komosinski 7 years ago
parent
commit
48be9dc260
  1. 73
      src/Avalonia.Base/AvaloniaObject.cs
  2. 13
      src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs
  3. 180
      src/Avalonia.Base/Utilities/DeferredSetter.cs
  4. 81
      src/Avalonia.Base/Utilities/DeferredSetterOptimized.cs
  5. 14
      src/Avalonia.Base/ValueStore.cs
  6. 17
      tests/Avalonia.Benchmarks/Base/DirectPropertyBenchmark.cs

73
src/Avalonia.Base/AvaloniaObject.cs

@ -508,45 +508,6 @@ namespace Avalonia
}
}
/// <summary>
/// A callback type for encapsulating complex logic for setting direct properties.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="value">The value to which to set the property.</param>
/// <param name="field">The backing field for the property.</param>
/// <param name="notifyWrapper">A wrapper for the property-changed notification.</param>
protected delegate void SetAndRaiseCallback<T>(T value, ref T field, Action<Action> notifyWrapper);
/// <summary>
/// Sets the backing field for a direct avalonia property, raising the
/// <see cref="PropertyChanged"/> event if the value has changed.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="field">The backing field.</param>
/// <param name="setterCallback">A callback called to actually set the value to the backing field.</param>
/// <param name="value">The value.</param>
/// <returns>
/// True if the value changed, otherwise false.
/// </returns>
protected bool SetAndRaise<T>(
AvaloniaProperty<T> property,
ref T field,
SetAndRaiseCallback<T> setterCallback,
T value)
{
Contract.Requires<ArgumentNullException>(setterCallback != null);
return Values.Setter.SetAndNotify(
property,
ref field,
(object update, ref T backing, Action<Action> notify) =>
{
setterCallback((T)update, ref backing, notify);
return true;
},
value);
}
/// <summary>
/// Sets the backing field for a direct avalonia property, raising the
/// <see cref="PropertyChanged"/> event if the value has changed.
@ -562,48 +523,16 @@ namespace Avalonia
{
VerifyAccess();
return SetAndRaise(
property,
ref field,
(T val, ref T backing, Action<Action> notifyWrapper)
=> SetAndRaiseCore(property, ref backing, val, notifyWrapper),
value);
}
protected bool SetAndRaiseOptimized<T>(AvaloniaProperty<T> property, ref T field, T value)
{
VerifyAccess();
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
DeferredSetterOptimized<T> setter = Values.GetDeferredSetter(property);
DeferredSetter<T> setter = Values.GetDeferredSetter(property);
return setter.SetAndNotify(this, property, ref field, value);
}
/// <summary>
/// Default assignment logic for SetAndRaise.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="field">The backing field.</param>
/// <param name="value">The value.</param>
/// <param name="notifyWrapper">A wrapper for the property-changed notification.</param>
/// <returns>
/// True if the value changed, otherwise false.
/// </returns>
private bool SetAndRaiseCore<T>(AvaloniaProperty property, ref T field, T value, Action<Action> notifyWrapper)
{
var old = field;
field = value;
notifyWrapper(() => RaisePropertyChanged(property, old, value, BindingPriority.LocalValue));
return true;
}
/// <summary>
/// Tries to cast a value to a type, taking into account that the value may be a
/// <see cref="BindingNotification"/>.

13
src/Avalonia.Base/Utilities/AvaloniaPropertyCollection.cs → src/Avalonia.Base/Utilities/AvaloniaPropertyValueStore.cs

@ -1,13 +1,20 @@
using System;
// 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 System.Collections.Generic;
namespace Avalonia.Utilities
{
internal sealed class AvaloniaPropertyCollection<TValue>
/// <summary>
/// Stores values with <see cref="AvaloniaProperty"/> as key.
/// </summary>
/// <typeparam name="TValue">Stored value type.</typeparam>
internal sealed class AvaloniaPropertyValueStore<TValue>
{
private Entry[] _entries;
public AvaloniaPropertyCollection()
public AvaloniaPropertyValueStore()
{
// The last item in the list is always int.MaxValue
_entries = new[] { new Entry { PropertyId = int.MaxValue, Value = default } };

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

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
// 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;
namespace Avalonia.Utilities
{
@ -8,161 +10,75 @@ namespace Avalonia.Utilities
/// Used to fix #855.
/// </summary>
/// <typeparam name="TSetRecord">The type of value with which to track the delayed assignment.</typeparam>
class DeferredSetter<TSetRecord>
internal sealed class DeferredSetter<TSetRecord>
{
private struct NotifyDisposable : IDisposable
private readonly SingleOrQueue<TSetRecord> _pendingValues;
private bool _isNotifying;
public DeferredSetter()
{
private readonly SettingStatus status;
_pendingValues = new SingleOrQueue<TSetRecord>();
}
internal NotifyDisposable(SettingStatus status)
{
this.status = status;
status.Notifying = true;
}
private static void SetAndRaisePropertyChanged(AvaloniaObject source, AvaloniaProperty<TSetRecord> property, ref TSetRecord backing, TSetRecord value)
{
var old = backing;
public void Dispose()
{
status.Notifying = false;
}
backing = value;
source.RaisePropertyChanged(property, old, value);
}
/// <summary>
/// Information on current setting/notification status of a property.
/// </summary>
private class SettingStatus
public bool SetAndNotify(
AvaloniaObject source,
AvaloniaProperty<TSetRecord> property,
ref TSetRecord backing,
TSetRecord value)
{
public bool Notifying { get; set; }
private SingleOrQueue<TSetRecord> pendingValues;
public SingleOrQueue<TSetRecord> PendingValues
if (!_isNotifying)
{
get
using (new NotifyDisposable(this))
{
return pendingValues ?? (pendingValues = new SingleOrQueue<TSetRecord>());
SetAndRaisePropertyChanged(source, property, ref backing, value);
}
}
}
private Dictionary<AvaloniaProperty, SettingStatus> _setRecords;
private Dictionary<AvaloniaProperty, SettingStatus> SetRecords
=> _setRecords ?? (_setRecords = new Dictionary<AvaloniaProperty, SettingStatus>());
if (!_pendingValues.Empty)
{
using (new NotifyDisposable(this))
{
while (!_pendingValues.Empty)
{
SetAndRaisePropertyChanged(source, property, ref backing, _pendingValues.Dequeue());
}
}
}
private SettingStatus GetOrCreateStatus(AvaloniaProperty property)
{
if (!SetRecords.TryGetValue(property, out var status))
{
status = new SettingStatus();
SetRecords.Add(property, status);
return true;
}
return status;
}
/// <summary>
/// Mark the property as currently notifying.
/// </summary>
/// <param name="property">The property to mark as notifying.</param>
/// <returns>Returns a disposable that when disposed, marks the property as done notifying.</returns>
private NotifyDisposable MarkNotifying(AvaloniaProperty property)
{
Contract.Requires<InvalidOperationException>(!IsNotifying(property));
SettingStatus status = GetOrCreateStatus(property);
return new NotifyDisposable(status);
}
/// <summary>
/// Check if the property is currently notifying listeners.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>If the property is currently notifying listeners.</returns>
private bool IsNotifying(AvaloniaProperty property)
=> SetRecords.TryGetValue(property, out var value) && value.Notifying;
/// <summary>
/// Add a pending assignment for the property.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value to assign.</param>
private void AddPendingSet(AvaloniaProperty property, TSetRecord value)
{
Contract.Requires<InvalidOperationException>(IsNotifying(property));
GetOrCreateStatus(property).PendingValues.Enqueue(value);
}
_pendingValues.Enqueue(value);
/// <summary>
/// Checks if there are any pending assignments for the property.
/// </summary>
/// <param name="property">The property to check.</param>
/// <returns>If the property has any pending assignments.</returns>
private bool HasPendingSet(AvaloniaProperty property)
{
return SetRecords.TryGetValue(property, out var status) && !status.PendingValues.Empty;
return false;
}
/// <summary>
/// Gets the first pending assignment for the property.
/// Disposable that marks the property as currently notifying.
/// When disposed, marks the property as done notifying.
/// </summary>
/// <param name="property">The property to check.</param>
/// <returns>The first pending assignment for the property.</returns>
private TSetRecord GetFirstPendingSet(AvaloniaProperty property)
private readonly struct NotifyDisposable : IDisposable
{
return GetOrCreateStatus(property).PendingValues.Dequeue();
}
public delegate bool SetterDelegate<TValue>(TSetRecord record, ref TValue backing, Action<Action> notifyCallback);
private readonly DeferredSetter<TSetRecord> _setter;
/// <summary>
/// Set the property and notify listeners while ensuring we don't get into a stack overflow as happens with #855 and #824
/// </summary>
/// <param name="property">The property to set.</param>
/// <param name="backing">The backing field for the property</param>
/// <param name="setterCallback">
/// A callback that actually sets the property.
/// The first parameter is the value to set, and the second is a wrapper that takes a callback that sends the property-changed notification.
/// </param>
/// <param name="value">The value to try to set.</param>
public bool SetAndNotify<TValue>(
AvaloniaProperty property,
ref TValue backing,
SetterDelegate<TValue> setterCallback,
TSetRecord value)
{
Contract.Requires<ArgumentNullException>(setterCallback != null);
if (!IsNotifying(property))
internal NotifyDisposable(DeferredSetter<TSetRecord> setter)
{
bool updated = false;
if (!object.Equals(value, backing))
{
updated = setterCallback(value, ref backing, notification =>
{
using (MarkNotifying(property))
{
notification();
}
});
}
while (HasPendingSet(property))
{
updated |= setterCallback(GetFirstPendingSet(property), ref backing, notification =>
{
using (MarkNotifying(property))
{
notification();
}
});
}
return updated;
_setter = setter;
_setter._isNotifying = true;
}
else if(!object.Equals(value, backing))
public void Dispose()
{
AddPendingSet(property, value);
_setter._isNotifying = false;
}
return false;
}
}
}

81
src/Avalonia.Base/Utilities/DeferredSetterOptimized.cs

@ -1,81 +0,0 @@
using System;
namespace Avalonia.Utilities
{
/// <summary>
/// A utility class to enable deferring assignment until after property-changed notifications are sent.
/// Used to fix #855.
/// </summary>
/// <typeparam name="TSetRecord">The type of value with which to track the delayed assignment.</typeparam>
internal sealed class DeferredSetterOptimized<TSetRecord>
{
private bool _isNotifying;
private readonly SingleOrQueue<TSetRecord> _pendingValues;
public DeferredSetterOptimized()
{
_pendingValues = new SingleOrQueue<TSetRecord>();
}
private static void SetAndRaisePropertyChanged(AvaloniaObject source, AvaloniaProperty<TSetRecord> property, ref TSetRecord backing, TSetRecord value)
{
var old = backing;
backing = value;
source.RaisePropertyChanged(property, old, value);
}
public bool SetAndNotify(
AvaloniaObject source,
AvaloniaProperty<TSetRecord> property,
ref TSetRecord backing,
TSetRecord value)
{
if (!_isNotifying)
{
using (new NotifyDisposable(this))
{
SetAndRaisePropertyChanged(source, property, ref backing, value);
}
if (!_pendingValues.Empty)
{
using (new NotifyDisposable(this))
{
while (!_pendingValues.Empty)
{
SetAndRaisePropertyChanged(source, property, ref backing, _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.
/// </summary>
private readonly struct NotifyDisposable : IDisposable
{
private readonly DeferredSetterOptimized<TSetRecord> _setter;
internal NotifyDisposable(DeferredSetterOptimized<TSetRecord> setter)
{
_setter = setter;
_setter._isNotifying = true;
}
public void Dispose()
{
_setter._isNotifying = false;
}
}
}
}

14
src/Avalonia.Base/ValueStore.cs

@ -7,15 +7,15 @@ namespace Avalonia
{
internal class ValueStore : IPriorityValueOwner
{
private readonly AvaloniaPropertyCollection<object> _propertyValues;
private readonly AvaloniaPropertyCollection<object> _deferredSetters;
private readonly AvaloniaPropertyValueStore<object> _propertyValues;
private readonly AvaloniaPropertyValueStore<object> _deferredSetters;
private readonly AvaloniaObject _owner;
public ValueStore(AvaloniaObject owner)
{
_owner = owner;
_propertyValues = new AvaloniaPropertyCollection<object>();
_deferredSetters = new AvaloniaPropertyCollection<object>();
_propertyValues = new AvaloniaPropertyValueStore<object>();
_deferredSetters = new AvaloniaPropertyValueStore<object>();
}
public IDisposable AddBinding(
@ -178,14 +178,14 @@ namespace Avalonia
return value;
}
public DeferredSetterOptimized<T> GetDeferredSetter<T>(AvaloniaProperty<T> property)
public DeferredSetter<T> GetDeferredSetter<T>(AvaloniaProperty<T> property)
{
if (_deferredSetters.TryGetValue(property, out var deferredSetter))
{
return (DeferredSetterOptimized<T>)deferredSetter;
return (DeferredSetter<T>)deferredSetter;
}
var newDeferredSetter = new DeferredSetterOptimized<T>();
var newDeferredSetter = new DeferredSetter<T>();
_deferredSetters.AddValue(property, newDeferredSetter);

17
tests/Avalonia.Benchmarks/Base/DirectPropertyBenchmark.cs

@ -16,17 +16,6 @@ namespace Avalonia.Benchmarks.Base
}
}
[Benchmark]
public void SetAndRaiseOptimized()
{
var obj = new DirectClass();
for (var i = 0; i < 100; ++i)
{
obj.IntValueOptimized += 1;
}
}
[Benchmark]
public void SetAndRaiseSimple()
{
@ -53,12 +42,6 @@ namespace Avalonia.Benchmarks.Base
set => SetAndRaise(IntValueProperty, ref _intValue, value);
}
public int IntValueOptimized
{
get => _intValue;
set => SetAndRaiseOptimized(IntValueProperty, ref _intValue, value);
}
public int IntValueSimple
{
get => _intValue;

Loading…
Cancel
Save