How to achieve precise sleep delay but avoid blocking AHK's thread(due to DLLCall\Sleep)?

Ask gaming related questions (AHK v1.1 and older)
ecvent0r
Posts: 14
Joined: 10 Feb 2021, 09:21

How to achieve precise sleep delay but avoid blocking AHK's thread(due to DLLCall\Sleep)?

Post by ecvent0r » 28 Mar 2021, 16:46

Hi everyone

I'm looking for advice on how to achieve rather precise delay timing (ballpark several milliseconds) for my key sends but do so in a way that doesn't hurt concurrency and responsiveness. Let me give some context.

I am writing scripts for games to automate small sequences of input so the the actual time the script is performing useful work is rather small, 5ms at most, the rest is sleep intervals to space the send commands apart. While sleep is in effect for one hotkey others can be launched and since the workload per hotkey is small AHK's concurrency model works well for my case. The issue is that AHK's Sleep command, which I used initially, is terribly inaccurate and can give a spread of as much as 40 ms especially at lower sleep intervals. So I switched to using DllCall("Sleep", "UInt", NrMs) from the Win32 API. It is affected by the current system timer resolution so it gives 1-2ms accuracy when the system timer resolution is set to 1ms, which is usually in effect while a game is running anyway because it requests it but otherwise I can always set it manually in the script using DllCall("NTDLL\NtSetTimerResolution", ...) or DllCall("Winmm\timeBeginPeriod", ...).

However after I started using longer sleep intervals I realized that DllCall("Sleep", "UInt", ...) is actually effectively a blocking call for AHK, because I somewhat foolishly thought that AHK operated like a thread pool with parallelism set to 1 and it would see that the thread is in "waiting state" and schedule another one from it's queue (thread is such an unfortunate choice of a name). But it really operates like a single thread in an event loop backed up by a task queue. So it doesn't detect that DllCall("Sleep", "UInt", ...) intends to sleep and doesn't switch tasks but can do so in case of it's own Sleep, NrMillis command which it controls, so it makes sense.

As I add more hotkeys I can't afford to use Win32's Sleep API especially for longer sleep intervals because of responsiveness concerns but I also need the superior timing it provides. So what are my options here?

I can't use a busy wait loop like below

Code: Select all

msTimestampStart := getQPCTimeStampMilliseconds()

while(getQPCTimeStampMilliseconds() - msTimestampStart < 500) {
	Sleep -1
}
because I don't want to steal CPU time from the game and reduce performance or introduce microstutters.

The only option I can think of with AutoHotKey 1.1.x is to split the DllCall("Sleep", "UInt", ...) with larger sleep intervals into smaller ones like this and manually force an interrupt. This way the script might be able to squeeze another hotkey or timer in between.

Code: Select all

interruptibleWin32Sleep(millis, uninterruptibleSleepStep) {
	millis := Ceil(Abs(millis))
	uninterruptibleSleepStep := Ceil(Abs(uninterruptibleSleepStep))
	if(millis > 1 && uninterruptibleSleepStep > 1) {
		msTimestampStart := getQPCTimeStampMilliseconds()
		elapsedDelay := 0
		loop {
			nextUninterruptibleSleepStep := Min(Ceil(millis - elapsedDelay), uninterruptibleSleepStep) 
			DllCall("Sleep", "UInt", nextUninterruptibleSleepStep)
			Sleep -1 ; force pending interruption
			elapsedDelay := getQPCTimeStampMilliseconds() - msTimestampStart
		} Until elapsedDelay >= millis
	}
}
According to the documentation for Critical the message check interval in the absence of Critical is 5ms so if I set uninterruptibleSleepStep to 5 in the function above will I be able to achieve at most a 5ms latency on any "pseudo threads" waiting to start?

Alternatively maybe at this point I should start looking into using AutoHotKey_H or AutoHotKey 2 (or are they the same thing?!). Doing a very quick search of the forum I learned that they have some kind of multithreading capabilities so I could potentially afford to have a thread blocked while others are running? Would I be able to solve this issue easier by migrating to v2?

Thanks!
User avatar
boiler
Posts: 17393
Joined: 21 Dec 2014, 02:44

Re: How to achieve precise sleep delay but avoid blocking AHK's thread(due to DLLCall\Sleep)?

Post by boiler » 28 Mar 2021, 18:39

AHK_H allows for multi-threading, so maybe that provides you some advantage. It is otherwise quite similar to v1. AHK v2 is not the same as _H, in fact it is much less like it than v1. I don’t think v2 changes anything that would be an advantage specifically for this (although it shouldn’t be worse either), but I could be wrong.

I would think that using an interpreted language like AHK is not ideally suited to your task. If it’s an option for you, I think you’d be better using a compiled language like C++ for its superior speed.
ecvent0r
Posts: 14
Joined: 10 Feb 2021, 09:21

Re: How to achieve precise sleep delay but avoid blocking AHK's thread(due to DLLCall\Sleep)?

Post by ecvent0r » 29 Mar 2021, 04:20

@boiler Thanks for the advice, I think I will start looking into what AHK_H and v2 can offer me. Switching to something like C++ would be too big an undertaking for me right now as I haven't touched it since university; I'm a Java/JavaScript guy. Besides, that would mean I would need to reimplement all the infrastructure AHK has in place for capturing input and so on, just doesn't seem worth it. The inherent overhead of AHK is something I can live with it's just the timing aspect that bugs me.
User avatar
boiler
Posts: 17393
Joined: 21 Dec 2014, 02:44

Re: How to achieve precise sleep delay but avoid blocking AHK's thread(due to DLLCall\Sleep)?

Post by boiler » 29 Mar 2021, 05:54

I’d think carefully about it before choosing v2 for this because it’s still in the alpha phase, and changes that break scripts are released relatively often. And like I said, it doesn’t have new capability that would help in this respect, as far as I know, and I have been through it pretty thoroughly. AHK v1 and _H are stable, meaning updates are backward compatible and won’t cause you to rewrite scripts.
killmatt01
Posts: 20
Joined: 25 Mar 2021, 00:12
Contact:

Re: How to achieve precise sleep delay but avoid blocking AHK's thread(due to DLLCall\Sleep)?

Post by killmatt01 » 01 May 2021, 11:22

@ecvent0r
I am not sure if it helps: https://docs.microsoft.com/zh-cn/windows/win32/api/synchapi/nf-synchapi-sleepex

And this is the precise sleep delay i am using...but it contains dllcall\sleep...

Code: Select all

SystemTime()
{
    freq := 0, tick := 0
    If (!freq)
        DllCall("QueryPerformanceFrequency", "Int64*", freq)
    DllCall("QueryPerformanceCounter", "Int64*", tick)
    Return tick / freq * 1000
}

HyperSleep(value)
{
    s_begin_time := SystemTime()
    freq := 0, t_current := 0
    DllCall("QueryPerformanceFrequency", "Int64*", freq)
    s_end_time := (s_begin_time + value) * freq / 1000 
    While, (t_current < s_end_time)
    {
        If (s_end_time - t_current) > 20000
        {
            DllCall("Winmm.dll\timeBeginPeriod", UInt, 1)
            DllCall("Sleep", "UInt", 1)
            DllCall("Winmm.dll\timeEndPeriod", UInt, 1)
            DllCall("QueryPerformanceCounter", "Int64*", t_current)
        }
        Else
            DllCall("QueryPerformanceCounter", "Int64*", t_current)
    }
}
Post Reply

Return to “Gaming Help (v1)”