Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid allocations in RaiseEvent when there's no listeners #15580

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/Avalonia.Base/Input/AccessKeyHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,7 @@ protected virtual void OnKeyDown(object? sender, KeyEventArgs e)
var match = matches.FirstOrDefault();

// If there was a match, raise the AccessKeyPressed event on it.
if (match is not null)
{
match.RaiseEvent(new RoutedEventArgs(AccessKeyPressedEvent));
}
match?.RaiseEvent(AccessKeyPressedEvent);
}
}

Expand Down Expand Up @@ -243,7 +240,7 @@ protected virtual void OnPreviewPointerPressed(object? sender, PointerEventArgs
}

/// <summary>
/// Closes the <see cref="MainMenu"/> and performs other bookeeping.
/// Closes the <see cref="MainMenu"/> and performs other bookkeeping.
/// </summary>
private void CloseMenu()
{
Expand Down
21 changes: 11 additions & 10 deletions src/Avalonia.Base/Input/DragDropDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,19 @@ private static DragDropEffects RaiseDragEvent(Interactive? target, IInputRoot in
if (target == null)
return DragDropEffects.None;

var p = ((Visual)inputRoot).TranslatePoint(point, target);

if (!p.HasValue)
if ((inputRoot as Visual)?.TranslatePoint(point, target) is not { } translatedPoint)
return DragDropEffects.None;

var args = new DragEventArgs(routedEvent, data, target, p.Value, modifiers)
{
RoutedEvent = routedEvent,
DragEffects = operation
};
target.RaiseEvent(args);
return args.DragEffects;
var args = target.RaiseEvent(
routedEvent,
static (e, ctx) =>
new DragEventArgs(e, ctx.data, ctx.target, ctx.translatedPoint, ctx.modifiers)
{
DragEffects = ctx.operation
},
(data, target, translatedPoint, modifiers, operation));

return args?.DragEffects ?? operation;
}

private DragDropEffects DragEnter(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,16 @@ protected override void PointerMoved(PointerEventArgs e)

var degree = GetAngleDegreeFromPoints(_firstPoint, _secondPoint);

var pinchEventArgs = new PinchEventArgs(scale, _origin, degree, _previousAngle - degree);
var angleDelta = _previousAngle - degree;

var pinchEventArgs = Target?.RaiseEvent(
Gestures.PinchEvent,
static (_, ctx) => new PinchEventArgs(ctx.scale, ctx.origin, ctx.degree, ctx.angleDelta),
(scale, origin: _origin, degree, angleDelta));

_previousAngle = degree;
Target?.RaiseEvent(pinchEventArgs);
e.Handled = pinchEventArgs.Handled;

e.Handled = pinchEventArgs?.Handled ?? false;
e.PreventGestureRecognition();
}
}
Expand Down Expand Up @@ -111,7 +117,7 @@ private bool RemoveContact(IPointer pointer)
_secondContact = null;
}

Target?.RaiseEvent(new PinchEndedEventArgs());
Target?.RaiseEvent(Gestures.PinchEndedEvent, static _ => new PinchEndedEventArgs());
return true;
}
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,13 @@ protected override void PointerMoved(PointerEventArgs e)
}

_pullInProgress = true;
var pullEventArgs = new PullGestureEventArgs(_gestureId, delta, PullDirection);
Target?.RaiseEvent(pullEventArgs);

e.Handled = pullEventArgs.Handled;
var pullEventArgs = Target.RaiseEvent(
Gestures.PullGestureEvent,
static (_, ctx) => new PullGestureEventArgs(ctx.gestureId, ctx.delta, ctx.pullDirection),
(gestureId: _gestureId, delta, pullDirection: PullDirection));

e.Handled = pullEventArgs?.Handled ?? false;
}
}

Expand Down Expand Up @@ -131,7 +134,10 @@ private void EndPull()
_initialPosition = default;
_pullInProgress = false;

Target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection));
Target?.RaiseEvent(
Gestures.PullGestureEndedEvent,
static (_, ctx) => new PullGestureEndedEventArgs(ctx.gestureId, ctx.pullDirection),
(gestureId: _gestureId, pullDirection: PullDirection));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,19 @@ protected override void PointerMoved(PointerEventArgs e)
_velocityTracker?.AddPosition(TimeSpan.FromMilliseconds(e.Timestamp), _pointerPressedPoint - rootPoint);

_lastMoveTimestamp = e.Timestamp;
Target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
RaiseScrollGestureEvent(vector);
_trackedRootPoint = rootPoint;
e.Handled = true;
}
}
}

private ScrollGestureEventArgs? RaiseScrollGestureEvent(Vector delta)
=> Target?.RaiseEvent(
Gestures.ScrollGestureEvent,
static (_, ctx) => new ScrollGestureEventArgs(ctx.gestureId, ctx.delta),
(gestureId: _gestureId, delta));

protected override void PointerCaptureLost(IPointer pointer)
{
if (pointer == _tracking) EndGesture();
Expand All @@ -156,12 +162,16 @@ void EndGesture()
{
_inertia = default;
_scrolling = false;
Target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId));

