Browse Source

Merge pull request #2943 from MarchingCube/memory-routed-event-dynamicinvoke

Improve RoutedEvent perf and memory usage.
Jumar Macato 6 years ago
parent
commit
c217f666b7

+ 4 - 0
src/Avalonia.Interactivity/EventSubscription.cs

@@ -5,8 +5,12 @@ using System;
 
 namespace Avalonia.Interactivity
 {
+    internal delegate void HandlerInvokeSignature(Delegate baseHandler, object sender, RoutedEventArgs args);
+
     internal class EventSubscription
     {
+        public HandlerInvokeSignature InvokeAdapter { get; set; }
+
         public Delegate Handler { get; set; }
 
         public RoutingStrategies Routes { get; set; }

+ 81 - 20
src/Avalonia.Interactivity/Interactive.cs

@@ -4,8 +4,6 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
 using Avalonia.Layout;
 using Avalonia.VisualTree;
 
@@ -18,15 +16,14 @@ namespace Avalonia.Interactivity
     {
         private Dictionary<RoutedEvent, List<EventSubscription>> _eventHandlers;
 
+        private static readonly Dictionary<Type, HandlerInvokeSignature> s_invokeHandlerCache = new Dictionary<Type, HandlerInvokeSignature>();
+
         /// <summary>
         /// Gets the interactive parent of the object for bubbling and tunneling events.
         /// </summary>
         IInteractive IInteractive.InteractiveParent => ((IVisual)this).VisualParent as IInteractive;
 
-        private Dictionary<RoutedEvent, List<EventSubscription>> EventHandlers
-        {
-            get { return _eventHandlers ?? (_eventHandlers = new Dictionary<RoutedEvent, List<EventSubscription>>()); }
-        }
+        private Dictionary<RoutedEvent, List<EventSubscription>> EventHandlers => _eventHandlers ?? (_eventHandlers = new Dictionary<RoutedEvent, List<EventSubscription>>());
 
         /// <summary>
         /// Adds a handler for the specified routed event.
@@ -45,24 +42,14 @@ namespace Avalonia.Interactivity
             Contract.Requires<ArgumentNullException>(routedEvent != null);
             Contract.Requires<ArgumentNullException>(handler != null);
 
-            List<EventSubscription> subscriptions;
-
-            if (!EventHandlers.TryGetValue(routedEvent, out subscriptions))
-            {
-                subscriptions = new List<EventSubscription>();
-                EventHandlers.Add(routedEvent, subscriptions);
-            }
-
-            var sub = new EventSubscription
+            var subscription = new EventSubscription
             {
                 Handler = handler,
                 Routes = routes,
                 AlsoIfHandled = handledEventsToo,
             };
 
-            subscriptions.Add(sub);
-
-            return Disposable.Create(() => subscriptions.Remove(sub));
+            return AddEventSubscription(routedEvent, subscription);
         }
 
         /// <summary>
@@ -80,7 +67,37 @@ namespace Avalonia.Interactivity
             RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
             bool handledEventsToo = false) where TEventArgs : RoutedEventArgs
         {
-            return AddHandler(routedEvent, (Delegate)handler, routes, handledEventsToo);
+            Contract.Requires<ArgumentNullException>(routedEvent != null);
+            Contract.Requires<ArgumentNullException>(handler != null);
+
+            // EventHandler delegate is not covariant, this forces us to create small wrapper
+            // that will cast our type erased instance and invoke it.
+            Type eventArgsType = routedEvent.EventArgsType;
+
+            if (!s_invokeHandlerCache.TryGetValue(eventArgsType, out var invokeAdapter))
+            {
+                void InvokeAdapter(Delegate baseHandler, object sender, RoutedEventArgs args)
+                {
+                    var typedHandler = (EventHandler<TEventArgs>)baseHandler;
+                    var typedArgs = (TEventArgs)args;
+
+                    typedHandler(sender, typedArgs);
+                }
+
+                invokeAdapter = InvokeAdapter;
+
+                s_invokeHandlerCache.Add(eventArgsType, invokeAdapter);
+            }
+
+            var subscription = new EventSubscription
+            {
+                InvokeAdapter = invokeAdapter,
+                Handler = handler,
+                Routes = routes,
+                AlsoIfHandled = handledEventsToo,
+            };
+
+            return AddEventSubscription(routedEvent, subscription);
         }
 
         /// <summary>
@@ -196,10 +213,54 @@ namespace Avalonia.Interactivity
 
                     if (correctRoute && notFinished)
                     {
-                        sub.Handler.DynamicInvoke(this, e);
+                        if (sub.InvokeAdapter != null)
+                        {
+                            sub.InvokeAdapter(sub.Handler, this, e);
+                        }
+                        else
+                        {
+                            sub.Handler.DynamicInvoke(this, e);
+                        }
                     }
                 }
             }
         }
+
+        private List<EventSubscription> GetEventSubscriptions(RoutedEvent routedEvent)
+        {
+            if (!EventHandlers.TryGetValue(routedEvent, out var subscriptions))
+            {
+                subscriptions = new List<EventSubscription>();
+                EventHandlers.Add(routedEvent, subscriptions);
+            }
+
+            return subscriptions;
+        }
+
+        private IDisposable AddEventSubscription(RoutedEvent routedEvent, EventSubscription subscription)
+        {
+            List<EventSubscription> subscriptions = GetEventSubscriptions(routedEvent);
+
+            subscriptions.Add(subscription);
+
+            return new UnsubscribeDisposable(subscriptions, subscription);
+        }
+
+        private sealed class UnsubscribeDisposable : IDisposable
+        {
+            private readonly List<EventSubscription> _subscriptions;
+            private readonly EventSubscription _subscription;
+
+            public UnsubscribeDisposable(List<EventSubscription> subscriptions, EventSubscription subscription)
+            {
+                _subscriptions = subscriptions;
+                _subscription = subscription;
+            }
+
+            public void Dispose()
+            {
+                _subscriptions.Remove(_subscription);
+            }
+        }
     }
 }