Browse Source

Remove virtual generic methods from ValueStore.

pull/7980/head
Steven Kirk 4 years ago
parent
commit
d42a936478
  1. 8
      src/Avalonia.Base/AvaloniaObject.cs
  2. 17
      src/Avalonia.Base/PropertyStore/BindingEntry.cs
  3. 13
      src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs
  4. 2
      src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs
  5. 3
      src/Avalonia.Base/PropertyStore/IValue.cs
  6. 17
      src/Avalonia.Base/PropertyStore/IValueSink.cs
  7. 5
      src/Avalonia.Base/PropertyStore/LocalValueEntry.cs
  8. 58
      src/Avalonia.Base/PropertyStore/PriorityValue.cs
  9. 45
      src/Avalonia.Base/PropertyStore/ValueOwner.cs
  10. 23
      src/Avalonia.Base/ValueStore.cs
  11. 49
      tests/Avalonia.Base.UnitTests/PriorityValueTests.cs

8
src/Avalonia.Base/AvaloniaObject.cs

@ -16,7 +16,7 @@ namespace Avalonia
/// <remarks>
/// This class is analogous to DependencyObject in WPF.
/// </remarks>
public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged, IValueSink
public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged
{
private AvaloniaObject? _inheritanceParent;
private List<IDisposable>? _directBindings;
@ -524,7 +524,7 @@ namespace Avalonia
return _propertyChanged?.GetInvocationList();
}
void IValueSink.ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
internal void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
var property = (StyledPropertyBase<T>)change.Property;
@ -563,7 +563,7 @@ namespace Avalonia
}
}
void IValueSink.Completed<T>(
internal void Completed<T>(
StyledPropertyBase<T> property,
IPriorityValueEntry entry,
Optional<T> oldValue)
@ -574,7 +574,7 @@ namespace Avalonia
oldValue,
default,
BindingPriority.Unset);
((IValueSink)this).ValueChanged(change);
ValueChanged(change);
}
/// <summary>

17
src/Avalonia.Base/PropertyStore/BindingEntry.cs

@ -18,19 +18,19 @@ namespace Avalonia.PropertyStore
/// <typeparam name="T">The property type.</typeparam>
internal class BindingEntry<T> : IBindingEntry, IPriorityValueEntry<T>, IObserver<BindingValue<T>>
{
private readonly IAvaloniaObject _owner;
private IValueSink _sink;
private readonly AvaloniaObject _owner;
private ValueOwner<T> _sink;
private IDisposable? _subscription;
private bool _isSubscribed;
private bool _batchUpdate;
private Optional<T> _value;
public BindingEntry(
IAvaloniaObject owner,
AvaloniaObject owner,
StyledPropertyBase<T> property,
IObservable<BindingValue<T>> source,
BindingPriority priority,
IValueSink sink)
ValueOwner<T> sink)
{
_owner = owner;
Property = property;
@ -50,7 +50,7 @@ namespace Avalonia.PropertyStore
{
_batchUpdate = false;
if (_sink is ValueStore)
if (_sink.IsValueStore)
Start();
}
@ -113,16 +113,15 @@ namespace Avalonia.PropertyStore
}
}
public void Reparent(IValueSink sink) => _sink = sink;
public void Reparent(PriorityValue<T> parent) => _sink = new(parent);
public void RaiseValueChanged(
IValueSink sink,
IAvaloniaObject owner,
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue)
{
sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner,
(AvaloniaProperty<T>)property,
oldValue.Cast<T>(),

13
src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs

@ -18,14 +18,14 @@ namespace Avalonia.PropertyStore
/// <typeparam name="T">The property type.</typeparam>
internal class ConstantValueEntry<T> : IPriorityValueEntry<T>, IConstantValueEntry
{
private IValueSink _sink;
private ValueOwner<T> _sink;
private Optional<T> _value;
public ConstantValueEntry(
StyledPropertyBase<T> property,
T value,
BindingPriority priority,
IValueSink sink)
ValueOwner<T> sink)
{
Property = property;
_value = value;
@ -37,7 +37,7 @@ namespace Avalonia.PropertyStore
StyledPropertyBase<T> property,
Optional<T> value,
BindingPriority priority,
IValueSink sink)
ValueOwner<T> sink)
{
Property = property;
_value = value;
@ -62,17 +62,16 @@ namespace Avalonia.PropertyStore
_sink.Completed(Property, this, oldValue);
}
public void Reparent(IValueSink sink) => _sink = sink;
public void Reparent(PriorityValue<T> sink) => _sink = new(sink);
public void Start() { }
public void RaiseValueChanged(
IValueSink sink,
IAvaloniaObject owner,
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue)
{
sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner,
(AvaloniaProperty<T>)property,
oldValue.Cast<T>(),

2
src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs

@ -5,7 +5,6 @@
/// </summary>
internal interface IPriorityValueEntry : IValue
{
void Reparent(IValueSink sink);
}
/// <summary>
@ -14,5 +13,6 @@
/// <typeparam name="T">The property type.</typeparam>
internal interface IPriorityValueEntry<T> : IPriorityValueEntry, IValue<T>
{
void Reparent(PriorityValue<T> parent);
}
}

