Simultaneous running of two timers. Topic is solved

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
hiahkforum
Posts: 47
Joined: 08 Feb 2024, 04:21

Simultaneous running of two timers.

Post by hiahkforum » 20 Feb 2024, 05:00

In the documentation I found an example of a timer that works well for me:

Code: Select all

counter := SecondCounter()

; An example class for counting the seconds...
class SecondCounter {
    __New() {
        this.interval := 1000
        this.count := -5
        ; Tick() has an implicit parameter "this" which is a reference to
        ; the object, so we need to create a function which encapsulates
        ; "this" and the method to call:
        this.timer := ObjBindMethod(this, "Tick")
    }
    Start() {
        SetTimer this.timer, this.interval
        ToolTip "Counter started"
    }
    Stop() {
        ; To turn off the timer, we must pass the same object as before:
        SetTimer this.timer, 0
		this.count := -5
        ToolTip "Counter stopped at " this.count
		Sleep 1000
		ToolTip
    }
    ; In this example, the timer calls this method:
    Tick() {
        ToolTip ++this.count
    }
}

#z::{
counter.Start
Sleep 5000
counter.Stop
}
I use it in a script that I run in multiple threads, but the timer only works for the first thread. How do I start a new timer with a new thread of my script so that they work simultaneously?

Rohwedder
Posts: 7772
Joined: 04 Jun 2014, 08:33
Location: Germany

Re: Simultaneous running of two timers.

Post by Rohwedder » 20 Feb 2024, 10:01

Hallo,
maybe Timers instead of the Sleep 5000 and Sleep 1000 ?:

Code: Select all

#Requires AutoHotkey v2.0
counter := SecondCounter()

; An example class for counting the seconds...
class SecondCounter {
    __New() {
        this.interval := 1000
        this.count := -5
        ; Tick() has an implicit parameter "this" which is a reference to
        ; the object, so we need to create a function which encapsulates
        ; "this" and the method to call:
        this.timer := ObjBindMethod(this, "Tick")
    }
    Start() {
        SetTimer this.timer, this.interval
        ToolTip "Counter started"
    }
    Stop() {
        ; To turn off the timer, we must pass the same object as before:
        SetTimer this.timer, 0
		this.count := -5
        ToolTip "Counter stopped at " this.count
		Static T := ToolTip.Bind()
		SetTimer T, -1000
    }
    ; In this example, the timer calls this method:
    Tick() {
        ToolTip ++this.count
    }
}

#z::{
counter.Start
Static T := () => counter.Stop()
SetTimer T, -5000
}
The use of Sleeps always carries the risk of blocking the current thread.
https://www.autohotkey.com/docs/v2/misc/Threads.htm

hiahkforum
Posts: 47
Joined: 08 Feb 2024, 04:21

Re: Simultaneous running of two timers.

Post by hiahkforum » 20 Feb 2024, 20:58

@Rohwedder Thank you for your help. It may work, but there is still only one ToolTip on the screen to visualize the timer. Is it possible to fix this?

Rohwedder
Posts: 7772
Joined: 04 Jun 2014, 08:33
Location: Germany

Re: Simultaneous running of two timers.

Post by Rohwedder » 21 Feb 2024, 03:57

Then try:

Code: Select all

#Requires AutoHotkey v2.0
CoordMode "Mouse", "Screen"
Guis := Array(), Downs := Array()
SetTimer CountDown, 1000
#z:: {
	MouseGetPos &x, &y
	Guis.Push(MyGui := Gui("+AlwaysOnTop -Caption +ToolWindow"))
	MyGui.SetFont("s14", "Consolas"), MyGui.MarginX := MyGui.MarginY := 0
	Downs.Push(MyGui.Add("Text",, 10)) ; < Start value
	MyGui.Show("X" x-5 " Y" y-5)	
}
CountDown() {
	For N, Counter in Downs {
		IF !Counter.Value
			Guis[N].Destroy(), Guis.RemoveAt(N),Downs.RemoveAt(N)	
		Else --Counter.Value
}}
A CountDown is started at every point where you press Win + Z

