Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Instance(): create another instance for an asynchronous task


  • Please log in to reply
10 replies to this topic
gwarble
  • Members
  • 624 posts
  • Last active: Aug 12 2016 07:49 PM
  • Joined: 23 May 2009
nothing groundbreaking here, but... this simple function will create another instance of your script or .exe with execution directed into a specific subroutine

this is useful for performing an asynchronous task which would normally pause your script until finishing, when you require other functions of your script to not be interrupted:
- asynchronous AHK tasks, like soundbeep, or a feature that would otherwise kill the functionality of your script
- DllCalls and windows features (animate window, text-to-speech )
- blat and CMDret.dll running getmail.exe (one example i've had good success with)
- nice side effect of first command line param being a label name to execute

Instance() v0.4 - Download Instance.ahk or read the Documentation [ Alternate download link ]
Tested in _Basic and _L
; Instance() - 0.4 - gwarble
;  creates another instance to perform a task
;    help: http://www.autohotkey.com/forum/viewtopic.php?p=310694
;
; Instance(Label,Params,AltWM)
;
;  Label   "" to initialize, in autoexecute section usually
;          "Label" to start a new instance, whose execution will be
;            redirected to the "Label" subroutine
;
;  Params  parameters to pass to new instance (retrieved normally, ie: %2%)
;          when initializing (ie: label="") Params is a label prefix for all calls
;          last param received by script is calling-instance's ProcessID
;
;  AltWM   Alternate WindowsMessage number if 0x1357 (arbitrary anyway) will conflict
;
;  Return  0 on normal initialization (ie: label="" and %1% <> any label:)
;          Process ID of new instance
;          0 on failed new instance (called label name not exist)

Instance(Label, Params="", WM="0x1357") {
 global 1
 If Label =
 {
  Label = %1%
  If (InStr(Label, Params) = 1)
  {
   If IsLabel(Label)
   {
    GoSub, %Label%
    Return 1
   }
  }
  Else
  {
   pDHW := A_DetectHiddenWindows
   DetectHiddenWindows, On
   WinGet, Instance_ID, List, %A_ScriptFullPath%
   Loop %Instance_ID%
    SendMessage, WM, WM, 0, , % "ahk_id " Instance_ID%A_Index%
   DetectHiddenWindows, %pDHW%
   OnMessage(WM, "Instance_")
  }
  Return 0
 }
 Else
 {
  If IsLabel(Label)
  {
   ProcessID := DllCall("GetCurrentProcessId")
   If A_IsCompiled
    Run, "%A_ScriptFullPath%" /f "%Label%" %Params% %ProcessID%,,,Instance_PID
   Else
    Run, "%A_AhkPath%" /f "%A_ScriptFullPath%" "%Label%" %Params% %ProcessID%,,,Instance_PID
   Return %Instance_PID%
  }
  Return 0
 }
 #SingleInstance, Off ;your script should probably have this anyway
}


Instance_(wParam, lParam) {
 Critical
 If lParam = 0
  ExitApp
 Else If IsLabel(Label := "Instance_" lParam)
  GoSub, % Label
 Return
}

Examples moved to second post...

- gwarble

additions/changes:
return PID of new instance when successful, for script-monitoring/status return
instance prefix to limit intereference with other cmdline param usage...
Instance_() aux function for OnMessages, which redirect to label Instance_%lParam%: scriptside
new: OnMessage() inside function for SnglInst, Force behavior and IPC
new: 3rd Param for Windows message number

gwarble
  • Members
  • 624 posts
  • Last active: Aug 12 2016 07:49 PM
  • Joined: 23 May 2009
these need updating to the new changes

here's how i really use it in an app...
#SingleInstance, Off  ;= recommended, see below to simulate Force
SetWorkingDir, %A_ScriptDir%

 If Instance("","-")
  Return    ;= stops auto-exec section if cmd line param 1 matches label

;= this section simulates #SingleInstance, Force behavior without interfering with instance()s
 DetectHiddenWindows, On  ;= this section should be inside Instance()
 WinGet, Instance_ID, List, %A_ScriptFullPath%
 Loop %Instance_ID%
  SendMessage, 0x1357, 0x1357, 0, , % "ahk_id " Instance_ID%A_Index%  ;= sends kill message to all instances... 
 DetectHiddenWindows, Off
 OnMessage(0x1357, "Instance_") ;= but only die if they are listening for this message...
;= and normal auto-exec continues...
;= ...
Return
simple example:
q::
w::
e::
 Instance("-Say","You pressed " A_ThisHotkey ".")
Return

1::
2::
3::
 Instance("-Beep",A_ThisHotkey  A_ThisHotkey A_ThisHotkey)
Return

-Say:
 PID = %0% ;= last param is ProcessID of caller
 Loop % PID - 1  ;= so use all params except label or ProcessID
  If (A_Index > 1)
   Say .= %A_Index% " "
 If Say =
  Say = Attention!
 Else
  Say = %Say%   ;= AutoTrim
 Menu, Tray, Tip, Saying:`n%Say%
 TTS(Say)
ExitApp

-Beep:  ;= beep a string of numbers, chars, somethings...
 PID = %0%   ;= last param is ProcessID of caller
 Loop % PID - 1  ;= so use all params except label or ProcessID
  If A_Index > 1
   Beep .= %A_Index% " "
 If Beep =
  Beep = CDFDC
 Else
  Beep = %Beep% ;= AutoTrim
 Menu, Tray, Tip, Beeping:`n%Beep%
 IfInString, Beep, `,, StringSplit, Beep, Beep, `, , %A_Space%%A_Tab%
 Else IfNotInString, Beep, `,, StringSplit, Beep, Beep, , %A_Space%%A_Tab%
 Loop % Beep0
  SoundBeep, BeepTone(Beep%A_Index%), 50
ExitApp

BeepTone(Tone) {
 If Tone not between 37 and 32767
 {
  Transform, Tone, Asc, % Tone
  Tone := Tone * Tone / 2 - 200
 }
Return Tone
}
advanced example:
;= you'll have to do some work to get this one to function in your script
;= with a call like:  Instance("-DownloadEmails", E_HWnd " " E_Skip " " E_Remn)
-DownloadEmails: ;= uses cmdret.dll and getmail.exe, from a second instance ;= updating caller's gui hWnd, and sendmessages a "finish" when done
 E_User     = test@test.com
 E_Pass     = password
 E_Pop      = pop.test.com
 Loop %0%
  CL%A_Index% := % %A_Index%
 CL1 := CL4-CL3+1
 Menu, Tray, Tip, Downloading %CL1% emails...`n%E_User%
 SetWorkingDir, %A_ScriptDir%\Emails\
 EmailGetStr = Resources\Tools\getmail.exe -u %E_User% -pw %E_Pass% -s %E_Pop% -b %CL3% -n %CL4%
 DllCall("Resources\Tools\cmdret.dll\RunInControl", "str", EmailGetStr, "Uint", CL2)
 SendMessage, 0x1357, 1357, 9, , % "ahk_pid " CL%0% ;= send "finised" back to calling script, which will run label Instance_9:
ExitApp ;= then close the instance

Instance_9: ;= this is called in main script when instance() does sendmessage (-downloademails)
 Gui, 14:Default
 FileList =
 Loop, %TempDir%Emails\MSG*.txt
  FileList = %FileList%%A_LoopFileFullPath%`n

 GoSub, ParseFileList

 FileDelete, Resources\Temp\Emails\MSG*.txt

 E_DLed2 += E_DLed

 GuiControlGet, E_GetMail, 14:
 StringSplit,   E_GetMailLine, E_GetMail, `n, %A_Space%%A_Tab%
 StringReplace, E_GetMail, E_GetMailLine4, % "There are "
 StringReplace, E_GetMail, E_GetMail, % " messages on the server."
 E_GetMailLine := "There are " E_GetMail " messages on the " E_User " server.`nOf those, " E_DLed2 " messages have been downloaded.`nThere are " E_Qty " messages in the " CompanyName " folder."
 GuiControl, 14:, E_GetMail, % E_GetMailLine
 E_GetMail = %E_GetMail%
 SB_SetText("Messages: " E_GetMail " on server, " E_Qty " in " CompanyName " folder",3)
Return


gwarble
  • Members
  • 624 posts
  • Last active: Aug 12 2016 07:49 PM
  • Joined: 23 May 2009
EDIT: ----- old version/syntax examples ----
note changes to functionality since these were posted
---

here's an example using TTS()

Say.ahk:
; Instance() example using TTS(), notice the instances are still
; asynchronous to eachother, ie they won't speak over eachother...
; but the script continues besides the first normal TTS() call, all
; the rest ("retry" or CTRL-1) are Instance() based

#SingleInstance Off
#Persistent
#NoEnv
SetWorkingDir, %A_ScriptDir%
Instance()       ;= checks for command line actions from other Instance("Say") calls

 TTS("First instance here... and you're waiting for me")
 Instance("Say")   ;= synchrous TTS() call from another instance

Msg:                           ;= goto loop
 MsgBox, 5, , Instance() test script`nRetry will create another instance to do a task
 IfMsgBox, Retry
 {
  Instance("Say") ;= starts another instance and runs the Say: subroutine
  Goto, Msg                    ;= loop make another msgbox
 }
ExitApp

^1::Instance("Say")

Say:
 Say = Second instance here, saying Hello World. 1, 2, 3, 4, 5.
 TrayTip, Saying:, %Say%
 Menu, Tray, Tip, ShopMon says: %Say%
 TTS(Say)
Return

#Include *i Instance.ahk
#Include *i TTS.ahk
get TTS.ahk here

- gwarble

EDIT: and if you need SingleInstance, Force behavior from your script when run normally (which obviously can't be used with this function) use something like this after your Instance() call in autoexecute to kill other instances by sendmessage, until i add it to the function:

SetTitleMatchMode, 2
DetectHiddenWindows, On
WinGet, InstanceID, List, %A_ScriptFullPath%
Loop %InstanceID%
SendMessage, 0x1111, 1, 187, , % "ahk_id " InstanceID%A_Index%
OnMessage(0x1111, "AnotherInstance")

AnotherInstance(wParam, lParam) {
Critical
If lParam = 187
ExitApp
}


here's one more example, a native windows call, AnimateWindow, being used... requires Notify() cuz i'm too lazy to make a gui just for this example

#SingleInstance Off
#Persistent
#NoEnv
SetWorkingDir, %A_ScriptDir%
Instance()       ;= checks for command line actions from other Instance("Say") calls

 Notify("you're waiting for this AnimateWindow DllCall","Press`nCTRL-1`nto create`nanother`ninstance`nthat you won't`nhave to wait for",-360,"SI=5000")
 Loop
  Notify("","",-1,"Wait",Notify(A_Index,"",5,"SI=1 ST=1 SC=1"))
Return

Notify:
 Notify("your script doesn't have to wait for these two!`n`n`n...","",30,"BT=0 SI=3000 GC=Green TC=White MC=White BC=White")
 Notify("","",5,"Wait",Notify("Second Instance here","`n`n`n`n`n`nand your script didn't wait!",30))
Return

^1:: ;= press a couple times to get two animations at once, not normally possible
 Instance("Notify")
Return

Notify() can be found here

Edit: ok one more example before i go to bed... how about URLDownloadToFile... a big file may take a while and you might want your script to perform other tasks... or download two files at once?!

#SingleInstance Off
#NoEnv
SetWorkingDir, %A_ScriptDir%
Instance()
 MsgBox, 1, , Press OK to download AutoHotkey.zip`nor Cancel to exit
 IfMsgBox, OK
  Instance("DownloadAHK")      ;= starts another instance
 MsgBox, notice the second tray icon?`nthat one is downloading AutoHotkey.zip...`nAnd the main script is displaying this msgbox!
Return

DownloadAHK:
 TrayTip, Downloading..., AutoHotkey.zip, 1
 UrlDownloadToFile, http://www.autohotkey.com/download/AutoHotkey.zip, AutoHotkey.zip
Return


HotKeyIt
  • Moderators
  • 7439 posts
  • Last active: Jun 22 2016 09:14 PM
  • Joined: 18 Jun 2008
Did you see Thread for AutoHotkey.dll.

This will give you real multithreading.

You can even share variables using Alias().

AutoHotkey.dll has got also a functionality to call a function and much more :)
Thread() will make it easier to use all the functionality.
Small example:
p1:=100
p2:="test"
ThreadId:=Thread("Thread:ThreadEnd")
Thread("Func(" p1 "," p2 ")","ahkFunction",threadId) ;call a function "func" and pass parameters
Thread("Goto","ahkLabel",ThreadId) ;call a label
WinWaitClose, ahk_class #32770
WinWaitClose, ahk_class #32770
MsgBox End
ExitApp
Thread:
;Autorun section
Return

Func(p1,p2){
	MsgBox % p1 "," p2
}
Goto:
MsgBox Goto
Return

ThreadEnd:
Return


gwarble
  • Members
  • 624 posts
  • Last active: Aug 12 2016 07:49 PM
  • Joined: 23 May 2009
thanks for the heads up... i did see that (much more elegant) method, and it was my motivation for wrapping the above functionality into a function last night...

i've been using the above method (manually, not in a function) for a while now to overcome the synchronous behavior shortcomings i've encountered, so i started converting to a function so i could make the transition in my existing scripts/apps to the .DLL and thread() more easily...

the only benefit to the above is it has no dependencies, so a simple script can be distributed/used without the 500kb dll, which is nice... otherwise it has many shortcomings (while the PID could be used to monitor the Instance() process, and send/on message functions could be used for live feedback on certain things, yuo'll never be able to share variables or all the fancy stuff possible with the dll (but maybe sometimes all you need is to sendmessage a value or string?, or even just do a task without feedback (like TTS)

anyway, thanks for all your great work on Thread()... it is all well above my head but i did get it working for a simple task and plan to implement it in my apps in the near future...

- gwarble

EDIT: any problem with putting #SingleInstance, Off inside the stdlib function? its required anyway (and seems to override if the script has "Force" enabled) and always will be required...

gwarble
  • Members
  • 624 posts
  • Last active: Aug 12 2016 07:49 PM
  • Joined: 23 May 2009
here's a fun example... while autohotkey.zip is downloading it allows you to play tetris, and then kills that instance when finished downloading...

download niwi's nice tetris game to get the bitmaps required, all credit to him for the tetris game

#SingleInstance Off
#Persistent
#NoEnv
SetWorkingDir, %A_ScriptDir%
If Instance()
 Return
 MsgBox, 1, , Press OK to download AutoHotkey.zip`nand play Tetris while you're waiting`nor Cancel to exit
 IfMsgBox, OK
  GoSub, DownloadAHK
Return

DownloadAHK:
 TrayTip, Downloading..., AutoHotkey.zip
 PID := Instance("Tetris")
 UrlDownloadToFile, http://www.autohotkey.com/download/AutoHotkey.zip, AutoHotkey.zip
 TrayTip, Finished Downloading, AutoHotkey.zip
 WinKill, ahk_pid %PID%
Return


; slightly modified version of Instance() without ExitApp and changed return vars...

Instance(Label="", Params="") {
 #SingleInstance, Off ;= required anyway, why not have it in here..?
 global 1             ;= first command line parameter needed
 If Label =           ;= initializing (ran from autoexecute section)
 {                    
  Label = %1%
  If IsLabel(Label)   ;= checks first parameter for matching label
  {
   GoSub, %Label%     ;= and runs it if found
   Return 1 ;ExitApp            ;= before exiting
  }
  Return 0            ;= normal initialization, no params or label found
 }
 Else                 ;= script call, with at least first parameter of "Label"
 {
  If IsLabel(Label)   ;= checks for matching label
  {
   If A_IsCompiled
    Run, "%A_ScriptFullPath%" "%Label%" %Params%,,,ProcessID
   Else
    Run, "%A_AhkPath%" "%A_ScriptFullPath%" "%Label%" %Params%,,,ProcessID
   Return %ProcessID%  ;= returns ProcessID of new Instance
  }
  Return 1
 }
}





;thanks to derniwi@web.de
Tetris:
;
; AutoHotkey Version: 1.0.31.05
; Language:       English
; Platform:       Win9x/NT
; Author:         derniwi@web.de
;
; Script Function:
;	Tetris game.
;
;
; ToDo:
; 2005-03-15: todo: High scores
; 2005-03-15: todo: Start level
; 2005-03-15: todo: Start with random figure

; History
; 2005/04/21 Version 1.00
;            - removing full lines rewritten
;            - changed some expressions to simple assignments
;            - changed the random start figures to just the basic types
;            - fixed a problem with the first turning of a new piece
;            - changed FuncCheckPos from sub routine to function

#MaxThreads 1

Start:
 SetBatchLines, -1
 Gosub, Sub_Initialize
 Suspend, On

 HotKey, Up, Sub_Pressed_Up
 HotKey, Down, Sub_Playing
 HotKey, Left, Sub_Pressed_Left
 HotKey, Right, Sub_Pressed_Right

 Gui, Show, w250 h230, %v_Window_Title%
 SetTimer, Sub_Playing, %v_timer%
 Suspend, Off
 Gosub, Sub_Restart
Return

GuiClose:
 ExitApp

Sub_Initialize:
; window title
 v_Window_Title = Tetris v1.00

; initialize the bricks
; Figure 1
 c_brick[1][1] = 1110
 c_brick[1][2] = 0100
 c_brick[1][3] = 0000
 c_brick[1][4] = 0000
 c_brick[2][1] = 0100
 c_brick[2][2] = 1100
 c_brick[2][3] = 0100
 c_brick[2][4] = 0000
 c_brick[3][1] = 0100
 c_brick[3][2] = 1110
 c_brick[3][3] = 0000
 c_brick[3][4] = 0000
 c_brick[4][1] = 1000
 c_brick[4][2] = 1100
 c_brick[4][3] = 1000
 c_brick[4][4] = 0000

; Figure 2
 c_brick[5][1] = 1100
 c_brick[5][2] = 1100
 c_brick[5][3] = 0000
 c_brick[5][4] = 0000
 c_brick[6][1] = 1100
 c_brick[6][2] = 1100
 c_brick[6][3] = 0000
 c_brick[6][4] = 0000
 c_brick[7][1] = 1100
 c_brick[7][2] = 1100
 c_brick[7][3] = 0000
 c_brick[7][4] = 0000
 c_brick[8][1] = 1100
 c_brick[8][2] = 1100
 c_brick[8][3] = 0000
 c_brick[8][4] = 0000

; Figure 3
 c_brick[9][1] = 1000
 c_brick[9][2] = 1000
 c_brick[9][3] = 1000
 c_brick[9][4] = 1000
 c_brick[10][1] = 1111
 c_brick[10][2] = 0000
 c_brick[10][3] = 0000
 c_brick[10][4] = 0000
 c_brick[11][1] = 1000
 c_brick[11][2] = 1000
 c_brick[11][3] = 1000
 c_brick[11][4] = 1000
 c_brick[12][1] = 1111
 c_brick[12][2] = 0000
 c_brick[12][3] = 0000
 c_brick[12][4] = 0000

; Figure 4
 c_brick[13][1] = 1000
 c_brick[13][2] = 1000
 c_brick[13][3] = 1100
 c_brick[13][4] = 0000
 c_brick[14][1] = 1110
 c_brick[14][2] = 1000
 c_brick[14][3] = 0000
 c_brick[14][4] = 0000
 c_brick[15][1] = 1100
 c_brick[15][2] = 0100
 c_brick[15][3] = 0100
 c_brick[15][4] = 0000
 c_brick[16][1] = 0010
 c_brick[16][2] = 1110
 c_brick[16][3] = 0000
 c_brick[16][4] = 0000

; Figure 5
 c_brick[17][1] = 0100
 c_brick[17][2] = 0100
 c_brick[17][3] = 1100
 c_brick[17][4] = 0000
 c_brick[18][1] = 1000
 c_brick[18][2] = 1110
 c_brick[18][3] = 0000
 c_brick[18][4] = 0000
 c_brick[19][1] = 1100
 c_brick[19][2] = 1000
 c_brick[19][3] = 1000
 c_brick[19][4] = 0000
 c_brick[20][1] = 1110
 c_brick[20][2] = 0010
 c_brick[20][3] = 0000
 c_brick[20][4] = 0000

; Figure 6
 c_brick[21][1] = 1100
 c_brick[21][2] = 0110
 c_brick[21][3] = 0000
 c_brick[21][4] = 0000
 c_brick[22][1] = 0100
 c_brick[22][2] = 1100
 c_brick[22][3] = 1000
 c_brick[22][4] = 0000
 c_brick[23][1] = 1100
 c_brick[23][2] = 0110
 c_brick[23][3] = 0000
 c_brick[23][4] = 0000
 c_brick[24][1] = 0100
 c_brick[24][2] = 1100
 c_brick[24][3] = 1000
 c_brick[24][4] = 0000

; Figure 7
 c_brick[25][1] = 0110
 c_brick[25][2] = 1100
 c_brick[25][3] = 0000
 c_brick[25][4] = 0000
 c_brick[26][1] = 1000
 c_brick[26][2] = 1100
 c_brick[26][3] = 0100
 c_brick[26][4] = 0000
 c_brick[27][1] = 0110
 c_brick[27][2] = 1100
 c_brick[27][3] = 0000
 c_brick[27][4] = 0000
 c_brick[28][1] = 1000
 c_brick[28][2] = 1100
 c_brick[28][3] = 0100
 c_brick[28][4] = 0000

; initialize variables
 v_StopGame = 0

; set constants, ok they are variables, but I use c_<anything> as constants
 c_sleep = 50 ; Delay for sleeping in milliseconds
 c_xmax = 12 ; max column
 c_ymax = 21 ; max row

; initalize the play field
 Loop, %c_ymax% ; rows
 {
  __row = %A_Index%
  Loop, %c_xmax% ; columns
  {
   __column = %A_Index%
   v_Field[%__column%][%__row%] = 0

   __x := 10 * __column
   __y := 10 * __row

;    Gui, Add, Picture, x%__x% y%__y% h9 w9 vv_Brick[1][%__column%][%__row%], Col1.bmp
    Loop, 7 ; add picture for every color
    {
     Gui, Add, Picture, x%__x% y%__y% h9 w9 vv_Brick[%A_Index%][%__column%][%__row%], Col%A_Index%.bmp
    }
  }
 }

; initialize the preview field
 Loop, 4 ; rows
 {
  __row = %A_Index%
  Loop, 4 ; columns
  {
   __column = %A_Index%

   __x := 10 * __column + 135
   __y := 10 * __row + 20

;    Gui, Add, Picture, x%__x% y%__y% h9 w9 vv_Preview[1][%__column%][%__row%], Col1.bmp
    Loop, 7 ; add picture for every color
    {
     Gui, Add, Picture, x%__x% y%__y% h9 w9 vv_Preview[%A_Index%][%__column%][%__row%], Col%A_Index%.bmp
    }
  }
 }

; draw the frame around the play field
 Gui, Add, Picture, x5 y5 w5 h215, Black.bmp
 Gui, Add, Picture, x130 y5 w5 h215, Black.bmp
 Gui, Add, Picture, x5 y220 w130 h5, Black.bmp

; draw the frame around the preview field
 Gui, Add, Picture, x140 y25 w5 h50, Black.bmp
 Gui, Add, Picture, x185 y25 w5 h50, Black.bmp
 Gui, Add, Picture, x145 y25 w40 h5, Black.bmp
 Gui, Add, Picture, x145 y70 w40 h5, Black.bmp

; write text
 Gui, Font, s8, Arial
 Gui, Add, Text, x140 y5 w70 h14, next piece:

 Gui, Font, s10, Arial
 Gui, Add, Text, x140 y90 w40 h15, Score:
 Gui, Add, Text, x180 y90 w60 h15 Right vv_score, 0
 Gui, Add, Text, x140 y110 w40 h15, Speed:
 Gui, Add, Text, x180 y110 w60 h15 Right vv_speed, 0

 Gui, Font, s8, Arial
 Gui, Add, Text, x140 y130 w35 h15, Keys:
 Gui, Add, Text, x140 y145 w35 h15, Left:
 Gui, Add, Text, x175 y145 w100 h15, move left
 Gui, Add, Text, x140 y160 w35 h15, Right:
 Gui, Add, Text, x175 y160 w100 h15, move right
 Gui, Add, Text, x140 y175 w35 h15, Down:
 Gui, Add, Text, x175 y175 w100 h15, drop piece
 Gui, Add, Text, x140 y190 w35 h15, Up:
 Gui, Add, Text, x175 y190 w100 h15, turn clockwise
 Gui, Font

; create the menu bar
 Menu, FileMenu, Add, &New game, Sub_MenuHandler
 Menu, FileMenu, Add, E&xit, Sub_MenuHandler
 Menu, MyMenuBar, Add, &File, :FileMenu
 Gui, Menu, MyMenuBar
Return


;###################
;# Sub_MenuHandler #
;###################
Sub_MenuHandler:
 __menu := A_ThisMenu
 __item := A_ThisMenuItem

 If __menu = FileMenu
 {
  If __item = E&xit
  {
   Goto, GuiClose
  }
  Else If __item = &New game
  {
   Gosub, Sub_Restart
  }
 }
Return


;#####################
;# Sub_Refresh_Score #
;#####################
; displays the actual score and speed using the variables
; v_score and v_speed. Also reset the timer.
Sub_Refresh_Score:
 GuiControl,, v_score, %v_score%  ; displays the score
 GuiControl,, v_speed, %v_speed%  ; displays the speed

 v_speed := v_score / 20
 Transform, v_speed, Floor, %v_speed%
 If v_speed > 10
  v_speed = 10

 If v_StopGame = 0
 {
  v_timer := (11 - v_speed) * 50
  __timer := v_timer - v_overtime
  v_StartTick := A_TickCount
  SetTimer, Sub_Playing, %__timer%
 }
Return


;################
;# Sub_NewPiece #
;################
;# Set a new piece randomly and set this variables:
;# v_fig: figure regarding to c_brick[Number][row]
;# v_col: color of the new piece, regarding to c_cols
;# v_xpos: x position
;# v_ypos: y position
Sub_NewPiece:
 v_fig := v_fignext
 v_col := v_colnext

 Random, v_fignext, 0, 6 ; new figure out of the seven shapes
 v_fignext := v_fignext * 4 + 1

 v_colnext := v_fignext / 4
 Transform, v_colnext, Ceil, %v_colnext%

; draw the preview figure
 Loop, 4 ; rows
 {
  __row = %A_Index%
  Loop, 4 ; columns
  {
   __column = %A_Index%
   __bricktmp := c_brick[%v_fignext%][%__row%]
   StringMid, __brick, __bricktmp, %__column%, 1

   Loop, 7
    GuiControl, Hide, v_Preview[%A_Index%][%__column%][%__row%]

   If __brick = 1
    GuiControl, Show, v_Preview[%v_colnext%][%__column%][%__row%]
  }
 }

 v_xpos = 6
 v_ypos = 1

 __Return := FuncCheckPos(v_xpos, v_ypos, v_fig)
 If __Return = 0
  Gosub, Sub_DrawPiece
 Else ; stop game, the new brick can't be created
  v_StopGame = 1
Return


;#################
;# Sub_DrawPiece #
;#################
Sub_DrawPiece:
 Loop, 4 ; rows
 {
  __row = %A_Index%
  Loop, 4 ; columns
  {
   __column = %A_Index%
   __bricktmp := c_brick[%v_fig%][%__row%]
   StringMid, __brick, __bricktmp, %__column%, 1

   __xpos := v_xpos + __column - 1
   __ypos := v_ypos + __row - 1

   If __brick = 1
    If v_xpos <= %c_xmax%
     If v_ypos <= %c_ymax%
      GuiControl, Show, v_Brick[%v_col%][%__xpos%][%__ypos%]
  }
 }
Return


;#################
;# Sub_HidePiece #
;#################
Sub_HidePiece:
 Loop, 4 ; rows
 {
  __row = %A_Index%
  Loop, 4 ; columns
  {
   __column = %A_Index%
   __bricktmp := c_brick[%v_fig%][%__row%]
   StringMid, __brick, __bricktmp, %__column%, 1

   __xpos := v_xpos + __column - 1
   __ypos := v_ypos + __row - 1

   If __brick = 1
    If v_xpos <= %c_xmax%
     If v_ypos <= %c_ymax%
      GuiControl, Hide, v_Brick[%v_col%][%__xpos%][%__ypos%]
  }
 }
Return


;################
;# Sub_SetPiece #
;################
Sub_SetPiece:
 Loop, 4 ; rows
 {
  __row = %A_Index%
  Loop, 4 ; columns
  {
   __column = %A_Index%
   __bricktmp := c_brick[%v_fig%][%__row%]
   StringMid, __brick, __bricktmp, %__column%, 1

   __xpos := v_xpos + __column - 1
   __ypos := v_ypos + __row - 1

   If __brick = 1
    If __xpos <= %c_xmax%
     If __ypos <= %c_ymax%
      v_Field[%__xpos%][%__ypos%] = %v_col%
  }
 }
Return


;##################
;# Sub_Pressed_Up #
;##################
;# The cursor up key was pressed, so turn off all hotkeys, turn the
;# active piece, sleep a little bit :-) and turn on all hotkeys.
Sub_Pressed_Up:
; v_timeremain := A_TickCount
 Gosub, Sub_InterruptOff
 IfWinNotActive, %v_Window_Title%
 {
  Send, {Up}
  Gosub, Sub_InterruptOn
  Return
 }

 Transform, __base, Mod, %v_fig%, 4
 If __base = 0
  __base = 4
 __base := v_fig - __base

 __fig := v_fig + 1
 Transform, __fig, Mod, %__fig%, 4
 If __fig = 0
  __fig = 4
 __fig += __base

 __Return := FuncCheckPos(v_xpos, v_ypos, __fig)
 If __Return = 0
 {
  Gosub, Sub_HidePiece
  v_fig = %__fig%
  Gosub, Sub_DrawPiece
  Sleep, %c_sleep%
 }

 Gosub, Sub_InterruptOn
Return


;####################
;# Sub_Pressed_Down #
;####################
; The cursor down key was pressed.
Sub_Pressed_Down:
 Gosub, Sub_InterruptOff
 IfWinNotActive, %v_Window_Title%
 {
  Send, {Down}
  Gosub, Sub_InterruptOn
  Return
 }

 __param2 := v_ypos + 1
 __Return := FuncCheckPos(v_xpos, __param2, v_fig)
 If __Return = 0
 {
  Gosub, Sub_HidePiece
  v_ypos += 1
  Gosub, Sub_DrawPiece
  Sleep, %c_sleep%
 }

 Gosub, Sub_InterruptOn
Return


;####################
;# Sub_Pressed_Left #
;####################
; The cursor left key was pressed
Sub_Pressed_Left:
 Gosub, Sub_InterruptOff
 IfWinNotActive, %v_Window_Title%
 {
  Send, {Left}
  Gosub, Sub_InterruptOn
  Return
 }

 __param1 := v_xpos - 1
 __Return := FuncCheckPos(__param1, v_ypos, v_fig)
 If __Return = 0
 {
  Gosub, Sub_HidePiece
  v_xpos -= 1
  Gosub, Sub_DrawPiece
  Sleep, %c_sleep%
 }

 Gosub, Sub_InterruptOn
Return


;#####################
;# Sub_Pressed_Right #
;#####################
; The cursor right key was pressed
Sub_Pressed_Right:
 Gosub, Sub_InterruptOff
 IfWinNotActive, %v_Window_Title%
 {
  Send, {Right}
  Gosub, Sub_InterruptOn
  Return
 }

 __param1 := v_xpos + 1
 __Return := FuncCheckPos(__param1, v_ypos, v_fig)
 If __Return = 0
 {
  Gosub, Sub_HidePiece
  v_xpos += 1
  Gosub, Sub_DrawPiece
  Sleep, %c_sleep%
 }

 Gosub, Sub_InterruptOn
Return


;###############
;# Sub_Restart #
;###############
; Restart the game, reset speed and score, clear
; the play field
Sub_Restart:
 Gosub, Sub_InterruptOff
 v_StopGame = 0
 v_speed = 0
 v_score = 0

; clear the play field
 Loop, %c_ymax% ; rows
 {
  __row = %A_Index%
  Loop, %c_xmax% ; columns
  {
   __column = %A_Index%
   v_Field[%__column%][%__row%] = 0

;   GuiControl, Hide, v_Brick[1][%__column%][%__row%]
   Loop, 7
    GuiControl, Hide, v_Brick[%A_Index%][%__column%][%__row%]
  }
 }

; new figure for the preview
 Random, v_fignext, 1, 28 ; new figure out of 28 possibilites
 v_colnext := v_fignext / 4
 Transform, v_colnext, Ceil, %v_colnext%

 Gosub, Sub_NewPiece
 Gosub, Sub_Refresh_Score
 Gosub, Sub_InterruptOn
Return


;###############
;# Sub_Playing #
;###############
Sub_Playing:
 Gosub, Sub_InterruptOff

 __param2 := v_ypos + 1
 __Return := FuncCheckPos(v_xpos, __param2, v_fig)
 If __Return = 0 ; possible move 
 {
  Gosub, Sub_HidePiece
  v_ypos += 1
  Gosub, Sub_DrawPiece
 }
 Else If __Return >= 2 ; bottom boundary collision
 {
  Gosub, Sub_SetPiece
  Gosub, Sub_DrawPiece
  Gosub, Sub_CheckLineCompletion
  Gosub, Sub_NewPiece
 }

 Gosub, Sub_Refresh_Score
 Gosub, Sub_InterruptOn
Return


;####################
;# Sub_InterruptOff #
;####################
; disables interrupting with keyboard or timer
Sub_InterruptOff:
 Suspend, On
 SetTimer, Sub_Playing, Off
 v_timeremain = %A_TickCount%
Return


;###################
;# Sub_InterruptOn #
;###################
; Enables interrupting with keyboard or timer
Sub_InterruptOn:
 __elapsed := A_TickCount - v_StartTick

If v_StopGame = 0
{
 If __elapsed > %v_timer%
  {
   v_overtime := __elapsed - v_timer
   Gosub, Sub_Playing
  }
  Else
  {
   __elapsed := v_timer - A_TickCount + v_StartTick
   SetTimer, Sub_Playing, %__elapsed%
  }

  SetTimer, Sub_Playing, On
  Suspend, Off
 }
Return


;################
;# FuncCheckPos #
;################
; Checks, if the piece could be moved to or rotated at
; the specified position.
; __param1: x position (upper left)
; __param2: y position (upper left)
; __param3: figure (1 of 28)
; __return: 0: possible,
;           1: left / right boundary collision
;           2: collision with other piece
;           3: bottom boundary collision
FuncCheckPos(__param1, __param2, __param3)
{
 global c_xmax
 global c_ymax
 global c_brick
 global v_Field
 
 __return = 0

 If __param1 = 0 ; move outside left
  Return 1

 If __param1 > %c_xmax% ; move outside right
  Return 1

 Loop, 4 ;rows
 {
  __row = %A_Index%
  Loop, 4 ;columns
  {
   __column = %A_Index%
   __bricktmp := c_brick[%__param3%][%__row%]
   StringMid, __brick, __bricktmp, %__column%, 1

   __xpos := __param1 + __column - 1
   __ypos := __param2 + __row - 1

   If __brick = 1
   {
    If __xpos <= %c_xmax%
    {
     If __ypos <= %c_ymax%
     {
      __field := v_Field[%__xpos%][%__ypos%]
      If __field > 0
      {
       __return = 2 ; collision
       Break
      }
     }
     Else
     {
      __return = 3 ; bottom boundary collision
      Break
     }
    }
    Else 
    {
     __return = 1 ; move outside right
     Break
    }
   } ;If __brick = 1
  } ;Loop, 4 ;columns
 } ;Loop, 4 ;rows
 Return __return
}


;###########################
;# Sub_CheckLineCompletion #
;###########################
; This routine checks if one or more lines are completed
; and will remove them. Also the score will be changed.
Sub_CheckLineCompletion:
 __score = 0

 __ypos := v_ypos + 4
 If __ypos > 21
  __ypos = 21
 
 Loop
 {
  __line = 1
  __lineOr = 0
  Loop, %c_xmax% ; all columns
  {
   __xpos = %A_Index%
   __field := v_Field[%__xpos%][%__ypos%]
   If __field > 0
    __field = 1
   __line := __line & __field
   __lineOr := __lineOr | __field
  }

  If __lineOr = 0 ; found an empty line?
	 break

  If __line = 1 ; found a complete line
  {
   __score += 1
   __y2 := c_ymax - 1
; remove the line
   Loop, %__y2%
   {
    __y := __ypos - A_Index + 1
    __line2Or = 0
    Loop, %c_xmax%
    {
     __x = %A_Index%
     __field := v_Field[%__x%][%__y%]

     GuiControl, Hide, v_Brick[%__field%][%__x%][%__y%]
     __y2 := __y - 1
     __field := v_Field[%__x%][%__y2%]
     __line2Or := __line2Or | __field
     v_Field[%__x%][%__y%] := __field
     __field := v_Field[%__x%][%__y%]
     GuiControl, Show, v_Brick[%__field%][%__x%][%__y%]
    }
    If __line2Or = 0
     Break
   }
  }
  Else
   __ypos -= 1
 }

 If __score > 0 ; add the score
  v_score := v_score + 2 ** __score - 1
Return


gwarble
  • Members
  • 624 posts
  • Last active: Aug 12 2016 07:49 PM
  • Joined: 23 May 2009
if your second instance will run for a while and you don't want your persistant application's hotkeys to be on for that instance, it may be wise to use:

Hotkey, n, Off

in the label called by Instance() to disable any hotkeys you define with a double-colon-label (which will be enabled without any action in the auto-execute section of your script, of course)

also use the tetris example syntax if you want to try it, so your auto-execute call should look like this to enable the second instance to run as a persistant script instead of ending the label with an ExitApp... so if you want it to exit after a task use ExitApp at the end of your label or somewhere called by a gui/hotkey/timer/etc...

If Instance()
 Return
;...
Return ; end of your normal auto-execute section

^1::Instance("/Beep")

/Beep:
-Beep:
 HotKey, ^1, Off
 SoundBeep, 220, 500
ExitApp

Instance() can also be used to make one script act as two seperate scripts... so:
Instance()
 Return ;= end of real auto-execute section

App1:
 ;autoexecute section when first command line parameter is "App1"
Return
App2:
 ;autoexecute section when first command line parameter is "App2"
Return

a little cleanup and a way to define which "labels" are caught by the instance() call are being added and i'll update the first post with the cleaned up version when i get to it

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

EDIT: any problem with putting #SingleInstance, Off inside the stdlib function? its required anyway ... and always will be required...

FYI, it would not be necessary if you used the /force command-line switch when launching additional instances.

Hotkey, n, Off

Suspend might be more appropriate. :)

