Still confused about SendLevel, #InputLevel, threading and interruptions?

Helpful script writing tricks and HowTo's
SAbboushi
Posts: 123
Joined: 08 Dec 2014, 22:13

Still confused about SendLevel, #InputLevel, threading and interruptions?

Post by SAbboushi » 17 May 2018, 16:41

The posts I found on SendLevel & #InputLevel led me to believe that there isn't a lot of knowledge about how they work.

I spent a few days trying to get a better handle on these topics and learnt a lot by using SciTe4AutoHotkey's debugger (single stepping through the code and examining Call Stack and KeyHistory and ListHotkeys). It took awhile to figure out where to start, so I'm posting this as a starting point for others who find themselves as confused as I was. At least my notes in the script may be helpful whereas the script may confuse you more!

Code: Select all

; LESSONS LEARNED:
;
; a script's SendLevel is (by default) the same as it's #InputLevel
; I can access the current SendLevel in code; need to use ListHotkeys to see #InputLevels of a hotkey
; Threads can trigger hook hotkeys if a thread's SendLevel is higher than a hotkey's #InputLevel.  


#NoEnv
#SingleInstance Force ; can't have script running more than once at the same time
#UseHook
;#MaxThreadsPerHotkey, 10 ; doesn't change real-time output, but does change when single-step debugging (default = 1)
;#MaxThreads, 99 ; doesn't change real-time output (default = 10)

; NOTE: the notes in this script are neither precise nor completed: precise = depends upon whether single-stepping through or running realtime and on whether setting #MaxThreadsPerHotkey, 10
; But it gives a flavor of how threading and SendLevel/#InputLevel work

Escape::
ExitApp

#KeyHistory, 500

; REALTIME OUTPUT OF THIS SCRIPT when press "a" to trigger "a::" hotkey: "ccddccffccffgggggggg" (will need to comment out ListHotkeys, ListLings and KeyHistory commands to run script real-time and see output, or (much harder): go through KeyHistory) 

; Single-step output:
;ccdd
;ccff
;g


; NOTE: a script's SendLevel is (by default) the same as it's #InputLevel
#InputLevel, 5
x::
	SendLevel, 1 ; this resets SendLevel, while #InputLevel remains 5
	CurrentSendLevel := A_SendLevel ; I can see the current SendLevel in code; need to use ListHotkeys to see #InputLevels
    ListHotkeys
return
    
#InputLevel, 0
a::
	SendLevel, 1
	CurrentSendLevel := A_SendLevel ; I can see the current SendLevel

	SendEvent bcbcdede ; types "ccdd"; b's & e's queued up as triggered hotkeys (bbee added to queue, which I suspect is now abbee)
						; Each of the two b's in "bcbcdede" will trigger the "b::" hotkey because the "a::" hotkey thread is at SendLevel 1 whereas the "b::" hotkey is (by default) set to #InputLevel 0.  Threads can trigger hook hotkeys if a thread's SendLevel is higher than a hotkey's #InputLevel.  
						; The two d's are typed because the "d::" hotkey is set to #InputLevel 2, so this hotkey thread ("a::" set to SendLevel 1) cannot trigger a higher level hotkey, so the d's are not treated as hotkeys in this thread.  
						; Oddly(?) I was expecting the two e's to be typed since "e::" remapping is also set to #InputLevel 2 (because it is below and thus subject to the prior "#InputLevel, 2" directive).  I can see the #InputLevels of all hotkeys using script's system tray icon>View>Hotkeys and their methods (or see "ListHotkeys" command)
	; NOTE: To be able to see thread interruptions in the Call Stack, make sure there is a command after the Send command and before the "return" statement; one more command executes after the send (at least in SciTE4AutoHotkey debugger, single-stepping) before thread is interrupted; if that command is "return", then the thread completes which doesn't give me an audit trail / demonstration of how thread interruption actually works (i.e. the thread falls off the call stack)
	ListHotkeys ; this thread interrupted after executing this line to run "b::" hotkey (see above "(bbee queued)"); since "a::" hotkey was interrupted, call stack is a>b (so I expect the queue/buffer? is now something like "abbee")
	KeyHistory
return

b:: ; #InputLevel 0 by default
    ;ListLines
    x := ; 
    ListHotkeys
        ; Type	    Off?	Level	Running	Name
        ; -------------------------------------------------------------------
        ; k-hook		    	    	    Escape
        ; k-hook		    	    1	    a
        ; k-hook		    	    1	    b
        ; k-hook		    2	    	    d
        ; k-hook		    2	    	    *e
        ; k-hook		    2	    	    *e up
        ; k-hook		    3	    	    f

    ;KeyHistory
    SendLevel, 3
    send cdcdefef ; types "ccff";  d's & e's interrupt this hotkey: updated queue: "abddeebee"
    ;ListLines
    x := ; added to "make sure there is a command after..." per above.  Return below is executed when running realtime (i.e. this thread falls off the call stack) whereas when single-stepping in debugger, this thread interrupted after executing this line to run "d::" hotkey (see above "abddeebee") and the thread remains on the call stack
    ;KeyHistory
return

#InputLevel, 2 ; affects d:: and e::
d::
    ListHotkeys ; this thread interrupted after executing this line to run "d::" hotkey again (i.e. to run 2nd "d" in "abddeebee"); after this line is run a second time, this thread is again interrupted to run "e::g" key remapping, then returns to run next line
        ; Callstack entry	BEFORE interrupted to run "e::g" hotkey
        ; ----------------
        ; d sub	
        ; d thread	
        ; d sub	
        ; d thread	
        ; b sub	
        ; b thread	
        ; a sub	
        ; a thread	

    ListLines
    x :=
    ListHotkeys
    ;KeyHistory
    
    send e ; this d:: thread interrupted yet again after running this line to run "e::g" hotkey againg: now I supect this line updated queue from abddbee (2 e's were just run and removed from queue) to something like abddebee (added an e back: call stack is currently a>b>d>d.  Check Callstack in debugger and see which hotkeys are running and how many of each are running in ListHotkeys window)
    ;ListLines

    x := ; ListHotkeys
    ;KeyHistory
return

e::g ; this hotkey (actually, pair of hotkeys: "*e::" and "*e Up") was run twice. Script's "Hotkeys and their methods" shows how key remappings actually create two hotkeys:
    ; Type	    Off?	Level	Running	Name
    ; -------------------------------------------------------------------
    ; k-hook		    	    	    Escape
    ; k-hook		    	    1	    a
    ; k-hook		    	    1	    b
    ; k-hook		    2	    2	    d
    ; k-hook		    2	    	    *e
    ; k-hook		    2	    	    *e up
    ; k-hook		    3	    	    f


#InputLevel, 3
f:: ; will not be triggered by other hotkeys in this script because their SendLevels are lower.  NOTE: a script's SendLevel is (by default) the same as it's #InputLevel
    ;ListLines
    x := ; ListHotkeys
    ;KeyHistory
    send h
    ;ListLines
    x := ; ListHotkeys
    ;KeyHistory
return

/*
SAbboushi
Posts: 123
Joined: 08 Dec 2014, 22:13

Re: Still confused about SendLevel, #InputLevel, threading and interruptions?

Post by SAbboushi » 17 May 2018, 18:45

ARGHHH!!!! Another lesson learned: I expected using SendLevel/#InputLevel would overcome the "last script launched wins" behavior when multiple scripts are running, but I find that is NOT the case...
Post Reply

Return to “Tutorials”