hiahkforum
Posts: 47
Joined: 08 Feb 2024, 04:21

Re: Simultaneous running of two timers.

Post by hiahkforum » 21 Feb 2024, 06:16

@Rohwedder This is a completely different timer implementation, but it works too, and even in multiple threads! :D

But can I ask a couple of silly questions? How do I make a variable from this code, so I don't have to paste the whole code every time? And how do I make the timer appear at position x=720, y=450 relative to the current window (or better "client")?

Rohwedder
Posts: 7772
Joined: 04 Jun 2014, 08:33
Location: Germany

Re: Simultaneous running of two timers.

Post by Rohwedder » 21 Feb 2024, 10:16

I interpreted "make a variable from this code" that you want call a function:

Code: Select all

#Requires AutoHotkey v2.0

#z::CountDown(10, 720, 450)
q:: { ; Circle from CountDowns
	Loop 8
		CountDown(10, 80*(2+Cos(α := Atan(1)*A_Index)), 80*(2+Sin(α))), Sleep(100)
} ; Sleep(100) delays the respective start times

CountDown(StartValue, X, Y) {
	WinGetPos &Wx, &Wy,,, "A" ; X, Y relative to active window
	MyGui := Gui("+AlwaysOnTop -Caption +ToolWindow")
	MyGui.SetFont("s14", "Consolas"), MyGui.MarginX := MyGui.MarginY := 0
	Down := MyGui.Add("Text", 1, StartValue) ; 1 = center
	MyGui.Show("X" Wx+X "Y" Wy+Y "NA")
	SetTimer(Count.Bind(MyGui, Down), 1000) ; every second
 	Count(MyGui, Down) {
	Down.Value?--Down.Value:(SetTimer(, False), MyGui.Destroy())
}}
Each gui now has its own Timer. Try the Circle from CountDowns.

hiahkforum
Posts: 47
Joined: 08 Feb 2024, 04:21

Re: Simultaneous running of two timers.

Post by hiahkforum » 22 Feb 2024, 06:30

@Rohwedder Wow! :wtf: With each adjustment, the simple timer function looks more and more like rocket science! The circle countdown is very beautiful! :bravo: Of course, I meant "function".

I am very grateful to you, a lot has already been done and I understand if this is all with help. But if I may, I would like to ask about the last two things:
1. In the first version of the timer made through ToolTip, I could add SoundPlay A_WinDir "\Media\Windows Navigation Start.wav" to the Tick() function and a sound would be played at each tick. It is very convenient.
2. Is it possible to add a sequence number before each new timer to see the order of the timers?

If this is all too complicated, then I understand, it is not necessary, but this is all that is missing for the ideal. :monkeysay:

Rohwedder
Posts: 7772
Joined: 04 Jun 2014, 08:33
Location: Germany

Re: Simultaneous running of two timers.

Post by Rohwedder » 22 Feb 2024, 07:41

SoundPlay? Then:

Code: Select all

#Requires AutoHotkey v2.0
#z::CountDown(10, 720, 450, A_WinDir "\Media\Windows Navigation Start.wav")
q:: { ; Circle from CountDowns
	Loop 8
		CountDown(10, 80*(2+Cos(α := Atan(1)*A_Index)), 80*(2+Sin(α))), Sleep(100)
} ; Sleep(100) delays the respective start times

CountDown(StartValue, X, Y, SoundPlayFile := 0) {
	WinGetPos &Wx, &Wy,,, "A" ; X, Y relative to active window
	MyGui := Gui("+AlwaysOnTop -Caption +ToolWindow")
	MyGui.SetFont("s14", "Consolas"), MyGui.MarginX := MyGui.MarginY := 0
	Down := MyGui.Add("Text", 1, StartValue) ; 1 = center
	MyGui.Show("X" Wx+X "Y" Wy+Y "NA")
	SetTimer(Count.Bind(MyGui, Down), 1000) ; every second
 	Count(MyGui, Down) {
	Down.Value?(--Down.Value, SoundPlayFile?SoundPlay(SoundPlayFile):"")
	:(SetTimer(, False), MyGui.Destroy())
}}
But a sequence number?
When Timers are created, i.e. waiting for their next execution, there is no "Order of the Timers"! Whoever wants to become active becomes the current thread (unless the current thread is critical or has a higher priority or …). I could add a Timer creation counter, but why? It would have no relevant information.