gwarble
  • Members
  • 624 posts
  • Last active: Aug 12 2016 07:49 PM
  • Joined: 23 May 2009
ah, great... thanks a ton, i never noticed either of those and they are perfect for this...

edit: here's a new version with both implemented... though in the long run Suspend may be better pulled out and handled in the calling script... i usually use Hotkey, n, n and n: instead... so i call them after the Instance() call and it doesnt matter... but i'm trying this out before i replace the first post...

anyone who has already tried, note different return values and autoexecute call should use if, and exitapp should be handled in label (or another subroutine) now to allow persistant scripts
thanks for the help
- gwarble

; Instance() - 0.3 - gwarble
;  creates another instance to perform a task
;  catches first command line parameter if matching label exists
;
; Instance(Label,Params)
;  Label   use 'If Instance() Return' in your autoexecute section for new instances
;          use 'Instance("Label")' to start a new instance, whose execution will be
;            redirected to the "Label" subroutine
;
;  Params  other parameters to pass to new instance, retrieved in
;          Label: as their normal command-line variables (ie: %2%)
;
;  Return  ProcessID of new instance, which can be monitored in various ways
;          0 on normal initialization (no action, continues autoexecute section)
;          0 if called label name is not found (when "Label" is used)
;
;  Example include | -Beep:                | label in your script w/ ExitApp
;          include | If Instance() Return  | in your autoexecute section
;             call | Instance("-Beep")     | for a new instance, directed to -Beep:
;              run | ahk.exe scr.ahk -Beep | from the command line for the same

