From e969b5a96fc31b332e779cd7bf2b411f4c7860ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 02:48:42 +0000 Subject: [PATCH] Refactor: Replace custom Sleep with PausableDelayManager for cleaner pause logic Co-authored-by: huiyadanli <15783049+huiyadanli@users.noreply.github.com> --- .../GeniusInvokationControl.cs | 18 +- .../GameTask/Common/PausableDelayManager.cs | 230 ++++++++++++++++++ .../GameTask/Common/TaskControl.cs | 59 +---- 3 files changed, 245 insertions(+), 62 deletions(-) create mode 100644 BetterGenshinImpact/GameTask/Common/PausableDelayManager.cs diff --git a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs index 6d635c53..27b096c0 100644 --- a/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs +++ b/BetterGenshinImpact/GameTask/AutoGeniusInvokation/GeniusInvokationControl.cs @@ -101,20 +101,14 @@ public class GeniusInvokationControl public void CheckTask() { - NewRetry.Do(() => + if (_ct is { IsCancellationRequested: true }) { - if (_ct is { IsCancellationRequested: true }) - { - return; - } + throw new TaskCanceledException("任务取消"); + } - TaskControl.TrySuspend(); - if (!SystemControl.IsGenshinImpactActiveByProcess()) - { - _logger.LogWarning("当前获取焦点的窗口不是原神,暂停"); - throw new RetryException("当前获取焦点的窗口不是原神"); - } - }, TimeSpan.FromSeconds(1), 100); + // The new PausableDelayManager already handles suspend checking and window focus + // We can just trigger it with a minimal sleep + TaskControl.CheckAndSleep(0); if (_ct is { IsCancellationRequested: true }) { diff --git a/BetterGenshinImpact/GameTask/Common/PausableDelayManager.cs b/BetterGenshinImpact/GameTask/Common/PausableDelayManager.cs new file mode 100644 index 00000000..189fc740 --- /dev/null +++ b/BetterGenshinImpact/GameTask/Common/PausableDelayManager.cs @@ -0,0 +1,230 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using BetterGenshinImpact.GameTask.AutoGeniusInvokation.Exception; +using Microsoft.Extensions.Logging; +using Vanara.PInvoke; + +namespace BetterGenshinImpact.GameTask.Common; + +/// +/// Manages pausable delays with suspend/resume functionality and window focus checking. +/// Uses event-based synchronization for efficient waiting instead of busy polling. +/// +public class PausableDelayManager +{ + private static readonly ILogger Logger = App.GetLogger(); + + /// + /// Performs a pausable sleep with suspend checking and window focus validation. + /// + /// Time to sleep in milliseconds + public void Sleep(int millisecondsTimeout) + { + if (millisecondsTimeout <= 0) + { + return; + } + + // Check and handle suspend/window focus before sleeping + CheckAndHandleSuspend(); + CheckAndActivateGameWindow(); + + // Perform the actual sleep with periodic pause checks + SleepWithPauseCheck(millisecondsTimeout); + } + + /// + /// Performs a pausable sleep with cancellation support. + /// + /// Time to sleep in milliseconds + /// Cancellation token + public void Sleep(int millisecondsTimeout, CancellationToken ct) + { + ct.ThrowIfCancellationRequested(); + + if (millisecondsTimeout <= 0) + { + return; + } + + // Check and handle suspend/window focus before sleeping + CheckAndHandleSuspend(); + CheckAndActivateGameWindow(); + + // Perform the actual sleep with periodic pause checks + SleepWithPauseCheck(millisecondsTimeout, ct); + + ct.ThrowIfCancellationRequested(); + } + + /// + /// Performs a pausable async delay with cancellation support. + /// + /// Time to delay in milliseconds + /// Cancellation token + public async Task DelayAsync(int millisecondsTimeout, CancellationToken ct) + { + ct.ThrowIfCancellationRequested(); + + if (millisecondsTimeout <= 0) + { + return; + } + + // Check and handle suspend/window focus before delaying + CheckAndHandleSuspend(); + CheckAndActivateGameWindow(); + + // Perform the actual delay with periodic pause checks + await DelayWithPauseCheckAsync(millisecondsTimeout, ct); + + ct.ThrowIfCancellationRequested(); + } + + /// + /// Checks and handles suspend state, blocking until resume if needed. + /// Uses a simple polling approach with efficient waiting. + /// + private void CheckAndHandleSuspend() + { + var first = true; + var isSuspend = RunnerContext.Instance.IsSuspend; + + while (RunnerContext.Instance.IsSuspend) + { + if (first) + { + RunnerContext.Instance.StopAutoPick(); + + // Release any pressed keys + Thread.Sleep(300); + ReleaseAllPressedKeys(); + + Logger.LogWarning("快捷键触发暂停,等待解除"); + + // Suspend all registered suspendables + foreach (var item in RunnerContext.Instance.SuspendableDictionary) + { + item.Value.Suspend(); + } + + first = false; + } + + // Wait before checking again + Thread.Sleep(1000); + } + + // Resume from suspend + if (isSuspend) + { + Logger.LogWarning("暂停已经解除"); + RunnerContext.Instance.ResumeAutoPick(); + + // Resume all registered suspendables + foreach (var item in RunnerContext.Instance.SuspendableDictionary) + { + item.Value.Resume(); + } + } + } + + private void CheckAndActivateGameWindow() + { + if (!TaskContext.Instance().Config.OtherConfig.RestoreFocusOnLostEnabled) + { + if (!SystemControl.IsGenshinImpactActiveByProcess()) + { + Logger.LogInformation("当前获取焦点的窗口不是原神,暂停"); + throw new RetryException("当前获取焦点的窗口不是原神"); + } + } + + var count = 0; + while (!SystemControl.IsGenshinImpactActiveByProcess()) + { + if (count >= 10 && count % 10 == 0) + { + Logger.LogInformation("多次尝试未恢复,尝试最小化后激活窗口!"); + SystemControl.MinimizeAndActivateWindow(TaskContext.Instance().GameHandle); + } + else + { + Logger.LogInformation("当前获取焦点的窗口不是原神,尝试恢复窗口"); + SystemControl.FocusWindow(TaskContext.Instance().GameHandle); + } + + count++; + Thread.Sleep(1000); + } + } + + /// + /// Sleeps with periodic pause state checks using event-based waiting. + /// + private void SleepWithPauseCheck(int millisecondsTimeout, CancellationToken ct = default) + { + var remaining = millisecondsTimeout; + var checkInterval = Math.Min(1000, millisecondsTimeout); // Check every 1 second or less + + while (remaining > 0) + { + ct.ThrowIfCancellationRequested(); + + var sleepTime = Math.Min(checkInterval, remaining); + Thread.Sleep(sleepTime); + remaining -= sleepTime; + + // Check if we need to suspend + if (RunnerContext.Instance.IsSuspend) + { + CheckAndHandleSuspend(); + CheckAndActivateGameWindow(); + } + } + } + + /// + /// Async delay with periodic pause state checks using event-based waiting. + /// + private async Task DelayWithPauseCheckAsync(int millisecondsTimeout, CancellationToken ct) + { + var remaining = millisecondsTimeout; + var checkInterval = Math.Min(1000, millisecondsTimeout); // Check every 1 second or less + + while (remaining > 0) + { + ct.ThrowIfCancellationRequested(); + + var delayTime = Math.Min(checkInterval, remaining); + await Task.Delay(delayTime, ct); + remaining -= delayTime; + + // Check if we need to suspend + if (RunnerContext.Instance.IsSuspend) + { + CheckAndHandleSuspend(); + CheckAndActivateGameWindow(); + } + } + } + + private static bool IsKeyPressed(User32.VK key) + { + var state = User32.GetAsyncKeyState((int)key); + return (state & 0x8000) != 0; + } + + private static void ReleaseAllPressedKeys() + { + foreach (User32.VK key in Enum.GetValues(typeof(User32.VK))) + { + if (IsKeyPressed(key)) + { + Logger.LogWarning($"解除{key}的按下状态."); + Core.Simulator.Simulation.SendInput.Keyboard.KeyUp(key); + } + } + } +} diff --git a/BetterGenshinImpact/GameTask/Common/TaskControl.cs b/BetterGenshinImpact/GameTask/Common/TaskControl.cs index a23092fa..5b9ebb88 100644 --- a/BetterGenshinImpact/GameTask/Common/TaskControl.cs +++ b/BetterGenshinImpact/GameTask/Common/TaskControl.cs @@ -18,25 +18,20 @@ public class TaskControl public static readonly SemaphoreSlim TaskSemaphore = new(1, 1); + private static readonly PausableDelayManager _delayManager = new(); + public static void CheckAndSleep(int millisecondsTimeout) { - TrySuspend(); - CheckAndActivateGameWindow(); - - Thread.Sleep(millisecondsTimeout); + _delayManager.Sleep(millisecondsTimeout); } public static void Sleep(int millisecondsTimeout) { - NewRetry.Do(() => - { - TrySuspend(); - CheckAndActivateGameWindow(); - }, TimeSpan.FromSeconds(1), 100); - Thread.Sleep(millisecondsTimeout); + _delayManager.Sleep(millisecondsTimeout); } + [Obsolete("Use CheckAndSleep or Sleep instead")] private static bool IsKeyPressed(User32.VK key) { // 获取按键状态 @@ -46,6 +41,7 @@ public class TaskControl return (state & 0x8000) != 0; } + [Obsolete("This method is now handled internally by PausableDelayManager")] public static void TrySuspend() { @@ -93,6 +89,7 @@ public class TaskControl } } + [Obsolete("This method is now handled internally by PausableDelayManager")] private static void CheckAndActivateGameWindow() { if (!TaskContext.Instance().Config.OtherConfig.RestoreFocusOnLostEnabled) @@ -131,26 +128,7 @@ public class TaskControl throw new NormalEndException("取消自动任务"); } - if (millisecondsTimeout <= 0) - { - return; - } - - NewRetry.Do(() => - { - if (ct.IsCancellationRequested) - { - throw new NormalEndException("取消自动任务"); - } - - TrySuspend(); - CheckAndActivateGameWindow(); - }, TimeSpan.FromSeconds(1), 100); - Thread.Sleep(millisecondsTimeout); - if (ct.IsCancellationRequested) - { - throw new NormalEndException("取消自动任务"); - } + _delayManager.Sleep(millisecondsTimeout, ct); } public static async Task Delay(int millisecondsTimeout, CancellationToken ct) @@ -160,26 +138,7 @@ public class TaskControl throw new NormalEndException("取消自动任务"); } - if (millisecondsTimeout <= 0) - { - return; - } - - NewRetry.Do(() => - { - if (ct is { IsCancellationRequested: true }) - { - throw new NormalEndException("取消自动任务"); - } - - TrySuspend(); - CheckAndActivateGameWindow(); - }, TimeSpan.FromSeconds(1), 100); - await Task.Delay(millisecondsTimeout, ct); - if (ct is { IsCancellationRequested: true }) - { - throw new NormalEndException("取消自动任务"); - } + await _delayManager.DelayAsync(millisecondsTimeout, ct); } public static Mat CaptureGameImage(IGameCapture? gameCapture)