hiahkforum
Posts: 47
Joined: 08 Feb 2024, 04:21

Re: Simultaneous running of two timers.

Post by hiahkforum » 22 Feb 2024, 08:10

@Rohwedder Thank you, SoundPlay works perfectly! I mean something like that to quickly visually identify the order of the timers.
timer.png
timer.png (4.01 KiB) Viewed 756 times

Rohwedder
Posts: 7772
Joined: 04 Jun 2014, 08:33
Location: Germany

Re: Simultaneous running of two timers.

Post by Rohwedder » 22 Feb 2024, 09:38

Then:

Code: Select all

#Requires AutoHotkey v2.0
#z::CountDown(10, 720, 450, A_WinDir "\Media\Windows Navigation Start.wav")MP
q:: { ; Circle from CountDowns
	Loop 8
		CountDown(10, 80*(2+Cos(α := Atan(1)*A_Index)), 80*(2+Sin(α))), Sleep(100)
} ; Sleep(100) delays the respective start times

CountDown(StartValue, X, Y, SoundPlayFile := 0) {
	Static Order := 0
	WinGetPos &Wx, &Wy,,, "A" ; X, Y relative to active window
	MyGui := Gui("+AlwaysOnTop -Caption +ToolWindow")
	MyGui.SetFont("s14", "Consolas"), MyGui.MarginX := MyGui.MarginY := 0
	MyGui.Add("Text", "BackgroundWhite", ++Order ".")
	Down := MyGui.Add("Text", "YP+0", StartValue)
	MyGui.Show("X" Wx+X "Y" Wy+Y "NA")
	SetTimer(Count.Bind(MyGui, Down), 1000) ; every second
 	Count(MyGui, Down) {
	Down.Value?(--Down.Value, SoundPlayFile?SoundPlay(SoundPlayFile):"")
	:(SetTimer(, False), MyGui.Destroy())
}}
Guis can be designed as you like!

hiahkforum
Posts: 47
Joined: 08 Feb 2024, 04:21

Re: Simultaneous running of two timers.

Post by hiahkforum » 24 Feb 2024, 05:40

@Rohwedder HUGE thank you! This works incredible, exactly what I wanted! :dance: But one more small thing, is it possible, when the last timer ends, to reset the timer sequence number as well? :roll:

Rohwedder
Posts: 7772
Joined: 04 Jun 2014, 08:33
Location: Germany

Re: Simultaneous running of two timers.

Post by Rohwedder » 24 Feb 2024, 06:28

Then:

Code: Select all

#Requires AutoHotkey v2.0
#z::CountDown(10, 720, 450, A_WinDir "\Media\Windows Navigation Start.wav")
q:: { ; Circle from CountDowns
	Loop 8
		CountDown(10, 80*(2+Cos(α := Atan(1)*A_Index)), 80*(2+Sin(α))), Sleep(100)
} ; Sleep(100) delays the respective start times

CountDown(StartValue, X, Y, SoundPlayFile := 0) {
	Static Order := 0, Active := 0
	WinGetPos &Wx, &Wy,,, "A" ; X, Y relative to active window
	MyGui := Gui("+AlwaysOnTop -Caption +ToolWindow")
	MyGui.SetFont("s14", "Consolas"), MyGui.MarginX := MyGui.MarginY := 0
	MyGui.Add("Text", "BackgroundWhite", (++Order *= !!Active++) ".")
	Down := MyGui.Add("Text", "YP+0", StartValue)
	MyGui.Show("X" Wx+X "Y" Wy+Y "NA")
	SetTimer(Count.Bind(MyGui, Down), 1000) ; every second
 	Count(MyGui, Down) {
	Down.Value?(--Down.Value, SoundPlayFile?SoundPlay(SoundPlayFile):"")
	:(SetTimer(, False), MyGui.Destroy(), --Active)
}}
Edit: Unwanted "0" suppressed.
Last edited by Rohwedder on 25 Feb 2024, 01:47, edited 1 time in total.