Instance(Label="", Params="") {
 global 1             ;= first command line parameter needed
 If Label =           ;= initializing (ran from autoexecute section)
 {
  Label = %1%
  If IsLabel(Label)   ;= checks first parameter for matching label
  {                   ;= and runs it if found
   Suspend, On        ;=== stop hotkeys, maybe this should be handled script-side in "label"
   GoSub, %Label%
   Return 1
  }
  Return 0            ;= normal initialization, no params or label match found
 }
 Else                 ;= script call for new instance, "Label" required
 {
  If IsLabel(Label)   ;= checks for matching label
  {                   ;= and creates new instance if found
   If A_IsCompiled
    Run, "%A_ScriptFullPath%" /f "%Label%" %Params%,,,ProcessID
   Else
    Run, "%A_AhkPath%" /f "%A_ScriptFullPath%" "%Label%" %Params%,,,ProcessID
   Return %ProcessID% ;= returns ProcessID of new instance
  }
  Return 0            ;= if called for a new instance, without matching label
 }
}


gwarble
  • Members
  • 624 posts
  • Last active: Aug 12 2016 07:49 PM
  • Joined: 23 May 2009
updated version 0.33

/force switch will allow a typically single instance script to permit a second instance - only problem is if the script is #SI, Force, and restarted normally (not with instance()), it will kill any secondary instances running, so if you want to prevent this use the post/onMessage method above with #SI, Off

