diff --git a/samples/TestApplication/TestApplication.csproj b/samples/TestApplication/TestApplication.csproj index b56417d523..32b23242ac 100644 --- a/samples/TestApplication/TestApplication.csproj +++ b/samples/TestApplication/TestApplication.csproj @@ -84,6 +84,10 @@ {FB05AC90-89BA-4F2F-A924-F37875FB547C} Perspex.Cairo + + {54f237d5-a70a-4752-9656-0c70b1a7b047} + Perspex.Gtk + {D211E587-D8BC-45B9-95A4-F297C8FA5200} Perspex.Animation diff --git a/src/Gtk/Perspex.Gtk/GtkPlatform.cs b/src/Gtk/Perspex.Gtk/GtkPlatform.cs index e6c2db7c9c..5b52ac84d1 100644 --- a/src/Gtk/Perspex.Gtk/GtkPlatform.cs +++ b/src/Gtk/Perspex.Gtk/GtkPlatform.cs @@ -3,6 +3,7 @@ using System; using System.Reactive.Disposables; +using System.Threading; using Perspex.Controls.Platform; using Perspex.Input.Platform; using Perspex.Input; @@ -51,6 +52,12 @@ namespace Perspex.Gtk Gtk.Application.RunIteration(); } + public void RunLoop(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + Gtk.Application.RunIteration(); + } + public IDisposable StartTimer(TimeSpan interval, Action tick) { var result = true; @@ -65,8 +72,13 @@ namespace Perspex.Gtk return Disposable.Create(() => result = false); } - public void Wake() + + + public void Signal() { + Gtk.Application.Invoke(delegate { Signaled?.Invoke(); }); } + + public event Action Signaled; } } \ No newline at end of file diff --git a/src/Perspex.Application/Application.cs b/src/Perspex.Application/Application.cs index ac93f82fec..5bdf187f71 100644 --- a/src/Perspex.Application/Application.cs +++ b/src/Perspex.Application/Application.cs @@ -144,7 +144,7 @@ namespace Perspex { var source = new CancellationTokenSource(); closable.Closed += (s, e) => source.Cancel(); - Dispatcher.UIThread.MainLoop(source.Token); + Dispatcher.MainLoop(source.Token); } /// diff --git a/src/Perspex.Base/Perspex.Base.csproj b/src/Perspex.Base/Perspex.Base.csproj index d90087add4..51e3d0663d 100644 --- a/src/Perspex.Base/Perspex.Base.csproj +++ b/src/Perspex.Base/Perspex.Base.csproj @@ -72,7 +72,7 @@ - + diff --git a/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs b/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs index 2410cf5a28..04cdd5aaba 100644 --- a/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs +++ b/src/Perspex.Base/Platform/IPlatformThreadingInterface.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Threading; namespace Perspex.Platform { @@ -10,16 +11,7 @@ namespace Perspex.Platform /// public interface IPlatformThreadingInterface { - /// - /// Checks whether there are messages waiting to be processed. - /// - /// True if there are messages waiting, otherwise false. - bool HasMessages(); - - /// - /// Process a single message from the windowing system, blocking until one is available. - /// - void ProcessMessage(); + void RunLoop(CancellationToken cancellationToken); /// /// Starts a timer. @@ -32,6 +24,9 @@ namespace Perspex.Platform /// /// Sends a message that causes to exit. /// - void Wake(); + void Signal(); + + event Action Signaled; + } } diff --git a/src/Perspex.Base/Threading/Dispatcher.cs b/src/Perspex.Base/Threading/Dispatcher.cs index 629492c2d0..29e59bb98e 100644 --- a/src/Perspex.Base/Threading/Dispatcher.cs +++ b/src/Perspex.Base/Threading/Dispatcher.cs @@ -4,7 +4,7 @@ using System; using System.Threading; using System.Threading.Tasks; -using Perspex.Win32.Threading; +using Perspex.Platform; namespace Perspex.Threading { @@ -15,41 +15,38 @@ namespace Perspex.Threading /// In Perspex, there is usually only a single in the application - /// the one for the UI thread, retrieved via the property. /// - public class Dispatcher + public static class Dispatcher { - private static readonly Dispatcher s_instance = new Dispatcher(); - - private readonly MainLoop _mainLoop = new MainLoop(); + private static readonly JobRunner _jobRunner = + new JobRunner(PerspexLocator.Current.GetService()); /// /// Initializes a new instance of the class. /// - private Dispatcher() + static Dispatcher() { + PerspexLocator.Current.GetService().Signaled += _jobRunner.RunJobs; } - /// - /// Gets the for the UI thread. - /// - public static Dispatcher UIThread => s_instance; - /// /// Runs the dispatcher's main loop. /// /// /// A cancellation token used to exit the main loop. /// - public void MainLoop(CancellationToken cancellationToken) + public static void MainLoop(CancellationToken cancellationToken) { - _mainLoop.Run(cancellationToken); + var platform = PerspexLocator.Current.GetService(); + cancellationToken.Register(platform.Signal); + platform.RunLoop(cancellationToken); } /// /// Runs continuations pushed on the loop. /// - public void RunJobs() + public static void RunJobs() { - _mainLoop.RunJobs(); + _jobRunner.RunJobs(); } /// @@ -58,9 +55,9 @@ namespace Perspex.Threading /// The method. /// The priority with which to invoke the method. /// A task that can be used to track the method's execution. - public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) + public static Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { - return _mainLoop.InvokeAsync(action, priority); + return _jobRunner.InvokeAsync(action, priority); } /// @@ -68,9 +65,9 @@ namespace Perspex.Threading /// /// The method. /// The priority with which to invoke the method. - internal void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal) + internal static void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { - _mainLoop.Post(action, priority); + _jobRunner.Post(action, priority); } } } \ No newline at end of file diff --git a/src/Perspex.Base/Threading/DispatcherTimer.cs b/src/Perspex.Base/Threading/DispatcherTimer.cs index 27b1a8cb36..bcd228d0c6 100644 --- a/src/Perspex.Base/Threading/DispatcherTimer.cs +++ b/src/Perspex.Base/Threading/DispatcherTimer.cs @@ -24,17 +24,6 @@ namespace Perspex.Threading public DispatcherTimer() { _priority = DispatcherPriority.Normal; - Dispatcher = Dispatcher.UIThread; - } - - /// - /// Initializes a new instance of the class. - /// - /// The priority to use. - public DispatcherTimer(DispatcherPriority priority) - { - _priority = priority; - Dispatcher = Dispatcher.UIThread; } /// @@ -42,10 +31,9 @@ namespace Perspex.Threading /// /// The priority to use. /// The dispatcher to use. - public DispatcherTimer(DispatcherPriority priority, Dispatcher dispatcher) + public DispatcherTimer(DispatcherPriority priority) { _priority = priority; - Dispatcher = dispatcher; } /// @@ -55,10 +43,9 @@ namespace Perspex.Threading /// The priority to use. /// The dispatcher to use. /// The event to call when the timer ticks. - public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback, Dispatcher dispatcher) + public DispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback) { _priority = priority; - Dispatcher = dispatcher; Interval = interval; Tick += callback; } @@ -79,13 +66,6 @@ namespace Perspex.Threading /// public event EventHandler Tick; - /// - /// Gets the dispatcher that the timer uses. - /// - public Dispatcher Dispatcher - { - get; } - /// /// Gets or sets the interval at which the timer ticks. /// diff --git a/src/Perspex.Base/Threading/MainLoop.cs b/src/Perspex.Base/Threading/JobRunner.cs similarity index 73% rename from src/Perspex.Base/Threading/MainLoop.cs rename to src/Perspex.Base/Threading/JobRunner.cs index b9d21d5ab5..67d5301224 100644 --- a/src/Perspex.Base/Threading/MainLoop.cs +++ b/src/Perspex.Base/Threading/JobRunner.cs @@ -6,41 +6,20 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Perspex.Platform; -using Perspex.Threading; -namespace Perspex.Win32.Threading +namespace Perspex.Threading { /// /// A main loop in a . /// - internal class MainLoop + internal class JobRunner { - private static readonly IPlatformThreadingInterface s_platform; - + private readonly IPlatformThreadingInterface _platform; private readonly Queue _queue = new Queue(); - /// - /// Initializes static members of the class. - /// - static MainLoop() + public JobRunner(IPlatformThreadingInterface platform) { - s_platform = PerspexLocator.Current.GetService(); - } - - /// - /// Runs the main loop. - /// - /// - /// A cancellation token used to exit the main loop. - /// - public void Run(CancellationToken cancellationToken) - { - while (!cancellationToken.IsCancellationRequested) - { - RunJobs(); - - s_platform.ProcessMessage(); - } + _platform = platform; } /// @@ -48,21 +27,15 @@ namespace Perspex.Win32.Threading /// public void RunJobs() { - Job job = null; - - while (job != null || _queue.Count > 0) + while (true) { - if (job == null) - { - lock (_queue) - { - job = _queue.Dequeue(); - } - } + Job job; - if (job.Priority < DispatcherPriority.Input && s_platform.HasMessages()) + lock (_queue) { - break; + if (_queue.Count == 0) + return; + job = _queue.Dequeue(); } if (job.TaskCompletionSource == null) @@ -81,8 +54,6 @@ namespace Perspex.Win32.Threading job.TaskCompletionSource.SetException(e); } } - - job = null; } } @@ -113,11 +84,14 @@ namespace Perspex.Win32.Threading private void AddJob(Job job) { + var needWake = false; lock (_queue) { + needWake = _queue.Count == 0; _queue.Enqueue(job); } - s_platform.Wake(); + if (needWake) + _platform.Signal(); } /// diff --git a/src/Perspex.Base/Threading/PerspexScheduler.cs b/src/Perspex.Base/Threading/PerspexScheduler.cs index 568baf12cb..4861600c53 100644 --- a/src/Perspex.Base/Threading/PerspexScheduler.cs +++ b/src/Perspex.Base/Threading/PerspexScheduler.cs @@ -7,7 +7,7 @@ using System.Reactive.Concurrency; namespace Perspex.Threading { /// - /// A reactive scheduler that uses Perspex's . + /// A reactive scheduler that uses Perspex's . /// public class PerspexScheduler : LocalScheduler { diff --git a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs index c1922b6f28..8ee323e423 100644 --- a/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs +++ b/src/Perspex.Base/Threading/PerspexSynchronizationContext.cs @@ -36,14 +36,14 @@ namespace Perspex.Threading /// public override void Post(SendOrPostCallback d, object state) { - Dispatcher.UIThread.Post(() => d(state)); + Dispatcher.Post(() => d(state)); } /// public override void Send(SendOrPostCallback d, object state) { // TODO: Add check for being on the main thread, we should invoke the method immediately in this case - Dispatcher.UIThread.InvokeAsync(() => d(state)).Wait(); + Dispatcher.InvokeAsync(() => d(state)).Wait(); } } } \ No newline at end of file diff --git a/src/Perspex.Controls/TopLevel.cs b/src/Perspex.Controls/TopLevel.cs index 33a7eca9a2..da59235dd6 100644 --- a/src/Perspex.Controls/TopLevel.cs +++ b/src/Perspex.Controls/TopLevel.cs @@ -43,11 +43,6 @@ namespace Perspex.Controls public static readonly PerspexProperty PointerOverElementProperty = PerspexProperty.Register(nameof(IInputRoot.PointerOverElement)); - /// - /// The dispatcher for the window. - /// - private readonly Dispatcher _dispatcher; - /// /// The render manager for the window.s /// @@ -126,8 +121,6 @@ namespace Perspex.Controls Size clientSize = ClientSize = PlatformImpl.ClientSize; - _dispatcher = Dispatcher.UIThread; - if (renderInterface != null) { _renderer = renderInterface.CreateRenderer(PlatformImpl.Handle, clientSize.Width, clientSize.Height); @@ -404,7 +397,7 @@ namespace Perspex.Controls /// private void HandleLayoutNeeded() { - _dispatcher.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render); + Dispatcher.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render); } /// @@ -420,7 +413,7 @@ namespace Perspex.Controls /// private void HandleRenderNeeded() { - _dispatcher.InvokeAsync( + Dispatcher.InvokeAsync( () => PlatformImpl.Invalidate(new Rect(ClientSize)), DispatcherPriority.Render); } diff --git a/src/Windows/Perspex.Win32/Win32Platform.cs b/src/Windows/Perspex.Win32/Win32Platform.cs index c513fbf97e..896bbcda65 100644 --- a/src/Windows/Perspex.Win32/Win32Platform.cs +++ b/src/Windows/Perspex.Win32/Win32Platform.cs @@ -8,6 +8,7 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Reactive.Disposables; using System.Runtime.InteropServices; +using System.Threading; using Perspex.Controls.Platform; using Perspex.Input; using Perspex.Platform; @@ -79,6 +80,17 @@ namespace Perspex.Win32 UnmanagedMethods.DispatchMessage(ref msg); } + public void RunLoop(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + UnmanagedMethods.MSG msg; + UnmanagedMethods.GetMessage(out msg, IntPtr.Zero, 0, 0); + UnmanagedMethods.TranslateMessage(ref msg); + UnmanagedMethods.DispatchMessage(ref msg); + } + } + public IDisposable StartTimer(TimeSpan interval, Action callback) { UnmanagedMethods.TimerProc timerDelegate = @@ -100,18 +112,27 @@ namespace Perspex.Win32 }); } - public void Wake() + private static readonly int SignalW = unchecked((int) 0xdeadbeaf); + private static readonly int SignalL = unchecked((int)0x12345678); + + public void Signal() { - //UnmanagedMethods.PostMessage( - // this.hwnd, - // (int)UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM, - // IntPtr.Zero, - // IntPtr.Zero); + UnmanagedMethods.PostMessage( + _hwnd, + (int) UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM, + new IntPtr(SignalW), + new IntPtr(SignalL)); } + public event Action Signaled; + [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { + if (msg == (int) UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM && wParam.ToInt64() == SignalW && lParam.ToInt64() == SignalL) + { + Signaled?.Invoke(); + } return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); }