Skip to content

Commit

Permalink
Fix input starvation issues on resource-constrained devices. (Avaloni…
Browse files Browse the repository at this point in the history
…aUI#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.
  • Loading branch information
grokys authored Apr 20, 2024
1 parent eb3c055 commit 8a65cb6
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/Avalonia.Base/Media/MediaContext.Compositor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,6 @@ void ICompositorScheduler.CommitRequested(Compositor compositor)
return;

// TODO: maybe skip the full render here?
ScheduleRender(true);
ScheduleRender(false);
}
}
13 changes: 10 additions & 3 deletions src/Avalonia.Base/Media/MediaContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Compositor> _requestedCommits = new();
Expand All @@ -37,12 +37,13 @@ private record TopLevelInfo(Compositor Compositor, CompositingRenderer Renderer

private readonly Dictionary<object, TopLevelInfo> _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();
Expand All @@ -57,8 +58,14 @@ public static MediaContext Instance
// 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<MediaContext>();

if (context == null)
AvaloniaLocator.CurrentMutable.Bind<MediaContext>().ToConstant(context = new(Dispatcher.UIThread));
{
var opts = AvaloniaLocator.Current.GetService<DispatcherOptions>() ?? new();
context = new MediaContext(Dispatcher.UIThread, opts.InputStarvationTimeout);
AvaloniaLocator.CurrentMutable.Bind<MediaContext>().ToConstant(context);
}

return context;
}
}
Expand Down
21 changes: 21 additions & 0 deletions src/Avalonia.Base/Threading/DispatcherOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;

namespace Avalonia.Threading;

/// <summary>
/// AppBuilder options for configuring the <see cref="Dispatcher"/>.
/// </summary>
public class DispatcherOptions
{
/// <summary>
/// Gets or sets a timeout after which the dispatcher will start prioritizing input events over
/// rendering. The default value is 1 second.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public TimeSpan InputStarvationTimeout { get; set; } = TimeSpan.FromSeconds(1);
}

0 comments on commit 8a65cb6

Please sign in to comment.