initialization call in your auto-execute section can now include Params parameter as a label prefix, which will narrow down conflicts for other command-line usage... so:
If Instance("","-") ; or Instance("","LongPrefix-")
 Return
would only redirect into labels like -Beep: or LongPrefix-Beep:

Suspend or Hotkey,,Off should be used in your Label to turn off any or specific double-colon label hotkeys used in the script... and if your script is Persistant, end your Label with ExitApp or other method of termination

-gwarble

Instance() v0.33 - Download Instance.ahk or read the Documentation
; Instance() - 0.33 - gwarble
;  creates another instance to perform a task
;    help: http://www.autohotkey.com/forum/viewtopic.php?p=310694
;
; Instance(Label,Params)
;
;  Label   "" to initialize, in autoexecute section usually
;          "Label" to start a new instance, whose execution will be
;            redirected to the "Label" subroutine
;
;  Params  parameters to pass to new instance (retrieved normally, ie: %2%)
;          when initializing (ie: label="") Params is a label prefix for all calls
;
;  Return  0 on normal initialization (ie: label="" and %1% <> any label:)
;          Process ID of new instance
;          0 on failed new instance (called label name not exist)

Instance(Label="", Params="") {
 global 1
 If Label =
 {
  Label = %1%
  If (InStr(Label, Params) = 1)
   If IsLabel(Label)
   {
    GoSub, %Label%
    Return 1
   }
  Return 0
 }
 Else
 {
  If IsLabel(Label)
  {
   If A_IsCompiled
    Run, "%A_ScriptFullPath%" /f "%Label%" %Params%,,,PID
   Else
    Run, "%A_AhkPath%" /f "%A_ScriptFullPath%" "%Label%" %Params%,,,PID
   Return %PID%
  }
  Return 0
 }
}

