Browse Source

Merge pull request #3651 from MarchingCube/opt-calendar-bench

Remove disposable allocations from routed event AddHandler in the common case.
pull/3656/head
Steven Kirk 6 years ago
committed by GitHub
parent
commit
22e3195db3
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Avalonia.Controls/Calendar/DatePicker.cs
  2. 3
      src/Avalonia.Controls/ComboBox.cs
  3. 2
      src/Avalonia.Controls/Primitives/Popup.cs
  4. 2
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  5. 4
      src/Avalonia.Interactivity/IInteractive.cs
  6. 32
      src/Avalonia.Interactivity/Interactive.cs
  7. 25
      src/Avalonia.Interactivity/InteractiveExtensions.cs
  8. 11
      tests/Avalonia.Benchmarks/NullGlyphRun.cs
  9. 4
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

2
src/Avalonia.Controls/Calendar/DatePicker.cs

@ -476,7 +476,7 @@ namespace Avalonia.Controls
{
_dropDownButton.Click += DropDownButton_Click;
_buttonPointerPressedSubscription =
_dropDownButton.AddHandler(PointerPressedEvent, DropDownButton_PointerPressed, handledEventsToo: true);
_dropDownButton.AddDisposableHandler(PointerPressedEvent, DropDownButton_PointerPressed, handledEventsToo: true);
}
if (_textBox != null)

3
src/Avalonia.Controls/ComboBox.cs

@ -9,6 +9,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.VisualTree;
@ -265,7 +266,7 @@ namespace Avalonia.Controls
var toplevel = this.GetVisualRoot() as TopLevel;
if (toplevel != null)
{
_subscriptionsOnOpen = toplevel.AddHandler(PointerWheelChangedEvent, (s, ev) =>
_subscriptionsOnOpen = toplevel.AddDisposableHandler(PointerWheelChangedEvent, (s, ev) =>
{
//eat wheel scroll event outside dropdown popup while it's open
if (IsDropDownOpen && (ev.Source as IVisual).GetVisualRoot() == toplevel)

2
src/Avalonia.Controls/Primitives/Popup.cs

@ -279,7 +279,7 @@ namespace Avalonia.Controls.Primitives
}
}
DeferCleanup(topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel));
DeferCleanup(topLevel.AddDisposableHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel));
DeferCleanup(InputManager.Instance?.Process.Subscribe(ListenForNonClientClick));

