Browse Source

Make DeferredSetter tied to AvaloniaProperty and use a regular Dictionary. Remove SettingStatus entries for bindings from the dictionary for bindings that don't create a queue of pending values.

Jeremy Koritzinsky 7 years ago
parent
commit
1ad7f27cf9

+ 1 - 1
src/Avalonia.Base/IPriorityValueOwner.cs

@@ -33,6 +33,6 @@ namespace Avalonia
         /// </summary>
         void VerifyAccess();
 
-        DeferredSetter<AvaloniaProperty, object> Setter { get; }
+        DeferredSetter<object> Setter { get; }
     }
 }

+ 42 - 16
src/Avalonia.Base/Utilities/DeferredSetter.cs

@@ -10,10 +10,8 @@ namespace Avalonia.Utilities
     /// A utility class to enable deferring assignment until after property-changed notifications are sent.
     /// Used to fix #855.
     /// </summary>
-    /// <typeparam name="TProperty">The type of the object that represents the property.</typeparam>
     /// <typeparam name="TSetRecord">The type of value with which to track the delayed assignment.</typeparam>
-    class DeferredSetter<TProperty, TSetRecord>
-        where TProperty: class
+    class DeferredSetter<TSetRecord>
     {
         private struct NotifyDisposable : IDisposable
         {
@@ -47,20 +45,37 @@ namespace Avalonia.Utilities
                     return pendingValues ?? (pendingValues = new SingleOrQueue<TSetRecord>());
                 }
             }
+
+            public bool IsSimpleSet => pendingValues?.HasTail != true;
         }
 
-        private readonly ConditionalWeakTable<TProperty, SettingStatus> setRecords = new ConditionalWeakTable<TProperty, SettingStatus>();
+        private Dictionary<AvaloniaProperty, SettingStatus> _setRecords;
+        private Dictionary<AvaloniaProperty, SettingStatus> SetRecords
+            => _setRecords ?? (_setRecords = new Dictionary<AvaloniaProperty, SettingStatus>());
+
+        private SettingStatus GetOrCreateStatus(AvaloniaProperty property)
+        {
+            if (!SetRecords.TryGetValue(property, out var status))
+            {
+                status = new SettingStatus();
+                SetRecords.Add(property, status);
+            }
+
+            return status;
+        }
 
         /// <summary>
         /// Mark the property as currently notifying.
         /// </summary>
         /// <param name="property">The property to mark as notifying.</param>
         /// <returns>Returns a disposable that when disposed, marks the property as done notifying.</returns>
-        private NotifyDisposable MarkNotifying(TProperty property)
+        private NotifyDisposable MarkNotifying(AvaloniaProperty property)
         {
             Contract.Requires<InvalidOperationException>(!IsNotifying(property));
-            
-            return new NotifyDisposable(setRecords.GetOrCreateValue(property));
+
+            SettingStatus status = GetOrCreateStatus(property);
+
+            return new NotifyDisposable(status);
         }
 
         /// <summary>
@@ -68,19 +83,19 @@ namespace Avalonia.Utilities
         /// </summary>
         /// <param name="property">The property.</param>
         /// <returns>If the property is currently notifying listeners.</returns>
-        private bool IsNotifying(TProperty property)
-            => setRecords.TryGetValue(property, out var value) && value.Notifying;
+        private bool IsNotifying(AvaloniaProperty property)
+            => SetRecords.TryGetValue(property, out var value) && value.Notifying;
 
         /// <summary>
         /// Add a pending assignment for the property.
         /// </summary>
         /// <param name="property">The property.</param>
         /// <param name="value">The value to assign.</param>
-        private void AddPendingSet(TProperty property, TSetRecord value)
+        private void AddPendingSet(AvaloniaProperty property, TSetRecord value)
         {
             Contract.Requires<InvalidOperationException>(IsNotifying(property));
 
-            setRecords.GetOrCreateValue(property).PendingValues.Enqueue(value);
+            GetOrCreateStatus(property).PendingValues.Enqueue(value);
         }
 
         /// <summary>
@@ -88,9 +103,9 @@ namespace Avalonia.Utilities
         /// </summary>
         /// <param name="property">The property to check.</param>
         /// <returns>If the property has any pending assignments.</returns>
-        private bool HasPendingSet(TProperty property)
+        private bool HasPendingSet(AvaloniaProperty property)
         {
-            return setRecords.TryGetValue(property, out var status) && !status.PendingValues.Empty;
+            return SetRecords.TryGetValue(property, out var status) && !status.PendingValues.Empty;
         }
 
         /// <summary>
@@ -98,9 +113,17 @@ namespace Avalonia.Utilities
         /// </summary>
         /// <param name="property">The property to check.</param>
         /// <returns>The first pending assignment for the property.</returns>
-        private TSetRecord GetFirstPendingSet(TProperty property)
+        private TSetRecord GetFirstPendingSet(AvaloniaProperty property)
+        {
+            return GetOrCreateStatus(property).PendingValues.Dequeue();
+        }
+
+        private void CleanupSetStatus(AvaloniaProperty property)
         {
-            return setRecords.GetOrCreateValue(property).PendingValues.Dequeue();
+            if (SetRecords.TryGetValue(property, out var status) && status.IsSimpleSet)
+            {
+                SetRecords.Remove(property);
+            }
         }
 
         public delegate bool SetterDelegate<TValue>(TSetRecord record, ref TValue backing, Action<Action> notifyCallback);
@@ -116,7 +139,7 @@ namespace Avalonia.Utilities
         /// </param>
         /// <param name="value">The value to try to set.</param>
         public bool SetAndNotify<TValue>(
-            TProperty property,
+            AvaloniaProperty property,
             ref TValue backing,
             SetterDelegate<TValue> setterCallback,
             TSetRecord value)
@@ -145,6 +168,9 @@ namespace Avalonia.Utilities
                         }
                     });
                 }
+
+                CleanupSetStatus(property);
+
                 return updated;
             }
             else if(!object.Equals(value, backing))

+ 1 - 1
src/Avalonia.Base/Utilities/SingleOrQueue.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Utilities
 
         private Queue<T> Tail => _tail ?? (_tail = new Queue<T>());
 
-        private bool HasTail => _tail != null;
+        public bool HasTail => _tail != null;
 
         public bool Empty { get; private set; } = true;
 

+ 3 - 3
src/Avalonia.Base/ValueStore.cs

@@ -168,14 +168,14 @@ namespace Avalonia
             return value;
         }
 
-        private DeferredSetter<AvaloniaProperty, object> _defferedSetter;
+        private DeferredSetter<object> _defferedSetter;
 
-        public DeferredSetter<AvaloniaProperty, object> Setter
+        public DeferredSetter<object> Setter
         {
             get
             {
                 return _defferedSetter ??
-                    (_defferedSetter = new DeferredSetter<AvaloniaProperty, object>());
+                    (_defferedSetter = new DeferredSetter<object>());
             }
         }
     }