hiahkforum
Posts: 47
Joined: 08 Feb 2024, 04:21

Re: Simultaneous running of two timers.

Post by hiahkforum » 24 Feb 2024, 20:49

@Rohwedder Can I say that I love you? :superhappy: I know this “0” is a necessity, so I don’t dare ask you for anything else. Everything works great! :thumbup:

hiahkforum
Posts: 47
Joined: 08 Feb 2024, 04:21

Re: Simultaneous running of two timers.

Post by hiahkforum » 25 Feb 2024, 01:06

In fact, this "0" can be defeated, perhaps not the most correct way, but it works. You just need to erase the period in the quotes or remove the quotes altogether. Yes, it’s better with a period or space, but at least this way.

Code: Select all

!!Active++ "."
timer1.5.png
timer1.5.png (5.03 KiB) Viewed 658 times

Code: Select all

!!Active++ ""
timer2.png
timer2.png (4.33 KiB) Viewed 659 times

Rohwedder
Posts: 7772
Joined: 04 Jun 2014, 08:33
Location: Germany

Re: Simultaneous running of two timers.

Post by Rohwedder » 25 Feb 2024, 01:48

This "0" was not a necessity but a bug that I had not seen!
(++Order *= !!Active++) "."
The added brackets change the order of the evaluation.
1. registration of the subsequent incrementation of Active.
2. the double ! turns the numerical value of Active into a logical value, i.e. 0 or 1.
3. assigned multiplied by the numerical value of Order, that is set to 0 in the case of a 0.
4. the previous incrementation of Order is executed.
5. the work within the brackets is completed, the string "." is added and the Text Control is set.
6. Now the incrementation of Active is executed.
Without the added brackets ++Order *= !!Active++ "." was evaluated as ++Order *= (!!Active++ ".")
i.e. an unwanted floating point number was created!

(Instructions requires much more space in English than in Autohotkey.)

hiahkforum
Posts: 47
Joined: 08 Feb 2024, 04:21

Re: Simultaneous running of two timers.

Post by hiahkforum » 14 Mar 2024, 17:55

Hi @Rohwedder, I made a function with an interruptible Sleep to break off the timer, but could you please tell me how I can also remove the timer GUI?

Code: Select all

Sleep35000() {
	Loop 500 {
		Sleep 70
		if GetKeyState('Esc', 'P')
			break
	}
}

Rohwedder
Posts: 7772
Joined: 04 Jun 2014, 08:33
Location: Germany

Re: Simultaneous running of two timers.

Post by Rohwedder » 15 Mar 2024, 01:31

Hallo,
Sleep35000() breaks any timers??
I have added a pressed escape key as abort criterion, see comment in script line 17:

Code: Select all

#Requires AutoHotkey v2.0
#z::CountDown(10, 720, 450, A_WinDir "\Media\Windows Navigation Start.wav")
q:: { ; Circle from CountDowns
	Loop 8
		CountDown(10, 80*(2+Cos(α := Atan(1)*A_Index)), 80*(2+Sin(α))), Sleep(100)
} ; Sleep(100) delays the respective start times

CountDown(StartValue, X, Y, SoundPlayFile := 0) {
	Static Order := 0, Active := 0
	WinGetPos &Wx, &Wy,,, "A" ; X, Y relative to active window
	MyGui := Gui("+AlwaysOnTop -Caption +ToolWindow")
	MyGui.SetFont("s14", "Consolas"), MyGui.MarginX := MyGui.MarginY := 0
	MyGui.Add("Text", "BackgroundWhite", (++Order *= !!Active++) ".")
	Down := MyGui.Add("Text", "YP+0", StartValue)
	MyGui.Show("X" Wx+X "Y" Wy+Y "NA")
	SetTimer(Count.Bind(MyGui, Down), 1000) ; every second
 	Count(MyGui, Down) { ; ↓ abort criterion 
	(Down.Value And !GetKeyState("Esc","P"))?(--Down.Value, SoundPlayFile
	?SoundPlay(SoundPlayFile):""):(SetTimer(, False), MyGui.Destroy(), --Active)
}}
Each timer started by CountDown() ends prematurely when it detects a pressed escape key.

