From 8a65cb6a68a6e2b2a2d269ecee084e848702f8ed Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 20 Apr 2024 03:29:12 +0300 Subject: [PATCH] Fix input starvation issues on resource-constrained devices. (#15137) * Use 0.2 seconds for input starvation cutoff. 1 second was too long when running on resource-constrained devices. * Allow input starvation code to run. Using `ScheduleRender(true)` here prevented the input starvation code from running. * Add DispatcherOptions. And allow setting the input starvation timeout there. --- .../Media/MediaContext.Compositor.cs | 2 +- src/Avalonia.Base/Media/MediaContext.cs | 13 +++++++++--- .../Threading/DispatcherOptions.cs | 21 +++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 src/Avalonia.Base/Threading/DispatcherOptions.cs diff --git a/src/Avalonia.Base/Media/MediaContext.Compositor.cs b/src/Avalonia.Base/Media/MediaContext.Compositor.cs index 0d6aec9ae2..ff8c2b090e 100644 --- a/src/Avalonia.Base/Media/MediaContext.Compositor.cs +++ b/src/Avalonia.Base/Media/MediaContext.Compositor.cs @@ -146,6 +146,6 @@ partial class MediaContext return; // TODO: maybe skip the full render here? - ScheduleRender(true); + ScheduleRender(false); } } diff --git a/src/Avalonia.Base/Media/MediaContext.cs b/src/Avalonia.Base/Media/MediaContext.cs index a21e62d746..9747d01c7e 100644 --- a/src/Avalonia.Base/Media/MediaContext.cs +++ b/src/Avalonia.Base/Media/MediaContext.cs @@ -16,7 +16,7 @@ internal partial class MediaContext : ICompositorScheduler private TimeSpan _inputMarkerAddedAt; private bool _isRendering; private bool _animationsAreWaitingForComposition; - private const double MaxSecondsWithoutInput = 1; + private readonly double MaxSecondsWithoutInput; private readonly Action _render; private readonly Action _inputMarkerHandler; private readonly HashSet _requestedCommits = new(); @@ -37,12 +37,13 @@ internal partial class MediaContext : ICompositorScheduler private readonly Dictionary _topLevels = new(); - private MediaContext(Dispatcher dispatcher) + private MediaContext(Dispatcher dispatcher, TimeSpan inputStarvationTimeout) { _render = Render; _inputMarkerHandler = InputMarkerHandler; _clock = new(this); _dispatcher = dispatcher; + MaxSecondsWithoutInput = inputStarvationTimeout.TotalSeconds; _animationsTimer.Tick += (_, _) => { _animationsTimer.Stop(); @@ -57,8 +58,14 @@ internal partial class MediaContext : ICompositorScheduler // Technically it's supposed to be a thread-static singleton, but we don't have multiple threads // and need to do a full reset for unit tests var context = AvaloniaLocator.Current.GetService(); + if (context == null) - AvaloniaLocator.CurrentMutable.Bind().ToConstant(context = new(Dispatcher.UIThread)); + { + var opts = AvaloniaLocator.Current.GetService() ?? new(); + context = new MediaContext(Dispatcher.UIThread, opts.InputStarvationTimeout); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(context); + } + return context; } } diff --git a/src/Avalonia.Base/Threading/DispatcherOptions.cs b/src/Avalonia.Base/Threading/DispatcherOptions.cs new file mode 100644 index 0000000000..e12497a598 --- /dev/null +++ b/src/Avalonia.Base/Threading/DispatcherOptions.cs @@ -0,0 +1,21 @@ +using System; + +namespace Avalonia.Threading; + +/// +/// AppBuilder options for configuring the . +/// +public class DispatcherOptions +{ + /// + /// Gets or sets a timeout after which the dispatcher will start prioritizing input events over + /// rendering. The default value is 1 second. + /// + /// + /// If no input events are processed within this time, the dispatcher will start prioritizing + /// input events over rendering to prevent the application from becoming unresponsive. This may + /// need to be lowered on resource-constrained platforms where input events are processed on + /// the same thread as rendering. + /// + public TimeSpan InputStarvationTimeout { get; set; } = TimeSpan.FromSeconds(1); +}