Target?.RaiseEvent(
Gestures.ScrollGestureEndedEvent,
static (_, gestureId) => new ScrollGestureEndedEventArgs(gestureId),
_gestureId);

_gestureId = 0;
_lastMoveTimestamp = null;
_rootTarget = null;
}

}


Expand All @@ -184,7 +194,12 @@ protected override void PointerReleased(PointerReleasedEventArgs e)
var savedGestureId = _gestureId;
var st = Stopwatch.StartNew();
var lastTime = TimeSpan.Zero;
Target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia));

Target?.RaiseEvent(
Gestures.ScrollGestureInertiaStartingEvent,
static (_, ctx) => new ScrollGestureInertiaStartingEventArgs(ctx.gestureId, ctx.inertia),
(gestureId: _gestureId, inertia: _inertia));

DispatcherTimer.Run(() =>
{
// Another gesture has started, finish the current one
Expand All @@ -198,10 +213,12 @@ protected override void PointerReleased(PointerReleasedEventArgs e)

var speed = _inertia * Math.Pow(InertialResistance, st.Elapsed.TotalSeconds);
var distance = speed * elapsedSinceLastTick.TotalSeconds;
var scrollGestureEventArgs = new ScrollGestureEventArgs(_gestureId, distance);
Target!.RaiseEvent(scrollGestureEventArgs);

if (!scrollGestureEventArgs.Handled || scrollGestureEventArgs.ShouldEndScrollGesture)
var scrollGestureEventArgs = RaiseScrollGestureEvent(distance);

if (scrollGestureEventArgs is null ||
!scrollGestureEventArgs.Handled ||
scrollGestureEventArgs.ShouldEndScrollGesture)
{
EndGesture();
return false;
Expand Down
54 changes: 36 additions & 18 deletions src/Avalonia.Base/Input/Gestures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,11 @@ private static void PointerPressed(RoutedEventArgs ev)
var e = (PointerPressedEventArgs)ev;
var visual = (Visual)ev.Source;

if(s_gestureState != null)
if (s_gestureState is { } gestureState)
{
if(s_gestureState.Value.Type == GestureStateType.Holding && ev.Source is Interactive i)
if (gestureState.Type == GestureStateType.Holding && ev.Source is Interactive interactive)
{
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_gestureState.Value.Pointer.Type, e));
RaiseHoldingEvent(interactive, HoldingState.Cancelled, gestureState.Pointer.Type, e);
}
s_holdCancellationToken?.Cancel();
s_holdCancellationToken?.Dispose();
Expand All @@ -256,10 +256,14 @@ private static void PointerPressed(RoutedEventArgs ev)
{
DispatcherTimer.RunOnce(() =>
{
if (s_gestureState != null && !token.IsCancellationRequested && e.Source is InputElement i && GetIsHoldingEnabled(i) && (e.Pointer.Type != PointerType.Mouse || GetIsHoldWithMouseEnabled(i)))
if (s_gestureState?.Pointer is { } pointer &&
!token.IsCancellationRequested &&
e.Source is Interactive interactive &&
GetIsHoldingEnabled(interactive) &&
(e.Pointer.Type != PointerType.Mouse || GetIsHoldWithMouseEnabled(interactive)))
{
s_gestureState = new GestureState(GestureStateType.Holding, s_gestureState.Value.Pointer);
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_gestureState.Value.Pointer.Type, e));
s_gestureState = new GestureState(GestureStateType.Holding, pointer);
RaiseHoldingEvent(interactive, HoldingState.Started, pointer.Type, e);
}
}, settings.HoldWaitDuration);
}
Expand All @@ -268,10 +272,10 @@ private static void PointerPressed(RoutedEventArgs ev)
{
if (s_lastPress.TryGetTarget(out var target) &&
target == e.Source &&
e.Source is Interactive i)
e.Source is Interactive interactive)
{
s_gestureState = new GestureState(GestureStateType.DoubleTapped, e.Pointer);
i.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e));
interactive.RaiseEvent(DoubleTappedEvent, static (e, ctx) => new TappedEventArgs(e, ctx), e);
}
}
}
Expand All @@ -286,29 +290,29 @@ private static void PointerReleased(RoutedEventArgs ev)
if (s_lastPress.TryGetTarget(out var target) &&
target == e.Source &&
e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right &&
e.Source is Interactive i)
e.Source is Interactive interactive)
{
var point = e.GetCurrentPoint((Visual)target);
var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
var settings = ((IInputRoot?)interactive.GetVisualRoot())?.PlatformSettings;
var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height));

