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

Headless TimeProvider support #16226

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open

Headless TimeProvider support #16226

wants to merge 14 commits into from

Conversation

maxkatz6
Copy link
Member

@maxkatz6 maxkatz6 commented Jul 4, 2024

What does the pull request do?

In Avalonia 0.10 we previously had APIs allowing custom defined IClock implementations, that were primarily useful for unit testing. Typical scenario is skipping time for animations or dispatcher timers without actually waiting in real time.

With 11.0 refactoring we have lost this API with an idea to reimplement it in some more controlled form.
This PR is partially inspired by Flutter pump method used in unit testing, as well as .NET 8 TimeProvider APIs.

Also, this PR adds Dispatcher.Idle headless extension, that is better suited to simulate user idle, comparing to RunJobs method.

New APIs:

// Avalonia.Headless package

+public static class HeadlessExtensions
+{
+     public static void Idle(this Dispatcher dispatcher);
+     public static void PulseTime(this Dispatcher dispatcher, TimeSpan duration);
+     public static void PulseRenderFrames(this Dispatcher dispatcher, int framesCount = 1);
+     public static void SetTimeProvider(this Dispatcher dispatcher, TimeProvider? timeProvider);
+}

public class AvaloniaHeadlessPlatformOptions
{
+      public int Fps { get; set; } = 60;

+      /// <summary>
+      /// Gets or sets a value indicating whether to use the real-time time provider. Default is true.
+      /// </summary>
+      /// <remarks>
+      /// When enabled, dispatcher and render timer will tick in real time as headless platform is running.
+      /// Operators like <see cref="System.Threading.Tasks.Task.Delay(TimeSpan)"/> can be used to control timers.
+      /// When disabled, headless time provider will be only controlled via <see cref="HeadlessExtensions.PulseTime"/> and <see cref="HeadlessExtensions.PulseRenderFrames"/> APIs. 
+      /// </remarks>
+      public bool UseRealtimeTimeProvider { get; set; } = true;
}

+[Unstable(ObsoletionMessages.MayBeRemovedInAvalonia12)]
public static class AvaloniaHeadlessPlatform
{
+    [Obsolete("Use Dispatcher.UIThread.PulseFrames instead.")]
     public static void ForceRenderTimerTick(int count = 1);
}

How was the solution implemented (if it's not obvious)?

  1. Compositor now relies on IRenderLoopTask callback timestamp instead of using Stopwatch. This timestamp then can be re-defined by the headless platform.
  2. Managed Dispatcher now allows to replace Stopwatch usage if that's required by the platform implementation. Headless does that. Note, it has no effect on most backends that don't use managed dispatcher.
  3. MediaContext (including animations engine) now uses Dispatcher.Now instead of Stopwatch. Combined with previous, it also can be controlled by the headless platform.
  4. And finally, headless platform has a set of new extensions to control over TimeProvider which is then used in headless implementations of Dispatcher and Render Timer.

There are some other usages of Stopwatch in the codebase but used for profiling and logging needs. Not really a target for these changes.

Checklist

@maxkatz6
Copy link
Member Author

maxkatz6 commented Jul 4, 2024

Any alternative naming ideas are welcomed.

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 11.2.999-cibuild0049883-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@laolarou726
Copy link
Contributor

@maxkatz6 Does Win32/macOS/Linux platforms support custom FPS adjustment?

@maxkatz6
Copy link
Member Author

maxkatz6 commented Jul 8, 2024

@laolarou726 no. It's not possible, when render timer is driven by OS vsync.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants