Browse Source

More platforms are now using IDispatcherImpl

pull/10691/head
Nikita Tsukanov 4 years ago
parent
commit
bc3768c2a0
  1. 4
      samples/ControlCatalog/App.xaml
  2. 9
      src/Avalonia.Base/Threading/IDispatcherImpl.cs
  3. 92
      src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
  4. 108
      src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs
  5. 4
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  6. 2
      src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs
  7. 86
      src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs
  8. 9
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  9. 11
      src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs
  10. 4
      src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs
  11. 12
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  12. 14
      src/Shared/RawEventGrouping.cs
  13. 4
      tests/Avalonia.RenderTests/TestBase.cs

4
samples/ControlCatalog/App.xaml

@ -60,7 +60,7 @@
<Style Selector="Label.h3">
<Setter Property="FontSize" Value="12" />
</Style>
</Application.Styles>
</Application.Styles><!--
<TrayIcon.Icons>
<TrayIcons>
<TrayIcon Icon="/Assets/test_icon.ico" ToolTipText="Avalonia Tray Icon ToolTip">
@ -81,5 +81,5 @@
</TrayIcon.Menu>
</TrayIcon>
</TrayIcons>
</TrayIcon.Icons>
</TrayIcon.Icons>-->
</Application>

9
src/Avalonia.Base/Threading/IDispatcherImpl.cs

@ -4,11 +4,8 @@ using Avalonia.Platform;
namespace Avalonia.Threading;
interface IDispatcherImpl
public interface IDispatcherImpl
{
//IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick);
bool CurrentThreadIsLoopThread { get; }
// Asynchronously triggers Signaled callback
@ -19,7 +16,7 @@ interface IDispatcherImpl
}
interface IDispatcherImplWithPendingInput : IDispatcherImpl
public interface IDispatcherImplWithPendingInput : IDispatcherImpl
{
// Checks if dispatcher implementation can
bool CanQueryPendingInput { get; }
@ -27,7 +24,7 @@ interface IDispatcherImplWithPendingInput : IDispatcherImpl
bool HasPendingInput { get; }
}
interface IControlledDispatcherImpl : IDispatcherImplWithPendingInput
public interface IControlledDispatcherImpl : IDispatcherImplWithPendingInput
{
// Runs the event loop
void RunLoop(CancellationToken token);

92
src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs

@ -1,92 +0,0 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Threading;
namespace Avalonia.Controls.Platform
{
[Unstable]
public class InternalPlatformThreadingInterface : IPlatformThreadingInterface
{
public InternalPlatformThreadingInterface()
{
TlsCurrentThreadIsLoopThread = true;
}
private readonly AutoResetEvent _signaled = new AutoResetEvent(false);
public void RunLoop(CancellationToken cancellationToken)
{
var handles = new[] { _signaled, cancellationToken.WaitHandle };
while (!cancellationToken.IsCancellationRequested)
{
Signaled?.Invoke(null);
WaitHandle.WaitAny(handles);
}
}
class TimerImpl : IDisposable
{
private readonly DispatcherPriority _priority;
private readonly TimeSpan _interval;
private readonly Action _tick;
private Timer? _timer;
private GCHandle _handle;
public TimerImpl(DispatcherPriority priority, TimeSpan interval, Action tick)
{
_priority = priority;
_interval = interval;
_tick = tick;
_timer = new Timer(OnTimer, null, interval, Timeout.InfiniteTimeSpan);
_handle = GCHandle.Alloc(_timer);
}
private void OnTimer(object? state)
{
if (_timer == null)
return;
Dispatcher.UIThread.Post(() =>
{
if (_timer == null)
return;
_tick();
_timer?.Change(_interval, Timeout.InfiniteTimeSpan);
});
}
public void Dispose()
{
_handle.Free();
_timer?.Dispose();
_timer = null;
}
}
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{
return new TimerImpl(priority, interval, tick);
}
public void Signal(DispatcherPriority prio)
{
_signaled.Set();
}
[ThreadStatic] private static bool TlsCurrentThreadIsLoopThread;
public bool CurrentThreadIsLoopThread => TlsCurrentThreadIsLoopThread;
public event Action<DispatcherPriority?>? Signaled;
#pragma warning disable CS0067
public event Action<TimeSpan>? Tick;
#pragma warning restore CS0067
}
}

108
src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs

@ -0,0 +1,108 @@
using System;
using System.Diagnostics;
using System.Threading;
using Avalonia.Metadata;
using Avalonia.Threading;
namespace Avalonia.Controls.Platform;
[Unstable]
public class ManagedDispatcherImpl : IControlledDispatcherImpl
{
private readonly IManagedDispatcherInputProvider? _inputProvider;
private readonly AutoResetEvent _wakeup = new(false);
private bool _signaled;
private readonly object _lock = new();
private readonly Stopwatch _clock = Stopwatch.StartNew();
private TimeSpan? _nextTimer;
private readonly Thread _loopThread = Thread.CurrentThread;
public interface IManagedDispatcherInputProvider
{
bool HasInput { get; }
void DispatchNextInputEvent();
}
public ManagedDispatcherImpl(IManagedDispatcherInputProvider? inputProvider)
{
_inputProvider = inputProvider;
}
public bool CurrentThreadIsLoopThread => _loopThread == Thread.CurrentThread;
public void Signal()
{
lock (_lock)
{
_signaled = true;
_wakeup.Set();
}
}
public event Action? Signaled;
public event Action? Timer;
public void UpdateTimer(int? dueTimeInTicks)
{
lock (_lock)
{
_nextTimer = dueTimeInTicks == null
? null
: _clock.Elapsed + TimeSpan.FromMilliseconds(dueTimeInTicks.Value);
if (!CurrentThreadIsLoopThread)
_wakeup.Set();
}
}
public bool CanQueryPendingInput => _inputProvider != null;
public bool HasPendingInput => _inputProvider?.HasInput ?? false;
public void RunLoop(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
bool signaled;
lock (_lock)
{
signaled = _signaled;
_signaled = false;
}
if (signaled)
{
Signaled?.Invoke();
continue;
}
bool fireTimer = false;
lock (_lock)
{
if (_nextTimer < _clock.Elapsed)
{
fireTimer = true;
_nextTimer = null;
}
}
if (fireTimer)
{
Timer?.Invoke();
continue;
}
if (_inputProvider?.HasInput == true)
{
_inputProvider.DispatchNextInputEvent();
continue;
}
if (_nextTimer != null)
{
var waitFor = _clock.Elapsed - _nextTimer.Value;
if (waitFor.TotalMilliseconds < 1)
continue;
_wakeup.WaitOne(waitFor);
}
else
_wakeup.WaitOne();
}
}
}