hiahkforum
Posts: 47
Joined: 08 Feb 2024, 04:21

Re: Simultaneous running of two timers.

Post by hiahkforum » 15 Mar 2024, 06:20

Sleep35000() breaks timer only in current thread with CountDown(), so it would be perfect if only the last started timer GUI can be removed and so on (if 3 timers running, the third one removes first, then the second one and for the last one the first).

Your current solution kind of works, but it removes timers in some random order and for triggering a remove operation Esc needs to be pressed up to 10 times (I think, it is bc of 1000 ms sleep for ticks).

Rohwedder
Posts: 7772
Joined: 04 Jun 2014, 08:33
Location: Germany

Re: Simultaneous running of two timers.  Topic is solved

Post by Rohwedder » 15 Mar 2024, 09:06

No, you didn't have to press Escape several times, just hold it down for at least 1 second until every timer would have detected the pressed escape key.
But no matter! Try this:

Code: Select all

#Requires AutoHotkey v2.0
Global Downs := Map(), Order := 0
#z::CountDown(10, 720, 450, A_WinDir "\Media\Windows Navigation Start.wav")
q:: { ; Circle from CountDowns
	Loop 8
		CountDown(10, 80*(2+Cos(α := Atan(1)*A_Index)), 80*(2+Sin(α))), Sleep(100)
} ; Sleep(100) delays the respective start times
w:: { ; ends the countdown with the last assigned Order number
	Global
	Try Downs[Order].Value := 0, Order := Max(--Order, 0)
}
CountDown(StartValue, X, Y, SoundPlayFile := 0) {
	Global Downs, Order
	Static Active := 0
	WinGetPos &Wx, &Wy,,, "A" ; X, Y relative to active window
	MyGui := Gui("+AlwaysOnTop -Caption +ToolWindow")
	MyGui.SetFont("s14", "Consolas"), MyGui.MarginX := MyGui.MarginY := 0
	MyGui.Add("Text", "BackgroundWhite", (++Order *= !!Active++) ".")
	Downs[Order] := Down := MyGui.Add("Text", "YP+0", StartValue)
	MyGui.Show("X" Wx+X "Y" Wy+Y "NA")
	SetTimer(Count.Bind(MyGui, Down), 1000) ; every second
 	Count(MyGui, Down) {
	Down.Value?(--Down.Value, SoundPlayFile?SoundPlay(SoundPlayFile):"")
	:(SetTimer(, False), MyGui.Destroy(), --Active)
}}
Hotkey W ends the countdown with the last assigned "Order" number. However, as the countdowns run completely independently and can be started with different durations, uniqueness is not guaranteed.

hiahkforum
Posts: 47
Joined: 08 Feb 2024, 04:21

Re: Simultaneous running of two timers.

Post by hiahkforum » 15 Mar 2024, 11:46

@Rohwedder Yes, it wasn't obvious, but that's right, with 1s press it works predictable, but now your solution works just perfect. :)

Actually, this interruptible Sleep isn't reliable at all. In case of 35s for Esc responsivness it's better to use Loop 500 and Sleep 70, but the timer accuracy is very poor, it counts 5-10 seconds more. Loop 35 and Sleep 1000 much more precise, but in this case Esc needs to be pressed for 1s, whis isn't that bad, I can live with that, but it seems to be not compatible with multiple threads at all. I think, it pauses loops until the last thread is done, which is horrible and have nothing related to "simultaneous running of two timers". Just Sleep doesn't stop counting when new threads are started (maybe I'm wrong technically, but it works well for me, just w/o interruptibility). If I'm not boring you too much, maybe you have some ideas on how to solve this? Perhaps this could even be built into your function. :shifty:
Last edited by hiahkforum on 15 Mar 2024, 13:03, edited 1 time in total.

Post Reply

Return to “Ask for Help (v2)”