Browse Source

Make EffectiveValue responsible for unsubscribing.

pull/8600/head
Steven Kirk 4 years ago
parent
commit
bd357e08dd
  1. 2
      src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
  2. 103
      src/Avalonia.Base/PropertyStore/EffectiveValue.cs
  3. 208
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  4. 2
      src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs
  5. 193
      src/Avalonia.Base/PropertyStore/ValueStore.cs

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

@ -143,7 +143,7 @@ namespace Avalonia.PropertyStore
_value = value.Value; _value = value.Value;
_hasValue = true; _hasValue = true;
if (_subscription is not null && _subscription != s_creatingQuiet) if (_subscription is not null && _subscription != s_creatingQuiet)
Frame.Owner?.OnBindingValueChanged(Property, Frame.Priority, value.Value); Frame.Owner?.OnBindingValueChanged(this, Frame.Priority);
} }
} }
else if (value.Type != BindingValueType.DoNothing) else if (value.Type != BindingValueType.DoNothing)

103
src/Avalonia.Base/PropertyStore/EffectiveValue.cs

@ -1,5 +1,4 @@
using System; using Avalonia.Data;
using Avalonia.Data;
namespace Avalonia.PropertyStore namespace Avalonia.PropertyStore
{ {
@ -11,6 +10,9 @@ namespace Avalonia.PropertyStore
/// </remarks> /// </remarks>
internal abstract class EffectiveValue internal abstract class EffectiveValue
{ {
private IValueEntry? _valueEntry;
private IValueEntry? _baseValueEntry;
/// <summary> /// <summary>
/// Gets the current effective value as a boxed value. /// Gets the current effective value as a boxed value.
/// </summary> /// </summary>
@ -33,52 +35,66 @@ namespace Avalonia.PropertyStore
public BindingPriority BasePriority { get; protected set; } public BindingPriority BasePriority { get; protected set; }
/// <summary> /// <summary>
/// Sets the value and base value, raising <see cref="AvaloniaObject.PropertyChanged"/> /// Begins a reevaluation pass on the effective value.
/// where necessary. /// </summary>
/// <param name="clearLocalValue">
/// Determines whether any current local value should be cleared.
/// </param>
/// <remarks>
/// This method resets the <see cref="Priority"/> and <see cref="BasePriority"/> properties
/// to Unset, pending reevaluation.
/// </remarks>
public void BeginReevaluation(bool clearLocalValue = false)
{
if (clearLocalValue || Priority != BindingPriority.LocalValue)
Priority = BindingPriority.Unset;
if (clearLocalValue || BasePriority != BindingPriority.LocalValue)
BasePriority = BindingPriority.Unset;
}
public void EndReevaluation()
{
if (Priority == BindingPriority.Unset)
{
_valueEntry?.Unsubscribe();
_valueEntry = null;
}
if (BasePriority == BindingPriority.Unset)
{
_baseValueEntry?.Unsubscribe();
_baseValueEntry = null;
}
}
/// <summary>
/// Sets the value and base value for a non-LocalValue priority, raising
/// <see cref="AvaloniaObject.PropertyChanged"/> where necessary.
/// </summary> /// </summary>
/// <param name="owner">The associated value store.</param> /// <param name="owner">The associated value store.</param>
/// <param name="property">The property being changed.</param>
/// <param name="value">The new value of the property.</param> /// <param name="value">The new value of the property.</param>
/// <param name="priority">The priority of the new value.</param> /// <param name="priority">The priority of the new value.</param>
public abstract void SetAndRaise( public abstract void SetAndRaise(
ValueStore owner, ValueStore owner,
AvaloniaProperty property, IValueEntry value,
object? value,
BindingPriority priority); BindingPriority priority);
/// <summary> /// <summary>
/// Sets the value and base value, raising <see cref="AvaloniaObject.PropertyChanged"/> /// Sets the value and base value for a non-LocalValue priority, raising
/// where necessary. /// <see cref="AvaloniaObject.PropertyChanged"/> where necessary.
/// </summary> /// </summary>
/// <param name="owner">The associated value store.</param> /// <param name="owner">The associated value store.</param>
/// <param name="property">The property being changed.</param>
/// <param name="value">The new value of the property.</param> /// <param name="value">The new value of the property.</param>
/// <param name="priority">The priority of the new value.</param> /// <param name="priority">The priority of the new value.</param>
/// <param name="baseValue">The new base value of the property.</param> /// <param name="baseValue">The new base value of the property.</param>
/// <param name="basePriority">The priority of the new base value.</param> /// <param name="basePriority">The priority of the new base value.</param>
public abstract void SetAndRaise( public abstract void SetAndRaise(
ValueStore owner, ValueStore owner,
AvaloniaProperty property, IValueEntry value,
object? value,
BindingPriority priority, BindingPriority priority,
object? baseValue, IValueEntry baseValue,
BindingPriority basePriority); BindingPriority basePriority);
/// <summary>
/// Sets the value, raising <see cref="AvaloniaObject.PropertyChanged"/>
/// where necessary.
/// </summary>
/// <param name="owner">The associated value store.</param>
/// <param name="entry">The value entry with the new value of the property.</param>
/// <param name="priority">The priority of the new value.</param>
/// <remarks>
/// This method does not set the base value.
/// </remarks>
public abstract void SetAndRaise(
ValueStore owner,
IValueEntry entry,
BindingPriority priority);
/// <summary> /// <summary>
/// Set the value priority, but leaves the value unchanged. /// Set the value priority, but leaves the value unchanged.
/// </summary> /// </summary>
@ -103,6 +119,14 @@ namespace Avalonia.PropertyStore
EffectiveValue? oldValue, EffectiveValue? oldValue,
EffectiveValue? newValue); EffectiveValue? newValue);
/// <summary>
/// Removes the current animation value and reverts to the base value, raising
/// <see cref="AvaloniaObject.PropertyChanged"/> where necessary.
/// </summary>
/// <param name="owner">The associated value store.</param>
/// <param name="property">The property being changed.</param>
public abstract void RemoveAnimationAndRaise(ValueStore owner, AvaloniaProperty property);
/// <summary> /// <summary>
/// Coerces the property value. /// Coerces the property value.
/// </summary> /// </summary>
@ -120,5 +144,28 @@ namespace Avalonia.PropertyStore
protected abstract object? GetBoxedValue(); protected abstract object? GetBoxedValue();
protected abstract object? GetBoxedBaseValue(); protected abstract object? GetBoxedBaseValue();
protected void UpdateValueEntry(IValueEntry? entry, BindingPriority priority)
{
if (priority <= Priority && entry != _valueEntry)
{
_valueEntry?.Unsubscribe();
_valueEntry = entry;
}
if (priority <= BasePriority &&
priority >= BindingPriority.LocalValue &&
entry != _baseValueEntry)
{
_baseValueEntry?.Unsubscribe();
_baseValueEntry = entry;
}
}
protected void UnsubscribeValueEntries()
{
_valueEntry?.Unsubscribe();
_baseValueEntry?.Unsubscribe();
}
} }
} }

