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:
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user