diff --git a/src/ModernOverlay.Win32/Win32OverlayWindow.cs b/src/ModernOverlay.Win32/Win32OverlayWindow.cs index 464cef8..a810b7f 100644 --- a/src/ModernOverlay.Win32/Win32OverlayWindow.cs +++ b/src/ModernOverlay.Win32/Win32OverlayWindow.cs @@ -253,6 +253,12 @@ public void RunFrameLoop(Func resolveInterval, Action renderFrame, Can ownerThread.RunFrameLoop(resolveInterval, renderFrame, cancellationToken); } + public void RunFrameLoop(Func resolveInterval, Func renderFrame, CancellationToken cancellationToken) + { + ThrowIfDisposed(); + ownerThread.RunFrameLoop(resolveInterval, renderFrame, cancellationToken); + } + public void Dispose() { if (disposed) diff --git a/src/ModernOverlay.Win32/Win32OwnerThread.cs b/src/ModernOverlay.Win32/Win32OwnerThread.cs index 000a00b..1e5cb3e 100644 --- a/src/ModernOverlay.Win32/Win32OwnerThread.cs +++ b/src/ModernOverlay.Win32/Win32OwnerThread.cs @@ -56,6 +56,16 @@ public void RunFrameLoop(TimeSpan interval, Action renderFrame, CancellationToke => RunFrameLoop(() => interval, renderFrame, cancellationToken); public void RunFrameLoop(Func resolveInterval, Action renderFrame, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(renderFrame); + RunFrameLoop(resolveInterval, () => + { + renderFrame(); + return true; + }, cancellationToken); + } + + public void RunFrameLoop(Func resolveInterval, Func renderFrame, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(resolveInterval); ArgumentNullException.ThrowIfNull(renderFrame); @@ -169,7 +179,7 @@ private void RunMessageLoop() DrainMessages(); } - private void RunFrameLoopCore(Func resolveInterval, Action renderFrame, CancellationToken cancellationToken) + private void RunFrameLoopCore(Func resolveInterval, Func renderFrame, CancellationToken cancellationToken) { TimeSpan interval = NormalizeFrameInterval(resolveInterval()); if (interval == TimeSpan.Zero) @@ -226,7 +236,7 @@ private void RunFrameLoopCore(Func resolveInterval, Action renderFrame SetFrameTimer(timer, interval); } - renderFrame(); + _ = renderFrame(); } } } @@ -254,8 +264,14 @@ private static TimeSpan NormalizeFrameInterval(TimeSpan interval) : throw new ArgumentOutOfRangeException(nameof(interval), "Frame interval cannot be negative."); } - private void RunUnlimitedFrameLoop(Action renderFrame, CancellationToken cancellationToken) + private void RunUnlimitedFrameLoop(Func renderFrame, CancellationToken cancellationToken) { + nint[] handles = + [ + workAvailable.SafeWaitHandle.DangerousGetHandle(), + cancellationToken.WaitHandle.SafeWaitHandle.DangerousGetHandle(), + ]; + while (!stopRequested && !cancellationToken.IsCancellationRequested) { DrainWorkItems(); @@ -266,7 +282,15 @@ private void RunUnlimitedFrameLoop(Action renderFrame, CancellationToken cancell break; } - renderFrame(); + if (!renderFrame()) + { + _ = NativeMethods.MsgWaitForMultipleObjectsEx( + (uint)handles.Length, + handles, + 1, + NativeMethods.QsAllInput, + NativeMethods.MwmoInputAvailable); + } } } diff --git a/src/ModernOverlay/OverlayWindow.cs b/src/ModernOverlay/OverlayWindow.cs index 3348fbb..1fa0588 100644 --- a/src/ModernOverlay/OverlayWindow.cs +++ b/src/ModernOverlay/OverlayWindow.cs @@ -406,22 +406,22 @@ public ValueTask DisposeAsync() return ValueTask.CompletedTask; } - private void RenderOneFrame() + private bool RenderOneFrame() { if (paused) { - return; + return false; } if (!desiredVisible && Options.HiddenRenderPolicy == HiddenRenderPolicy.Pause) { - return; + return false; } SyncTargetBoundsIfDue(start: DateTimeOffset.UtcNow); if (targetRenderPaused) { - return; + return false; } DateTimeOffset start = DateTimeOffset.UtcNow; @@ -430,10 +430,11 @@ private void RenderOneFrame() try { nativeWindow.InvokeOnOwnerThread(() => RenderOneFrameOnOwnerThread(start)); + return true; } catch when (Options.ExceptionPolicy == RenderExceptionPolicy.Continue) { - return; + return false; } catch when (Options.ExceptionPolicy == RenderExceptionPolicy.PauseOverlay) { diff --git a/tests/ModernOverlay.Tests/OverlayWindowThreadingTests.cs b/tests/ModernOverlay.Tests/OverlayWindowThreadingTests.cs index cf9c451..8f5c293 100644 --- a/tests/ModernOverlay.Tests/OverlayWindowThreadingTests.cs +++ b/tests/ModernOverlay.Tests/OverlayWindowThreadingTests.cs @@ -303,6 +303,27 @@ public async Task HiddenOverlayPausesRenderingByDefault() Assert.AreEqual(0, overlay.FrameStats.FrameCount); } + [TestMethod] + [TestCategory("WindowsIntegration")] + public async Task HiddenUnlimitedOverlayPausesWithoutRendering() + { + using var runCancellation = new CancellationTokenSource(TimeSpan.FromMilliseconds(250)); + int renderAttempts = 0; + + await using OverlayWindow overlay = await OverlayWindow.CreateAsync(new OverlayWindowOptions + { + IsVisible = false, + FrameRateLimit = FrameRateLimit.Unlimited, + }); + + overlay.Render += _ => renderAttempts++; + + await overlay.RunAsync(runCancellation.Token); + + Assert.AreEqual(0, renderAttempts); + Assert.AreEqual(0, overlay.FrameStats.FrameCount); + } + [TestMethod] [TestCategory("WindowsIntegration")] public async Task PauseSuppressesRenderingEvenWhenHiddenRenderingContinues()