i'll update the first post when fully bug tested, thanks to lexikos for recommendations

planning to add:
comma-seperated param list to allow "-" or "/" switches, for example, with Instance("","-,/,LongPrefix-")
aux. function (maybe inside instance()) to ease inter-process-comm ("i'm finished" via win msgs at the very least)
possibly a set of aux. functions for common uses like TTS/download...

EDIT: something like this for proper "singleinstance, force" behavior:
Instance[color=red]_[/color](wParam, lParam) {
 Critical
 If lParam = 0
  ExitApp ;#SingleInstance, Force
 Else If IsLabel(_Label := "Instance_" lParam)
  GoSub, % _Label
 Return
}
and the script would have something like:
 If Instance("","-")
  Return

 SetTitleMatchMode, 2 ;= a better way to detect other instances?
 DetectHiddenWindows, On
 WinGet, Instance_ID, List, %A_ScriptFullPath%
 Loop %Instance_ID%
  SendMessage, 0x1357, 0x1357, 0, , % "ahk_id " Instance_ID%A_Index%
 OnMessage(0x1357, "Instance_")
Return ;= end auto-exececute section

1::
2::
3:: ;= using pid's will replace this to talk to a specific instance
4::
 WinGet, Instance_ID, List, %A_ScriptFullPath%
 Loop % Instance_ID
  SendMessage, 0x1357, 0x1357, %A_ThisHotkey2%, , % "ahk_id " Instance_ID%A_Index%
