From 1c9cc115012bf15990eae3852fc6a0a0defdfb06 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 15 Dec 2014 21:14:11 +0100 Subject: [PATCH] Run animation on UI thread. --- Perspex.Base/Perspex.Base.csproj | 1 + .../Platform/IPlatformThreadingInterface.cs | 2 ++ Perspex.Base/Threading/DispatcherTimer.cs | 19 ++++++++++++ Perspex.Base/Threading/MainLoop.cs | 26 +++++++++++----- Perspex.Base/Threading/PerspexScheduler.cs | 31 +++++++++++++++++++ .../Presenters/ScrollContentPresenter.cs | 2 -- TestApplication/Program.cs | 3 +- .../Perspex.Win32/Interop/UnmanagedMethods.cs | 3 ++ Windows/Perspex.Win32/Win32Platform.cs | 16 +++++++--- 9 files changed, 88 insertions(+), 15 deletions(-) create mode 100644 Perspex.Base/Threading/PerspexScheduler.cs diff --git a/Perspex.Base/Perspex.Base.csproj b/Perspex.Base/Perspex.Base.csproj index 18f0441456..715391fbfa 100644 --- a/Perspex.Base/Perspex.Base.csproj +++ b/Perspex.Base/Perspex.Base.csproj @@ -54,6 +54,7 @@ + diff --git a/Perspex.Base/Platform/IPlatformThreadingInterface.cs b/Perspex.Base/Platform/IPlatformThreadingInterface.cs index 8f7282e0ad..d594abeb49 100644 --- a/Perspex.Base/Platform/IPlatformThreadingInterface.cs +++ b/Perspex.Base/Platform/IPlatformThreadingInterface.cs @@ -16,6 +16,8 @@ namespace Perspex.Platform /// public interface IPlatformThreadingInterface { + bool HasMessages(); + /// /// Process a single message from the windowing system, blocking until one is available. /// diff --git a/Perspex.Base/Threading/DispatcherTimer.cs b/Perspex.Base/Threading/DispatcherTimer.cs index b7912fa983..04230fef7c 100644 --- a/Perspex.Base/Threading/DispatcherTimer.cs +++ b/Perspex.Base/Threading/DispatcherTimer.cs @@ -7,6 +7,7 @@ namespace Perspex.Threading { using System; + using System.Reactive.Disposables; using Perspex.Platform; using Splat; @@ -105,6 +106,24 @@ namespace Perspex.Threading set; } + public static IDisposable Run(Func action, TimeSpan interval, DispatcherPriority priority = DispatcherPriority.Normal) + { + var timer = new DispatcherTimer(priority); + + timer.Interval = interval; + timer.Tick += (s, e) => + { + if (!action()) + { + timer.Stop(); + } + }; + + timer.Start(); + + return Disposable.Create(() => timer.Stop()); + } + public void Start() { if (!this.IsEnabled) diff --git a/Perspex.Base/Threading/MainLoop.cs b/Perspex.Base/Threading/MainLoop.cs index 88afba36a6..f53aeee5d5 100644 --- a/Perspex.Base/Threading/MainLoop.cs +++ b/Perspex.Base/Threading/MainLoop.cs @@ -30,14 +30,21 @@ namespace Perspex.Win32.Threading { while (!cancellationToken.IsCancellationRequested) { - Job job; + Job job = null; - // TODO: Dispatch windows messages in preference to lower priority jobs. - while (this.queue.Count > 0) + while (job != null || this.queue.Count > 0) { - lock (this.queue) + if (job == null) { - job = this.queue.Dequeue(); + lock (this.queue) + { + job = this.queue.Dequeue(); + } + } + + if (job.Priority < DispatcherPriority.Input && platform.HasMessages()) + { + break; } try @@ -49,6 +56,8 @@ namespace Perspex.Win32.Threading { job.TaskCompletionSource.SetException(e); } + + job = null; } platform.ProcessMessage(); @@ -57,7 +66,7 @@ namespace Perspex.Win32.Threading public Task InvokeAsync(Action action, DispatcherPriority priority) { - var job = new Job(action); + var job = new Job(action, priority); lock (this.queue) { @@ -70,14 +79,17 @@ namespace Perspex.Win32.Threading private class Job { - public Job(Action action) + public Job(Action action, DispatcherPriority priority) { this.Action = action; + this.Priority = priority; this.TaskCompletionSource = new TaskCompletionSource(); } public Action Action { get; private set; } + public DispatcherPriority Priority { get; private set; } + public TaskCompletionSource TaskCompletionSource { get; set; } } } diff --git a/Perspex.Base/Threading/PerspexScheduler.cs b/Perspex.Base/Threading/PerspexScheduler.cs new file mode 100644 index 0000000000..653c7695c4 --- /dev/null +++ b/Perspex.Base/Threading/PerspexScheduler.cs @@ -0,0 +1,31 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Threading +{ + using System; + using System.Reactive.Concurrency; + using System.Reactive.Disposables; + + public class PerspexScheduler : LocalScheduler + { + private static readonly PerspexScheduler instance = new PerspexScheduler(); + + public static PerspexScheduler Instance + { + get { return instance; } + } + + public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) + { + return DispatcherTimer.Run(() => + { + action(this, state); + return false; + }, dueTime); + } + } +} diff --git a/Perspex.Controls/Presenters/ScrollContentPresenter.cs b/Perspex.Controls/Presenters/ScrollContentPresenter.cs index 5b93e5d41c..8a795d5199 100644 --- a/Perspex.Controls/Presenters/ScrollContentPresenter.cs +++ b/Perspex.Controls/Presenters/ScrollContentPresenter.cs @@ -8,8 +8,6 @@ namespace Perspex.Controls.Presenters { using System; using System.Linq; - using System.Reactive.Disposables; - using Perspex.Controls.Primitives; using Perspex.Input; using Perspex.Layout; diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index a70d2ab273..1c98450cde 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -8,6 +8,7 @@ using Perspex.Diagnostics; using Perspex.Layout; using Perspex.Media; using Perspex.Media.Imaging; +using Perspex.Threading; #if PERSPEX_GTK using Perspex.Gtk; #else @@ -462,7 +463,7 @@ namespace TestApplication }, }; - Observable.Interval(TimeSpan.FromMilliseconds(10)) + Observable.Interval(TimeSpan.FromMilliseconds(10), PerspexScheduler.Instance) .Subscribe(x => { ((RotateTransform)rect1.RenderTransform).Angle = x; diff --git a/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index 992821a563..cba2a32f43 100644 --- a/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -511,6 +511,9 @@ namespace Perspex.Win32.Interop [DllImport("user32.dll")] public static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName); + [DllImport("user32.dll")] + public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); + [DllImport("user32.dll")] public static extern IntPtr PostMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); diff --git a/Windows/Perspex.Win32/Win32Platform.cs b/Windows/Perspex.Win32/Win32Platform.cs index aebe84e9ae..523683dd81 100644 --- a/Windows/Perspex.Win32/Win32Platform.cs +++ b/Windows/Perspex.Win32/Win32Platform.cs @@ -57,6 +57,12 @@ namespace Perspex.Win32 locator.Register(() => instance, typeof(IPlatformThreadingInterface)); } + public bool HasMessages() + { + UnmanagedMethods.MSG msg; + return UnmanagedMethods.PeekMessage(out msg, IntPtr.Zero, 0, 0, 0); + } + public void ProcessMessage() { UnmanagedMethods.MSG msg; @@ -88,11 +94,11 @@ namespace Perspex.Win32 public void Wake() { - UnmanagedMethods.PostMessage( - this.hwnd, - (int)UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM, - IntPtr.Zero, - IntPtr.Zero); + //UnmanagedMethods.PostMessage( + // this.hwnd, + // (int)UnmanagedMethods.WindowsMessage.WM_DISPATCH_WORK_ITEM, + // IntPtr.Zero, + // IntPtr.Zero); } [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]