使用WinAPI中提供的功能,是否可以确保根据毫秒精确的时间戳调用特定功能?如果可以的话,正确的实现方法是什么?
我正在尝试编写工具辅助的Speedrun软件。这种类型的软件会在脚本启动后的非常确切的时刻发送用户输入命令,以执行人为无法实现的输入,从而更快地完成视频游戏。一个典型的序列如下所示:
以下列出了我到目前为止尝试过的内容。由于我对高精度计时器的低级细节没有经验,所以我得到了一些结果,但是却不了解它们为什么会这样:
Note that a ready thread is not guaranteed to run immediately. Consequently, the thread may not run until some time after the sleep interval elapses.
中对此进行了很好的解释,我的理解是睡眠不足以完成此任务。目前,我不确定下一步要去哪里。我的两个猜测是,可能需要花很多时间来迭代繁忙的循环并使用(QPCNow-QPCStart)/ QPF来检查时间,以至于引入上述延迟,或者该过程被OS Scheduler中断了循环执行过程中,控制权返回为时已晚。
游戏具有100%确定性,并锁定在60 fps。我坚信,如果我设法使输入的时间准确,结果总是20分之20,但是在这一点上,我开始怀疑这不可能。
编辑:根据要求,这里是简化的测试版本。第二次调用ExecuteAtTime之后的断点,并查看TimeBeforeInput变量。对我来说,它读取1029和6017(我省略了小数点),这意味着代码应该在执行29和17毫秒后执行。 免责声明:该代码不是为了证明良好的编程习惯而编写的。
#include "stdafx.h"
#include <windows.h>
__int64 g_TimeStart = 0;
double g_Frequency = 0.0;
double g_TimeBeforeFirstInput = 0.0;
double g_TimeBeforeSecondInput = 0.0;
double GetMSSinceStart(double& debugOutput)
{
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
debugOutput = double(now.QuadPart - g_TimeStart) / g_Frequency;
return debugOutput;
}
void ExecuteAtTime(double ms, INPUT* keys, double& debugOutput)
{
while(GetMSSinceStart(debugOutput) < ms)
{
}
SendInput(2, keys, sizeof(INPUT));
}
INPUT* InitKeys()
{
INPUT* result = new INPUT[2];
ZeroMemory(result, 2*sizeof(INPUT));
INPUT winKey;
winKey.type = INPUT_KEYBOARD;
winKey.ki.wScan = 0;
winKey.ki.time = 0;
winKey.ki.dwExtraInfo = 0;
winKey.ki.wVk = VK_LWIN;
winKey.ki.dwFlags = 0;
result[0] = winKey;
winKey.ki.dwFlags = KEYEVENTF_KEYUP;
result[1] = winKey;
return result;
}
int _tmain(int argc, _TCHAR* argv[])
{
INPUT* keys = InitKeys();
LARGE_INTEGER qpf;
QueryPerformanceFrequency(&qpf);
g_Frequency = double(qpf.QuadPart) / 1000.0;
LARGE_INTEGER qpcStart;
QueryPerformanceCounter(&qpcStart);
g_TimeStart = qpcStart.QuadPart;
//Opens windows start panel one second after launch
ExecuteAtTime(1000.0, keys, g_TimeBeforeFirstInput);
//Closes windows start panel 5 seconds later
ExecuteAtTime(6000.0, keys, g_TimeBeforeSecondInput);
delete[] keys;
Sleep(1000);
return 0;
}