Skip to content

Commit

Permalink
Merge pull request #19257 from unoplatform/mergify/bp/release/stable/…
Browse files Browse the repository at this point in the history
…5.6/pr-19230

perf(anim): Register only once to the JS animationframe callback (backport #19230)
  • Loading branch information
jeromelaban authored Jan 17, 2025
2 parents cff4cc8 + 2b303b6 commit 9f7ec84
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 194 deletions.
Original file line number Diff line number Diff line change
@@ -1,29 +1,65 @@
using System;
using System.Runtime.InteropServices.JavaScript;
using Uno.Disposables;
using Uno.Foundation.Logging;
using Windows.UI.Core;

namespace __Microsoft.UI.Xaml.Media.Animation
namespace Microsoft.UI.Xaml.Media.Animation
{
internal partial class RenderingLoopAnimator
{
internal static partial class NativeMethods
private static WeakEventHelper.WeakEventCollection _frameHandlers = new();

internal static IDisposable RegisterFrameEvent(Action action)
{
[JSImport("globalThis.Microsoft.UI.Xaml.Media.Animation.RenderingLoopAnimator.createInstance")]
internal static partial void CreateInstance(IntPtr managedHandle, double id);
var requiresEnable = _frameHandlers.IsEmpty;

[JSImport("globalThis.Microsoft.UI.Xaml.Media.Animation.RenderingLoopAnimator.destroyInstance")]
internal static partial void DestroyInstance(double jsHandle);
var disposable = WeakEventHelper.RegisterEvent(
_frameHandlers,
action,
(h, s, a) => (h as Action)?.Invoke());

[JSImport("globalThis.Microsoft.UI.Xaml.Media.Animation.RenderingLoopAnimator.disableFrameReporting")]
internal static partial void DisableFrameReporting(double jsHandle);
if (requiresEnable)
{
SetEnabledNative(true);
}

[JSImport("globalThis.Microsoft.UI.Xaml.Media.Animation.RenderingLoopAnimator.enableFrameReporting")]
internal static partial void EnableFrameReporting(double jsHandle);
return Disposable.Create(() =>
{
disposable.Dispose();

if (_frameHandlers.IsEmpty)
{
SetEnabledNative(false);
}
});
}

[JSImport("globalThis.Microsoft.UI.Xaml.Media.Animation.RenderingLoopAnimator.setAnimationFramesInterval")]
internal static partial void SetAnimationFramesInterval(double jsHandle);
[JSExport]
public static void OnFrame()
{
_frameHandlers.Invoke(null, null);

if (_frameHandlers.IsEmpty)
{
SetEnabledNative(false);
}
}

[JSImport("globalThis.Microsoft.UI.Xaml.Media.Animation.RenderingLoopAnimator.setStartFrameDelay")]
internal static partial void SetStartFrameDelay(double jsHandle, double delayMs);
private static void SetEnabledNative(bool enabled)
{
if (typeof(RenderingLoopAnimator).Log().IsEnabled(LogLevel.Trace))
{
typeof(RenderingLoopAnimator).Log().Trace($"SetEnabledNative: {enabled}");
}

NativeMethods.SetEnabled(enabled);
}

internal static partial class NativeMethods
{
[JSImport("globalThis.Microsoft.UI.Xaml.Media.Animation.RenderingLoopAnimator.setEnabled")]
internal static partial void SetEnabled(bool enabled);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,130 +1,124 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.InteropServices.JavaScript;
using System.Threading;
using Uno.Disposables;
using Uno.Foundation;
using Uno.Foundation.Interop;
using Uno.Foundation.Logging;
using Uno.UI.__Resources;
using Windows.UI.Core;
using Windows.System;

using NativeMethods = __Microsoft.UI.Xaml.Media.Animation.RenderingLoopAnimator.NativeMethods;
namespace Microsoft.UI.Xaml.Media.Animation;

namespace Microsoft.UI.Xaml.Media.Animation
internal abstract class RenderingLoopAnimator<T> : CPUBoundAnimator<T> where T : struct
{
internal abstract class RenderingLoopAnimator<T> : CPUBoundAnimator<T>, IJSObject where T : struct
private bool _isEnabled;
private IDisposable _frameEvent;
private DispatcherQueueTimer _delayRequest;

protected RenderingLoopAnimator(T from, T to)
: base(from, to)
{
}

protected override void EnableFrameReporting()
{
protected RenderingLoopAnimator(T from, T to)
: base(from, to)
if (this.Log().IsEnabled(LogLevel.Trace))
{
Handle = JSObjectHandle.Create(this, Metadata.Instance);
this.Log().Trace("EnableFrameReporting");
}

public JSObjectHandle Handle { get; }

protected override void EnableFrameReporting()
if (_isEnabled)
{
if (Handle.IsAlive)
{
NativeMethods.EnableFrameReporting(Handle.JSHandle);
}
else if (this.Log().IsEnabled(LogLevel.Debug))
{
this.Log().Debug("Cannot EnableFrameReporting as Handle is no longer alive.");
}
return;
}

protected override void DisableFrameReporting()
_isEnabled = true;

RegisterFrameEvent();
}

protected override void DisableFrameReporting()
{
if (this.Log().IsEnabled(LogLevel.Trace))
{
if (Handle.IsAlive)
{
NativeMethods.DisableFrameReporting(Handle.JSHandle);
}
else if (this.Log().IsEnabled(LogLevel.Debug))
{
this.Log().Debug("Cannot DisableFrameReporting as Handle is no longer alive.");
}
this.Log().Trace("DisableFrameReporting");
}

protected override void SetStartFrameDelay(long delayMs)

_isEnabled = false;
UnscheduleFrame();
}

protected override void SetStartFrameDelay(long delayMs)
{
if (this.Log().IsEnabled(LogLevel.Trace))
{
if (Handle.IsAlive)
{
NativeMethods.SetStartFrameDelay(Handle.JSHandle, delayMs);
}
else if (this.Log().IsEnabled(LogLevel.Debug))
{
this.Log().Debug("Cannot SetStartFrameDelay as Handle is no longer alive.");
}
this.Log().Trace($"SetStartFrameDelay: {delayMs}");
}

protected override void SetAnimationFramesInterval()
UnscheduleFrame();

if (_isEnabled)
{
if (Handle.IsAlive)
{
NativeMethods.SetAnimationFramesInterval(Handle.JSHandle);
}
else if (this.Log().IsEnabled(LogLevel.Debug))
_delayRequest = new DispatcherQueueTimer()
{
this.Log().Debug("Cannot SetAnimationFramesInterval as Handle is no longer alive.");
}
}
Interval = TimeSpan.FromMilliseconds(delayMs),
IsRepeating = false
};

private void OnFrame() => OnFrame(null, null);
_delayRequest.Tick += (s, e) =>
{
_delayRequest = null;

/// <inheritdoc />
public override void Dispose()
{
// WARNING: If the Dispose is invoked by the GC, it has most probably already disposed the Handle,
// which means that we have already lost ability to dispose/stop the native object!
OnFrame();

base.Dispose();
Handle.Dispose();
// Restore the render loop now that we have completed the delay
RegisterFrameEvent();
};

GC.SuppressFinalize(this);
_delayRequest.Start();
}
}

~RenderingLoopAnimator()
protected override void SetAnimationFramesInterval()
{
if (this.Log().IsEnabled(LogLevel.Trace))
{
Dispose();
this.Log().Trace("SetAnimationFramesInterval");
}

private class Metadata : IJSObjectMetadata
{
public static Metadata Instance { get; } = new Metadata();
UnscheduleFrame();

private Metadata() { }

/// <inheritdoc />
public long CreateNativeInstance(IntPtr managedHandle)
{
var id = RenderingLoopAnimatorMetadataIdProvider.Next();

NativeMethods.CreateInstance(managedHandle, id);

return id;
}
if (_isEnabled)
{
OnFrame();
}
}

/// <inheritdoc />
public string GetNativeInstance(IntPtr managedHandle, long jsHandle)
=> $"Microsoft.UI.Xaml.Media.Animation.RenderingLoopAnimator.getInstance(\"{jsHandle}\")";
private void UnscheduleFrame()
{
if (_delayRequest != null)
{
_delayRequest.Stop();
_delayRequest = null;
}

/// <inheritdoc />
public void DestroyNativeInstance(IntPtr managedHandle, long jsHandle)
=> NativeMethods.DestroyInstance(jsHandle);
_frameEvent?.Dispose();
_frameEvent = null;
}

/// <inheritdoc />
public object InvokeManaged(object instance, string method, string parameters)
{
switch (method)
{
case "OnFrame":
((RenderingLoopAnimator<T>)instance).OnFrame();
break;

default:
throw new ArgumentOutOfRangeException(nameof(method));
}

return null;
}
}
private void RegisterFrameEvent()
{
_frameEvent?.Dispose();
_frameEvent = RenderingLoopAnimator.RegisterFrameEvent(OnFrame);
}

private void OnFrame()
=> OnFrame(null, null);
}
Loading

0 comments on commit 9f7ec84

Please sign in to comment.