Refactor: Replace custom Sleep with PausableDelayManager for cleaner pause logic

Co-authored-by: huiyadanli <15783049+huiyadanli@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-11-21 02:48:42 +00:00
parent 40c84face6
commit e969b5a96f
3 changed files with 245 additions and 62 deletions

View File

@@ -101,20 +101,14 @@ public class GeniusInvokationControl
public void CheckTask() public void CheckTask()
{ {
NewRetry.Do(() => if (_ct is { IsCancellationRequested: true })
{ {
if (_ct is { IsCancellationRequested: true }) throw new TaskCanceledException("任务取消");
{ }
return;
}
TaskControl.TrySuspend(); // The new PausableDelayManager already handles suspend checking and window focus
if (!SystemControl.IsGenshinImpactActiveByProcess()) // We can just trigger it with a minimal sleep
{ TaskControl.CheckAndSleep(0);
_logger.LogWarning("当前获取焦点的窗口不是原神,暂停");
throw new RetryException("当前获取焦点的窗口不是原神");
}
}, TimeSpan.FromSeconds(1), 100);
if (_ct is { IsCancellationRequested: true }) if (_ct is { IsCancellationRequested: true })
{ {

View File

@@ -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;
/// <summary>
/// Manages pausable delays with suspend/resume functionality and window focus checking.
/// Uses event-based synchronization for efficient waiting instead of busy polling.
/// </summary>
public class PausableDelayManager
{
private static readonly ILogger Logger = App.GetLogger<PausableDelayManager>();
/// <summary>
/// Performs a pausable sleep with suspend checking and window focus validation.
/// </summary>
/// <param name="millisecondsTimeout">Time to sleep in milliseconds</param>
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);
}
/// <summary>
/// Performs a pausable sleep with cancellation support.
/// </summary>
/// <param name="millisecondsTimeout">Time to sleep in milliseconds</param>
/// <param name="ct">Cancellation token</param>
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();
}
/// <summary>
/// Performs a pausable async delay with cancellation support.
/// </summary>
/// <param name="millisecondsTimeout">Time to delay in milliseconds</param>
/// <param name="ct">Cancellation token</param>
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();
}
/// <summary>
/// Checks and handles suspend state, blocking until resume if needed.
/// Uses a simple polling approach with efficient waiting.
/// </summary>
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);
}
}
/// <summary>
/// Sleeps with periodic pause state checks using event-based waiting.
/// </summary>
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();
}
}
}
/// <summary>
/// Async delay with periodic pause state checks using event-based waiting.
/// </summary>
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);
}
}
}
}

View File

@@ -18,25 +18,20 @@ public class TaskControl
public static readonly SemaphoreSlim TaskSemaphore = new(1, 1); public static readonly SemaphoreSlim TaskSemaphore = new(1, 1);
private static readonly PausableDelayManager _delayManager = new();
public static void CheckAndSleep(int millisecondsTimeout) public static void CheckAndSleep(int millisecondsTimeout)
{ {
TrySuspend(); _delayManager.Sleep(millisecondsTimeout);
CheckAndActivateGameWindow();
Thread.Sleep(millisecondsTimeout);
} }
public static void Sleep(int millisecondsTimeout) public static void Sleep(int millisecondsTimeout)
{ {
NewRetry.Do(() => _delayManager.Sleep(millisecondsTimeout);
{
TrySuspend();
CheckAndActivateGameWindow();
}, TimeSpan.FromSeconds(1), 100);
Thread.Sleep(millisecondsTimeout);
} }
[Obsolete("Use CheckAndSleep or Sleep instead")]
private static bool IsKeyPressed(User32.VK key) private static bool IsKeyPressed(User32.VK key)
{ {
// 获取按键状态 // 获取按键状态
@@ -46,6 +41,7 @@ public class TaskControl
return (state & 0x8000) != 0; return (state & 0x8000) != 0;
} }
[Obsolete("This method is now handled internally by PausableDelayManager")]
public static void TrySuspend() public static void TrySuspend()
{ {
@@ -93,6 +89,7 @@ public class TaskControl
} }
} }
[Obsolete("This method is now handled internally by PausableDelayManager")]
private static void CheckAndActivateGameWindow() private static void CheckAndActivateGameWindow()
{ {
if (!TaskContext.Instance().Config.OtherConfig.RestoreFocusOnLostEnabled) if (!TaskContext.Instance().Config.OtherConfig.RestoreFocusOnLostEnabled)
@@ -131,26 +128,7 @@ public class TaskControl
throw new NormalEndException("取消自动任务"); throw new NormalEndException("取消自动任务");
} }
if (millisecondsTimeout <= 0) _delayManager.Sleep(millisecondsTimeout, ct);
{
return;
}
NewRetry.Do(() =>
{
if (ct.IsCancellationRequested)
{
throw new NormalEndException("取消自动任务");
}
TrySuspend();
CheckAndActivateGameWindow();
}, TimeSpan.FromSeconds(1), 100);
Thread.Sleep(millisecondsTimeout);
if (ct.IsCancellationRequested)
{
throw new NormalEndException("取消自动任务");
}
} }
public static async Task Delay(int millisecondsTimeout, CancellationToken ct) public static async Task Delay(int millisecondsTimeout, CancellationToken ct)
@@ -160,26 +138,7 @@ public class TaskControl
throw new NormalEndException("取消自动任务"); throw new NormalEndException("取消自动任务");
} }
if (millisecondsTimeout <= 0) await _delayManager.DelayAsync(millisecondsTimeout, ct);
{
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("取消自动任务");
}
} }
public static Mat CaptureGameImage(IGameCapture? gameCapture) public static Mat CaptureGameImage(IGameCapture? gameCapture)