3
src/Avalonia.Base/PropertyStore/IValue.cs

@ -11,8 +11,7 @@ namespace Avalonia.PropertyStore
Optional<object?> GetValue();
void Start();
void RaiseValueChanged(
IValueSink sink,
IAvaloniaObject owner,
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue);

17
src/Avalonia.Base/PropertyStore/IValueSink.cs

@ -1,17 +0,0 @@
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Represents an entity that can receive change notifications in a <see cref="ValueStore"/>.
/// </summary>
internal interface IValueSink
{
void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change);
void Completed<T>(
StyledPropertyBase<T> property,
IPriorityValueEntry entry,
Optional<T> oldValue);
}
}

5
src/Avalonia.Base/PropertyStore/LocalValueEntry.cs

@ -25,13 +25,12 @@ namespace Avalonia.PropertyStore
public void Start() { }
public void RaiseValueChanged(
IValueSink sink,
IAvaloniaObject owner,
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue)
{
sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner,
(AvaloniaProperty<T>)property,
oldValue.Cast<T>(),

58
src/Avalonia.Base/PropertyStore/PriorityValue.cs

@ -23,10 +23,10 @@ namespace Avalonia.PropertyStore
/// <see cref="IPriorityValueEntry{T}"/> entries (sorted first by priority and then in the order
/// they were added) plus a local value.
/// </remarks>
internal class PriorityValue<T> : IPriorityValue, IValue<T>, IValueSink, IBatchUpdate
internal class PriorityValue<T> : IPriorityValue, IValue<T>, IBatchUpdate
{
private readonly IAvaloniaObject _owner;
private readonly IValueSink _sink;
private readonly AvaloniaObject _owner;
private readonly ValueStore _store;
private readonly List<IPriorityValueEntry<T>> _entries = new List<IPriorityValueEntry<T>>();
private readonly Func<IAvaloniaObject, T, T>? _coerceValue;
private Optional<T> _localValue;
@ -35,13 +35,13 @@ namespace Avalonia.PropertyStore
private bool _batchUpdate;
public PriorityValue(
IAvaloniaObject owner,
AvaloniaObject owner,
StyledPropertyBase<T> property,
IValueSink sink)
ValueStore store)
{
_owner = owner;
Property = property;
_sink = sink;
_store = store;
if (property.HasCoercion)
{
@ -51,11 +51,11 @@ namespace Avalonia.PropertyStore
}
public PriorityValue(
IAvaloniaObject owner,
AvaloniaObject owner,
StyledPropertyBase<T> property,
IValueSink sink,
ValueStore store,
IPriorityValueEntry<T> existing)
: this(owner, property, sink)
: this(owner, property, store)
{
existing.Reparent(this);
_entries.Add(existing);
@ -82,9 +82,9 @@ namespace Avalonia.PropertyStore
}
public PriorityValue(
IAvaloniaObject owner,
AvaloniaObject owner,
StyledPropertyBase<T> property,
IValueSink sink,
ValueStore sink,
LocalValueEntry<T> existing)
: this(owner, property, sink)
{
@ -155,7 +155,7 @@ namespace Avalonia.PropertyStore
else
{
var insert = FindInsertPoint(priority);
var entry = new ConstantValueEntry<T>(Property, value, priority, this);
var entry = new ConstantValueEntry<T>(Property, value, priority, new ValueOwner<T>(this));
_entries.Insert(insert, entry);
result = entry;
}
@ -172,7 +172,7 @@ namespace Avalonia.PropertyStore
public BindingEntry<T> AddBinding(IObservable<BindingValue<T>> source, BindingPriority priority)
{
var binding = new BindingEntry<T>(_owner, Property, source, priority, this);
var binding = new BindingEntry<T>(_owner, Property, source, priority, new(this));
var insert = FindInsertPoint(binding.Priority);
_entries.Insert(insert, binding);
@ -193,13 +193,12 @@ namespace Avalonia.PropertyStore
public void Start() => UpdateEffectiveValue(null);
public void RaiseValueChanged(
IValueSink sink,
IAvaloniaObject owner,
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue)
{
sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner,
(AvaloniaProperty<T>)property,
oldValue.Cast<T>(),
@ -207,7 +206,7 @@ namespace Avalonia.PropertyStore
Priority));
}
void IValueSink.ValueChanged<TValue>(AvaloniaPropertyChangedEventArgs<TValue> change)
public void ValueChanged<TValue>(AvaloniaPropertyChangedEventArgs<TValue> change)
{
if (change.Priority == BindingPriority.LocalValue)
{
@ -220,22 +219,15 @@ namespace Avalonia.PropertyStore
}
}
void IValueSink.Completed<TValue>(
StyledPropertyBase<TValue> property,
IPriorityValueEntry entry,
Optional<TValue> oldValue)
public void Completed(IPriorityValueEntry entry, Optional<T> oldValue)
{
_entries.Remove((IPriorityValueEntry<T>)entry);
if (oldValue is Optional<T> o)
{
UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>(
_owner,
Property,
o,
default,
entry.Priority));
}
UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>(
_owner,
Property,
oldValue,
default,
entry.Priority));
}
private int FindInsertPoint(BindingPriority priority)
@ -315,7 +307,7 @@ namespace Avalonia.PropertyStore
var old = _value;
_value = value;
_sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
_store.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
_owner,
Property,
old,
@ -326,7 +318,7 @@ namespace Avalonia.PropertyStore
{
change.MarkNonEffectiveValue();
change.SetOldValue(default);
_sink.ValueChanged(change);
_store.ValueChanged(change);
}
}
}

