Skip to content

Commit

Permalink
perf: [Wasm] Improve DOM routed events dispatch performance
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromelaban committed Dec 1, 2020
1 parent c108230 commit f90906d
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 37 deletions.
18 changes: 15 additions & 3 deletions src/Uno.UI.Runtime.WebAssembly/Xaml/UIElementWasmExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,15 @@ public static void SetHtmlContent(this UIElement element, string html)
/// </summary>
public static void RegisterHtmlEventHandler(this UIElement element, string eventName, EventHandler handler)
{
element.RegisterEventHandler(eventName, handler);
element.RegisterEventHandler(eventName, handler, UIElement.GenericEventHandlers.RaiseEventHandler);
}

/// <summary>
/// Unregister previously registered event with RegisterHtmlEventHandler.
/// </summary>
public static void UnregisterHtmlEventHandler(this UIElement element, string eventName, EventHandler handler)
{
element.UnregisterEventHandler(eventName, handler);
element.UnregisterEventHandler(eventName, handler, UIElement.GenericEventHandlers.RaiseEventHandler);
}

/// <summary>
Expand All @@ -189,6 +189,7 @@ public static void RegisterHtmlCustomEventHandler(this UIElement element, string
element.RegisterEventHandler(
eventName,
handler,
RaiseCustomEventHandler,
eventExtractor: extractor,
payloadConverter: (_, s) => new HtmlCustomEventArgs(s));
}
Expand All @@ -198,7 +199,18 @@ public static void RegisterHtmlCustomEventHandler(this UIElement element, string
/// </summary>
public static void UnregisterHtmlCustomEventHandler(this UIElement element, string eventName, EventHandler<HtmlCustomEventArgs> handler)
{
element.UnregisterEventHandler(eventName, handler);
element.UnregisterEventHandler(eventName, handler, RaiseCustomEventHandler);
}

private static object RaiseCustomEventHandler(Delegate d, object sender, object args)
{
if (d is RoutedEventHandler handler && args is RoutedEventArgs routedEventArgs)
{
handler(sender, routedEventArgs);
return null;
}

throw new InvalidOperationException($"The parameters for invoking GenericEventHandlers.RaiseEventHandler with {d} are incorrect");
}
}
}
8 changes: 4 additions & 4 deletions src/Uno.UI/UI/Xaml/Controls/Image/Image.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,17 @@ private void OnImageOpened(object sender, RoutedEventArgs e)

public event RoutedEventHandler ImageOpened
{
add => _htmlImage.RegisterEventHandler("load", value);
remove => _htmlImage.UnregisterEventHandler("load", value);
add => _htmlImage.RegisterEventHandler("load", value, GenericEventHandlers.RaiseRoutedEventHandler);
remove => _htmlImage.UnregisterEventHandler("load", value, GenericEventHandlers.RaiseRoutedEventHandler);
}

private ExceptionRoutedEventArgs ImageFailedConverter(object sender, string e)
=> new ExceptionRoutedEventArgs(sender, e);

public event ExceptionRoutedEventHandler ImageFailed
{
add => _htmlImage.RegisterEventHandler("error", value, payloadConverter: ImageFailedConverter);
remove => _htmlImage.UnregisterEventHandler("error", value);
add => _htmlImage.RegisterEventHandler("error", value, GenericEventHandlers.RaiseExceptionRoutedEventHandler, payloadConverter: ImageFailedConverter);
remove => _htmlImage.UnregisterEventHandler("error", value, GenericEventHandlers.RaiseExceptionRoutedEventHandler);
}