Return

Instance_1:
Instance_2:
Instance_3:
Instance_4:
 Notify(A_ThisLabel,"",1,"SI=0 ST=0")
Return
the hotkey example is showing super simple IPC between the two... so using the same onmessage with lparam >= 0; a label names Instance_n: will be run, where n = lparam

so if it isnt obvious, this will listen to a WinMessage_, if received lparam=0, exitapp... so when the script is run (not an instance()) it will send 0 to other instances to tell them to quit...

this (with #SingleInstance, Off) is to prevent a normal new instance from killing any instance()-created instances, while still killing any normal persistant running versions of the script/exe

so you can have your one main app running, with as many instance()s running as you want, and the main app will still behave as si, force like your app may require, without interrupting the instance()s...

gwarble
  • Members
  • 624 posts
  • Last active: Aug 12 2016 07:49 PM
  • Joined: 23 May 2009
hey all
i know there are much more elegant ways to do real multithreading, but i still use this function for simple things and figured i'd post what i'm using now... with all of the above singleInstance stuff implemented internally (and with the included Instance_() function for OnMessage)

If Instance("","-")   ; this is in the autoexecute section to initialize
 Return               ; and to redirect when a special instance is started
Instance("-Beep")     ; starts a new instance of the script which redirects
                      ; the autoexecute section (via above) into label "-Beep"
; Instance() - 0.4 - gwarble
;  creates another instance to perform a task
;    help: http://www.autohotkey.com/forum/viewtopic.php?p=310694
;
; Instance(Label,Params,AltWM)
;
;  Label   "" to initialize, in autoexecute section usually
;          "Label" to start a new instance, whose execution will be
;            redirected to the "Label" subroutine
;
;  Params  parameters to pass to new instance (retrieved normally, ie: %2%)
;          when initializing (ie: label="") Params is a label prefix for all calls
;          last param received by script is calling-instance's ProcessID
;
;  AltWM   Alternate WindowsMessage number if 0x1357 (arbitrary anyway) will conflict
;
;  Return  0 on normal initialization (ie: label="" and %1% <> any label:)
;          Process ID of new instance
;          0 on failed new instance (called label name not exist)

Instance(Label, Params="", WM="0x1357") {
 global 1
 If Label =
 {
  Label = %1%
  If (InStr(Label, Params) = 1)
  {
   If IsLabel(Label)
   {
    GoSub, %Label%
    Return 1
   }
  }
  Else
  {
   pDHW := A_DetectHiddenWindows
   DetectHiddenWindows, On
   WinGet, Instance_ID, List, %A_ScriptFullPath%
   Loop %Instance_ID%
    SendMessage, WM, WM, 0, , % "ahk_id " Instance_ID%A_Index%
   DetectHiddenWindows, %pDHW%
   OnMessage(WM, "Instance_")
  }
  Return 0
 }
 Else
 {
  If IsLabel(Label)
  {
   ProcessID := DllCall("GetCurrentProcessId")
   If A_IsCompiled
    Run, "%A_ScriptFullPath%" /f "%Label%" %Params% %ProcessID%,,,Instance_PID
   Else
    Run, "%A_AhkPath%" /f "%A_ScriptFullPath%" "%Label%" %Params% %ProcessID%,,,Instance_PID 
   Return %Instance_PID%
  }
  Return 0
 }
 #SingleInstance, Off ;your script should probably have this anyway
}


Instance_(wParam, lParam) {
 Critical
 If lParam = 0
  ExitApp
 Else If IsLabel(Label := "Instance_" lParam)
  GoSub, % Label
 Return
}

EDIT Feb 4, 2012: i won't bother bumping this thread with no actual user feedback since posted in over two years (but some good reference feedback from Lex and HotK... thanks guys)
but FYI it appears to work fine with _L on first attempt since i finally took the plunge)