Sfoglia il codice sorgente

More platforms are now using IDispatcherImpl

Nikita Tsukanov 2 anni fa
parent
commit
bc3768c2a0

+ 2 - 2
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>

+ 3 - 6
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);

+ 0 - 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 - 0
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();
+        }
+    }
+}

+ 2 - 2
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)

+ 1 - 1
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>()

+ 0 - 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;
-    }
-}

+ 7 - 2
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)

+ 3 - 8
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;

+ 1 - 3
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)
         {

+ 5 - 7
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 - 0
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();

+ 3 - 1
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);
             }
         }