45
src/Avalonia.Base/PropertyStore/ValueOwner.cs

@ -0,0 +1,45 @@
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Represents a union type of <see cref="ValueStore"/> and <see cref="PriorityValue{T}"/>,
/// which are the valid owners of a value store <see cref="IValue"/>.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
internal readonly struct ValueOwner<T>
{
private readonly ValueStore? _store;
private readonly PriorityValue<T>? _priorityValue;
public ValueOwner(ValueStore o)
{
_store = o;
_priorityValue = null;
}
public ValueOwner(PriorityValue<T> v)
{
_store = null;
_priorityValue = v;
}
public bool IsValueStore => _store is not null;
public void Completed(StyledPropertyBase<T> property, IPriorityValueEntry entry, Optional<T> oldValue)
{
if (_store is not null)
_store?.Completed(property, entry, oldValue);
else
_priorityValue!.Completed(entry, oldValue);
}
public void ValueChanged(AvaloniaPropertyChangedEventArgs<T> e)
{
if (_store is not null)
_store?.ValueChanged(e);
else
_priorityValue!.ValueChanged(e);
}
}
}

23
src/Avalonia.Base/ValueStore.cs

@ -21,16 +21,15 @@ namespace Avalonia
/// - For a single binding it will be an instance of <see cref="BindingEntry{T}"/>
/// - For all other cases it will be an instance of <see cref="PriorityValue{T}"/>
/// </remarks>
internal class ValueStore : IValueSink
internal class ValueStore
{
private readonly AvaloniaObject _owner;
private readonly IValueSink _sink;
private readonly AvaloniaPropertyValueStore<IValue> _values;
private BatchUpdate? _batchUpdate;
public ValueStore(AvaloniaObject owner)
{
_sink = _owner = owner;
_owner = owner;
_values = new AvaloniaPropertyValueStore<IValue>();
}
@ -122,7 +121,7 @@ namespace Avalonia
}
else
{
var entry = new ConstantValueEntry<T>(property, value, priority, this);
var entry = new ConstantValueEntry<T>(property, value, priority, new(this));
AddValue(property, entry);
NotifyValueChanged<T>(property, default, value, priority);
result = entry;
@ -151,7 +150,7 @@ namespace Avalonia
}
else
{
var entry = new BindingEntry<T>(_owner, property, source, priority, this);
var entry = new BindingEntry<T>(_owner, property, source, priority, new(this));
AddValue(property, entry);
return entry;
}
@ -187,7 +186,7 @@ namespace Avalonia
// so there's no way to mark them for removal at the end of a batch update. Instead convert
// them to a constant value entry with Unset priority in the event of a local value being
// cleared during a batch update.
var sentinel = new ConstantValueEntry<T>(property, Optional<T>.Empty, BindingPriority.Unset, _sink);
var sentinel = new ConstantValueEntry<T>(property, Optional<T>.Empty, BindingPriority.Unset, new(this));
_values.SetValue(property, sentinel);
}
@ -222,7 +221,7 @@ namespace Avalonia
return null;
}
void IValueSink.ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
public void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
if (_batchUpdate is object)
{
@ -233,11 +232,11 @@ namespace Avalonia
}
else
{
_sink.ValueChanged(change);
_owner.ValueChanged(change);
}
}
void IValueSink.Completed<T>(
public void Completed<T>(
StyledPropertyBase<T> property,
IPriorityValueEntry entry,
Optional<T> oldValue)
@ -248,7 +247,7 @@ namespace Avalonia
if (_batchUpdate is null)
{
_values.Remove(property);
_sink.Completed(property, entry, oldValue);
_owner.Completed(property, entry, oldValue);
}
else
{
@ -352,7 +351,7 @@ namespace Avalonia
{
if (_batchUpdate is null)
{
_sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
_owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
_owner,
property,
oldValue,
@ -451,7 +450,7 @@ namespace Avalonia
};
// Call _sink.ValueChanged with an appropriately typed AvaloniaPropertyChangedEventArgs<T>.
slot.RaiseValueChanged(_owner._sink, _owner._owner, entry.property, oldValue, newValue);
slot.RaiseValueChanged(_owner._owner, entry.property, oldValue, newValue);
// During batch update values can't be removed immediately because they're needed to raise
// the _sink.ValueChanged notification. They instead mark themselves for removal by setting

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

@ -10,8 +10,8 @@ namespace Avalonia.Base.UnitTests
{
public class PriorityValueTests
{
private static readonly IValueSink NullSink = new MockSink();
private static readonly IAvaloniaObject Owner = Mock.Of<IAvaloniaObject>();
private static readonly AvaloniaObject Owner = new AvaloniaObject();
private static readonly ValueStore ValueStore = new ValueStore(Owner);
private static readonly StyledProperty<string> TestProperty = new StyledProperty<string>(
"Test",
typeof(PriorityValueTests),
@ -23,12 +23,12 @@ namespace Avalonia.Base.UnitTests
var target = new PriorityValue<string>(
Owner,
TestProperty,
NullSink,
ValueStore,
new ConstantValueEntry<string>(
TestProperty,
"1",
BindingPriority.StyleTrigger,
NullSink));
new(ValueStore)));
Assert.Equal("1", target.GetValue().Value);
Assert.Equal(BindingPriority.StyleTrigger, target.Priority);
@ -40,7 +40,7 @@ namespace Avalonia.Base.UnitTests
var target = new PriorityValue<string>(
Owner,
TestProperty,
NullSink);
ValueStore);
target.SetValue("animation", BindingPriority.Animation);
target.SetValue("local", BindingPriority.LocalValue);
@ -60,7 +60,7 @@ namespace Avalonia.Base.UnitTests
var target = new PriorityValue<string>(
Owner,
TestProperty,
NullSink);
ValueStore);
target.SetValue("1", BindingPriority.LocalValue);
target.SetValue("2", BindingPriority.LocalValue);
@ -74,7 +74,7 @@ namespace Avalonia.Base.UnitTests
var target = new PriorityValue<string>(
Owner,
TestProperty,
NullSink);
ValueStore);
target.SetValue("1", BindingPriority.Style);
target.SetValue("2", BindingPriority.Animation);
@ -93,7 +93,7 @@ namespace Avalonia.Base.UnitTests
var target = new PriorityValue<string>(
Owner,
TestProperty,
NullSink);
ValueStore);
Assert.Equal(BindingPriority.Unset, target.Priority);
target.SetValue("style", BindingPriority.Style);
@ -109,7 +109,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void Binding_With_Same_Priority_Should_Be_Appended()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
@ -129,7 +129,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void Binding_With_Higher_Priority_Should_Be_Appended()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
@ -149,7 +149,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void Binding_With_Lower_Priority_Should_Be_Prepended()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
@ -169,7 +169,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void Second_Binding_With_Lower_Priority_Should_Be_Inserted_In_Middle()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
var source3 = new Source("3");
@ -191,7 +191,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void Competed_Binding_Should_Be_Removed()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
var source3 = new Source("3");
@ -214,7 +214,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void Value_Should_Come_From_Last_Entry()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
var source3 = new Source("3");
@ -229,7 +229,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void LocalValue_Should_Override_LocalValue_Binding()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
target.AddBinding(source1, BindingPriority.LocalValue).Start();
@ -241,7 +241,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void LocalValue_Should_Override_Style_Binding()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
target.AddBinding(source1, BindingPriority.Style).Start();
@ -253,7 +253,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void LocalValue_Should_Not_Override_Animation_Binding()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
target.AddBinding(source1, BindingPriority.Animation).Start();
@ -265,7 +265,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void NonAnimated_Value_Should_Be_Correct_1()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
var source3 = new Source("3");
@ -281,7 +281,7 @@ namespace Avalonia.Base.UnitTests
[Fact]
public void NonAnimated_Value_Should_Be_Correct_2()
{
var target = new PriorityValue<string>(Owner, TestProperty, NullSink);
var target = new PriorityValue<string>(Owner, TestProperty, ValueStore);
var source1 = new Source("1");
var source2 = new Source("2");
var source3 = new Source("3");
@ -310,16 +310,5 @@ namespace Avalonia.Base.UnitTests
public void OnCompleted() => _observer.OnCompleted();
}
private class MockSink : IValueSink
{
public void Completed<T>(StyledPropertyBase<T> property, IPriorityValueEntry entry, Optional<T> oldValue)
{
}
public void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
}
}
}
}

Loading…
Cancel
Save