MicroTimer - Sub-10ms timers for AHK (C# DLL)

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
evilC
Posts: 4823
Joined: 27 Feb 2014, 12:30

MicroTimer - Sub-10ms timers for AHK (C# DLL)

01 Apr 2017, 11:38

Github Page (Docs etc)
Download



I don't really feel that we have a decent solution for timers down to 1ms, which (Especially when trying to write mouse "Delta" movement scripts, is a bit of a pain.
QPX just doesn't quite scratch that itch for me...

So now that I am getting into C# coding, I thought I would try to implement something better.

I found an cool little library on Codeplex by Ken Loveday, so I wrapped that into a DLL, and added some tweaks to have it behave basically like SetTimer.
It uses Lexikos' CLR library to load the DLL and create a timer object from the DLL.
You pass it a Function Object and a time in the same format as SetTimer (in ms, negative to only fire once) and it fires the callback at the frequency you requested.

It's not 100% accurate, but it is pretty good (Especially once it settles down) and it goes down to 1ms, so you can, for example, get nice regular 1ms calls to mouse_event so your mouse moves smoothly.

Here is a demo script to map the arrow keys to mouse delta movement, with it making the DLL call once every millisecond.

Code: Select all

#include CLR.ahk
/*
Demo Script for MicroTimer

Converts Arrow Keys to mouse cursor, with 1ms move time for the mouse
*/

#SingleInstance force

if (!FileExist("MicroTimer\MicroTimer.dll")){
	MsgBox DLL Not found
	ExitApp
}
asm := CLR_LoadLibrary("MicroTimer\MicroTimer.dll")
; Use CLR to instantiate a class from within the DLL
mt := asm.CreateInstance("MicroTimer")

XState := 0
YState := 0
XTimer := mt.Create(Func("MoveX"), 1)
YTimer := mt.Create(Func("MoveY"), 1)
return

MoveX(){
	global XState
	DllCall("user32.dll\mouse_event", "UInt", 0x0001, "UInt", XState, "UInt", 0, "UInt", 0, "UPtr", 0)
}

MoveY(){
	global YState
	DllCall("user32.dll\mouse_event", "UInt", 0x0001, "UInt", 0, "UInt", YState, "UInt", 0, "UPtr", 0)
}

XEvent(evt){
	global XState, XTimer
	XState := evt
	XTimer.SetState(abs(XState))
}

YEvent(evt){
	global YState, YTimer
	YState := evt
	YTimer.SetState(abs(YState))
}

~Left::
	XEvent(-1)
	return
	
~Right::
	XEvent(1)
	return

~Left up::
~Right up::
	XEvent(0)
	return
	
~Up::
	YEvent(-1)
	return

~Down::
	YEvent(1)
	return

up up::
down up::
	YEvent(0)
	return

^Esc::
	ExitApp
Last edited by evilC on 23 Jun 2017, 04:34, edited 1 time in total.
User avatar
Soft
Posts: 174
Joined: 07 Jan 2015, 13:18
Location: Seoul
Contact:

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

02 Apr 2017, 08:13

awesome lib, cant wait to try this!
AutoHotkey & AutoHotkey_H v1.1.22.07
User avatar
rommmcek
Posts: 1473
Joined: 15 Aug 2014, 15:18

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

02 Apr 2017, 10:46

To demonstrate full superiority one should insert in the demo:

Code: Select all

SetBatchLines, -1
Mind-blowing!
User avatar
evilC
Posts: 4823
Joined: 27 Feb 2014, 12:30

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

02 Apr 2017, 12:40

SetBatchLines, -1 does not seem to make the cursor any smoother for me, but it does seem to up the CPU usage quite a bit.

Also, yesterday I added another method that you can call - CreateMicro.
It works the same way as Create, but the passed value is in MICROseconds, so 500 would he half a MilliSecond,

This is not in the release version yet - you can just download the DLL from the Gihub repo.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

04 Apr 2017, 04:26

evilC wrote: It's not 100% accurate, but it is pretty good (Especially once it settles down)
I got some wonky results when I tried it (the example) first, it was inaccurate and the mouse moved highly irregular. But after running the script a few times, it worked flawlessly, a bit curious I think :think:

I think it is very nice with custom DLLs like this, it is a good way to extend AHK's usage, I wonder why we don't see more of it.

Looking forward to see where this goes, thanks for sharing. :thumbup:
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

04 Apr 2017, 04:33

Maybe this is interesting for you Acquiring high-resolution time stamps
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile
User avatar
evilC
Posts: 4823
Joined: 27 Feb 2014, 12:30

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

04 Apr 2017, 04:34

Yeah, I noticed stuff like that.

These DLLs are stupid easy to write, and with CLR providing object-style access - it seems like a logical way to extend AHK's functionality.
I have tried to implement some RawInput in a C# DLL, but I need to work out how to make a dummy window that handles the messages.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

04 Apr 2017, 05:25

I just remembered this, did it not work as you intended? If I recall correctly, it crashed when I tested it.
User avatar
evilC
Posts: 4823
Joined: 27 Feb 2014, 12:30

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

04 Apr 2017, 07:11

From QueueTimer:
DllCall("kernel32.dll\CreateTimerQueueTimer", "Ptr", &hTimer, "Ptr", this.hQueue, "UInt", this.RegisteredCallback, "UInt", 0, "UInt", this.delay, "UInt", this.period, "Int", 0x00000000)
I do not see how to alter this to work for sub-ms values. this.period is the parameter that controls the tick rate, and it only appears to support ms?
MicroTimer goes down to microseconds.

But in general, no particular reason other than getting some experience with C# ;)
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

04 Apr 2017, 07:13

With this function and a sleep of 0.001 seconds I got the most QPC diffs between 0.001004 and 0.001006 seconds

edit: see my next post
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

04 Apr 2017, 07:52

Sleep 10 ms example with QueryPerformanceCounter function (msdn)

Code: Select all

#NoEnv
SetBatchLines -1

; ===============================================================================================================================

QPC(1), QPC_Sleep(0.001), Diff := QPC(0)                         ; -> ~ 0.001004 - 0.001006 sec

MsgBox % Diff

; ===============================================================================================================================

QPC_Sleep(S)
{
    static C1, C2, Q := DllCall("QueryPerformanceFrequency", "int64*", F)
    DllCall("QueryPerformanceCounter", "int64*", C1)
    while (((C2 - C1) / F) < S)
        DllCall("QueryPerformanceCounter", "int64*", C2)
    return true
}

; ===============================================================================================================================

QPC(R := 0)
{
    static P := 0, F := 0, Q := DllCall("QueryPerformanceFrequency", "int64*", F)
    return !DllCall("QueryPerformanceCounter", "int64*", Q) + (R ? (P := Q) / F : (Q - P) / F) 
}

; ===============================================================================================================================

Sleep 10 ms example with GetSystemTimePreciseAsFileTime function (msdn) [min os win8]

Code: Select all

#NoEnv
SetBatchLines -1

; ===============================================================================================================================

QPC(1), GetSystemTimePreciseAsFileTime(10000), Diff := QPC(0)    ; -> ~ 0.001007 sec

MsgBox % Diff

; ===============================================================================================================================

/*
GetSystemTimePreciseAsFileTime function
retrieves the current system date and time with the highest possible level of precision (<1us)

FILETIME structure
contains a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC)

100 ns  ->  0.1 µs  ->  0.001 ms  ->  0.00001 s


1     sec  ->  1000 ms  ->  1000000 µs
0.1   sec  ->   100 ms  ->   100000 µs
0.001 sec  ->    10 ms  ->    10000 µs
*/

GetSystemTimePreciseAsFileTime(US)  ; https://msdn.microsoft.com/en-us/library/hh706895(v=vs.85).aspx
{
    static FT1, FT2
    DllCall("GetSystemTimePreciseAsFileTime", "int64*", FT1)
    while ((FT2 - FT1) < US)
        DllCall("GetSystemTimePreciseAsFileTime", "int64*", FT2)
    return true
}

; ===============================================================================================================================

QPC(R := 0)
{
    static P := 0, F := 0, Q := DllCall("QueryPerformanceFrequency", "int64*", F)
    return !DllCall("QueryPerformanceCounter", "int64*", Q) + (R ? (P := Q) / F : (Q - P) / F) 
}

; ===============================================================================================================================
GetSystemTimePreciseAsFileTime wrote:Note
This function is best suited for high-resolution time-of-day measurements, or time stamps that are synchronized to UTC. For high-resolution interval measurements, use QueryPerformanceCounter or KeQueryPerformanceCounter. For more info about acquiring high-resolution time stamps, see Acquiring high-resolution time stamps.

Maybe because longer sleeps here use more cpu (while-loop).. but for such small sleeps you wont noticed it.. and for longer sleeps you can use sleep itself ^^
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile
User avatar
evilC
Posts: 4823
Joined: 27 Feb 2014, 12:30

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

04 Apr 2017, 08:11

Surely "higher CPU usage with longer sleeps" is not correct?
No matter how long it sleeps, if waiting consumes a percentage of CPU, then yes, a shorter sleep consumes less CPU cycles, but it is still using the same percentage of CPU while it is active.

I just did a test with MicroTimer, however, and much the same story - ~6.5% CPU usage for a 10 second sleep
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

04 Apr 2017, 08:12

jNizM wrote:Here is an example with GetSystemTimePreciseAsFileTime
That code never leaves the loop on my pc.
evilC wrote:Yeah, but the CPU usage of that QPC code is insane!
It uses ~7.5% of my work PC's CPU (Only a dual core 2.3Gig Xeon, but still...)
Your timer did ~33 % on my pc :o
I'm pretty sure it uses a while sometic<something somewhere too. Is it multi-threading?
jNizM wrote:Maybe because longer sleeps here use more cpu.. but for such small sleeps you wont notice it.. and for longer sleeps you can use sleep itself ^^
Not to long ago this was discussed, I think it was in an evilC-thread too (edit: this), I came up with this, it does a regular sleep before the qpc-loop, to save cpu resources.

Code: Select all

DllCall("QueryPerformanceCounter", "Int64P", sT1)
pSleep.do(55.3)
DllCall("QueryPerformanceCounter", "Int64P", sT2)
MsgBox, % (sT2-sT1)*1000/pSleep.F . " ms."

class pSleep
{
	static F:=pSleep.getQPF()
	static cal:=0.05 ; Maybe not a good idea.
	do(t) {
		DllCall("QueryPerformanceCounter", "Int64P", sT1)
		crit:=A_IsCritical
		Critical, On
		res:=pSleep.getTimerResolution(),dt:=pSleep.cal
		if (t>res) ; Do regular sleep before qpc loop, if t is long enough.
			(DllCall("Sleep", "Int", t-res),DllCall("QueryPerformanceCounter", "Int64P", sT2),dt:=(sT2-sT1)*1000/pSleep.F)+pSleep.cal
		t-=dt,DllCall("QueryPerformanceCounter", "Int64P", loopStart),loopTic:=loopStart
		while (loopTic-loopStart<t*pSleep.F/1000)
			DllCall("QueryPerformanceCounter", "Int64P", loopTic)	
		Critical, % crit
		return
	}
	getTimerResolution() {
		DllCall("ntdll.dll\NtQueryTimerResolution", "UPtr*", MinimumResolution, "UPtr*", MaximumResolution, "UPtr*", CurrentResolution)
		return Ceil(CurrentResolution/10000) ; Resolutions are reported as 100-nanoseconds
	}
	getQPF() {
		DllCall("QueryPerformanceFrequency", "Int64P", F)
		return F
	}
}
Anyways, this isn't useable for timers, which this thread is about ;)
Last edited by Helgef on 04 Apr 2017, 08:54, edited 1 time in total.
User avatar
evilC
Posts: 4823
Joined: 27 Feb 2014, 12:30

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

04 Apr 2017, 08:19

Hmm, it seems the old QueueTimer code us WAY less CPU intensive. I see 0% CPU usage for that with a 10s sleep

So with the new code, you can go to sub-ms accuracy, but you sacrifice CPU.
I wonder if I can maybe re-implement this C# code using this technique, and have it use the existing (MicroLibrary) code for sub-ms sleeps, or CreateTimerQueueTimer based code for ms-and-above sleeps.
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

04 Apr 2017, 08:24

GetSystemTimePreciseAsFileTime (where 10000 = 10ms) works for me but min os support is win8
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

04 Apr 2017, 08:33

jNizM wrote: min os support is win8
Ah, I see, I'm on win7.
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: MicroTimer - Sub-10ms timers for AHK (C# DLL)

04 Apr 2017, 08:37

Next to sleep there exist NtDelayExecution and ZwDelayExecution (100 nanoseconds)

call stack for sleep function

Code: Select all

0  ntdll.dll        ZwDelayExecution    
1  kernel32.dll     SleepEx     
2  kernel32.dll     Sleep
But there are to inconsistent
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: hiahkforum, mcd and 67 guests