From 54399d2a51eac2c1664779c2f546b428ed1c2c5c Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 5 Sep 2019 22:53:48 +0200 Subject: [PATCH 1/6] Use ValueTuple instead of Tuple to reduce alocations. Invoke raise callback without reflection. --- src/Avalonia.Interactivity/RoutedEvent.cs | 50 ++++++----------------- 1 file changed, 12 insertions(+), 38 deletions(-) diff --git a/src/Avalonia.Interactivity/RoutedEvent.cs b/src/Avalonia.Interactivity/RoutedEvent.cs index 2d752133c1..cfbaddb327 100644 --- a/src/Avalonia.Interactivity/RoutedEvent.cs +++ b/src/Avalonia.Interactivity/RoutedEvent.cs @@ -4,7 +4,6 @@ using System; using System.Reactive.Subjects; using System.Reflection; -using System.Runtime.ExceptionServices; namespace Avalonia.Interactivity { @@ -18,8 +17,8 @@ namespace Avalonia.Interactivity public class RoutedEvent { - private Subject> _raised = new Subject>(); - private Subject _routeFinished = new Subject(); + private readonly Subject<(object, RoutedEventArgs)> _raised = new Subject<(object, RoutedEventArgs)>(); + private readonly Subject _routeFinished = new Subject(); public RoutedEvent( string name, @@ -38,31 +37,15 @@ namespace Avalonia.Interactivity RoutingStrategies = routingStrategies; } - public Type EventArgsType - { - get; - private set; - } + public Type EventArgsType { get; } - public string Name - { - get; - private set; - } + public string Name { get; } - public Type OwnerType - { - get; - private set; - } + public Type OwnerType { get; } - public RoutingStrategies RoutingStrategies - { - get; - private set; - } + public RoutingStrategies RoutingStrategies { get; } - public IObservable> Raised => _raised; + public IObservable<(object, RoutedEventArgs)> Raised => _raised; public IObservable RouteFinished => _routeFinished; public static RoutedEvent Register( @@ -98,29 +81,20 @@ namespace Avalonia.Interactivity { return Raised.Subscribe(args => { - var sender = args.Item1; - var e = args.Item2; + (object sender, RoutedEventArgs e) = args; - if (targetType.GetTypeInfo().IsAssignableFrom(sender.GetType().GetTypeInfo()) && - ((e.Route == RoutingStrategies.Direct) || (e.Route & routes) != 0) && + if (targetType.IsInstanceOfType(sender) && + (e.Route == RoutingStrategies.Direct || (e.Route & routes) != 0) && (!e.Handled || handledEventsToo)) { - try - { - handler.DynamicInvoke(sender, e); - } - catch (TargetInvocationException ex) - { - // Unwrap the inner exception. - ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); - } + handler(sender, e); } }); } internal void InvokeRaised(object sender, RoutedEventArgs e) { - _raised.OnNext(Tuple.Create(sender, e)); + _raised.OnNext((sender, e)); } internal void InvokeRouteFinished(RoutedEventArgs e) From 04c2dfcc5484891761522764a8359e3b73638c9b Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 8 Sep 2019 14:26:35 +0200 Subject: [PATCH 2/6] Compiled expressions for RoutedEvent. --- .../EventSubscription.cs | 4 +++ src/Avalonia.Interactivity/Interactive.cs | 26 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Interactivity/EventSubscription.cs b/src/Avalonia.Interactivity/EventSubscription.cs index 9f763edcd3..ff2a28e570 100644 --- a/src/Avalonia.Interactivity/EventSubscription.cs +++ b/src/Avalonia.Interactivity/EventSubscription.cs @@ -5,8 +5,12 @@ using System; namespace Avalonia.Interactivity { + internal delegate void InvokeSignature(Delegate func, object sender, RoutedEventArgs args); + internal class EventSubscription { + public InvokeSignature RaiseHandler { get; set; } + public Delegate Handler { get; set; } public RoutingStrategies Routes { get; set; } diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 911fc2130e..033686a8c8 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Reactive.Disposables; using System.Reactive.Linq; using Avalonia.Layout; @@ -28,6 +29,10 @@ namespace Avalonia.Interactivity get { return _eventHandlers ?? (_eventHandlers = new Dictionary>()); } } + + + private static Dictionary s_invokeCache = new Dictionary(); + /// /// Adds a handler for the specified routed event. /// @@ -53,8 +58,27 @@ namespace Avalonia.Interactivity EventHandlers.Add(routedEvent, subscriptions); } + if (!s_invokeCache.TryGetValue(routedEvent.EventArgsType, out InvokeSignature raiseFunc)) + { + ParameterExpression funcParameter = Expression.Parameter(typeof(Delegate), "func"); + ParameterExpression senderParameter = Expression.Parameter(typeof(object), "sender"); + ParameterExpression argsParameter = Expression.Parameter(typeof(RoutedEventArgs), "args"); + + UnaryExpression convertedFunc = Expression.Convert(funcParameter, typeof(EventHandler<>).MakeGenericType(routedEvent.EventArgsType)); + UnaryExpression convertedArgs = Expression.Convert(argsParameter, routedEvent.EventArgsType); + + InvocationExpression invokeDelegate = Expression.Invoke(convertedFunc, senderParameter, convertedArgs); + + raiseFunc = Expression + .Lambda(invokeDelegate, funcParameter, senderParameter, argsParameter) + .Compile(); + + s_invokeCache.Add(routedEvent.EventArgsType, raiseFunc); + } + var sub = new EventSubscription { + RaiseHandler = raiseFunc, Handler = handler, Routes = routes, AlsoIfHandled = handledEventsToo, @@ -196,7 +220,7 @@ namespace Avalonia.Interactivity if (correctRoute && notFinished) { - sub.Handler.DynamicInvoke(this, e); + sub.RaiseHandler(sub.Handler, this, e); } } } From 4a7f370fc520413a4c59709c8c10ab25048722e9 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 8 Sep 2019 20:23:38 +0200 Subject: [PATCH 3/6] Cleanup. --- src/Avalonia.Interactivity/Interactive.cs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 033686a8c8..59dfbcb8ab 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reactive.Disposables; -using System.Reactive.Linq; using Avalonia.Layout; using Avalonia.VisualTree; @@ -19,19 +18,14 @@ namespace Avalonia.Interactivity { private Dictionary> _eventHandlers; + private static readonly Dictionary s_invokeCache = new Dictionary(); + /// /// Gets the interactive parent of the object for bubbling and tunneling events. /// IInteractive IInteractive.InteractiveParent => ((IVisual)this).VisualParent as IInteractive; - private Dictionary> EventHandlers - { - get { return _eventHandlers ?? (_eventHandlers = new Dictionary>()); } - } - - - - private static Dictionary s_invokeCache = new Dictionary(); + private Dictionary> EventHandlers => _eventHandlers ?? (_eventHandlers = new Dictionary>()); /// /// Adds a handler for the specified routed event. @@ -58,14 +52,16 @@ namespace Avalonia.Interactivity EventHandlers.Add(routedEvent, subscriptions); } - if (!s_invokeCache.TryGetValue(routedEvent.EventArgsType, out InvokeSignature raiseFunc)) + Type eventArgsType = routedEvent.EventArgsType; + + if (!s_invokeCache.TryGetValue(eventArgsType, out InvokeSignature raiseFunc)) { ParameterExpression funcParameter = Expression.Parameter(typeof(Delegate), "func"); ParameterExpression senderParameter = Expression.Parameter(typeof(object), "sender"); ParameterExpression argsParameter = Expression.Parameter(typeof(RoutedEventArgs), "args"); - UnaryExpression convertedFunc = Expression.Convert(funcParameter, typeof(EventHandler<>).MakeGenericType(routedEvent.EventArgsType)); - UnaryExpression convertedArgs = Expression.Convert(argsParameter, routedEvent.EventArgsType); + UnaryExpression convertedFunc = Expression.Convert(funcParameter, typeof(EventHandler<>).MakeGenericType(eventArgsType)); + UnaryExpression convertedArgs = Expression.Convert(argsParameter, eventArgsType); InvocationExpression invokeDelegate = Expression.Invoke(convertedFunc, senderParameter, convertedArgs); @@ -73,7 +69,7 @@ namespace Avalonia.Interactivity .Lambda(invokeDelegate, funcParameter, senderParameter, argsParameter) .Compile(); - s_invokeCache.Add(routedEvent.EventArgsType, raiseFunc); + s_invokeCache.Add(eventArgsType, raiseFunc); } var sub = new EventSubscription From 45f86a925f4bf0766b40b209cfd42558a8a7bcc9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 8 Sep 2019 22:43:47 +0300 Subject: [PATCH 4/6] Fixed possible NRE in Gestures.cs --- src/Avalonia.Input/Gestures.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs index bb8c8b8c40..ea4892ebfc 100644 --- a/src/Avalonia.Input/Gestures.cs +++ b/src/Avalonia.Input/Gestures.cs @@ -31,7 +31,7 @@ namespace Avalonia.Input RoutedEvent.Register( "ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures)); - private static WeakReference s_lastPress; + private static WeakReference s_lastPress = new WeakReference(null); static Gestures() { From 5cae6bee7b94253df022fe20ecf48bd4f3660780 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 8 Sep 2019 21:55:17 +0200 Subject: [PATCH 5/6] Simplify creation of invoke adapters. --- .../EventSubscription.cs | 2 +- src/Avalonia.Interactivity/Interactive.cs | 115 ++++++++++++------ 2 files changed, 79 insertions(+), 38 deletions(-) diff --git a/src/Avalonia.Interactivity/EventSubscription.cs b/src/Avalonia.Interactivity/EventSubscription.cs index ff2a28e570..b495142998 100644 --- a/src/Avalonia.Interactivity/EventSubscription.cs +++ b/src/Avalonia.Interactivity/EventSubscription.cs @@ -9,7 +9,7 @@ namespace Avalonia.Interactivity internal class EventSubscription { - public InvokeSignature RaiseHandler { get; set; } + public InvokeSignature InvokeAdapter { get; set; } public Delegate Handler { get; set; } diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 59dfbcb8ab..0f607241ec 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; -using System.Reactive.Disposables; using Avalonia.Layout; using Avalonia.VisualTree; @@ -44,45 +42,14 @@ namespace Avalonia.Interactivity Contract.Requires(routedEvent != null); Contract.Requires(handler != null); - List subscriptions; - - if (!EventHandlers.TryGetValue(routedEvent, out subscriptions)) - { - subscriptions = new List(); - EventHandlers.Add(routedEvent, subscriptions); - } - - Type eventArgsType = routedEvent.EventArgsType; - - if (!s_invokeCache.TryGetValue(eventArgsType, out InvokeSignature raiseFunc)) - { - ParameterExpression funcParameter = Expression.Parameter(typeof(Delegate), "func"); - ParameterExpression senderParameter = Expression.Parameter(typeof(object), "sender"); - ParameterExpression argsParameter = Expression.Parameter(typeof(RoutedEventArgs), "args"); - - UnaryExpression convertedFunc = Expression.Convert(funcParameter, typeof(EventHandler<>).MakeGenericType(eventArgsType)); - UnaryExpression convertedArgs = Expression.Convert(argsParameter, eventArgsType); - - InvocationExpression invokeDelegate = Expression.Invoke(convertedFunc, senderParameter, convertedArgs); - - raiseFunc = Expression - .Lambda(invokeDelegate, funcParameter, senderParameter, argsParameter) - .Compile(); - - s_invokeCache.Add(eventArgsType, raiseFunc); - } - - var sub = new EventSubscription + var subscription = new EventSubscription { - RaiseHandler = raiseFunc, Handler = handler, Routes = routes, AlsoIfHandled = handledEventsToo, }; - subscriptions.Add(sub); - - return Disposable.Create(() => subscriptions.Remove(sub)); + return AddEventSubscription(routedEvent, subscription); } /// @@ -100,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(routedEvent != null); + Contract.Requires(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_invokeCache.TryGetValue(eventArgsType, out var invokeAdapter)) + { + void InvokeAdapter(Delegate func, object sender, RoutedEventArgs args) + { + var typedHandler = (EventHandler)func; + var typedArgs = (TEventArgs)args; + + typedHandler(sender, typedArgs); + } + + invokeAdapter = InvokeAdapter; + + s_invokeCache.Add(eventArgsType, invokeAdapter); + } + + var subscription = new EventSubscription + { + InvokeAdapter = invokeAdapter, + Handler = handler, + Routes = routes, + AlsoIfHandled = handledEventsToo, + }; + + return AddEventSubscription(routedEvent, subscription); } /// @@ -216,10 +213,54 @@ namespace Avalonia.Interactivity if (correctRoute && notFinished) { - sub.RaiseHandler(sub.Handler, this, e); + if (sub.InvokeAdapter != null) + { + sub.InvokeAdapter(sub.Handler, this, e); + } + else + { + sub.Handler.DynamicInvoke(this, e); + } } } } } + + private List GetEventSubscriptions(RoutedEvent routedEvent) + { + if (!EventHandlers.TryGetValue(routedEvent, out var subscriptions)) + { + subscriptions = new List(); + EventHandlers.Add(routedEvent, subscriptions); + } + + return subscriptions; + } + + private IDisposable AddEventSubscription(RoutedEvent routedEvent, EventSubscription subscription) + { + List subscriptions = GetEventSubscriptions(routedEvent); + + subscriptions.Add(subscription); + + return new UnsubscribeDisposable(subscriptions, subscription); + } + + private sealed class UnsubscribeDisposable : IDisposable + { + private readonly List _subscriptions; + private readonly EventSubscription _subscription; + + public UnsubscribeDisposable(List subscriptions, EventSubscription subscription) + { + _subscriptions = subscriptions; + _subscription = subscription; + } + + public void Dispose() + { + _subscriptions.Remove(_subscription); + } + } } } From f5b317decbf7bededba9d4746a09261d6e6c1aa4 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 8 Sep 2019 22:33:39 +0200 Subject: [PATCH 6/6] Naming. --- src/Avalonia.Interactivity/EventSubscription.cs | 4 ++-- src/Avalonia.Interactivity/Interactive.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Interactivity/EventSubscription.cs b/src/Avalonia.Interactivity/EventSubscription.cs index b495142998..e8fb1bfaf1 100644 --- a/src/Avalonia.Interactivity/EventSubscription.cs +++ b/src/Avalonia.Interactivity/EventSubscription.cs @@ -5,11 +5,11 @@ using System; namespace Avalonia.Interactivity { - internal delegate void InvokeSignature(Delegate func, object sender, RoutedEventArgs args); + internal delegate void HandlerInvokeSignature(Delegate baseHandler, object sender, RoutedEventArgs args); internal class EventSubscription { - public InvokeSignature InvokeAdapter { get; set; } + public HandlerInvokeSignature InvokeAdapter { get; set; } public Delegate Handler { get; set; } diff --git a/src/Avalonia.Interactivity/Interactive.cs b/src/Avalonia.Interactivity/Interactive.cs index 0f607241ec..f8d388ec89 100644 --- a/src/Avalonia.Interactivity/Interactive.cs +++ b/src/Avalonia.Interactivity/Interactive.cs @@ -16,7 +16,7 @@ namespace Avalonia.Interactivity { private Dictionary> _eventHandlers; - private static readonly Dictionary s_invokeCache = new Dictionary(); + private static readonly Dictionary s_invokeHandlerCache = new Dictionary(); /// /// Gets the interactive parent of the object for bubbling and tunneling events. @@ -74,11 +74,11 @@ namespace Avalonia.Interactivity // that will cast our type erased instance and invoke it. Type eventArgsType = routedEvent.EventArgsType; - if (!s_invokeCache.TryGetValue(eventArgsType, out var invokeAdapter)) + if (!s_invokeHandlerCache.TryGetValue(eventArgsType, out var invokeAdapter)) { - void InvokeAdapter(Delegate func, object sender, RoutedEventArgs args) + void InvokeAdapter(Delegate baseHandler, object sender, RoutedEventArgs args) { - var typedHandler = (EventHandler)func; + var typedHandler = (EventHandler)baseHandler; var typedArgs = (TEventArgs)args; typedHandler(sender, typedArgs); @@ -86,7 +86,7 @@ namespace Avalonia.Interactivity invokeAdapter = InvokeAdapter; - s_invokeCache.Add(eventArgsType, invokeAdapter); + s_invokeHandlerCache.Add(eventArgsType, invokeAdapter); } var subscription = new EventSubscription