4
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -6,6 +6,7 @@ using Avalonia.Input.Platform;
using Avalonia.Platform;
using Avalonia.Remote.Protocol;
using Avalonia.Rendering;
using Avalonia.Threading;
namespace Avalonia.DesignerSupport.Remote
{
@ -46,13 +47,12 @@ namespace Avalonia.DesignerSupport.Remote
{
s_transport = transport;
var instance = new PreviewerWindowingPlatform();
var threading = new InternalPlatformThreadingInterface();
AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToSingleton<ClipboardStub>()
.Bind<ICursorFactory>().ToSingleton<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(threading)
.Bind<IDispatcherImpl>().ToConstant(new ManagedDispatcherImpl(null))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IWindowingPlatform>().ToConstant(instance)

2
src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs

@ -60,7 +60,7 @@ namespace Avalonia.Headless
internal static void Initialize(AvaloniaHeadlessPlatformOptions opts)
{
AvaloniaLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>().ToConstant(new HeadlessPlatformThreadingInterface())
.Bind<IDispatcherImpl>().ToConstant(new ManagedDispatcherImpl(null))
.Bind<IClipboard>().ToSingleton<HeadlessClipboardStub>()
.Bind<ICursorFactory>().ToSingleton<HeadlessCursorFactoryStub>()
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()

86
src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs

@ -1,86 +0,0 @@
using System;
using Avalonia.Reactive;
using System.Threading;
using Avalonia.Platform;
using Avalonia.Threading;
namespace Avalonia.Headless
{
class HeadlessPlatformThreadingInterface : IPlatformThreadingInterface
{
public HeadlessPlatformThreadingInterface()
{
_thread = Thread.CurrentThread;
}
private AutoResetEvent _event = new AutoResetEvent(false);
private Thread _thread;
private object _lock = new object();
private DispatcherPriority? _signaledPriority;
public void RunLoop(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
DispatcherPriority? signaled = null;
lock (_lock)
{
signaled = _signaledPriority;
_signaledPriority = null;
}
if(signaled.HasValue)
Signaled?.Invoke(signaled);
WaitHandle.WaitAny(new[] {cancellationToken.WaitHandle, _event}, TimeSpan.FromMilliseconds(20));
}
}
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{
if (interval.TotalMilliseconds < 10)
interval = TimeSpan.FromMilliseconds(10);
var stopped = false;
Timer timer = null;
timer = new Timer(_ =>
{
if (stopped)
return;
Dispatcher.UIThread.Post(() =>
{
try
{
tick();
}
finally
{
if (!stopped)
timer.Change(interval, Timeout.InfiniteTimeSpan);
}
});
},
null, interval, Timeout.InfiniteTimeSpan);
return Disposable.Create(() =>
{
stopped = true;
timer.Dispose();
});
}
public void Signal(DispatcherPriority priority)
{
lock (_lock)
{
if (_signaledPriority == null || _signaledPriority.Value > priority)
{
_signaledPriority = priority;
}
_event.Set();
}
}
public bool CurrentThreadIsLoopThread => _thread == Thread.CurrentThread;
public event Action<DispatcherPriority?> Signaled;
}
}

9
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -8,13 +8,15 @@ using Avalonia.LinuxFramebuffer.Output;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
namespace Avalonia.LinuxFramebuffer
namespace Avalonia.LinuxFramebuffer
{
class FramebufferToplevelImpl : ITopLevelImpl, IScreenInfoProvider
{
private readonly IOutputBackend _outputBackend;
private readonly IInputBackend _inputBackend;
private readonly RawEventGrouper _inputQueue;
public IInputRoot InputRoot { get; private set; }
@ -22,9 +24,12 @@ namespace Avalonia.LinuxFramebuffer
{
_outputBackend = outputBackend;
_inputBackend = inputBackend;
_inputQueue = new RawEventGrouper(groupedInput => Input?.Invoke(groupedInput),
LinuxFramebufferPlatform.EventGrouperDispatchQueue);
Surfaces = new object[] { _outputBackend };
_inputBackend.Initialize(this, e => Input?.Invoke(e));
_inputBackend.Initialize(this, e =>
Dispatcher.UIThread.Post(() => _inputQueue.HandleEvent(e), DispatcherPriority.Send ));
}
public IRenderer CreateRenderer(IRenderRoot root)

11
src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs

@ -14,12 +14,10 @@ namespace Avalonia.LinuxFramebuffer.Input.EvDev
private int _epoll;
private Action<RawInputEventArgs> _onInput;
private IInputRoot _inputRoot;
private RawEventGroupingThreadingHelper _inputQueue;
public EvDevBackend(EvDevDeviceDescription[] devices)
{
_deviceDescriptions = devices;
_inputQueue = new RawEventGroupingThreadingHelper(e => _onInput?.Invoke(e));
}
unsafe void InputThread()
@ -45,12 +43,9 @@ namespace Avalonia.LinuxFramebuffer.Input.EvDev
}
}
private void OnRawEvent(RawInputEventArgs obj)
{
_inputQueue.OnEvent(obj);
}
private void OnRawEvent(RawInputEventArgs obj) => _onInput?.Invoke(obj);
public void Initialize(IScreenInfoProvider info, Action<RawInputEventArgs> onInput)
{
_onInput = onInput;

4
src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs

@ -13,14 +13,12 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
private IInputRoot _inputRoot;
private TouchDevice _touch = new TouchDevice();
private const string LibInput = nameof(Avalonia.LinuxFramebuffer) + "/" + nameof(Avalonia.LinuxFramebuffer.Input) + "/" + nameof(LibInput);
private readonly RawEventGroupingThreadingHelper _inputQueue;
private Action<RawInputEventArgs> _onInput;
private Dictionary<int, Point> _pointers = new Dictionary<int, Point>();
public LibInputBackend()
{
var ctx = libinput_path_create_context();
_inputQueue = new(e => _onInput?.Invoke(e));
new Thread(() => InputThread(ctx)).Start();
}
@ -58,7 +56,7 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
}
}
private void ScheduleInput(RawInputEventArgs ev) => _inputQueue.OnEvent(ev);
private void ScheduleInput(RawInputEventArgs ev) => _onInput.Invoke(ev);
private void HandleTouch(IntPtr ev, LibInputEventType type)
{

12
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -16,6 +16,8 @@ using Avalonia.LinuxFramebuffer.Output;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
#nullable enable
namespace Avalonia.LinuxFramebuffer
@ -23,9 +25,7 @@ namespace Avalonia.LinuxFramebuffer
class LinuxFramebufferPlatform
{
IOutputBackend _fb;
private static readonly Stopwatch St = Stopwatch.StartNew();
internal static uint Timestamp => (uint)St.ElapsedTicks;
public static InternalPlatformThreadingInterface? Threading;
public static ManualRawEventGrouperDispatchQueue EventGrouperDispatchQueue = new();
internal static Compositor Compositor { get; private set; } = null!;
@ -34,18 +34,16 @@ namespace Avalonia.LinuxFramebuffer
{
_fb = backend;
}
void Initialize()
{
Threading = new InternalPlatformThreadingInterface();
if (_fb is IGlOutputBackend gl)
AvaloniaLocator.CurrentMutable.Bind<IPlatformGraphics>().ToConstant(gl.PlatformGraphics);
var opts = AvaloniaLocator.Current.GetService<LinuxFramebufferPlatformOptions>() ?? new LinuxFramebufferPlatformOptions();
AvaloniaLocator.CurrentMutable
.Bind<IPlatformThreadingInterface>().ToConstant(Threading)
.Bind<IDispatcherImpl>().ToConstant(new ManagedDispatcherImpl(new ManualRawEventGrouperDispatchQueueDispatcherInputProvider(EventGrouperDispatchQueue)))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(opts.Fps))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<ICursorFactory>().ToTransient<CursorFactoryStub>()

14
src/Shared/RawEventGrouping.cs

@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using Avalonia.Collections.Pooled;
using Avalonia.Controls.Platform;
using Avalonia.Input.Raw;
using Avalonia.Threading;
@ -34,6 +35,19 @@ class ManualRawEventGrouperDispatchQueue : IRawEventGrouperDispatchQueue
}
}
internal class ManualRawEventGrouperDispatchQueueDispatcherInputProvider : ManagedDispatcherImpl.IManagedDispatcherInputProvider
{
private readonly ManualRawEventGrouperDispatchQueue _queue;
public ManualRawEventGrouperDispatchQueueDispatcherInputProvider(ManualRawEventGrouperDispatchQueue queue)
{
_queue = queue;
}
public bool HasInput => _queue.HasJobs;
public void DispatchNextInputEvent() => _queue.DispatchNext();
}
internal class AutomaticRawEventGrouperDispatchQueue : IRawEventGrouperDispatchQueue
{
private readonly Queue<(RawInputEventArgs args, Action<RawInputEventArgs> handler)> _inputQueue = new();

4
tests/Avalonia.RenderTests/TestBase.cs

@ -17,6 +17,7 @@ using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Media;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using Avalonia.Utilities;
using SixLabors.ImageSharp.PixelFormats;
using Image = SixLabors.ImageSharp.Image;
#if AVALONIA_SKIA
@ -122,7 +123,8 @@ namespace Avalonia.Direct2D1.RenderTests
// Free pools
for (var c = 0; c < 11; c++)
TestThreadingInterface.RunTimers();
foreach (var dp in Dispatcher.SnapshotTimersForUnitTests())
dp.ForceFire();
writableBitmap.Save(compositedPath);
}
}

Loading…
Cancel
Save