208
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using Avalonia.Data; using Avalonia.Data;
using JetBrains.Annotations;
namespace Avalonia.PropertyStore namespace Avalonia.PropertyStore
{ {
@ -60,44 +61,117 @@ namespace Avalonia.PropertyStore
public override void SetAndRaise( public override void SetAndRaise(
ValueStore owner, ValueStore owner,
AvaloniaProperty property, IValueEntry value,
object? value,
BindingPriority priority) BindingPriority priority)
{ {
// `value` should already have been converted to the correct type and Debug.Assert(priority != BindingPriority.LocalValue);
// validated by this point. UpdateValueEntry(value, priority);
SetAndRaise(owner, (StyledPropertyBase<T>)property, (T)value!, priority); SetAndRaiseCore(owner, (StyledPropertyBase<T>)value.Property, (T)value.GetValue()!, priority);
} }
public override void SetAndRaise( public override void SetAndRaise(
ValueStore owner, ValueStore owner,
AvaloniaProperty property, IValueEntry value,
object? value,
BindingPriority priority, BindingPriority priority,
object? baseValue, IValueEntry baseValue,
BindingPriority basePriority) BindingPriority basePriority)
{ {
SetAndRaise(owner, (StyledPropertyBase<T>)property, (T)value!, priority, (T)baseValue!, basePriority); Debug.Assert(priority != BindingPriority.LocalValue);
SetAndRaiseCore(owner, (StyledPropertyBase<T>)value.Property, (T)value.GetValue()!, priority, (T)baseValue!, basePriority);
} }
public override void SetAndRaise( public void SetLocalValueAndRaise(
ValueStore owner, ValueStore owner,
IValueEntry entry, StyledPropertyBase<T> property,
BindingPriority priority) T value)
{ {
var value = entry is IValueEntry<T> typed ? typed.GetValue() : (T)entry.GetValue()!; SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue);
SetAndRaise(owner, (StyledPropertyBase<T>)entry.Property, value, priority);
} }
/// <summary> public bool TryGetBaseValue([MaybeNullWhen(false)] out T value)
/// Sets the value and base value, raising <see cref="AvaloniaObject.PropertyChanged"/> {
/// where necessary. value = _baseValue!;
/// </summary> return BasePriority != BindingPriority.Unset;
/// <param name="owner">The object on which to raise events.</param> }
/// <param name="property">The property being changed.</param>
/// <param name="value">The new value of the property.</param> public override void RaiseInheritedValueChanged(
/// <param name="priority">The priority of the new value.</param> AvaloniaObject owner,
public void SetAndRaise( AvaloniaProperty property,
EffectiveValue? oldValue,
EffectiveValue? newValue)
{
Debug.Assert(oldValue is not null || newValue is not null);
var p = (StyledPropertyBase<T>)property;
var o = oldValue is not null ? ((EffectiveValue<T>)oldValue).Value : p.GetDefaultValue(owner.GetType());
var n = newValue is not null ? ((EffectiveValue<T>)newValue).Value : p.GetDefaultValue(owner.GetType());
var priority = newValue is not null ? BindingPriority.Inherited : BindingPriority.Unset;
if (!EqualityComparer<T>.Default.Equals(o, n))
{
owner.RaisePropertyChanged(p, o, n, priority, true);
}
}
public override void RemoveAnimationAndRaise(ValueStore owner, AvaloniaProperty property)
{
Debug.Assert(Priority != BindingPriority.Animation);
Debug.Assert(BasePriority != BindingPriority.Unset);
UpdateValueEntry(null, BindingPriority.Animation);
SetAndRaiseCore(owner, (StyledPropertyBase<T>)property, _baseValue!, BasePriority);
}
public override void CoerceValue(ValueStore owner, AvaloniaProperty property)
{
if (_uncommon is null)
return;
SetAndRaiseCore(
owner,
(StyledPropertyBase<T>)property,
_uncommon._uncoercedValue!,
Priority,
_uncommon._uncoercedBaseValue!,
BasePriority);
}
public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property)
{
UnsubscribeValueEntries();
DisposeAndRaiseUnset(owner, (StyledPropertyBase<T>)property);
}
public void DisposeAndRaiseUnset(ValueStore owner, StyledPropertyBase<T> property)
{
BindingPriority priority;
T oldValue;
if (property.Inherits && owner.TryGetInheritedValue(property, out var i))
{
oldValue = ((EffectiveValue<T>)i).Value;
priority = BindingPriority.Inherited;
}
else
{
oldValue = property.GetDefaultValue(owner.GetType());
priority = BindingPriority.Unset;
}
if (!EqualityComparer<T>.Default.Equals(oldValue, Value))
{
owner.Owner.RaisePropertyChanged(property, Value, oldValue, priority, true);
if (property.Inherits)
owner.OnInheritedEffectiveValueDisposed(property, Value);
}
}
protected override object? GetBoxedValue() => Value;
protected override object? GetBoxedBaseValue()
{
return BasePriority != BindingPriority.Unset ? _baseValue : AvaloniaProperty.UnsetValue;
}
private void SetAndRaiseCore(
ValueStore owner, ValueStore owner,
StyledPropertyBase<T> property, StyledPropertyBase<T> property,
T value, T value,
@ -144,17 +218,7 @@ namespace Avalonia.PropertyStore
} }
} }
/// <summary> private void SetAndRaiseCore(
/// Sets the value and base value, raising <see cref="AvaloniaObject.PropertyChanged"/>
/// where necessary.
/// </summary>
/// <param name="owner">The object on which to raise events.</param>
/// <param name="property">The property being changed.</param>
/// <param name="value">The new value of the property.</param>
/// <param name="priority">The priority of the new value.</param>
/// <param name="baseValue">The new base value of the property.</param>
/// <param name="basePriority">The priority of the new base value.</param>
public void SetAndRaise(
ValueStore owner, ValueStore owner,
StyledPropertyBase<T> property, StyledPropertyBase<T> property,
T value, T value,
@ -187,7 +251,7 @@ namespace Avalonia.PropertyStore
} }
if (priority != BindingPriority.Unset && if (priority != BindingPriority.Unset &&
(BasePriority == BindingPriority.Unset || (BasePriority == BindingPriority.Unset ||
!EqualityComparer<T>.Default.Equals(_baseValue, bv))) !EqualityComparer<T>.Default.Equals(_baseValue, bv)))
{ {
_baseValue = v; _baseValue = v;
@ -212,80 +276,6 @@ namespace Avalonia.PropertyStore
} }
} }
public bool TryGetBaseValue([MaybeNullWhen(false)] out T value)
{
value = _baseValue!;
return BasePriority != BindingPriority.Unset;
}
public override void RaiseInheritedValueChanged(
AvaloniaObject owner,
AvaloniaProperty property,
EffectiveValue? oldValue,
EffectiveValue? newValue)
{
Debug.Assert(oldValue is not null || newValue is not null);
var p = (StyledPropertyBase<T>)property;
var o = oldValue is not null ? ((EffectiveValue<T>)oldValue).Value : p.GetDefaultValue(owner.GetType());
var n = newValue is not null ? ((EffectiveValue<T>)newValue).Value : p.GetDefaultValue(owner.GetType());
var priority = newValue is not null ? BindingPriority.Inherited : BindingPriority.Unset;
if (!EqualityComparer<T>.Default.Equals(o, n))
{
owner.RaisePropertyChanged(p, o, n, priority, true);
}
}
public override void CoerceValue(ValueStore owner, AvaloniaProperty property)
{
if (_uncommon is null)
return;
SetAndRaise(
owner,
(StyledPropertyBase<T>)property,
_uncommon._uncoercedValue!,
Priority,
_uncommon._uncoercedBaseValue!,
BasePriority);
}
public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property)
{
DisposeAndRaiseUnset(owner, (StyledPropertyBase<T>)property);
}
public void DisposeAndRaiseUnset(ValueStore owner, StyledPropertyBase<T> property)
{
BindingPriority priority;
T oldValue;
if (property.Inherits && owner.TryGetInheritedValue(property, out var i))
{
oldValue = ((EffectiveValue<T>)i).Value;
priority = BindingPriority.Inherited;
}
else
{
oldValue = property.GetDefaultValue(owner.GetType());
priority = BindingPriority.Unset;
}
if (!EqualityComparer<T>.Default.Equals(oldValue, Value))
{
owner.Owner.RaisePropertyChanged(property, Value, oldValue, priority, true);
if (property.Inherits)
owner.OnInheritedEffectiveValueDisposed(property, Value);
}
}
protected override object? GetBoxedValue() => Value;
protected override object? GetBoxedBaseValue()
{
return BasePriority != BindingPriority.Unset ? _baseValue : AvaloniaProperty.UnsetValue;
}
private class UncommonFields private class UncommonFields
{ {
public Func<IAvaloniaObject, T, T>? _coerce; public Func<IAvaloniaObject, T, T>? _coerce;

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

@ -41,7 +41,7 @@ namespace Avalonia.PropertyStore
return e; return e;
} }
public IDisposable AddValue<T>(StyledPropertyBase<T> property, T value) public ImmediateValueEntry<T> AddValue<T>(StyledPropertyBase<T> property, T value)
{ {
var e = new ImmediateValueEntry<T>(this, property, value); var e = new ImmediateValueEntry<T>(this, property, value);
Add(e); Add(e);

193
src/Avalonia.Base/PropertyStore/ValueStore.cs

@ -62,10 +62,7 @@ namespace Avalonia.PropertyStore
var result = frame.AddBinding(property, source); var result = frame.AddBinding(property, source);
if (effective is null || priority <= effective.Priority) if (effective is null || priority <= effective.Priority)
{
result.Start(); result.Start();
UnsubscribeInactiveValues(frameIndex, property);
}
return result; return result;
} }
@ -92,10 +89,7 @@ namespace Avalonia.PropertyStore
var result = frame.AddBinding(property, source); var result = frame.AddBinding(property, source);
if (effective is null || priority <= effective.Priority) if (effective is null || priority <= effective.Priority)
{
result.Start(); result.Start();
UnsubscribeInactiveValues(frameIndex, property);
}
return result; return result;
} }
@ -122,10 +116,7 @@ namespace Avalonia.PropertyStore
var result = frame.AddBinding(property, source); var result = frame.AddBinding(property, source);
if (effective is null || priority <= effective.Priority) if (effective is null || priority <= effective.Priority)
{
result.Start(); result.Start();
UnsubscribeInactiveValues(frameIndex, property);
}
return result; return result;
} }
@ -177,26 +168,43 @@ namespace Avalonia.PropertyStore
throw new ArgumentException($"{value} is not a valid value for '{property.Name}."); throw new ArgumentException($"{value} is not a valid value for '{property.Name}.");
} }
IDisposable? result = null;
if (priority != BindingPriority.LocalValue) if (priority != BindingPriority.LocalValue)
{ {
var frame = GetOrCreateImmediateValueFrame(property, priority, out var frameIndex); var frame = GetOrCreateImmediateValueFrame(property, priority, out var frameIndex);
result = frame.AddValue(property, value); var result = frame.AddValue(property, value);
UnsubscribeInactiveValues(frameIndex, property);
}
if (TryGetEffectiveValue(property, out var existing)) if (TryGetEffectiveValue(property, out var existing))
{ {
var effective = (EffectiveValue<T>)existing; var effective = (EffectiveValue<T>)existing;
effective.SetAndRaise(this, property, value, priority); effective.SetAndRaise(this, result, priority);
}
else
{
var defaultValue = property.GetDefaultValue(Owner.GetType());
var effectiveValue = new EffectiveValue<T>(Owner, property, defaultValue, BindingPriority.Unset);
AddEffectiveValue(property, effectiveValue);
effectiveValue.SetAndRaise(this, result, priority);
}
return result;
} }
else else
{ {
AddEffectiveValueAndRaise(property, value, priority); if (TryGetEffectiveValue(property, out var existing))
} {
var effective = (EffectiveValue<T>)existing;
effective.SetLocalValueAndRaise(this, property, value);
}
else
{
var defaultValue = property.GetDefaultValue(Owner.GetType());
var effectiveValue = new EffectiveValue<T>(Owner, property, defaultValue, BindingPriority.Unset);
AddEffectiveValue(property, effectiveValue);
effectiveValue.SetLocalValueAndRaise(this, property, value);
}
return result; return null;
}
} }
public object? GetValue(AvaloniaProperty property) public object? GetValue(AvaloniaProperty property)
@ -349,40 +357,15 @@ namespace Avalonia.PropertyStore
/// Called by non-LocalValue binding entries to re-evaluate the effective value when the /// Called by non-LocalValue binding entries to re-evaluate the effective value when the
/// binding produces a new value. /// binding produces a new value.
/// </summary> /// </summary>
/// <param name="property">The bound property.</param> /// <param name="entry">The binding entry.</param>
/// <param name="priority">The priority of binding which produced a new value.</param> /// <param name="priority">The priority of binding which produced a new value.</param>
/// <param name="value">The new value.</param>
public void OnBindingValueChanged( public void OnBindingValueChanged(
AvaloniaProperty property, IValueEntry entry,
BindingPriority priority, BindingPriority priority)
object? value)
{ {
Debug.Assert(priority != BindingPriority.LocalValue); Debug.Assert(priority != BindingPriority.LocalValue);
if (TryGetEffectiveValue(property, out var existing)) var property = entry.Property;
{
if (priority <= existing.Priority)
ReevaluateEffectiveValue(property, existing);
}
else
{
AddEffectiveValueAndRaise(property, value, priority);
}
}
/// <summary>
/// Called by non-LocalValue binding entries to re-evaluate the effective value when the
/// binding produces a new value.
/// </summary>
/// <param name="property">The bound property.</param>
/// <param name="priority">The priority of binding which produced a new value.</param>
/// <param name="value">The new value.</param>
public void OnBindingValueChanged<T>(
StyledPropertyBase<T> property,
BindingPriority priority,
T value)
{
Debug.Assert(priority != BindingPriority.LocalValue);
if (TryGetEffectiveValue(property, out var existing)) if (TryGetEffectiveValue(property, out var existing))
{ {
@ -391,7 +374,7 @@ namespace Avalonia.PropertyStore
} }
else else
{ {
AddEffectiveValueAndRaise(property, value, priority); AddEffectiveValueAndRaise(property, entry, priority);
} }
} }
@ -413,8 +396,8 @@ namespace Avalonia.PropertyStore
} }
/// <summary> /// <summary>
/// Called by a <see cref="BindingEntry{T}"/> to re-evaluate the effective value when the /// Called by a <see cref="BindingEntryBase{TValue, TSource}"/> to re-evaluate the
/// binding completes or terminates on error. /// effective value when the binding completes or terminates on error.
/// </summary> /// </summary>
/// <param name="property">The previously bound property.</param> /// <param name="property">The previously bound property.</param>
/// <param name="frame">The frame which contained the binding.</param> /// <param name="frame">The frame which contained the binding.</param>
@ -697,31 +680,14 @@ namespace Avalonia.PropertyStore
/// event and notifies inheritance children if necessary . /// event and notifies inheritance children if necessary .
/// </summary> /// </summary>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="value">The property value.</param> /// <param name="entry">The value entry.</param>
/// <param name="priority">The value priority.</param> /// <param name="priority">The value priority.</param>
private void AddEffectiveValueAndRaise(AvaloniaProperty property, object? value, BindingPriority priority) private void AddEffectiveValueAndRaise(AvaloniaProperty property, IValueEntry entry, BindingPriority priority)
{ {
Debug.Assert(priority < BindingPriority.Inherited); Debug.Assert(priority < BindingPriority.Inherited);
var effectiveValue = property.CreateEffectiveValue(Owner); var effectiveValue = property.CreateEffectiveValue(Owner);
AddEffectiveValue(property, effectiveValue); AddEffectiveValue(property, effectiveValue);
effectiveValue.SetAndRaise(this, property, value, priority); effectiveValue.SetAndRaise(this, entry, priority);
}
/// <summary>
/// Adds a new effective value, raises the initial <see cref="AvaloniaObject.PropertyChanged"/>
/// event and notifies inheritance children if necessary .
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
/// <param name="property">The property.</param>
/// <param name="value">The property value.</param>
/// <param name="priority">The value priority.</param>
private void AddEffectiveValueAndRaise<T>(StyledPropertyBase<T> property, T value, BindingPriority priority)
{
Debug.Assert(priority < BindingPriority.Inherited);
var defaultValue = property.GetDefaultValue(Owner.GetType());
var effectiveValue = new EffectiveValue<T>(Owner, property, defaultValue, BindingPriority.Unset);
AddEffectiveValue(property, effectiveValue);
effectiveValue.SetAndRaise(this, property, value, priority);
} }
private bool RemoveEffectiveValue(AvaloniaProperty property) private bool RemoveEffectiveValue(AvaloniaProperty property)
@ -796,14 +762,8 @@ namespace Avalonia.PropertyStore
var generation = _frameGeneration; var generation = _frameGeneration;
// Reset all non-LocalValue effective value to Unset priority. // Notify the existing effective value that reevaluation is starting.
if (current is not null) current?.BeginReevaluation(ignoreLocalValue);
{
if (ignoreLocalValue || current.Priority != BindingPriority.LocalValue)
current.SetPriority(BindingPriority.Unset);
if (ignoreLocalValue || current.BasePriority != BindingPriority.LocalValue)
current.SetBasePriority(BindingPriority.Unset);
}
// Iterate the frames to get the effective value. // Iterate the frames to get the effective value.
for (var i = _frames.Count - 1; i >= 0; --i) for (var i = _frames.Count - 1; i >= 0; --i)
@ -821,7 +781,16 @@ namespace Avalonia.PropertyStore
return; return;
} }
if (foundEntry && entry!.HasValue) // We're interested in the value if:
// - There is no current effective value, or
// - The value's priority is higher than the current effective value's priority, or
// - The value is a non-animation value and its priority is higher than the current
// effective value's base priority
var isRelevantPriority = current is null ||
priority < current.Priority ||
(priority > BindingPriority.Animation && priority < current.BasePriority);
if (foundEntry && isRelevantPriority && entry!.HasValue)
{ {
if (current is not null) if (current is not null)
{ {
@ -838,19 +807,13 @@ namespace Avalonia.PropertyStore
if (generation != _frameGeneration) if (generation != _frameGeneration)
goto restart; goto restart;
if (current is not null && if (current?.Priority < BindingPriority.Unset &&
current.Priority < BindingPriority.Unset && current?.BasePriority < BindingPriority.Unset)
current.BasePriority < BindingPriority.Unset) break;
{
// If the active state of one of the trailing frames has changed since
// the last read, then we need to re-evaluate the effective values of all
// properties.
if (UnsubscribeInactiveValues(i, property))
ReevaluateEffectiveValues();
return;
}
} }
current?.EndReevaluation();
if (current?.Priority == BindingPriority.Unset) if (current?.Priority == BindingPriority.Unset)
{ {
if (current.BasePriority == BindingPriority.Unset) if (current.BasePriority == BindingPriority.Unset)
@ -860,7 +823,7 @@ namespace Avalonia.PropertyStore
} }
else else
{ {
current.SetAndRaise(this, property, current.BaseValue, current.BasePriority); current.RemoveAnimationAndRaise(this, property);
} }
} }
} }
@ -885,16 +848,9 @@ namespace Avalonia.PropertyStore
var generation = _frameGeneration; var generation = _frameGeneration;
var count = _effectiveValues.Count; var count = _effectiveValues.Count;
// Reset all non-LocalValue effective values to Unset priority. // Notify the existing effective values that reevaluation is starting.
for (var i = 0; i < count; ++i) for (var i = 0; i < count; ++i)
{ _effectiveValues[i].BeginReevaluation();
var e = _effectiveValues[i];
if (e.Priority != BindingPriority.LocalValue)
e.SetPriority(BindingPriority.Unset);
if (e.BasePriority != BindingPriority.LocalValue)
e.SetBasePriority(BindingPriority.Unset);
}
// Iterate the frames, setting and creating effective values. // Iterate the frames, setting and creating effective values.
for (var i = _frames.Count - 1; i >= 0; --i) for (var i = _frames.Count - 1; i >= 0; --i)
@ -912,10 +868,9 @@ namespace Avalonia.PropertyStore
{ {
var entry = frame.GetEntry(j); var entry = frame.GetEntry(j);
var property = entry.Property; var property = entry.Property;
EffectiveValue? effectiveValue;
// Unsubscribe and skip if we already have a value/base value for this property. // Unsubscribe and skip if we already have a value/base value for this property.
if (_effectiveValues.TryGetValue(property, out effectiveValue) == true && if (_effectiveValues.TryGetValue(property, out var effectiveValue) == true &&
effectiveValue.BasePriority < BindingPriority.Unset) effectiveValue.BasePriority < BindingPriority.Unset)
{ {
entry.Unsubscribe(); entry.Unsubscribe();
@ -949,6 +904,7 @@ namespace Avalonia.PropertyStore
for (var i = 0; i < count; ++i) for (var i = 0; i < count; ++i)
{ {
_effectiveValues.GetKeyValue(i, out var key, out var e); _effectiveValues.GetKeyValue(i, out var key, out var e);
e.EndReevaluation();
if (e.Priority == BindingPriority.Unset) if (e.Priority == BindingPriority.Unset)
{ {
@ -973,33 +929,6 @@ namespace Avalonia.PropertyStore
} }
} }
private bool UnsubscribeInactiveValues(int activeFrameIndex, AvaloniaProperty property)
{
var foundBaseValue = _frames[activeFrameIndex].Priority != BindingPriority.Animation;
var activeChanged = false;
for (var i = activeFrameIndex - 1; i >= 0; --i)
{
var frame = _frames[i];
if (!foundBaseValue && frame.Priority > BindingPriority.Animation)
{
foundBaseValue = true;
continue;
}
if ((foundBaseValue || frame.Priority <= BindingPriority.Animation) &&
frame.TryGetEntryIfActive(property, out var entry, out var changed))
{
if (changed && frame.EntryCount > 1)
activeChanged = true;
entry.Unsubscribe();
}
}
return activeChanged;
}
private bool TryGetEffectiveValue( private bool TryGetEffectiveValue(
AvaloniaProperty property, AvaloniaProperty property,
[NotNullWhen(true)] out EffectiveValue? value) [NotNullWhen(true)] out EffectiveValue? value)

Loading…
Cancel
Save