if (tapRect.ContainsExclusive(point.Position))
{
if (s_gestureState?.Type == GestureStateType.Holding)
if (s_gestureState is { Type: GestureStateType.Holding } gestureState)
{
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed, s_lastPressPoint, s_gestureState.Value.Pointer.Type, e));
RaiseHoldingEvent(interactive, HoldingState.Completed, gestureState.Pointer.Type, e);
}
else if (e.InitialPressMouseButton == MouseButton.Right)
{
i.RaiseEvent(new TappedEventArgs(RightTappedEvent, e));
interactive.RaiseEvent(RightTappedEvent, static (e, ctx) => new TappedEventArgs(e, ctx), e);
}
//GestureStateType.DoubleTapped needed here to prevent invoking Tapped event when DoubleTapped is called.
//This behaviour matches UWP behaviour.
else if (s_gestureState?.Type != GestureStateType.DoubleTapped)
{
i.RaiseEvent(new TappedEventArgs(TappedEvent, e));
interactive.RaiseEvent(TappedEvent, static (e, ctx) => new TappedEventArgs(e, ctx), e);
}
}
s_gestureState = null;
Expand All @@ -327,10 +331,10 @@ private static void PointerMoved(RoutedEventArgs ev)
var e = (PointerEventArgs)ev;
if (s_lastPress.TryGetTarget(out var target))
{
if (e.Pointer == s_gestureState?.Pointer && ev.Source is Interactive i)
if (e.Pointer == s_gestureState?.Pointer && ev.Source is Interactive interactive)
{
var point = e.GetCurrentPoint((Visual)target);
var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
var settings = ((IInputRoot?)interactive.GetVisualRoot())?.PlatformSettings;
var holdSize = new Size(4, 4);
var holdRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(holdSize.Width, holdSize.Height));
Expand All @@ -340,9 +344,9 @@ private static void PointerMoved(RoutedEventArgs ev)
return;
}

if (s_gestureState.Value.Type == GestureStateType.Holding)
if (s_gestureState is { Type: GestureStateType.Holding } gestureState)
{
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_gestureState.Value.Pointer.Type, e));
RaiseHoldingEvent(interactive, HoldingState.Cancelled, gestureState.Pointer.Type, e);
}
}
}
Expand All @@ -353,5 +357,19 @@ private static void PointerMoved(RoutedEventArgs ev)
s_gestureState = null;
}
}

private static void RaiseHoldingEvent(
Interactive interactive,
HoldingState holdingState,
PointerType pointerType,
PointerEventArgs pointerEventArgs)
=> interactive.RaiseEvent(
HoldingEvent,
static (_, ctx) => new HoldingRoutedEventArgs(
ctx.holdingState,
s_lastPressPoint,
ctx.pointerType,
ctx.pointerEventArgs),
(holdingState, pointerType, pointerEventArgs));
}
}
50 changes: 48 additions & 2 deletions src/Avalonia.Base/Input/InputExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Interactivity;
using Avalonia.VisualTree;

#nullable enable

namespace Avalonia.Input
{
/// <summary>
Expand Down Expand Up @@ -79,6 +78,53 @@ public static IEnumerable<IInputElement> GetInputElementsAt(this IInputElement e
/// <inheritdoc cref="InputHitTest(IInputElement, Point, Func{Visual, bool}, bool)"/>
public static IInputElement? InputHitTest(this IInputElement element, Point p, Func<Visual, bool> filter) => InputHitTest(element, p, filter, true);

internal static TEventArgs? RaiseEvent<TEventArgs, TContext>(
this IInputElement inputElement,
RoutedEvent<TEventArgs> routedEvent,
Func<RoutedEvent<TEventArgs>, TContext, TEventArgs> eventArgsFactory,
TContext context)
where TEventArgs : RoutedEventArgs
{
if (inputElement is InputElement typedInputElement)
{
return typedInputElement.RaiseEvent(routedEvent, eventArgsFactory, context);
}

var eventArgs = eventArgsFactory(routedEvent, context);
inputElement.RaiseEvent(eventArgs);
return eventArgs;
}

internal static TEventArgs? RaiseEvent<TEventArgs>(
this IInputElement inputElement,
RoutedEvent<TEventArgs> routedEvent,
Func<RoutedEvent<TEventArgs>, TEventArgs> eventArgsFactory)
where TEventArgs : RoutedEventArgs
{
if (inputElement is InputElement typedInputElement)
{
return typedInputElement.RaiseEvent(routedEvent, eventArgsFactory);
}

var eventArgs = eventArgsFactory(routedEvent);
inputElement.RaiseEvent(eventArgs);
return eventArgs;
}

internal static RoutedEventArgs? RaiseEvent(
this IInputElement inputElement,
RoutedEvent<RoutedEventArgs> routedEvent)
{
if (inputElement is InputElement typedInputElement)
{
return typedInputElement.RaiseEvent(routedEvent);
}

var eventArgs = new RoutedEventArgs(routedEvent);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't it make sense to pass a factory here too?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would require adding all the new RaiseEvent overloads to the IInputElement interface, which I wasn't comfortable with, as I don't think it should be that specific.

Since IInputElement isn't client implementable, we should never get to that fallback anyways.

inputElement.RaiseEvent(eventArgs);
return eventArgs;
}

private static bool IsHitTestVisible(Visual visual) => visual is { IsVisible: true, IsAttachedToVisualTree: true } and IInputElement { IsHitTestVisible: true };

private static bool IsHitTestVisible_EnabledOnly(Visual visual) => IsHitTestVisible(visual) && visual is IInputElement { IsEffectivelyEnabled: true };
Expand Down
Loading
Loading