2
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@ -22,7 +22,7 @@ namespace Avalonia.Diagnostics
}
}
return root.AddHandler(
return root.AddDisposableHandler(
InputElement.KeyDownEvent,
PreviewKeyDown,
RoutingStrategies.Tunnel);

4
src/Avalonia.Interactivity/IInteractive.cs

@ -23,7 +23,7 @@ namespace Avalonia.Interactivity
/// <param name="routes">The routing strategies to listen to.</param>
/// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
/// <returns>A disposable that terminates the event subscription.</returns>
IDisposable AddHandler(
void AddHandler(
RoutedEvent routedEvent,
Delegate handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
@ -38,7 +38,7 @@ namespace Avalonia.Interactivity
/// <param name="routes">The routing strategies to listen to.</param>
/// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
/// <returns>A disposable that terminates the event subscription.</returns>
IDisposable AddHandler<TEventArgs>(
void AddHandler<TEventArgs>(
RoutedEvent<TEventArgs> routedEvent,
EventHandler<TEventArgs> handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,

32
src/Avalonia.Interactivity/Interactive.cs

@ -27,8 +27,7 @@ namespace Avalonia.Interactivity
/// <param name="handler">The handler.</param>
/// <param name="routes">The routing strategies to listen to.</param>
/// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
/// <returns>A disposable that terminates the event subscription.</returns>
public IDisposable AddHandler(
public void AddHandler(
RoutedEvent routedEvent,
Delegate handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
@ -38,7 +37,8 @@ namespace Avalonia.Interactivity
handler = handler ?? throw new ArgumentNullException(nameof(handler));
var subscription = new EventSubscription(handler, routes, handledEventsToo);
return AddEventSubscription(routedEvent, subscription);
AddEventSubscription(routedEvent, subscription);
}
/// <summary>
@ -49,8 +49,7 @@ namespace Avalonia.Interactivity
/// <param name="handler">The handler.</param>
/// <param name="routes">The routing strategies to listen to.</param>
/// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
/// <returns>A disposable that terminates the event subscription.</returns>
public IDisposable AddHandler<TEventArgs>(
public void AddHandler<TEventArgs>(
RoutedEvent<TEventArgs> routedEvent,
EventHandler<TEventArgs> handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
@ -69,7 +68,7 @@ namespace Avalonia.Interactivity
var subscription = new EventSubscription(handler, routes, handledEventsToo, (baseHandler, sender, args) => InvokeAdapter(baseHandler, sender, args));
return AddEventSubscription(routedEvent, subscription);
AddEventSubscription(routedEvent, subscription);
}
/// <summary>
@ -188,7 +187,7 @@ namespace Avalonia.Interactivity
return result;
}
private IDisposable AddEventSubscription(RoutedEvent routedEvent, EventSubscription subscription)
private void AddEventSubscription(RoutedEvent routedEvent, EventSubscription subscription)
{
_eventHandlers ??= new Dictionary<RoutedEvent, List<EventSubscription>>();
@ -199,8 +198,6 @@ namespace Avalonia.Interactivity
}
subscriptions.Add(subscription);
return new UnsubscribeDisposable(subscriptions, subscription);
}
private readonly struct EventSubscription
@ -225,22 +222,5 @@ namespace Avalonia.Interactivity
public bool HandledEventsToo { get; }
}
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);
}
}
}
}

25
src/Avalonia.Interactivity/InteractiveExtensions.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
namespace Avalonia.Interactivity
@ -11,6 +12,28 @@ namespace Avalonia.Interactivity
/// </summary>
public static class InteractiveExtensions
{
/// <summary>
/// Adds a handler for the specified routed event and returns a disposable that can terminate the event subscription.
/// </summary>
/// <typeparam name="TEventArgs">The type of the event's args.</typeparam>
/// <param name="o">Target for adding given event handler.</param>
/// <param name="routedEvent">The routed event.</param>
/// <param name="handler">The handler.</param>
/// <param name="routes">The routing strategies to listen to.</param>
/// <param name="handledEventsToo">Whether handled events should also be listened for.</param>
/// <returns>A disposable that terminates the event subscription.</returns>
public static IDisposable AddDisposableHandler<TEventArgs>(this IInteractive o, RoutedEvent<TEventArgs> routedEvent,
EventHandler<TEventArgs> handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false) where TEventArgs : RoutedEventArgs
{
o.AddHandler(routedEvent, handler, routes, handledEventsToo);
return Disposable.Create(
(instance: o, handler, routedEvent),
state => state.instance.RemoveHandler(state.routedEvent, state.handler));
}
/// <summary>
/// Gets an observable for a <see cref="RoutedEvent{TEventArgs}"/>.
/// </summary>
@ -31,7 +54,7 @@ namespace Avalonia.Interactivity
o = o ?? throw new ArgumentNullException(nameof(o));
routedEvent = routedEvent ?? throw new ArgumentNullException(nameof(routedEvent));
return Observable.Create<TEventArgs>(x => o.AddHandler(
return Observable.Create<TEventArgs>(x => o.AddDisposableHandler(
routedEvent,
(_, e) => x.OnNext(e),
routes,

11
tests/Avalonia.Benchmarks/NullGlyphRun.cs

@ -0,0 +1,11 @@
using Avalonia.Platform;
namespace Avalonia.Benchmarks
{
internal class NullGlyphRun : IGlyphRunImpl
{
public void Dispose()
{
}
}
}

4
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@ -72,7 +72,9 @@ namespace Avalonia.Benchmarks
public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun, out double width)
{
throw new NotImplementedException();
width = default;
return new NullGlyphRun();
}
}
}

Loading…
Cancel
Save