mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-03-15 07:43:20 +08:00
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:
@@ -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 })
|
||||||
{
|
{
|
||||||
|
|||||||
230
BetterGenshinImpact/GameTask/Common/PausableDelayManager.cs
Normal file
230
BetterGenshinImpact/GameTask/Common/PausableDelayManager.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user