committed by
Dariusz Komosinski
6 changed files with 420 additions and 136 deletions
@ -0,0 +1,143 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Utilities |
|||
{ |
|||
internal sealed class AvaloniaPropertyCollection<TValue> |
|||
{ |
|||
private Entry[] _entries; |
|||
|
|||
public AvaloniaPropertyCollection() |
|||
{ |
|||
// The last item in the list is always int.MaxValue
|
|||
_entries = new[] { new Entry { PropertyId = int.MaxValue, Value = default } }; |
|||
} |
|||
|
|||
private (int, bool) TryFindEntry(int propertyId) |
|||
{ |
|||
if (_entries.Length <= 12) |
|||
{ |
|||
// For small lists, we use an optimized linear search. Since the last item in the list
|
|||
// is always int.MaxValue, we can skip a conditional branch in each iteration.
|
|||
// By unrolling the loop, we can skip another unconditional branch in each iteration.
|
|||
|
|||
if (_entries[0].PropertyId >= propertyId) |
|||
return (0, _entries[0].PropertyId == propertyId); |
|||
if (_entries[1].PropertyId >= propertyId) |
|||
return (1, _entries[1].PropertyId == propertyId); |
|||
if (_entries[2].PropertyId >= propertyId) |
|||
return (2, _entries[2].PropertyId == propertyId); |
|||
if (_entries[3].PropertyId >= propertyId) |
|||
return (3, _entries[3].PropertyId == propertyId); |
|||
if (_entries[4].PropertyId >= propertyId) |
|||
return (4, _entries[4].PropertyId == propertyId); |
|||
if (_entries[5].PropertyId >= propertyId) |
|||
return (5, _entries[5].PropertyId == propertyId); |
|||
if (_entries[6].PropertyId >= propertyId) |
|||
return (6, _entries[6].PropertyId == propertyId); |
|||
if (_entries[7].PropertyId >= propertyId) |
|||
return (7, _entries[7].PropertyId == propertyId); |
|||
if (_entries[8].PropertyId >= propertyId) |
|||
return (8, _entries[8].PropertyId == propertyId); |
|||
if (_entries[9].PropertyId >= propertyId) |
|||
return (9, _entries[9].PropertyId == propertyId); |
|||
if (_entries[10].PropertyId >= propertyId) |
|||
return (10, _entries[10].PropertyId == propertyId); |
|||
} |
|||
else |
|||
{ |
|||
int low = 0; |
|||
int high = _entries.Length; |
|||
int id; |
|||
|
|||
while (high - low > 3) |
|||
{ |
|||
int pivot = (high + low) / 2; |
|||
id = _entries[pivot].PropertyId; |
|||
|
|||
if (propertyId == id) |
|||
return (pivot, true); |
|||
|
|||
if (propertyId <= id) |
|||
high = pivot; |
|||
else |
|||
low = pivot + 1; |
|||
} |
|||
|
|||
do |
|||
{ |
|||
id = _entries[low].PropertyId; |
|||
|
|||
if (id == propertyId) |
|||
return (low, true); |
|||
|
|||
if (id > propertyId) |
|||
break; |
|||
|
|||
++low; |
|||
} |
|||
while (low < high); |
|||
} |
|||
|
|||
return (0, false); |
|||
} |
|||
|
|||
public bool TryGetValue(AvaloniaProperty property, out TValue value) |
|||
{ |
|||
(int index, bool found) = TryFindEntry(property.Id); |
|||
if (!found) |
|||
{ |
|||
value = default; |
|||
return false; |
|||
} |
|||
|
|||
value = _entries[index].Value; |
|||
return true; |
|||
} |
|||
|
|||
public void AddValueInternal(AvaloniaProperty property, TValue value) |
|||
{ |
|||
Entry[] entries = new Entry[_entries.Length + 1]; |
|||
|
|||
for (int i = 0; i < _entries.Length; ++i) |
|||
{ |
|||
if (_entries[i].PropertyId > property.Id) |
|||
{ |
|||
if (i > 0) |
|||
{ |
|||
Array.Copy(_entries, 0, entries, 0, i); |
|||
} |
|||
|
|||
entries[i] = new Entry { PropertyId = property.Id, Value = value }; |
|||
Array.Copy(_entries, i, entries, i + 1, _entries.Length - i); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
_entries = entries; |
|||
} |
|||
|
|||
public void SetValueInternal(AvaloniaProperty property, TValue value) |
|||
{ |
|||
_entries[TryFindEntry(property.Id).Item1].Value = value; |
|||
} |
|||
|
|||
public Dictionary<AvaloniaProperty, TValue> ToDictionary() |
|||
{ |
|||
var dict = new Dictionary<AvaloniaProperty, TValue>(_entries.Length - 1); |
|||
|
|||
for (int i = 0; i < _entries.Length - 1; ++i) |
|||
{ |
|||
dict.Add(AvaloniaPropertyRegistry.Instance.FindRegistered(_entries[i].PropertyId), _entries[i].Value); |
|||
} |
|||
|
|||
return dict; |
|||
} |
|||
|
|||
private struct Entry |
|||
{ |
|||
internal int PropertyId; |
|||
internal TValue Value; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
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>(); |
|||
} |
|||
|
|||
public bool SetAndNotify( |
|||
AvaloniaObject source, |
|||
AvaloniaProperty<TSetRecord> property, |
|||
ISetterHandler handler, |
|||
ref TSetRecord backing, |
|||
TSetRecord value) |
|||
{ |
|||
if (!_isNotifying) |
|||
{ |
|||
bool updated; |
|||
|
|||
using (new NotifyDisposable(this)) |
|||
{ |
|||
updated = handler.Update(source, property, ref backing, value); |
|||
} |
|||
|
|||
if (!_pendingValues.Empty) |
|||
{ |
|||
using (new NotifyDisposable(this)) |
|||
{ |
|||
while (!_pendingValues.Empty) |
|||
{ |
|||
updated = handler.Update(source, property, ref backing, _pendingValues.Dequeue()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return updated; |
|||
} |
|||
|
|||
_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; |
|||
} |
|||
} |
|||
|
|||
public interface ISetterHandler |
|||
{ |
|||
bool Update( |
|||
AvaloniaObject source, |
|||
AvaloniaProperty<TSetRecord> property, |
|||
ref TSetRecord backing, |
|||
TSetRecord value); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
using BenchmarkDotNet.Attributes; |
|||
|
|||
namespace Avalonia.Benchmarks.Base |
|||
{ |
|||
[MemoryDiagnoser] |
|||
public class DirectPropertyBenchmark |
|||
{ |
|||
[Benchmark(Baseline = true)] |
|||
public void SetAndRaiseOriginal() |
|||
{ |
|||
var obj = new DirectClass(); |
|||
|
|||
for (var i = 0; i < 100; ++i) |
|||
{ |
|||
obj.IntValue += 1; |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void SetAndRaiseOptimized() |
|||
{ |
|||
var obj = new DirectClass(); |
|||
|
|||
for (var i = 0; i < 100; ++i) |
|||
{ |
|||
obj.IntValueOptimized += 1; |
|||
} |
|||
} |
|||
|
|||
[Benchmark] |
|||
public void SetAndRaiseSimple() |
|||
{ |
|||
var obj = new DirectClass(); |
|||
|
|||
for (var i = 0; i < 100; ++i) |
|||
{ |
|||
obj.IntValueSimple += 1; |
|||
} |
|||
} |
|||
|
|||
class DirectClass : AvaloniaObject |
|||
{ |
|||
private int _intValue; |
|||
|
|||
public static readonly DirectProperty<DirectClass, int> IntValueProperty = |
|||
AvaloniaProperty.RegisterDirect<DirectClass, int>(nameof(IntValue), |
|||
o => o.IntValue, |
|||
(o, v) => o.IntValue = v); |
|||
|
|||
public int IntValue |
|||
{ |
|||
get => _intValue; |
|||
set => SetAndRaise(IntValueProperty, ref _intValue, value); |
|||
} |
|||
|
|||
public int IntValueOptimized |
|||
{ |
|||
get => _intValue; |
|||
set => SetAndRaiseOptimized(IntValueProperty, ref _intValue, value); |
|||
} |
|||
|
|||
public int IntValueSimple |
|||
{ |
|||
get => _intValue; |
|||
set |
|||
{ |
|||
VerifyAccess(); |
|||
|
|||
if (_intValue == value) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var old = _intValue; |
|||
_intValue = value; |
|||
|
|||
RaisePropertyChanged(IntValueProperty, old, _intValue); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue