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()
{
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 })
{

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);
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)