partial void OnSourceChanged(DependencyPropertyChangedEventArgs e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,13 @@ public ScrollBarVisibility HorizontalScrollBarVisibility
private protected override void OnLoaded()
{
base.OnLoaded();
RegisterEventHandler("scroll", (EventHandler)OnScroll);
RegisterEventHandler("scroll", (EventHandler)OnScroll, GenericEventHandlers.RaiseEventHandler);
}

private protected override void OnUnloaded()
{
base.OnUnloaded();
UnregisterEventHandler("scroll", (EventHandler)OnScroll);
UnregisterEventHandler("scroll", (EventHandler)OnScroll, GenericEventHandlers.RaiseEventHandler);
}

public void ScrollTo(double? horizontalOffset, double? verticalOffset, bool disableAnimation)
Expand Down
4 changes: 2 additions & 2 deletions src/Uno.UI/UI/Xaml/Controls/TextBox/TextBoxView.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ public TextBoxView(TextBox textBox, bool isMultiline)

private event EventHandler HtmlInput
{
add => RegisterEventHandler("input", value);
remove => UnregisterEventHandler("input", value);
add => RegisterEventHandler("input", value, GenericEventHandlers.RaiseEventHandler);
remove => UnregisterEventHandler("input", value, GenericEventHandlers.RaiseEventHandler);
}

internal bool IsMultiline { get; }
Expand Down
12 changes: 6 additions & 6 deletions src/Uno.UI/UI/Xaml/FrameworkElement.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public event RoutedEventHandler Loading
}
else
{
RegisterEventHandler("loading", value);
RegisterEventHandler("loading", value, GenericEventHandlers.RaiseRoutedEventHandler);
}
}
remove
Expand All @@ -135,7 +135,7 @@ public event RoutedEventHandler Loading
}
else
{
UnregisterEventHandler("loading", value);
UnregisterEventHandler("loading", value, GenericEventHandlers.RaiseRoutedEventHandler);
}
}
}
Expand All @@ -151,7 +151,7 @@ public event RoutedEventHandler Loaded
}
else
{
RegisterEventHandler("loaded", value);
RegisterEventHandler("loaded", value, GenericEventHandlers.RaiseRoutedEventHandler);
}
}
remove
Expand All @@ -162,7 +162,7 @@ public event RoutedEventHandler Loaded
}
else
{
UnregisterEventHandler("loaded", value);
UnregisterEventHandler("loaded", value, GenericEventHandlers.RaiseRoutedEventHandler);
}
}
}
Expand All @@ -178,7 +178,7 @@ public event RoutedEventHandler Unloaded
}
else
{
RegisterEventHandler("unloaded", value);
RegisterEventHandler("unloaded", value, GenericEventHandlers.RaiseRoutedEventHandler);
}
}
remove
Expand All @@ -189,7 +189,7 @@ public event RoutedEventHandler Unloaded
}
else
{
UnregisterEventHandler("unloaded", value);
UnregisterEventHandler("unloaded", value, GenericEventHandlers.RaiseRoutedEventHandler);
}
}
}
Expand Down
51 changes: 36 additions & 15 deletions src/Uno.UI/UI/Xaml/UIElement.EventRegistration.wasm.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using Microsoft.Extensions.Logging;
using Uno;
using Uno.Extensions;
using Uno.Logging;
using Uno.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace Windows.UI.Xaml
{
partial class UIElement
{
private delegate bool RawEventHandler(UIElement sender, string paylaod);

internal delegate object GenericEventHandler(Delegate d, object sender, object args);

private class EventRegistration
{
private static readonly string[] noRegistrationEventNames = { "loading", "loaded", "unloaded", "pointerenter", "pointerleave", "pointerdown", "pointerup", "pointercancel" };

private class InvocationItem
{
public InvocationItem(Delegate handler, GenericEventHandler invoker)
{
Handler = handler;
Invoker = invoker;
}

public Delegate Handler { get; }
public GenericEventHandler Invoker { get; }
}

private readonly UIElement _owner;
private readonly string _eventName;
private readonly EventArgsParser _payloadConverter;
private readonly Action _subscribeCommand;

private List<Delegate> _invocationList = new List<Delegate>();
private List<Delegate> _pendingInvocationList;
private List<InvocationItem> _invocationList = new List<InvocationItem>();
private List<InvocationItem> _pendingInvocationList;
private bool _isSubscribed = false;
private bool _isDispatching;

Expand All @@ -48,34 +64,38 @@ public EventRegistration(
}
}

public void Add(Delegate handler)
public void Add(Delegate handler, GenericEventHandler invoker)
{
var invocationItem = new InvocationItem(handler, invoker);

// Do not alter the invocation list while enumerating it (_isDispatching)
var invocationList = _isDispatching
? _pendingInvocationList ?? (_pendingInvocationList = new List<Delegate>(_invocationList))
? _pendingInvocationList ?? (_pendingInvocationList = new List<InvocationItem>(_invocationList))
: _invocationList;

if (invocationList.Contains(handler))
if (invocationList.Contains(invocationItem))
{
return;
}

invocationList.Add(handler);
invocationList.Add(invocationItem);
if (_subscribeCommand != null && invocationList.Count == 1 && !_isSubscribed)
{
_subscribeCommand();
_isSubscribed = true;
}
}

public void Remove(Delegate handler)
public void Remove(Delegate handler, GenericEventHandler invoker)
{
var invocationItem = new InvocationItem(handler, invoker);

// Do not alter the invocation list while enumerating it (_isDispatching)
var invocationList = _isDispatching
? _pendingInvocationList ?? (_pendingInvocationList = new List<Delegate>(_invocationList))
? _pendingInvocationList ?? (_pendingInvocationList = new List<InvocationItem>(_invocationList))
: _invocationList;

invocationList.Remove(handler);
invocationList.Remove(invocationItem);

// TODO: Removing handler in HTML not supported yet
// var command = $"Uno.UI.WindowManager.current.unregisterEventOnView(\"{HtmlId}\", \"{eventName}\");";
Expand Down Expand Up @@ -106,9 +126,9 @@ public bool Dispatch(EventArgs eventArgs, string nativeEventPayload)
args = _payloadConverter(_owner, nativeEventPayload);
}

foreach (var handler in _invocationList)
foreach (var invocationItem in _invocationList)
{
if (handler is RawEventHandler rawHandler)
if (invocationItem.Handler is RawEventHandler rawHandler)
{
if (rawHandler(_owner, nativeEventPayload))
{
Expand All @@ -117,7 +137,7 @@ public bool Dispatch(EventArgs eventArgs, string nativeEventPayload)
}
else
{
var result = handler.DynamicInvoke(_owner, args);
var result = invocationItem.Invoker(invocationItem.Handler, _owner, args);

if (result is bool isHandedInManaged && isHandedInManaged)
{
Expand Down Expand Up @@ -154,6 +174,7 @@ public bool Dispatch(EventArgs eventArgs, string nativeEventPayload)
internal void RegisterEventHandler(
string eventName,
Delegate handler,
GenericEventHandler invoker,
bool onCapturePhase = false,
HtmlEventExtractor? eventExtractor = null,
EventArgsParser payloadConverter = null)
Expand All @@ -173,14 +194,14 @@ internal void RegisterEventHandler(
payloadConverter);
}

registration.Add(handler);
registration.Add(handler, invoker);
}

internal void UnregisterEventHandler(string eventName, Delegate handler)
internal void UnregisterEventHandler(string eventName, Delegate handler, GenericEventHandler invoker)
{
if (_eventHandlers.TryGetValue(eventName, out var registration))
{
registration.Remove(handler);
registration.Remove(handler, invoker);
}
else if (this.Log().IsEnabled(LogLevel.Debug))
{
Expand Down
65 changes: 65 additions & 0 deletions src/Uno.UI/UI/Xaml/UIElement.GenericEventHandlers.wasm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;

namespace Windows.UI.Xaml
{
partial class UIElement
{
/// <summary>
/// Generic handlers for DOM mapped events
/// </summary>
internal static class GenericEventHandlers
{
internal static object RaiseEventHandler(Delegate d, object sender, object args)
{
if (d is EventHandler handler)
{
handler(sender, args as EventArgs);
return null;
}

throw new InvalidOperationException($"The parameters for invoking GenericEventHandlers.RaiseEventHandler with {d} are incorrect");
}

internal static object RaiseRawEventHandler(Delegate d, object sender, object args)
{
if (d is RawEventHandler handler)
{
return handler(sender as UIElement, args as string);
}

throw new InvalidOperationException($"The parameters for invoking GenericEventHandlers.RaiseEventHandler with {d} are incorrect");
}

internal static object RaiseRoutedEventHandler(Delegate d, object sender, object args)
{
if (d is RoutedEventHandler handler)
{
handler(sender, args as RoutedEventArgs);
return null;
}

throw new InvalidOperationException($"The parameters for invoking GenericEventHandlers.RaiseEventHandler with {d} are incorrect");
}

internal static object RaiseExceptionRoutedEventHandler(Delegate d, object sender, object args)
{
if (d is ExceptionRoutedEventHandler handler)
{
handler(sender, args as ExceptionRoutedEventArgs);
return null;
}
return null;
}

internal static object RaiseRoutedEventHandlerWithHandled(Delegate d, object sender, object args)
{
if (d is RoutedEventHandlerWithHandled handler)
{
handler(sender, args as RoutedEventArgs);
return null;
}
return null;
}
}
}
}
12 changes: 7 additions & 5 deletions src/Uno.UI/UI/Xaml/UIElement.Pointers.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ partial void AddPointerHandler(RoutedEvent routedEvent, int handlersCount, objec
// as on UWP, even if the event are RoutedEvents, PointerEntered and PointerExited
// are routed only in some particular cases (entering at once on multiple controls),
// it's easier to handle this in managed code.
RegisterEventHandler("pointerenter", (RawEventHandler)DispatchNativePointerEnter);
RegisterEventHandler("pointerleave", (RawEventHandler)DispatchNativePointerLeave);
RegisterEventHandler("pointerdown", (RawEventHandler)DispatchNativePointerDown);
RegisterEventHandler("pointerup", (RawEventHandler)DispatchNativePointerUp);
RegisterEventHandler("pointercancel", (RawEventHandler)DispatchNativePointerCancel); //https://www.w3.org/TR/pointerevents/#the-pointercancel-event
RegisterEventHandler("pointerenter", (RawEventHandler)DispatchNativePointerEnter, GenericEventHandlers.RaiseRawEventHandler);
RegisterEventHandler("pointerleave", (RawEventHandler)DispatchNativePointerLeave, GenericEventHandlers.RaiseRawEventHandler);
RegisterEventHandler("pointerdown", (RawEventHandler)DispatchNativePointerDown, GenericEventHandlers.RaiseRawEventHandler);
RegisterEventHandler("pointerup", (RawEventHandler)DispatchNativePointerUp, GenericEventHandlers.RaiseRawEventHandler);
RegisterEventHandler("pointercancel", (RawEventHandler)DispatchNativePointerCancel, GenericEventHandlers.RaiseRawEventHandler); //https://www.w3.org/TR/pointerevents/#the-pointercancel-event
}

switch (routedEvent.Flag)
Expand All @@ -75,6 +75,7 @@ partial void AddPointerHandler(RoutedEvent routedEvent, int handlersCount, objec
RegisterEventHandler(
"pointermove",
handler: (RawEventHandler)DispatchNativePointerMove,
invoker: GenericEventHandlers.RaiseRawEventHandler,
onCapturePhase: false,
eventExtractor: HtmlEventExtractor.PointerEventExtractor
);
Expand All @@ -90,6 +91,7 @@ partial void AddPointerHandler(RoutedEvent routedEvent, int handlersCount, objec
RegisterEventHandler(
"wheel",
handler: (RawEventHandler)DispatchNativePointerWheel,
invoker: GenericEventHandlers.RaiseRawEventHandler,
onCapturePhase: false,
eventExtractor: HtmlEventExtractor.PointerEventExtractor
);
Expand Down
1 change: 1 addition & 0 deletions src/Uno.UI/UI/Xaml/UIElement.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,7 @@ partial void AddKeyHandler(RoutedEvent routedEvent, int handlersCount, object ha
RegisterEventHandler(
domEventName,
handler: new RoutedEventHandlerWithHandled((snd, args) => RaiseEvent(routedEvent, args)),
invoker: GenericEventHandlers.RaiseRoutedEventHandlerWithHandled,
onCapturePhase: false,
eventExtractor: HtmlEventExtractor.KeyboardEventExtractor,
payloadConverter: PayloadToKeyArgs
Expand Down

0 comments on commit f90906d

Please sign in to comment.