diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs index e29e7339ae..7beab5f497 100644 --- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs +++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs @@ -13,6 +13,8 @@ namespace Avalonia /// public class AvaloniaPropertyRegistry { + private readonly Dictionary _allProperties = + new Dictionary(); private readonly Dictionary> _registered = new Dictionary>(); private readonly Dictionary> _attached = @@ -148,6 +150,16 @@ namespace Avalonia return FindRegistered(o.GetType(), name); } + /// + /// Finds a registered property by Id. + /// + /// The property Id. + /// The registered property or null if no matching property found. + public AvaloniaProperty FindRegistered(int id) + { + return _allProperties.TryGetValue(id, out var value) ? value : null; + } + /// /// Checks whether a is registered on a type. /// @@ -202,7 +214,8 @@ namespace Avalonia { inner.Add(property.Id, property); } - + + _allProperties[property.Id] = property; _registeredCache.Clear(); } @@ -238,6 +251,7 @@ namespace Avalonia inner.Add(property.Id, property); } + _allProperties[property.Id] = property; _attachedCache.Clear(); } } diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs index adbe89aceb..f78aaab221 100644 --- a/src/Avalonia.Base/ValueStore.cs +++ b/src/Avalonia.Base/ValueStore.cs @@ -7,13 +7,21 @@ namespace Avalonia { internal class ValueStore : IPriorityValueOwner { + struct Entry + { + internal int PropertyId; + internal object Value; + } + private readonly AvaloniaObject _owner; - private readonly Dictionary _values = - new Dictionary(); + private Entry[] _entries; public ValueStore(AvaloniaObject owner) { _owner = owner; + + // The last item in the list is always int.MaxValue + _entries = new[] { new Entry { PropertyId = int.MaxValue, Value = null } }; } public IDisposable AddBinding( @@ -23,7 +31,7 @@ namespace Avalonia { PriorityValue priorityValue; - if (_values.TryGetValue(property, out var v)) + if (TryGetValue(property, out var v)) { priorityValue = v as PriorityValue; @@ -31,13 +39,13 @@ namespace Avalonia { priorityValue = CreatePriorityValue(property); priorityValue.SetValue(v, (int)BindingPriority.LocalValue); - _values[property] = priorityValue; + SetValueInternal(property, priorityValue); } } else { priorityValue = CreatePriorityValue(property); - _values.Add(property, priorityValue); + AddValueInternal(property, priorityValue); } return priorityValue.Add(source, (int)priority); @@ -47,7 +55,7 @@ namespace Avalonia { PriorityValue priorityValue; - if (_values.TryGetValue(property, out var v)) + if (TryGetValue(property, out var v)) { priorityValue = v as PriorityValue; @@ -55,7 +63,7 @@ namespace Avalonia { if (priority == (int)BindingPriority.LocalValue) { - _values[property] = Validate(property, value); + SetValueInternal(property, Validate(property, value)); Changed(property, priority, v, value); return; } @@ -63,7 +71,7 @@ namespace Avalonia { priorityValue = CreatePriorityValue(property); priorityValue.SetValue(v, (int)BindingPriority.LocalValue); - _values[property] = priorityValue; + SetValueInternal(property, priorityValue); } } } @@ -76,14 +84,14 @@ namespace Avalonia if (priority == (int)BindingPriority.LocalValue) { - _values.Add(property, Validate(property, value)); + AddValueInternal(property, Validate(property, value)); Changed(property, priority, AvaloniaProperty.UnsetValue, value); return; } else { priorityValue = CreatePriorityValue(property); - _values.Add(property, priorityValue); + AddValueInternal(property, priorityValue); } } @@ -100,13 +108,22 @@ namespace Avalonia _owner.PriorityValueChanged(property, priority, oldValue, newValue); } - public IDictionary GetSetValues() => _values; + public IDictionary GetSetValues() + { + var dict = new Dictionary(_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; + } public object GetValue(AvaloniaProperty property) { var result = AvaloniaProperty.UnsetValue; - if (_values.TryGetValue(property, out var value)) + if (TryGetValue(property, out var value)) { result = (value is PriorityValue priorityValue) ? priorityValue.Value : value; } @@ -116,12 +133,12 @@ namespace Avalonia public bool IsAnimating(AvaloniaProperty property) { - return _values.TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating; + return TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating; } public bool IsSet(AvaloniaProperty property) { - if (_values.TryGetValue(property, out var value)) + if (TryGetValue(property, out var value)) { return ((value as PriorityValue)?.Value ?? value) != AvaloniaProperty.UnsetValue; } @@ -131,7 +148,7 @@ namespace Avalonia public void Revalidate(AvaloniaProperty property) { - if (_values.TryGetValue(property, out var value)) + if (TryGetValue(property, out var value)) { (value as PriorityValue)?.Revalidate(); } @@ -178,5 +195,114 @@ namespace Avalonia (_deferredSetter = new DeferredSetter()); } } + + private bool TryGetValue(AvaloniaProperty property, out object value) + { + (int index, bool found) = TryFindEntry(property.Id); + if (!found) + { + value = null; + return false; + } + + value = _entries[index].Value; + return true; + } + + private void AddValueInternal(AvaloniaProperty property, object 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; + } + + private void SetValueInternal(AvaloniaProperty property, object value) + { + _entries[TryFindEntry(property.Id).Item1].Value = value; + } + + private (int, bool) TryFindEntry(int propertyId) + { + if (_entries.Length <= 20) + { + // 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); + if (_entries[11].PropertyId >= propertyId) return (11, _entries[11].PropertyId == propertyId); + if (_entries[12].PropertyId >= propertyId) return (12, _entries[12].PropertyId == propertyId); + if (_entries[13].PropertyId >= propertyId) return (13, _entries[13].PropertyId == propertyId); + if (_entries[14].PropertyId >= propertyId) return (14, _entries[14].PropertyId == propertyId); + if (_entries[15].PropertyId >= propertyId) return (15, _entries[15].PropertyId == propertyId); + if (_entries[16].PropertyId >= propertyId) return (16, _entries[16].PropertyId == propertyId); + if (_entries[17].PropertyId >= propertyId) return (17, _entries[17].PropertyId == propertyId); + if (_entries[18].PropertyId >= propertyId) return (18, _entries[18].PropertyId == propertyId); + } + else + { + int low = 0; + int high = _entries.Length; + int id; + + if (high > 0) + { + 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); + } } }