Код вызова WinAPI в определенное время - PullRequest
0 голосов
/ 20 ноября 2018

Используя функции, доступные в WinAPI, можно ли гарантировать, что определенная функция вызывается в соответствии с точной отметкой времени в миллисекунды?И если да, то какова будет правильная реализация?

Я пытаюсь написать программное обеспечение с поддержкой инструментов speedrun.Этот тип программного обеспечения отправляет пользовательские команды ввода в очень точные моменты после запуска сценария, чтобы выполнить невозможные для человека вводы, которые позволяют быстрее завершить видеоигры.Типичная последовательность выглядит примерно так:

  1. При 0 миллисекундах отправляется событие нажатия правой клавиши
  2. В 5450 миллисекунд отправляется правая кнопка вверх и событие нажатия клавиши вверх
  3. На 5460 миллисекундах отправьте событие нажатия левой клавиши
  4. и т. Д.

То, что я пробовал до сих пор, перечислено ниже.Поскольку у меня нет опыта в нюансах низкого уровня таймеров высокой точности, у меня есть некоторые результаты, но я не понимаю, почему они таковы:

  • Использование Sleep в сочетании с timeBeginPeriod , установленный на 1 между входами, дал худшие результаты.Из 20 казней 0 выполнили требование сроков.Я считаю, что это хорошо объяснено в документации по сну 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. Насколько я понимаю, Сон не подходит для этой задачи.
  • Использование проверки цикла ожидания занятости GetTickCount64 с timeBeginPeriod , установленным в 1, дало немного лучшие результаты.Из 20 казней 2 выполнили требование сроков, но, видимо, это было просто счастливое обстоятельство.Я просмотрел некоторую информацию об этой функции синхронизации, и я подозреваю, что она не обновляется достаточно часто, чтобы обеспечить точность в 1 миллисекунду.
  • Замена GetTickCount64 на QueryPerformanceCounter немного улучшил ситуацию.Из 20 казней 8 успешно завершены.Я написал регистратор, который будет хранить временные метки QPC непосредственно перед отправкой каждого ввода и выводить значения в файл после завершения последовательности.Я даже дошел до того, что заранее выделил место для всех переменных в моем коде, чтобы убедиться, что время не тратится впустую на ненужные явные выделения памяти.Значения журнала отличаются от меток времени, которые я предоставляю программе, от 1 до 40 миллисекунд.Программирование общего назначения может с этим смириться, но в моем случае один кадр в игре составляет 16,7 мс, поэтому в худшем случае с такими задержками я могу опоздать на 3 кадра, что противоречит цели всего эксперимента.
  • Установка высокого приоритета процесса не имеет никакого значения.

На данный момент я не уверен, где искать дальше.Мои два предположения состоят в том, что, возможно, время, необходимое для итерации цикла занятости и проверки времени с помощью (QPCNow - QPCStart) / QPF, само по себе достаточно велико, чтобы ввести упомянутую задержку, или что процесс прерывается планировщиком ОС в каком-то местевдоль выполнения цикла и управление возвращается слишком поздно.

Игра на 100% детерминирована и заблокирована на 60 кадрах в секунду.Я убежден, что, если мне удастся обеспечить точную синхронизацию ввода, результат всегда будет 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...