-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Batching for AvaloniaObject property values. #5070
Merged
Merged
Changes from 6 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
34b5f07
Added failing tests for #5027.
grokys 7469597
Added benchmark for fluent RepeatButton.
grokys d8fbd95
Don't subscribe to inner observable if not active.
grokys 72a4317
Lazily evaluate <Template>s in setters.
grokys 662b1e0
More tests for #5027.
grokys 0f23811
Implement batching for AvaloniaObject property values.
grokys e542c5b
Removed old code.
grokys d256e30
Remove unused field.
grokys 27df5a2
Fix LocalValue bindings during batch update.
grokys c4c4cc8
Handle batching binding completion.
grokys 42bab93
Handle nested batch updates.
grokys 8f74fb2
Merge branch 'master' into fixes/5027-avaloniaobject-batching
danwalmsley b319a08
Merge branch 'master' into fixes/5027-avaloniaobject-batching
grokys ba2c4c2
Merge branch 'master' into fixes/5027-avaloniaobject-batching
grokys 88bc11b
Merge branch 'master' into fixes/5027-avaloniaobject-batching
MarchingCube b8a7066
Merge branch 'master' into fixes/5027-avaloniaobject-batching
danwalmsley 25413c8
fix build.
danwalmsley 38996ce
Merge branch 'master' into fixes/5027-avaloniaobject-batching
MarchingCube a5b8860
Merge branch 'master' into fixes/5027-avaloniaobject-batching
grokys File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace Avalonia.PropertyStore | ||
{ | ||
internal interface IBatchUpdate | ||
{ | ||
void BeginBatchUpdate(); | ||
void EndBatchUpdate(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,14 +18,16 @@ 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> : IValue<T>, IValueSink | ||
internal class PriorityValue<T> : IValue<T>, IValueSink, IBatchUpdate | ||
{ | ||
private readonly IAvaloniaObject _owner; | ||
private readonly IValueSink _sink; | ||
private readonly List<IPriorityValueEntry<T>> _entries = new List<IPriorityValueEntry<T>>(); | ||
private readonly Func<IAvaloniaObject, T, T>? _coerceValue; | ||
private Optional<T> _localValue; | ||
private Optional<T> _value; | ||
private bool _isCalculatingValue; | ||
private bool _batchUpdate; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably worth packing these into an flags enum to save 4 bytes per value stored. |
||
|
||
public PriorityValue( | ||
IAvaloniaObject owner, | ||
|
@@ -53,6 +55,18 @@ public PriorityValue( | |
existing.Reparent(this); | ||
_entries.Add(existing); | ||
|
||
if (existing is IBindingEntry binding && | ||
existing.Priority == BindingPriority.LocalValue) | ||
{ | ||
// Bit of a special case here: if we have a local value binding that is being | ||
// promoted to a priority value we need to make sure the binding is subscribed | ||
// even if we've got a batch operation in progress because otherwise we don't know | ||
// whether the binding or a subsequent SetValue with local priority will win. A | ||
// notification won't be sent during batch update anyway because it will be | ||
// caught and stored for later by the ValueStore. | ||
binding.Start(ignoreBatchUpdate: true); | ||
} | ||
|
||
var v = existing.GetValue(); | ||
|
||
if (v.HasValue) | ||
|
@@ -78,6 +92,28 @@ public PriorityValue( | |
public IReadOnlyList<IPriorityValueEntry<T>> Entries => _entries; | ||
Optional<object> IValue.GetValue() => _value.ToObject(); | ||
|
||
public void BeginBatchUpdate() | ||
{ | ||
_batchUpdate = true; | ||
|
||
foreach (var entry in _entries) | ||
{ | ||
(entry as IBatchUpdate)?.BeginBatchUpdate(); | ||
} | ||
} | ||
|
||
public void EndBatchUpdate() | ||
{ | ||
_batchUpdate = false; | ||
|
||
foreach (var entry in _entries) | ||
{ | ||
(entry as IBatchUpdate)?.EndBatchUpdate(); | ||
} | ||
|
||
UpdateEffectiveValue(null); | ||
} | ||
|
||
public void ClearLocalValue() | ||
{ | ||
UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>( | ||
|
@@ -137,7 +173,22 @@ public BindingEntry<T> AddBinding(IObservable<BindingValue<T>> source, BindingPr | |
return binding; | ||
} | ||
|
||
public void CoerceValue() => UpdateEffectiveValue(null); | ||
public void UpdateEffectiveValue() => UpdateEffectiveValue(null); | ||
public void Start() => UpdateEffectiveValue(null); | ||
|
||
public void RaiseValueChanged( | ||
IValueSink sink, | ||
IAvaloniaObject owner, | ||
AvaloniaProperty property, | ||
Optional<object> oldValue) | ||
{ | ||
sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>( | ||
owner, | ||
(AvaloniaProperty<T>)property, | ||
oldValue.GetValueOrDefault<T>(), | ||
_value, | ||
Priority)); | ||
} | ||
|
||
void IValueSink.ValueChanged<TValue>(AvaloniaPropertyChangedEventArgs<TValue> change) | ||
{ | ||
|
@@ -146,7 +197,7 @@ void IValueSink.ValueChanged<TValue>(AvaloniaPropertyChangedEventArgs<TValue> ch | |
_localValue = default; | ||
} | ||
|
||
if (change is AvaloniaPropertyChangedEventArgs<T> c) | ||
if (!_isCalculatingValue && change is AvaloniaPropertyChangedEventArgs<T> c) | ||
{ | ||
UpdateEffectiveValue(c); | ||
} | ||
|
@@ -188,41 +239,47 @@ private int FindInsertPoint(BindingPriority priority) | |
|
||
public (Optional<T>, BindingPriority) CalculateValue(BindingPriority maxPriority) | ||
{ | ||
var reachedLocalValues = false; | ||
_isCalculatingValue = true; | ||
|
||
for (var i = _entries.Count - 1; i >= 0; --i) | ||
try | ||
{ | ||
var entry = _entries[i]; | ||
|
||
if (entry.Priority < maxPriority) | ||
for (var i = _entries.Count - 1; i >= 0; --i) | ||
{ | ||
continue; | ||
var entry = _entries[i]; | ||
|
||
if (entry.Priority < maxPriority) | ||
{ | ||
continue; | ||
} | ||
|
||
entry.Start(); | ||
|
||
if (entry.Priority >= BindingPriority.LocalValue && | ||
maxPriority <= BindingPriority.LocalValue && | ||
_localValue.HasValue) | ||
{ | ||
return (_localValue, BindingPriority.LocalValue); | ||
} | ||
|
||
var entryValue = entry.GetValue(); | ||
|
||
if (entryValue.HasValue) | ||
{ | ||
return (entryValue, entry.Priority); | ||
} | ||
} | ||
|
||
if (!reachedLocalValues && | ||
entry.Priority >= BindingPriority.LocalValue && | ||
maxPriority <= BindingPriority.LocalValue && | ||
_localValue.HasValue) | ||
if (maxPriority <= BindingPriority.LocalValue && _localValue.HasValue) | ||
{ | ||
return (_localValue, BindingPriority.LocalValue); | ||
} | ||
|
||
var entryValue = entry.GetValue(); | ||
|
||
if (entryValue.HasValue) | ||
{ | ||
return (entryValue, entry.Priority); | ||
} | ||
return (default, BindingPriority.Unset); | ||
} | ||
|
||
if (!reachedLocalValues && | ||
maxPriority <= BindingPriority.LocalValue && | ||
_localValue.HasValue) | ||
finally | ||
{ | ||
return (_localValue, BindingPriority.LocalValue); | ||
_isCalculatingValue = false; | ||
} | ||
|
||
return (default, BindingPriority.Unset); | ||
} | ||
|
||
private void UpdateEffectiveValue(AvaloniaPropertyChangedEventArgs<T>? change) | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worth packing these into an flags enum to save 4 bytes per value stored.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If only life would be that simple 😄 Depending on the type of the entry alignment of this class will change, for example Sharplab reducing size will do nothing since there is no free space and it will get aligned.